Compare commits
280 commits
master
...
feature/mo
Author | SHA1 | Date | |
---|---|---|---|
fmodf | 8a39ebe2ee | ||
fmodf | 2d6cdeba1d | ||
fmodf | e1b74520a2 | ||
fmodf | 02a8caabe7 | ||
fmodf | 1b0a62db32 | ||
fmodf | dc993147f5 | ||
fmodf | f29d52834d | ||
fmodf | 2881a3a9ab | ||
fmodf | ba4b5b8ca8 | ||
fmodf | 405ed6be65 | ||
fmodf | 0446bd1991 | ||
fmodf | a97111007c | ||
fmodf | a28e829c1a | ||
fmodf | ba7955ab87 | ||
fmodf | 05ce6de8d7 | ||
fmodf | 301e6f0f62 | ||
fmodf | b80d485041 | ||
fmodf | 225e3916f8 | ||
fmodf | 080c54bb88 | ||
fmodf | 2345027150 | ||
fmodf | 0f173753db | ||
fmodf | 4193230a40 | ||
fmodf | cd84cdb48b | ||
fmodf | 7aa08313dd | ||
fmodf | 0c7284cc3e | ||
fmodf | 7f2ac43722 | ||
fmodf | 794d870904 | ||
fmodf | ce2a89ff04 | ||
fmodf | e0e96bc6d4 | ||
fmodf | 97d2875675 | ||
fmodf | d7cfbf9f0f | ||
fmodf | e93129d94c | ||
fmodf | 579ed1af33 | ||
fmodf | 5fc60e3aa4 | ||
fmodf | 3712026b38 | ||
fmodf | 8917c87df6 | ||
fmodf | fbf5a34a0b | ||
fmodf | a8f17b68df | ||
fmodf | 2f4ab62b04 | ||
fmodf | 9d92bc9a98 | ||
fmodf | 900280377f | ||
fmodf | 25429eef26 | ||
fmodf | e620087909 | ||
fmodf | e04ee03c29 | ||
fmodf | 9bc64997b7 | ||
fmodf | f98d22ebfc | ||
fmodf | e92fcb1c81 | ||
fmodf | 5ef03b7486 | ||
fmodf | cde3694d19 | ||
fmodf | f749a0d089 | ||
fmodf | fcbaa35c3f | ||
fmodf | 0d21b50b08 | ||
fmodf | 6f4eaebb39 | ||
fmodf | bcd956954b | ||
fmodf | 71007b819f | ||
fmodf | 9fbafb0ed5 | ||
fmodf | a364f11e15 | ||
fmodf | c801a82ca1 | ||
fmodf | d363217e21 | ||
fmodf | 50d67739f8 | ||
fmodf | dce9438b52 | ||
fmodf | 68b5ab17b5 | ||
fmodf | ada11790d4 | ||
fmodf | 8a1a27850e | ||
fmodf | b62a05e1cd | ||
fmodf | 48c39c6b2f | ||
fmodf | 2a26bee8ca | ||
fmodf | 6a3b2559e8 | ||
fmodf | 69e48f02ad | ||
fmodf | d5639fe452 | ||
fmodf | 4d35351474 | ||
fmodf | 3c8f86d509 | ||
fmodf | 9c5d478aff | ||
fmodf | 441044ddae | ||
fmodf | 655e074cf1 | ||
fmodf | 813cf50a34 | ||
fmodf | 3aacc311a0 | ||
fmodf | 0929662b39 | ||
fmodf | f17e093a6d | ||
fmodf | 34a550cc64 | ||
fmodf | 317dee1e26 | ||
fmodf | 42eac074fa | ||
fmodf | 554053fdba | ||
fmodf | a45584eda0 | ||
fmodf | 51a39cce42 | ||
fmodf | 196d5f2176 | ||
fmodf | 5a0b3536bc | ||
fmodf | f0cf5e18ec | ||
fmodf | a8a11c6489 | ||
fmodf | 2f5f289e3f | ||
fmodf | 1025518f55 | ||
fmodf | dfa9ce3ec5 | ||
fmodf | ed82109382 | ||
fmodf | 0f5019252b | ||
fmodf | 2e16f4a879 | ||
fmodf | 930a73168d | ||
fmodf | 2de949c410 | ||
fmodf | f6a9b6b163 | ||
fmodf | 8026771490 | ||
fmodf | c559fab590 | ||
fmodf | 865b2881ba | ||
fmodf | 4a64621746 | ||
fmodf | d948a10117 | ||
fmodf | 9850213f80 | ||
fmodf | 4c03959aa8 | ||
fmodf | 93df47b6ae | ||
fmodf | b55dfe98fd | ||
fmodf | 089c47c1a4 | ||
fmodf | 7f3ec8b754 | ||
fmodf | d9e8b01b2f | ||
fmodf | bba24a4929 | ||
fmodf | ba93689d26 | ||
fmodf | 64d948eea9 | ||
fmodf | b0ee26bd62 | ||
fmodf | a68a0b781c | ||
fmodf | c7997f438d | ||
fmodf | 389d9d44a1 | ||
fmodf | a09087ffe4 | ||
fmodf | 016b3511b6 | ||
fmodf | 6a6a077b85 | ||
fmodf | 46a358e926 | ||
fmodf | 601d2b9152 | ||
fmodf | b1604ab115 | ||
fmodf | 6c911652d5 | ||
fmodf | 58aa50e730 | ||
fmodf | 4af286ecee | ||
fmodf | 8d85a81e70 | ||
fmodf | 8fadc53470 | ||
fmodf | e45bbbecd2 | ||
fmodf | 0b7e1e5b81 | ||
fmodf | df2ea2eefb | ||
fmodf | 8f9eced353 | ||
fmodf | e1d7393139 | ||
fmodf | afad9d9641 | ||
fmodf | 5df60e82cd | ||
fmodf | 697624e2f9 | ||
fmodf | 9e6a3cc5c7 | ||
fmodf | e288e44d45 | ||
fmodf | d5872f47db | ||
fmodf | 673097831e | ||
fmodf | 9c4f8b9f4e | ||
fmodf | 4afea365a2 | ||
fmodf | 4cd68fa756 | ||
fmodf | d655e72336 | ||
fmodf | d5f97dbf24 | ||
fmodf | aaaa8a3dd8 | ||
fmodf | 6373ded7c0 | ||
fmodf | b48686ec9e | ||
fmodf | 1a397a4643 | ||
fmodf | d81094c024 | ||
fmodf | e781e76f43 | ||
fmodf | df56001998 | ||
fmodf | 8b4e50de7c | ||
fmodf | 2b1674a35e | ||
fmodf | de5f593072 | ||
fmodf | 50633b2f7a | ||
fmodf | 4ed380afd1 | ||
fmodf | 87ca6b0978 | ||
fmodf | 9c6522839f | ||
fmodf | aaf2a0a39d | ||
fmodf | 423c1be232 | ||
fmodf | 3cf73bfdea | ||
fmodf | 8845370bf0 | ||
fmodf | 89765241a3 | ||
fmodf | 9654667af0 | ||
fmodf | 354e9df0ef | ||
fmodf | 0554b1d262 | ||
fmodf | 70446adbd1 | ||
fmodf | 5925e07011 | ||
fmodf | 7da120521b | ||
fmodf | 446271d7ba | ||
fmodf | 5c9143b9dc | ||
fmodf | 768378e05f | ||
fmodf | ae9843b61c | ||
fmodf | f38560fb2d | ||
fmodf | f45fb69997 | ||
fmodf | 64047eb936 | ||
fmodf | 57010e7fae | ||
fmodf | ad94afa92b | ||
fmodf | 79272bc220 | ||
fmodf | 51122902a3 | ||
fmodf | 6ef0e3ec33 | ||
fmodf | 68ce04fd1f | ||
fmodf | 6bffcedb0d | ||
fmodf | 9397f83f95 | ||
fmodf | 1c0ce668a4 | ||
fmodf | b0031d1e50 | ||
fmodf | d7b597ca0d | ||
fmodf | 5e9e53dbce | ||
fmodf | a8bca45732 | ||
fmodf | e1e4095589 | ||
fmodf | f92929c829 | ||
fmodf | 0d30dd199f | ||
fmodf | 0fc7f682d8 | ||
fmodf | 20265255e7 | ||
fmodf | 2fed518d81 | ||
fmodf | eb74ad27e3 | ||
fmodf | c400da7fb8 | ||
fmodf | 05c6a5698a | ||
fmodf | 332b903d6d | ||
fmodf | 27e6096042 | ||
fmodf | de06b3c323 | ||
fmodf | a764385379 | ||
fmodf | a1f8aa0a82 | ||
fmodf | 4f4e8b0a89 | ||
fmodf | 752d8ee4af | ||
fmodf | 5a87fd419d | ||
fmodf | a7928efbf2 | ||
fmodf | 8f4d574f19 | ||
fmodf | ff891869be | ||
fmodf | be0061921e | ||
fmodf | 58c612d6a2 | ||
fmodf | affc5afdc9 | ||
fmodf | 57dc5e1d81 | ||
fmodf | 7eae6c295c | ||
fmodf | 73f0f8762f | ||
fmodf | 16c0208f21 | ||
fmodf | 26269c6118 | ||
fmodf | 118c9b6efa | ||
fmodf | 4be16a9656 | ||
fmodf | ec3092f199 | ||
fmodf | de7be9ea68 | ||
fmodf | fbf8df2c9a | ||
fmodf | 3f4a2d4cde | ||
fmodf | 3d4ade5346 | ||
fmodf | c0a19d86fd | ||
fmodf | a45479d0c6 | ||
fmodf | 48d7030090 | ||
fmodf | 2d8019217a | ||
fmodf | 43dde437c5 | ||
fmodf | 20db5d5f2d | ||
fmodf | 7424e9171a | ||
fmodf | 6906c094ea | ||
fmodf | 3ab551e8de | ||
fmodf | 1c745f2b25 | ||
fmodf | 4717aa8649 | ||
fmodf | f387236d9d | ||
fmodf | 80c56f1d48 | ||
fmodf | 43f01272ab | ||
fmodf | 1a51090b03 | ||
fmodf | dba6463116 | ||
fmodf | 1f414b9344 | ||
fmodf | fc4610192b | ||
fmodf | 50feee4f74 | ||
fmodf | 9df49aa422 | ||
fmodf | 2736e72e9d | ||
fmodf | 4d7e8d2fbd | ||
fmodf | 4c6aaea4d8 | ||
fmodf | cad4d3dcb0 | ||
fmodf | 3e668aa808 | ||
fmodf | 54f2b3b825 | ||
fmodf | f33c3de839 | ||
fmodf | c95460b80e | ||
fmodf | cc20619e2c | ||
fmodf | f1fd81841e | ||
fmodf | 7603ace54e | ||
fmodf | b75eed2677 | ||
fmodf | d62937af4b | ||
fmodf | ab08d43225 | ||
fmodf | 507bc16846 | ||
fmodf | ac6eb97b16 | ||
fmodf | f31f0c54c5 | ||
fmodf | 8cbd60d30d | ||
fmodf | 93ee79ee20 | ||
fmodf | a7fddc9022 | ||
fmodf | 4316eb2ca1 | ||
fmodf | 4b4de74eda | ||
fmodf | 4d02aaea5b | ||
fmodf | 97ac8373c9 | ||
fmodf | 68a136e4b7 | ||
fmodf | aaa5428bfc | ||
fmodf | 39b8a4c6bc | ||
fmodf | 050a12b1cc | ||
fmodf | f9d776da64 | ||
fmodf | 129b63fd16 | ||
fmodf | 0535e59bb3 | ||
fmodf | 05d79d058f | ||
fmodf | eefce61e3a | ||
fmodf | 44f3c598cd | ||
fmodf | ac15a7e8c5 |
|
@ -1,26 +0,0 @@
|
|||
[update]
|
||||
tasks = ["interfaces", "code", "normalize"]
|
||||
|
||||
[update.interfaces]
|
||||
paths = ["."]
|
||||
defaultToBase = true
|
||||
ignoreEmptyStrings = false
|
||||
unstripped = false
|
||||
|
||||
[update.code]
|
||||
codePaths = ["."]
|
||||
localizablePaths = ["."]
|
||||
defaultToKeys = true
|
||||
additive = false
|
||||
unstripped = false
|
||||
|
||||
[update.normalize]
|
||||
paths = ["."]
|
||||
sourceLocale = "en"
|
||||
harmonizeWithSource = true
|
||||
sortByKeys = true
|
||||
|
||||
[lint]
|
||||
paths = ["."]
|
||||
duplicateKeys = true
|
||||
emptyValues = true
|
14
.gitignore
vendored
|
@ -110,4 +110,16 @@ xcuserdata
|
|||
#
|
||||
# We're using source-control, so this is a "feature" that we do not want!
|
||||
|
||||
*.moved-aside
|
||||
*.moved-aside
|
||||
/.idea
|
||||
/Snikket/.idea
|
||||
/Snikket.xcodeproj
|
||||
/Info.plist
|
||||
/old/.idea
|
||||
/Engine/Info.plist
|
||||
/Snikket/Snikket.entitlements
|
||||
/XMPPSwift/Client/VoIP/rickroll.mp4
|
||||
/.nvim
|
||||
/buildServer.json
|
||||
TODO.txt
|
||||
PASSWD.txt
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string></string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>SwiftGen_SwiftGenCLI</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>SwiftGen.SwiftGenCLI.resources</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>SwiftGen_SwiftGenCLI</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>13A233</string>
|
||||
<key>DTPlatformName</key>
|
||||
<string>macosx</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>11.3</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>20E214</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>macosx11.3</string>
|
||||
<key>DTXcode</key>
|
||||
<string>1300</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>13A233</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.11</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,43 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if palettes %}
|
||||
{% set enumName %}{{param.enumName|default:"ColorName"}}{% endset %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
{% if enumName != 'NSColor' %}{{accessModifier}} enum {{enumName}} { }{% endif %}
|
||||
#elseif os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
{% if enumName != 'UIColor' %}{{accessModifier}} enum {{enumName}} { }{% endif %}
|
||||
#endif
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length
|
||||
|
||||
// MARK: - Colors
|
||||
|
||||
// swiftlint:disable identifier_name line_length type_body_length
|
||||
{{accessModifier}} extension {{enumName}} {
|
||||
{% macro h2f hex %}{{hex|hexToInt|int255toFloat}}{% endmacro %}
|
||||
{% macro enumBlock colors accessPrefix %}
|
||||
{% for color in colors %}
|
||||
/// 0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}} (r: {{color.red|hexToInt}}, g: {{color.green|hexToInt}}, b: {{color.blue|hexToInt}}, a: {{color.alpha|hexToInt}})
|
||||
{{accessPrefix}}static let {{color.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = #colorLiteral(red: {% call h2f color.red %}, green: {% call h2f color.green %}, blue: {% call h2f color.blue %}, alpha: {% call h2f color.alpha %})
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
{% if palettes.count > 1 or param.forceFileNameEnum %}
|
||||
{% set accessPrefix %}{{accessModifier}} {% endset %}
|
||||
{% for palette in palettes %}
|
||||
enum {{palette.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call enumBlock palette.colors accessPrefix %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call enumBlock palettes.first.colors "" %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length type_body_length
|
||||
{% else %}
|
||||
// No color found
|
||||
{% endif %}
|
|
@ -0,0 +1,43 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if palettes %}
|
||||
{% set enumName %}{{param.enumName|default:"ColorName"}}{% endset %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
{% if enumName != 'NSColor' %}{{accessModifier}} enum {{enumName}} { }{% endif %}
|
||||
#elseif os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
{% if enumName != 'UIColor' %}{{accessModifier}} enum {{enumName}} { }{% endif %}
|
||||
#endif
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length
|
||||
|
||||
// MARK: - Colors
|
||||
|
||||
// swiftlint:disable identifier_name line_length type_body_length
|
||||
{{accessModifier}} extension {{enumName}} {
|
||||
{% macro h2f hex %}{{hex|hexToInt|int255toFloat}}{% endmacro %}
|
||||
{% macro enumBlock colors accessPrefix %}
|
||||
{% for color in colors %}
|
||||
/// 0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}} (r: {{color.red|hexToInt}}, g: {{color.green|hexToInt}}, b: {{color.blue|hexToInt}}, a: {{color.alpha|hexToInt}})
|
||||
{{accessPrefix}}static let {{color.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = #colorLiteral(red: {% call h2f color.red %}, green: {% call h2f color.green %}, blue: {% call h2f color.blue %}, alpha: {% call h2f color.alpha %})
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
{% if palettes.count > 1 or param.forceFileNameEnum %}
|
||||
{% set accessPrefix %}{{accessModifier}} {% endset %}
|
||||
{% for palette in palettes %}
|
||||
enum {{palette.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call enumBlock palette.colors accessPrefix %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call enumBlock palettes.first.colors "" %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length type_body_length
|
||||
{% else %}
|
||||
// No color found
|
||||
{% endif %}
|
|
@ -0,0 +1,84 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if palettes %}
|
||||
{% set colorAlias %}{{param.colorAliasName|default:"Color"}}{% endset %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
#if os(macOS)
|
||||
import AppKit.NSColor
|
||||
{{accessModifier}} typealias {{colorAlias}} = NSColor
|
||||
#elseif os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit.UIColor
|
||||
{{accessModifier}} typealias {{colorAlias}} = UIColor
|
||||
#endif
|
||||
|
||||
// swiftlint:disable superfluous_disable_command file_length implicit_return
|
||||
|
||||
// MARK: - Colors
|
||||
|
||||
// swiftlint:disable identifier_name line_length type_body_length
|
||||
{% set enumName %}{{param.enumName|default:"ColorName"}}{% endset %}
|
||||
{{accessModifier}} struct {{enumName}} {
|
||||
{{accessModifier}} let rgbaValue: UInt32
|
||||
{{accessModifier}} var color: {{colorAlias}} { return {{colorAlias}}(named: self) }
|
||||
|
||||
{% macro rgbaValue color %}0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}}{% endmacro %}
|
||||
{% macro enumBlock colors %}
|
||||
{% for color in colors %}
|
||||
/// <span style="display:block;width:3em;height:2em;border:1px solid black;background:#{{color.red}}{{color.green}}{{color.blue}}"></span>
|
||||
/// Alpha: {{color.alpha|hexToInt|int255toFloat|percent}} <br/> (0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}})
|
||||
{{accessModifier}} static let {{color.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}(rgbaValue: {% call rgbaValue color %})
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
{% if palettes.count > 1 or param.forceFileNameEnum %}
|
||||
{% for palette in palettes %}
|
||||
{{accessModifier}} enum {{palette.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call enumBlock palette.colors %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call enumBlock palettes.first.colors %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length type_body_length
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
internal extension {{colorAlias}} {
|
||||
convenience init(rgbaValue: UInt32) {
|
||||
let components = RGBAComponents(rgbaValue: rgbaValue).normalized
|
||||
self.init(red: components[0], green: components[1], blue: components[2], alpha: components[3])
|
||||
}
|
||||
}
|
||||
|
||||
private struct RGBAComponents {
|
||||
let rgbaValue: UInt32
|
||||
|
||||
private var shifts: [UInt32] {
|
||||
[
|
||||
rgbaValue >> 24, // red
|
||||
rgbaValue >> 16, // green
|
||||
rgbaValue >> 8, // blue
|
||||
rgbaValue // alpha
|
||||
]
|
||||
}
|
||||
|
||||
private var components: [CGFloat] {
|
||||
shifts.map {
|
||||
CGFloat($0 & 0xff)
|
||||
}
|
||||
}
|
||||
|
||||
var normalized: [CGFloat] {
|
||||
components.map { $0 / 255.0 }
|
||||
}
|
||||
}
|
||||
|
||||
{{accessModifier}} extension {{colorAlias}} {
|
||||
convenience init(named color: {{enumName}}) {
|
||||
self.init(rgbaValue: color.rgbaValue)
|
||||
}
|
||||
}
|
||||
{% else %}
|
||||
// No color found
|
||||
{% endif %}
|
|
@ -0,0 +1,84 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if palettes %}
|
||||
{% set colorAlias %}{{param.colorAliasName|default:"Color"}}{% endset %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
#if os(macOS)
|
||||
import AppKit.NSColor
|
||||
{{accessModifier}} typealias {{colorAlias}} = NSColor
|
||||
#elseif os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit.UIColor
|
||||
{{accessModifier}} typealias {{colorAlias}} = UIColor
|
||||
#endif
|
||||
|
||||
// swiftlint:disable superfluous_disable_command file_length implicit_return
|
||||
|
||||
// MARK: - Colors
|
||||
|
||||
// swiftlint:disable identifier_name line_length type_body_length
|
||||
{% set enumName %}{{param.enumName|default:"ColorName"}}{% endset %}
|
||||
{{accessModifier}} struct {{enumName}} {
|
||||
{{accessModifier}} let rgbaValue: UInt32
|
||||
{{accessModifier}} var color: {{colorAlias}} { return {{colorAlias}}(named: self) }
|
||||
|
||||
{% macro rgbaValue color %}0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}}{% endmacro %}
|
||||
{% macro enumBlock colors %}
|
||||
{% for color in colors %}
|
||||
/// <span style="display:block;width:3em;height:2em;border:1px solid black;background:#{{color.red}}{{color.green}}{{color.blue}}"></span>
|
||||
/// Alpha: {{color.alpha|hexToInt|int255toFloat|percent}} <br/> (0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}})
|
||||
{{accessModifier}} static let {{color.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}(rgbaValue: {% call rgbaValue color %})
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
{% if palettes.count > 1 or param.forceFileNameEnum %}
|
||||
{% for palette in palettes %}
|
||||
{{accessModifier}} enum {{palette.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call enumBlock palette.colors %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call enumBlock palettes.first.colors %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length type_body_length
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
internal extension {{colorAlias}} {
|
||||
convenience init(rgbaValue: UInt32) {
|
||||
let components = RGBAComponents(rgbaValue: rgbaValue).normalized
|
||||
self.init(red: components[0], green: components[1], blue: components[2], alpha: components[3])
|
||||
}
|
||||
}
|
||||
|
||||
private struct RGBAComponents {
|
||||
let rgbaValue: UInt32
|
||||
|
||||
private var shifts: [UInt32] {
|
||||
[
|
||||
rgbaValue >> 24, // red
|
||||
rgbaValue >> 16, // green
|
||||
rgbaValue >> 8, // blue
|
||||
rgbaValue // alpha
|
||||
]
|
||||
}
|
||||
|
||||
private var components: [CGFloat] {
|
||||
shifts.map {
|
||||
CGFloat($0 & 0xff)
|
||||
}
|
||||
}
|
||||
|
||||
var normalized: [CGFloat] {
|
||||
components.map { $0 / 255.0 }
|
||||
}
|
||||
}
|
||||
|
||||
{{accessModifier}} extension {{colorAlias}} {
|
||||
convenience init(named color: {{enumName}}) {
|
||||
self.init(rgbaValue: color.rgbaValue)
|
||||
}
|
||||
}
|
||||
{% else %}
|
||||
// No color found
|
||||
{% endif %}
|
|
@ -0,0 +1,211 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
// swiftlint:disable superfluous_disable_command implicit_return
|
||||
// swiftlint:disable sorted_imports
|
||||
import CoreData
|
||||
import Foundation
|
||||
{% for import in param.extraImports %}
|
||||
import {{ import }}
|
||||
{% empty %}
|
||||
{# If extraImports is a single String instead of an array, `for` considers it empty but we still have to check if there's a single String value #}
|
||||
{% if param.extraImports %}import {{ param.extraImports }}{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
// swiftlint:disable attributes file_length vertical_whitespace_closing_braces
|
||||
// swiftlint:disable identifier_name line_length type_body_length
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
|
||||
{% for model in models %}
|
||||
{% for name, entity in model.entities %}
|
||||
{% set superclass %}{{ model.entities[entity.superEntity].className|default:"NSManagedObject" }}{% endset %}
|
||||
{% set entityClassName %}{{ entity.className|default:"NSManagedObject" }}{% endset %}
|
||||
// MARK: - {{ entity.name }}
|
||||
|
||||
{% if not entity.shouldGenerateCode %}
|
||||
// Note: '{{ entity.name }}' has codegen enabled for Xcode, skipping code generation.
|
||||
|
||||
{% elif entityClassName|contains:"." %}
|
||||
// Warning: '{{ entityClassName }}' cannot be a valid type name, skipping code generation.
|
||||
|
||||
{% else %}
|
||||
{% if param.generateObjcName %}
|
||||
@objc({{ entityClassName }})
|
||||
{% endif %}
|
||||
{{ accessModifier }} class {{ entityClassName }}: {{ superclass }} {
|
||||
{% set override %}{% if superclass != "NSManagedObject" %}override {% endif %}{% endset %}
|
||||
{{ override }}{{ accessModifier }} class var entityName: String {
|
||||
return "{{ entity.name }}"
|
||||
}
|
||||
|
||||
{{ override }}{{ accessModifier }} class func entity(in managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? {
|
||||
return NSEntityDescription.entity(forEntityName: entityName, in: managedObjectContext)
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "makeFetchRequest", message: "To avoid collisions with the less concrete method in `NSManagedObject`, please use `makeFetchRequest()` instead.")
|
||||
@nonobjc {{ accessModifier }} class func fetchRequest() -> NSFetchRequest<{{ entityClassName }}> {
|
||||
return NSFetchRequest<{{ entityClassName }}>(entityName: entityName)
|
||||
}
|
||||
|
||||
@nonobjc {{ accessModifier }} class func makeFetchRequest() -> NSFetchRequest<{{ entityClassName }}> {
|
||||
return NSFetchRequest<{{ entityClassName }}>(entityName: entityName)
|
||||
}
|
||||
|
||||
// swiftlint:disable discouraged_optional_boolean discouraged_optional_collection
|
||||
{% for attribute in entity.attributes %}
|
||||
{% if attribute.userInfo.RawType %}
|
||||
{% set rawType attribute.userInfo.RawType %}
|
||||
{% set unwrapOptional attribute.userInfo.unwrapOptional %}
|
||||
{{ accessModifier }} var {{ attribute.name }}: {{ rawType }}{% if not unwrapOptional %}?{% endif %} {
|
||||
get {
|
||||
let key = "{{ attribute.name }}"
|
||||
willAccessValue(forKey: key)
|
||||
defer { didAccessValue(forKey: key) }
|
||||
|
||||
{% if unwrapOptional %}
|
||||
guard let value = primitiveValue(forKey: key) as? {{ rawType }}.RawValue,
|
||||
let result = {{ rawType }}(rawValue: value) else {
|
||||
fatalError("Could not convert value for key '\(key)' to type '{{ rawType }}'")
|
||||
}
|
||||
return result
|
||||
{% else %}
|
||||
guard let value = primitiveValue(forKey: key) as? {{ rawType }}.RawValue else {
|
||||
return nil
|
||||
}
|
||||
return {{ rawType }}(rawValue: value)
|
||||
{% endif %}
|
||||
}
|
||||
set {
|
||||
let key = "{{ attribute.name }}"
|
||||
willChangeValue(forKey: key)
|
||||
defer { didChangeValue(forKey: key) }
|
||||
|
||||
setPrimitiveValue(newValue{% if not unwrapOptional %}?{% endif %}.rawValue, forKey: key)
|
||||
}
|
||||
}
|
||||
{% elif attribute.usesScalarValueType and attribute.isOptional %}
|
||||
{{ accessModifier }} var {{ attribute.name }}: {{ attribute.typeName }}? {
|
||||
get {
|
||||
let key = "{{ attribute.name }}"
|
||||
willAccessValue(forKey: key)
|
||||
defer { didAccessValue(forKey: key) }
|
||||
|
||||
return primitiveValue(forKey: key) as? {{ attribute.typeName }}
|
||||
}
|
||||
set {
|
||||
let key = "{{ attribute.name }}"
|
||||
willChangeValue(forKey: key)
|
||||
defer { didChangeValue(forKey: key) }
|
||||
|
||||
setPrimitiveValue(newValue, forKey: key)
|
||||
}
|
||||
}
|
||||
{% else %}
|
||||
@NSManaged {{ accessModifier }} var {{ attribute.name }}: {{ attribute.typeName }}{% if attribute.isOptional %}?{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for relationship in entity.relationships %}
|
||||
{% if relationship.isToMany %}
|
||||
@NSManaged {{ accessModifier }} var {{ relationship.name }}: {% if relationship.isOrdered %}NSOrderedSet{% else %}Set<{{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}>{% endif %}{% if relationship.isOptional %}?{% endif %}
|
||||
{% else %}
|
||||
@NSManaged {{ accessModifier }} var {{ relationship.name }}: {{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}{% if relationship.isOptional %}?{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for fetchedProperty in entity.fetchedProperties %}
|
||||
@NSManaged {{ accessModifier }} var {{ fetchedProperty.name }}: [{{ fetchedProperty.fetchRequest.entity }}]
|
||||
{% endfor %}
|
||||
// swiftlint:enable discouraged_optional_boolean discouraged_optional_collection
|
||||
}
|
||||
|
||||
{% for relationship in entity.relationships where relationship.isToMany %}
|
||||
{% set destinationEntityClassName %}{{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}{% endset %}
|
||||
{% set collectionClassName %}{% if relationship.isOrdered %}NSOrderedSet{% else %}Set<{{ destinationEntityClassName }}>{% endif %}{% endset %}
|
||||
{% set relationshipName %}{{ relationship.name | upperFirstLetter }}{% endset %}
|
||||
// MARK: Relationship {{ relationshipName }}
|
||||
|
||||
extension {{ entityClassName }} {
|
||||
{% if relationship.isOrdered %}
|
||||
@objc(insertObject:in{{ relationshipName }}AtIndex:)
|
||||
@NSManaged public func insertInto{{ relationshipName }}(_ value: {{ destinationEntityClassName }}, at idx: Int)
|
||||
|
||||
@objc(removeObjectFrom{{ relationshipName }}AtIndex:)
|
||||
@NSManaged public func removeFrom{{ relationshipName }}(at idx: Int)
|
||||
|
||||
@objc(insert{{ relationshipName }}:atIndexes:)
|
||||
@NSManaged public func insertInto{{ relationshipName }}(_ values: [{{ destinationEntityClassName }}], at indexes: NSIndexSet)
|
||||
|
||||
@objc(remove{{ relationshipName }}AtIndexes:)
|
||||
@NSManaged public func removeFrom{{ relationshipName }}(at indexes: NSIndexSet)
|
||||
|
||||
@objc(replaceObjectIn{{ relationshipName }}AtIndex:withObject:)
|
||||
@NSManaged public func replace{{ relationshipName }}(at idx: Int, with value: {{ destinationEntityClassName }})
|
||||
|
||||
@objc(replace{{ relationshipName }}AtIndexes:with{{ relationshipName }}:)
|
||||
@NSManaged public func replace{{ relationshipName }}(at indexes: NSIndexSet, with values: [{{ destinationEntityClassName }}])
|
||||
|
||||
{% endif %}
|
||||
@objc(add{{ relationshipName }}Object:)
|
||||
@NSManaged public func addTo{{ relationshipName }}(_ value: {{ destinationEntityClassName }})
|
||||
|
||||
@objc(remove{{ relationshipName }}Object:)
|
||||
@NSManaged public func removeFrom{{ relationshipName }}(_ value: {{ destinationEntityClassName }})
|
||||
|
||||
@objc(add{{ relationshipName }}:)
|
||||
@NSManaged public func addTo{{ relationshipName }}(_ values: {{ collectionClassName }})
|
||||
|
||||
@objc(remove{{ relationshipName }}:)
|
||||
@NSManaged public func removeFrom{{ relationshipName }}(_ values: {{ collectionClassName }})
|
||||
}
|
||||
|
||||
{% endfor %}
|
||||
{% if model.fetchRequests[entity.name].count > 0 %}
|
||||
// MARK: Fetch Requests
|
||||
|
||||
extension {{ entityClassName }} {
|
||||
{% for fetchRequest in model.fetchRequests[entity.name] %}
|
||||
{% set resultTypeName %}{% filter removeNewlines:"leading" %}
|
||||
{% if fetchRequest.resultType == "Object" %}
|
||||
{{ entityClassName }}
|
||||
{% elif fetchRequest.resultType == "Object ID" %}
|
||||
NSManagedObjectID
|
||||
{% elif fetchRequest.resultType == "Dictionary" %}
|
||||
[String: Any]
|
||||
{% endif %}
|
||||
{% endfilter %}{% endset %}
|
||||
class func fetch{{ fetchRequest.name | upperFirstLetter }}({% filter removeNewlines:"leading" %}
|
||||
managedObjectContext: NSManagedObjectContext
|
||||
{% for variableName, variableType in fetchRequest.substitutionVariables %}
|
||||
, {{ variableName | lowerFirstWord }}: {{ variableType }}
|
||||
{% endfor %}
|
||||
{% endfilter %}) throws -> [{{ resultTypeName }}] {
|
||||
guard let persistentStoreCoordinator = managedObjectContext.persistentStoreCoordinator else {
|
||||
fatalError("Managed object context has no persistent store coordinator for getting fetch request templates")
|
||||
}
|
||||
let model = persistentStoreCoordinator.managedObjectModel
|
||||
let substitutionVariables: [String: Any] = [
|
||||
{% for variableName, variableType in fetchRequest.substitutionVariables %}
|
||||
"{{ variableName }}": {{ variableName | lowerFirstWord }}{{ "," if not forloop.last }}
|
||||
{% empty %}
|
||||
:
|
||||
{% endfor %}
|
||||
]
|
||||
|
||||
guard let fetchRequest = model.fetchRequestFromTemplate(withName: "{{ fetchRequest.name }}", substitutionVariables: substitutionVariables) else {
|
||||
fatalError("No fetch request template named '{{ fetchRequest.name }}' found.")
|
||||
}
|
||||
|
||||
guard let result = try managedObjectContext.fetch(fetchRequest) as? [{{ resultTypeName }}] else {
|
||||
fatalError("Unable to cast fetch result to correct result type.")
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
// swiftlint:enable identifier_name line_length type_body_length
|
|
@ -0,0 +1,211 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
// swiftlint:disable superfluous_disable_command implicit_return
|
||||
// swiftlint:disable sorted_imports
|
||||
import CoreData
|
||||
import Foundation
|
||||
{% for import in param.extraImports %}
|
||||
import {{ import }}
|
||||
{% empty %}
|
||||
{# If extraImports is a single String instead of an array, `for` considers it empty but we still have to check if there's a single String value #}
|
||||
{% if param.extraImports %}import {{ param.extraImports }}{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
// swiftlint:disable attributes file_length vertical_whitespace_closing_braces
|
||||
// swiftlint:disable identifier_name line_length type_body_length
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
|
||||
{% for model in models %}
|
||||
{% for name, entity in model.entities %}
|
||||
{% set superclass %}{{ model.entities[entity.superEntity].className|default:"NSManagedObject" }}{% endset %}
|
||||
{% set entityClassName %}{{ entity.className|default:"NSManagedObject" }}{% endset %}
|
||||
// MARK: - {{ entity.name }}
|
||||
|
||||
{% if not entity.shouldGenerateCode %}
|
||||
// Note: '{{ entity.name }}' has codegen enabled for Xcode, skipping code generation.
|
||||
|
||||
{% elif entityClassName|contains:"." %}
|
||||
// Warning: '{{ entityClassName }}' cannot be a valid type name, skipping code generation.
|
||||
|
||||
{% else %}
|
||||
{% if param.generateObjcName %}
|
||||
@objc({{ entityClassName }})
|
||||
{% endif %}
|
||||
{{ accessModifier }} class {{ entityClassName }}: {{ superclass }} {
|
||||
{% set override %}{% if superclass != "NSManagedObject" %}override {% endif %}{% endset %}
|
||||
{{ override }}{{ accessModifier }} class var entityName: String {
|
||||
return "{{ entity.name }}"
|
||||
}
|
||||
|
||||
{{ override }}{{ accessModifier }} class func entity(in managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? {
|
||||
return NSEntityDescription.entity(forEntityName: entityName, in: managedObjectContext)
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "makeFetchRequest", message: "To avoid collisions with the less concrete method in `NSManagedObject`, please use `makeFetchRequest()` instead.")
|
||||
@nonobjc {{ accessModifier }} class func fetchRequest() -> NSFetchRequest<{{ entityClassName }}> {
|
||||
return NSFetchRequest<{{ entityClassName }}>(entityName: entityName)
|
||||
}
|
||||
|
||||
@nonobjc {{ accessModifier }} class func makeFetchRequest() -> NSFetchRequest<{{ entityClassName }}> {
|
||||
return NSFetchRequest<{{ entityClassName }}>(entityName: entityName)
|
||||
}
|
||||
|
||||
// swiftlint:disable discouraged_optional_boolean discouraged_optional_collection
|
||||
{% for attribute in entity.attributes %}
|
||||
{% if attribute.userInfo.RawType %}
|
||||
{% set rawType attribute.userInfo.RawType %}
|
||||
{% set unwrapOptional attribute.userInfo.unwrapOptional %}
|
||||
{{ accessModifier }} var {{ attribute.name }}: {{ rawType }}{% if not unwrapOptional %}?{% endif %} {
|
||||
get {
|
||||
let key = "{{ attribute.name }}"
|
||||
willAccessValue(forKey: key)
|
||||
defer { didAccessValue(forKey: key) }
|
||||
|
||||
{% if unwrapOptional %}
|
||||
guard let value = primitiveValue(forKey: key) as? {{ rawType }}.RawValue,
|
||||
let result = {{ rawType }}(rawValue: value) else {
|
||||
fatalError("Could not convert value for key '\(key)' to type '{{ rawType }}'")
|
||||
}
|
||||
return result
|
||||
{% else %}
|
||||
guard let value = primitiveValue(forKey: key) as? {{ rawType }}.RawValue else {
|
||||
return nil
|
||||
}
|
||||
return {{ rawType }}(rawValue: value)
|
||||
{% endif %}
|
||||
}
|
||||
set {
|
||||
let key = "{{ attribute.name }}"
|
||||
willChangeValue(forKey: key)
|
||||
defer { didChangeValue(forKey: key) }
|
||||
|
||||
setPrimitiveValue(newValue{% if not unwrapOptional %}?{% endif %}.rawValue, forKey: key)
|
||||
}
|
||||
}
|
||||
{% elif attribute.usesScalarValueType and attribute.isOptional %}
|
||||
{{ accessModifier }} var {{ attribute.name }}: {{ attribute.typeName }}? {
|
||||
get {
|
||||
let key = "{{ attribute.name }}"
|
||||
willAccessValue(forKey: key)
|
||||
defer { didAccessValue(forKey: key) }
|
||||
|
||||
return primitiveValue(forKey: key) as? {{ attribute.typeName }}
|
||||
}
|
||||
set {
|
||||
let key = "{{ attribute.name }}"
|
||||
willChangeValue(forKey: key)
|
||||
defer { didChangeValue(forKey: key) }
|
||||
|
||||
setPrimitiveValue(newValue, forKey: key)
|
||||
}
|
||||
}
|
||||
{% else %}
|
||||
@NSManaged {{ accessModifier }} var {{ attribute.name }}: {{ attribute.typeName }}{% if attribute.isOptional %}?{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for relationship in entity.relationships %}
|
||||
{% if relationship.isToMany %}
|
||||
@NSManaged {{ accessModifier }} var {{ relationship.name }}: {% if relationship.isOrdered %}NSOrderedSet{% else %}Set<{{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}>{% endif %}{% if relationship.isOptional %}?{% endif %}
|
||||
{% else %}
|
||||
@NSManaged {{ accessModifier }} var {{ relationship.name }}: {{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}{% if relationship.isOptional %}?{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for fetchedProperty in entity.fetchedProperties %}
|
||||
@NSManaged {{ accessModifier }} var {{ fetchedProperty.name }}: [{{ fetchedProperty.fetchRequest.entity }}]
|
||||
{% endfor %}
|
||||
// swiftlint:enable discouraged_optional_boolean discouraged_optional_collection
|
||||
}
|
||||
|
||||
{% for relationship in entity.relationships where relationship.isToMany %}
|
||||
{% set destinationEntityClassName %}{{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}{% endset %}
|
||||
{% set collectionClassName %}{% if relationship.isOrdered %}NSOrderedSet{% else %}Set<{{ destinationEntityClassName }}>{% endif %}{% endset %}
|
||||
{% set relationshipName %}{{ relationship.name | upperFirstLetter }}{% endset %}
|
||||
// MARK: Relationship {{ relationshipName }}
|
||||
|
||||
extension {{ entityClassName }} {
|
||||
{% if relationship.isOrdered %}
|
||||
@objc(insertObject:in{{ relationshipName }}AtIndex:)
|
||||
@NSManaged public func insertInto{{ relationshipName }}(_ value: {{ destinationEntityClassName }}, at idx: Int)
|
||||
|
||||
@objc(removeObjectFrom{{ relationshipName }}AtIndex:)
|
||||
@NSManaged public func removeFrom{{ relationshipName }}(at idx: Int)
|
||||
|
||||
@objc(insert{{ relationshipName }}:atIndexes:)
|
||||
@NSManaged public func insertInto{{ relationshipName }}(_ values: [{{ destinationEntityClassName }}], at indexes: NSIndexSet)
|
||||
|
||||
@objc(remove{{ relationshipName }}AtIndexes:)
|
||||
@NSManaged public func removeFrom{{ relationshipName }}(at indexes: NSIndexSet)
|
||||
|
||||
@objc(replaceObjectIn{{ relationshipName }}AtIndex:withObject:)
|
||||
@NSManaged public func replace{{ relationshipName }}(at idx: Int, with value: {{ destinationEntityClassName }})
|
||||
|
||||
@objc(replace{{ relationshipName }}AtIndexes:with{{ relationshipName }}:)
|
||||
@NSManaged public func replace{{ relationshipName }}(at indexes: NSIndexSet, with values: [{{ destinationEntityClassName }}])
|
||||
|
||||
{% endif %}
|
||||
@objc(add{{ relationshipName }}Object:)
|
||||
@NSManaged public func addTo{{ relationshipName }}(_ value: {{ destinationEntityClassName }})
|
||||
|
||||
@objc(remove{{ relationshipName }}Object:)
|
||||
@NSManaged public func removeFrom{{ relationshipName }}(_ value: {{ destinationEntityClassName }})
|
||||
|
||||
@objc(add{{ relationshipName }}:)
|
||||
@NSManaged public func addTo{{ relationshipName }}(_ values: {{ collectionClassName }})
|
||||
|
||||
@objc(remove{{ relationshipName }}:)
|
||||
@NSManaged public func removeFrom{{ relationshipName }}(_ values: {{ collectionClassName }})
|
||||
}
|
||||
|
||||
{% endfor %}
|
||||
{% if model.fetchRequests[entity.name].count > 0 %}
|
||||
// MARK: Fetch Requests
|
||||
|
||||
extension {{ entityClassName }} {
|
||||
{% for fetchRequest in model.fetchRequests[entity.name] %}
|
||||
{% set resultTypeName %}{% filter removeNewlines:"leading" %}
|
||||
{% if fetchRequest.resultType == "Object" %}
|
||||
{{ entityClassName }}
|
||||
{% elif fetchRequest.resultType == "Object ID" %}
|
||||
NSManagedObjectID
|
||||
{% elif fetchRequest.resultType == "Dictionary" %}
|
||||
[String: Any]
|
||||
{% endif %}
|
||||
{% endfilter %}{% endset %}
|
||||
class func fetch{{ fetchRequest.name | upperFirstLetter }}({% filter removeNewlines:"leading" %}
|
||||
managedObjectContext: NSManagedObjectContext
|
||||
{% for variableName, variableType in fetchRequest.substitutionVariables %}
|
||||
, {{ variableName | lowerFirstWord }}: {{ variableType }}
|
||||
{% endfor %}
|
||||
{% endfilter %}) throws -> [{{ resultTypeName }}] {
|
||||
guard let persistentStoreCoordinator = managedObjectContext.persistentStoreCoordinator else {
|
||||
fatalError("Managed object context has no persistent store coordinator for getting fetch request templates")
|
||||
}
|
||||
let model = persistentStoreCoordinator.managedObjectModel
|
||||
let substitutionVariables: [String: Any] = [
|
||||
{% for variableName, variableType in fetchRequest.substitutionVariables %}
|
||||
"{{ variableName }}": {{ variableName | lowerFirstWord }}{{ "," if not forloop.last }}
|
||||
{% empty %}
|
||||
:
|
||||
{% endfor %}
|
||||
]
|
||||
|
||||
guard let fetchRequest = model.fetchRequestFromTemplate(withName: "{{ fetchRequest.name }}", substitutionVariables: substitutionVariables) else {
|
||||
fatalError("No fetch request template named '{{ fetchRequest.name }}' found.")
|
||||
}
|
||||
|
||||
guard let result = try managedObjectContext.fetch(fetchRequest) as? [{{ resultTypeName }}] else {
|
||||
fatalError("Unable to cast fetch result to correct result type.")
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
// swiftlint:enable identifier_name line_length type_body_length
|
|
@ -0,0 +1,103 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if groups.count > 0 %}
|
||||
{% set enumName %}{{param.enumName|default:"Files"}}{% endset %}
|
||||
{% set useExt %}{% if param.useExtension|default:"true" %}true{% endif %}{% endset %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
{% set resourceType %}{{param.resourceTypeName|default:"File"}}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command file_length line_length implicit_return
|
||||
|
||||
// MARK: - Files
|
||||
|
||||
{% macro groupBlock group %}
|
||||
{% for file in group.files %}
|
||||
{% call fileBlock file %}
|
||||
{% endfor %}
|
||||
{% for dir in group.directories %}
|
||||
{% call dirBlock dir %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
{% macro fileBlock file %}
|
||||
/// {% if file.path and param.preservePath %}{{file.path}}/{% endif %}{{file.name}}{% if file.ext %}.{{file.ext}}{% endif %}
|
||||
{% set identifier %}{{ file.name }}{% if useExt %}.{{ file.ext }}{% endif %}{% endset %}
|
||||
{{accessModifier}} static let {{identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{resourceType}}(name: "{{file.name}}", ext: {% if file.ext %}"{{file.ext}}"{% else %}nil{% endif %}, relativePath: "{{file.path if param.preservePath}}", mimeType: "{{file.mimeType}}")
|
||||
{% endmacro %}
|
||||
{% macro dirBlock directory %}
|
||||
{% for file in directory.files %}
|
||||
{% call fileBlock file %}
|
||||
{% endfor %}
|
||||
{% for dir in directory.directories %}
|
||||
{% call dirBlock dir %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
// swiftlint:disable explicit_type_interface identifier_name
|
||||
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
{{accessModifier}} enum {{enumName}} {
|
||||
{% if groups.count > 1 or param.forceFileNameEnum %}
|
||||
{% for group in groups %}
|
||||
{{accessModifier}} enum {{group.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call groupBlock group %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call groupBlock groups.first %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable explicit_type_interface identifier_name
|
||||
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
{{accessModifier}} struct {{resourceType}} {
|
||||
{{accessModifier}} let name: String
|
||||
{{accessModifier}} let ext: String?
|
||||
{{accessModifier}} let relativePath: String
|
||||
{{accessModifier}} let mimeType: String
|
||||
|
||||
{{accessModifier}} var url: URL {
|
||||
return url(locale: nil)
|
||||
}
|
||||
|
||||
{{accessModifier}} func url(locale: Locale?) -> URL {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
let url = bundle.url(
|
||||
forResource: name,
|
||||
withExtension: ext,
|
||||
subdirectory: relativePath,
|
||||
localization: locale?.identifier
|
||||
)
|
||||
guard let result = url else {
|
||||
let file = name + (ext.flatMap { ".\($0)" } ?? "")
|
||||
fatalError("Could not locate file named \(file)")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
{{accessModifier}} var path: String {
|
||||
return path(locale: nil)
|
||||
}
|
||||
|
||||
{{accessModifier}} func path(locale: Locale?) -> String {
|
||||
return url(locale: locale).path
|
||||
}
|
||||
}
|
||||
{% if not param.bundle %}
|
||||
|
||||
// swiftlint:disable convenience_type explicit_type_interface
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type explicit_type_interface
|
||||
{% endif %}
|
||||
{% else %}
|
||||
// No files found
|
||||
{% endif %}
|
|
@ -0,0 +1,103 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if groups.count > 0 %}
|
||||
{% set enumName %}{{param.enumName|default:"Files"}}{% endset %}
|
||||
{% set useExt %}{% if param.useExtension|default:"true" %}true{% endif %}{% endset %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
{% set resourceType %}{{param.resourceTypeName|default:"File"}}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command file_length line_length implicit_return
|
||||
|
||||
// MARK: - Files
|
||||
|
||||
{% macro groupBlock group %}
|
||||
{% for file in group.files %}
|
||||
{% call fileBlock file %}
|
||||
{% endfor %}
|
||||
{% for dir in group.directories %}
|
||||
{% call dirBlock dir %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
{% macro fileBlock file %}
|
||||
/// {% if file.path and param.preservePath %}{{file.path}}/{% endif %}{{file.name}}{% if file.ext %}.{{file.ext}}{% endif %}
|
||||
{% set identifier %}{{ file.name }}{% if useExt %}.{{ file.ext }}{% endif %}{% endset %}
|
||||
{{accessModifier}} static let {{identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{resourceType}}(name: "{{file.name}}", ext: {% if file.ext %}"{{file.ext}}"{% else %}nil{% endif %}, relativePath: "{{file.path if param.preservePath}}", mimeType: "{{file.mimeType}}")
|
||||
{% endmacro %}
|
||||
{% macro dirBlock directory %}
|
||||
{% for file in directory.files %}
|
||||
{% call fileBlock file %}
|
||||
{% endfor %}
|
||||
{% for dir in directory.directories %}
|
||||
{% call dirBlock dir %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
// swiftlint:disable explicit_type_interface identifier_name
|
||||
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
{{accessModifier}} enum {{enumName}} {
|
||||
{% if groups.count > 1 or param.forceFileNameEnum %}
|
||||
{% for group in groups %}
|
||||
{{accessModifier}} enum {{group.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call groupBlock group %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call groupBlock groups.first %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable explicit_type_interface identifier_name
|
||||
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
{{accessModifier}} struct {{resourceType}} {
|
||||
{{accessModifier}} let name: String
|
||||
{{accessModifier}} let ext: String?
|
||||
{{accessModifier}} let relativePath: String
|
||||
{{accessModifier}} let mimeType: String
|
||||
|
||||
{{accessModifier}} var url: URL {
|
||||
return url(locale: nil)
|
||||
}
|
||||
|
||||
{{accessModifier}} func url(locale: Locale?) -> URL {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
let url = bundle.url(
|
||||
forResource: name,
|
||||
withExtension: ext,
|
||||
subdirectory: relativePath,
|
||||
localization: locale?.identifier
|
||||
)
|
||||
guard let result = url else {
|
||||
let file = name + (ext.flatMap { ".\($0)" } ?? "")
|
||||
fatalError("Could not locate file named \(file)")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
{{accessModifier}} var path: String {
|
||||
return path(locale: nil)
|
||||
}
|
||||
|
||||
{{accessModifier}} func path(locale: Locale?) -> String {
|
||||
return url(locale: locale).path
|
||||
}
|
||||
}
|
||||
{% if not param.bundle %}
|
||||
|
||||
// swiftlint:disable convenience_type explicit_type_interface
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type explicit_type_interface
|
||||
{% endif %}
|
||||
{% else %}
|
||||
// No files found
|
||||
{% endif %}
|
|
@ -0,0 +1,107 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if groups.count > 0 %}
|
||||
{% set enumName %}{{param.enumName|default:"Files"}}{% endset %}
|
||||
{% set useExt %}{% if param.useExtension|default:"true" %}true{% endif %}{% endset %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
{% set resourceType %}{{param.resourceTypeName|default:"File"}}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command file_length line_length implicit_return
|
||||
|
||||
// MARK: - Files
|
||||
|
||||
{% macro groupBlock group %}
|
||||
{% for file in group.files %}
|
||||
{% call fileBlock file %}
|
||||
{% endfor %}
|
||||
{% for dir in group.directories %}
|
||||
{% call dirBlock dir "" %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
{% macro fileBlock file %}
|
||||
/// {% if file.path and param.preservePath %}{{file.path}}/{% endif %}{{file.name}}{% if file.ext %}.{{file.ext}}{% endif %}
|
||||
{% set identifier %}{{ file.name }}{% if useExt %}.{{ file.ext }}{% endif %}{% endset %}
|
||||
{{accessModifier}} static let {{identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{resourceType}}(name: "{{file.name}}", ext: {% if file.ext %}"{{file.ext}}"{% else %}nil{% endif %}, relativePath: "{{file.path if param.preservePath}}", mimeType: "{{file.mimeType}}")
|
||||
{% endmacro %}
|
||||
{% macro dirBlock directory parent %}
|
||||
{% set fullDir %}{{parent}}{{directory.name}}/{% endset %}
|
||||
/// {{ fullDir }}
|
||||
{{accessModifier}} enum {{directory.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% for file in directory.files %}
|
||||
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
|
||||
{% endfor %}
|
||||
{% for dir in directory.directories %}
|
||||
{% filter indent:2 %}{% call dirBlock dir fullDir %}{% endfilter %}
|
||||
{% endfor %}
|
||||
}
|
||||
{% endmacro %}
|
||||
// swiftlint:disable explicit_type_interface identifier_name
|
||||
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
{{accessModifier}} enum {{enumName}} {
|
||||
{% if groups.count > 1 or param.forceFileNameEnum %}
|
||||
{% for group in groups %}
|
||||
{{accessModifier}} enum {{group.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call groupBlock group %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call groupBlock groups.first %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable explicit_type_interface identifier_name
|
||||
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
{{accessModifier}} struct {{resourceType}} {
|
||||
{{accessModifier}} let name: String
|
||||
{{accessModifier}} let ext: String?
|
||||
{{accessModifier}} let relativePath: String
|
||||
{{accessModifier}} let mimeType: String
|
||||
|
||||
{{accessModifier}} var url: URL {
|
||||
return url(locale: nil)
|
||||
}
|
||||
|
||||
{{accessModifier}} func url(locale: Locale?) -> URL {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
let url = bundle.url(
|
||||
forResource: name,
|
||||
withExtension: ext,
|
||||
subdirectory: relativePath,
|
||||
localization: locale?.identifier
|
||||
)
|
||||
guard let result = url else {
|
||||
let file = name + (ext.flatMap { ".\($0)" } ?? "")
|
||||
fatalError("Could not locate file named \(file)")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
{{accessModifier}} var path: String {
|
||||
return path(locale: nil)
|
||||
}
|
||||
|
||||
{{accessModifier}} func path(locale: Locale?) -> String {
|
||||
return url(locale: locale).path
|
||||
}
|
||||
}
|
||||
{% if not param.bundle %}
|
||||
|
||||
// swiftlint:disable convenience_type explicit_type_interface
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type explicit_type_interface
|
||||
{% endif %}
|
||||
{% else %}
|
||||
// No files found
|
||||
{% endif %}
|
|
@ -0,0 +1,107 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if groups.count > 0 %}
|
||||
{% set enumName %}{{param.enumName|default:"Files"}}{% endset %}
|
||||
{% set useExt %}{% if param.useExtension|default:"true" %}true{% endif %}{% endset %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
{% set resourceType %}{{param.resourceTypeName|default:"File"}}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command file_length line_length implicit_return
|
||||
|
||||
// MARK: - Files
|
||||
|
||||
{% macro groupBlock group %}
|
||||
{% for file in group.files %}
|
||||
{% call fileBlock file %}
|
||||
{% endfor %}
|
||||
{% for dir in group.directories %}
|
||||
{% call dirBlock dir "" %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
{% macro fileBlock file %}
|
||||
/// {% if file.path and param.preservePath %}{{file.path}}/{% endif %}{{file.name}}{% if file.ext %}.{{file.ext}}{% endif %}
|
||||
{% set identifier %}{{ file.name }}{% if useExt %}.{{ file.ext }}{% endif %}{% endset %}
|
||||
{{accessModifier}} static let {{identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{resourceType}}(name: "{{file.name}}", ext: {% if file.ext %}"{{file.ext}}"{% else %}nil{% endif %}, relativePath: "{{file.path if param.preservePath}}", mimeType: "{{file.mimeType}}")
|
||||
{% endmacro %}
|
||||
{% macro dirBlock directory parent %}
|
||||
{% set fullDir %}{{parent}}{{directory.name}}/{% endset %}
|
||||
/// {{ fullDir }}
|
||||
{{accessModifier}} enum {{directory.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% for file in directory.files %}
|
||||
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
|
||||
{% endfor %}
|
||||
{% for dir in directory.directories %}
|
||||
{% filter indent:2 %}{% call dirBlock dir fullDir %}{% endfilter %}
|
||||
{% endfor %}
|
||||
}
|
||||
{% endmacro %}
|
||||
// swiftlint:disable explicit_type_interface identifier_name
|
||||
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
{{accessModifier}} enum {{enumName}} {
|
||||
{% if groups.count > 1 or param.forceFileNameEnum %}
|
||||
{% for group in groups %}
|
||||
{{accessModifier}} enum {{group.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call groupBlock group %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call groupBlock groups.first %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable explicit_type_interface identifier_name
|
||||
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
{{accessModifier}} struct {{resourceType}} {
|
||||
{{accessModifier}} let name: String
|
||||
{{accessModifier}} let ext: String?
|
||||
{{accessModifier}} let relativePath: String
|
||||
{{accessModifier}} let mimeType: String
|
||||
|
||||
{{accessModifier}} var url: URL {
|
||||
return url(locale: nil)
|
||||
}
|
||||
|
||||
{{accessModifier}} func url(locale: Locale?) -> URL {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
let url = bundle.url(
|
||||
forResource: name,
|
||||
withExtension: ext,
|
||||
subdirectory: relativePath,
|
||||
localization: locale?.identifier
|
||||
)
|
||||
guard let result = url else {
|
||||
let file = name + (ext.flatMap { ".\($0)" } ?? "")
|
||||
fatalError("Could not locate file named \(file)")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
{{accessModifier}} var path: String {
|
||||
return path(locale: nil)
|
||||
}
|
||||
|
||||
{{accessModifier}} func path(locale: Locale?) -> String {
|
||||
return url(locale: locale).path
|
||||
}
|
||||
}
|
||||
{% if not param.bundle %}
|
||||
|
||||
// swiftlint:disable convenience_type explicit_type_interface
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type explicit_type_interface
|
||||
{% endif %}
|
||||
{% else %}
|
||||
// No files found
|
||||
{% endif %}
|
|
@ -0,0 +1,110 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if families %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
{% set fontType %}{{param.fontTypeName|default:"FontConvertible"}}{% endset %}
|
||||
#if os(macOS)
|
||||
import AppKit.NSFont
|
||||
#elseif os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit.UIFont
|
||||
#endif
|
||||
|
||||
// Deprecated typealiases
|
||||
@available(*, deprecated, renamed: "{{fontType}}.Font", message: "This typealias will be removed in SwiftGen 7.0")
|
||||
{{accessModifier}} typealias {{param.fontAliasName|default:"Font"}} = {{fontType}}.Font
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length
|
||||
// swiftlint:disable implicit_return
|
||||
|
||||
// MARK: - Fonts
|
||||
|
||||
// swiftlint:disable identifier_name line_length type_body_length
|
||||
{% macro transformPath path %}{% filter removeNewlines %}
|
||||
{% if param.preservePath %}
|
||||
{{path}}
|
||||
{% else %}
|
||||
{{path|basename}}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{{accessModifier}} enum {{param.enumName|default:"FontFamily"}} {
|
||||
{% for family in families %}
|
||||
{{accessModifier}} enum {{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% for font in family.fonts %}
|
||||
{{accessModifier}} static let {{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{fontType}}(name: "{{font.name}}", family: "{{family.name}}", path: "{% call transformPath font.path %}")
|
||||
{% endfor %}
|
||||
{{accessModifier}} static let all: [{{fontType}}] = [{% for font in family.fonts %}{{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{{ ", " if not forloop.last }}{% endfor %}]
|
||||
}
|
||||
{% endfor %}
|
||||
{{accessModifier}} static let allCustomFonts: [{{fontType}}] = [{% for family in families %}{{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.all{{ ", " if not forloop.last }}{% endfor %}].flatMap { $0 }
|
||||
{{accessModifier}} static func registerAllCustomFonts() {
|
||||
allCustomFonts.forEach { $0.register() }
|
||||
}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length type_body_length
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
{{accessModifier}} struct {{fontType}} {
|
||||
{{accessModifier}} let name: String
|
||||
{{accessModifier}} let family: String
|
||||
{{accessModifier}} let path: String
|
||||
|
||||
#if os(macOS)
|
||||
{{accessModifier}} typealias Font = NSFont
|
||||
#elseif os(iOS) || os(tvOS) || os(watchOS)
|
||||
{{accessModifier}} typealias Font = UIFont
|
||||
#endif
|
||||
|
||||
{{accessModifier}} func font(size: CGFloat) -> Font! {
|
||||
return Font(font: self, size: size)
|
||||
}
|
||||
|
||||
{{accessModifier}} func register() {
|
||||
// swiftlint:disable:next conditional_returns_on_newline
|
||||
guard let url = url else { return }
|
||||
CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil)
|
||||
}
|
||||
|
||||
fileprivate var url: URL? {
|
||||
{% if param.lookupFunction %}
|
||||
return {{param.lookupFunction}}(name, family, path)
|
||||
{% else %}
|
||||
return {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil)
|
||||
{% endif %}
|
||||
}
|
||||
}
|
||||
|
||||
{{accessModifier}} extension {{fontType}}.Font {
|
||||
convenience init?(font: {{fontType}}, size: CGFloat) {
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
if !UIFont.fontNames(forFamilyName: font.family).contains(font.name) {
|
||||
font.register()
|
||||
}
|
||||
#elseif os(macOS)
|
||||
if let url = font.url, CTFontManagerGetScopeForURL(url as CFURL) == .none {
|
||||
font.register()
|
||||
}
|
||||
#endif
|
||||
|
||||
self.init(name: font.name, size: size)
|
||||
}
|
||||
}
|
||||
{% if not param.bundle and not param.lookupFunction %}
|
||||
|
||||
// swiftlint:disable convenience_type
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type
|
||||
{% endif %}
|
||||
{% else %}
|
||||
// No fonts found
|
||||
{% endif %}
|
|
@ -0,0 +1,113 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if families %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
{% set fontType %}{{param.fontTypeName|default:"FontConvertible"}}{% endset %}
|
||||
#if os(macOS)
|
||||
import AppKit.NSFont
|
||||
#elseif os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit.UIFont
|
||||
#endif
|
||||
|
||||
// Deprecated typealiases
|
||||
@available(*, deprecated, renamed: "{{fontType}}.Font", message: "This typealias will be removed in SwiftGen 7.0")
|
||||
{{accessModifier}} typealias {{param.fontAliasName|default:"Font"}} = {{fontType}}.Font
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length
|
||||
|
||||
// MARK: - Fonts
|
||||
|
||||
// swiftlint:disable identifier_name line_length type_body_length
|
||||
{% macro transformPath path %}{% filter removeNewlines %}
|
||||
{% if param.preservePath %}
|
||||
{{path}}
|
||||
{% else %}
|
||||
{{path|basename}}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{{accessModifier}} enum {{param.enumName|default:"FontFamily"}} {
|
||||
{% for family in families %}
|
||||
{{accessModifier}} enum {{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% for font in family.fonts %}
|
||||
{{accessModifier}} static let {{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{fontType}}(name: "{{font.name}}", family: "{{family.name}}", path: "{% call transformPath font.path %}")
|
||||
{% endfor %}
|
||||
{{accessModifier}} static let all: [{{fontType}}] = [{% for font in family.fonts %}{{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{{ ", " if not forloop.last }}{% endfor %}]
|
||||
}
|
||||
{% endfor %}
|
||||
{{accessModifier}} static let allCustomFonts: [{{fontType}}] = [{% for family in families %}{{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.all{{ ", " if not forloop.last }}{% endfor %}].flatMap { $0 }
|
||||
{{accessModifier}} static func registerAllCustomFonts() {
|
||||
allCustomFonts.forEach { $0.register() }
|
||||
}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length type_body_length
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
{{accessModifier}} struct {{fontType}} {
|
||||
{{accessModifier}} let name: String
|
||||
{{accessModifier}} let family: String
|
||||
{{accessModifier}} let path: String
|
||||
|
||||
#if os(macOS)
|
||||
{{accessModifier}} typealias Font = NSFont
|
||||
#elseif os(iOS) || os(tvOS) || os(watchOS)
|
||||
{{accessModifier}} typealias Font = UIFont
|
||||
#endif
|
||||
|
||||
{{accessModifier}} func font(size: CGFloat) -> Font {
|
||||
guard let font = Font(font: self, size: size) else {
|
||||
fatalError("Unable to initialize font '\(name)' (\(family))")
|
||||
}
|
||||
return font
|
||||
}
|
||||
|
||||
{{accessModifier}} func register() {
|
||||
// swiftlint:disable:next conditional_returns_on_newline
|
||||
guard let url = url else { return }
|
||||
CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil)
|
||||
}
|
||||
|
||||
fileprivate var url: URL? {
|
||||
// swiftlint:disable:next implicit_return
|
||||
{% if param.lookupFunction %}
|
||||
return {{param.lookupFunction}}(name, family, path)
|
||||
{% else %}
|
||||
return {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil)
|
||||
{% endif %}
|
||||
}
|
||||
}
|
||||
|
||||
{{accessModifier}} extension {{fontType}}.Font {
|
||||
convenience init?(font: {{fontType}}, size: CGFloat) {
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
if !UIFont.fontNames(forFamilyName: font.family).contains(font.name) {
|
||||
font.register()
|
||||
}
|
||||
#elseif os(macOS)
|
||||
if let url = font.url, CTFontManagerGetScopeForURL(url as CFURL) == .none {
|
||||
font.register()
|
||||
}
|
||||
#endif
|
||||
|
||||
self.init(name: font.name, size: size)
|
||||
}
|
||||
}
|
||||
{% if not param.bundle and not param.lookupFunction %}
|
||||
|
||||
// swiftlint:disable convenience_type
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type
|
||||
{% endif %}
|
||||
{% else %}
|
||||
// No fonts found
|
||||
{% endif %}
|
|
@ -0,0 +1,157 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if platform and storyboards %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
{% set isAppKit %}{% if platform == "macOS" %}true{% endif %}{% endset %}
|
||||
{% set prefix %}{% if isAppKit %}NS{% else %}UI{% endif %}{% endset %}
|
||||
{% set controller %}{% if isAppKit %}Controller{% else %}ViewController{% endif %}{% endset %}
|
||||
// swiftlint:disable sorted_imports
|
||||
import Foundation
|
||||
{% for module in modules where module != env.PRODUCT_MODULE_NAME and module != param.module %}
|
||||
import {{module}}
|
||||
{% endfor %}
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length implicit_return
|
||||
|
||||
// MARK: - Storyboard Scenes
|
||||
|
||||
// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name
|
||||
{% macro moduleName item %}{% filter removeNewlines %}
|
||||
{% if item.moduleIsPlaceholder %}
|
||||
{{ env.PRODUCT_MODULE_NAME|default:param.module }}
|
||||
{% else %}
|
||||
{{ item.module }}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro className item %}{% filter removeNewlines %}
|
||||
{% set module %}{% call moduleName item %}{% endset %}
|
||||
{% if module and ( not param.ignoreTargetModule or module != env.PRODUCT_MODULE_NAME and module != param.module ) %}
|
||||
{{module}}.
|
||||
{% endif %}
|
||||
{{item.type}}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{{accessModifier}} enum {{param.enumName|default:"StoryboardScene"}} {
|
||||
{% for storyboard in storyboards %}
|
||||
{% set storyboardName %}{{storyboard.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}{% endset %}
|
||||
{{accessModifier}} enum {{storyboardName}}: StoryboardType {
|
||||
{{accessModifier}} static let storyboardName = "{{storyboard.name}}"
|
||||
{% if storyboard.initialScene %}
|
||||
|
||||
{% set sceneClass %}{% call className storyboard.initialScene %}{% endset %}
|
||||
{{accessModifier}} static let initialScene = InitialSceneType<{{sceneClass}}>(storyboard: {{storyboardName}}.self)
|
||||
{% endif %}
|
||||
{% for scene in storyboard.scenes %}
|
||||
|
||||
{% set sceneID %}{{scene.identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
|
||||
{% set sceneClass %}{% call className scene %}{% endset %}
|
||||
{{accessModifier}} static let {{sceneID}} = SceneType<{{sceneClass}}>(storyboard: {{storyboardName}}.self, identifier: "{{scene.identifier}}")
|
||||
{% endfor %}
|
||||
}
|
||||
{% endfor %}
|
||||
}
|
||||
// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
{{accessModifier}} protocol StoryboardType {
|
||||
static var storyboardName: String { get }
|
||||
}
|
||||
|
||||
{{accessModifier}} extension StoryboardType {
|
||||
static var storyboard: {{prefix}}Storyboard {
|
||||
let name = {% if isAppKit %}NSStoryboard.Name({% endif %}self.storyboardName{% if isAppKit %}){% endif %}
|
||||
{% if param.lookupFunction %}
|
||||
return {{param.lookupFunction}}(name)
|
||||
{% else %}
|
||||
return {{prefix}}Storyboard(name: name, bundle: {{param.bundle|default:"BundleToken.bundle"}})
|
||||
{% endif %}
|
||||
}
|
||||
}
|
||||
|
||||
{{accessModifier}} struct SceneType<T{% if not isAppKit %}: UIViewController{% endif %}> {
|
||||
{{accessModifier}} let storyboard: StoryboardType.Type
|
||||
{{accessModifier}} let identifier: String
|
||||
|
||||
{{accessModifier}} func instantiate() -> T {
|
||||
let identifier = {% if isAppKit %}NSStoryboard.SceneIdentifier({% endif %}self.identifier{% if isAppKit %}){% endif %}
|
||||
guard let controller = storyboard.storyboard.instantiate{{controller}}(withIdentifier: identifier) as? T else {
|
||||
fatalError("{{controller}} '\(identifier)' is not of the expected class \(T.self).")
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
{% if isAppKit %}
|
||||
@available(macOS 10.15, *)
|
||||
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController {
|
||||
return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block)
|
||||
}
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController {
|
||||
return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block)
|
||||
}
|
||||
{% else %}
|
||||
@available(iOS 13.0, tvOS 13.0, *)
|
||||
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T {
|
||||
return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block)
|
||||
}
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
{{accessModifier}} struct InitialSceneType<T{% if not isAppKit %}: UIViewController{% endif %}> {
|
||||
{{accessModifier}} let storyboard: StoryboardType.Type
|
||||
|
||||
{{accessModifier}} func instantiate() -> T {
|
||||
guard let controller = storyboard.storyboard.instantiateInitial{{controller}}() as? T else {
|
||||
fatalError("{{controller}} is not of the expected class \(T.self).")
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
{% if isAppKit %}
|
||||
@available(macOS 10.15, *)
|
||||
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController {
|
||||
guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else {
|
||||
fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.")
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController {
|
||||
guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else {
|
||||
fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.")
|
||||
}
|
||||
return controller
|
||||
}
|
||||
{% else %}
|
||||
@available(iOS 13.0, tvOS 13.0, *)
|
||||
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T {
|
||||
guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else {
|
||||
fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.")
|
||||
}
|
||||
return controller
|
||||
}
|
||||
{% endif %}
|
||||
}
|
||||
{% if not param.bundle and not param.lookupFunction %}
|
||||
|
||||
// swiftlint:disable convenience_type
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type
|
||||
{% endif %}
|
||||
{% elif storyboards %}
|
||||
// Mixed AppKit and UIKit storyboard files found, please invoke swiftgen with these separately
|
||||
{% else %}
|
||||
// No storyboard found
|
||||
{% endif %}
|
|
@ -0,0 +1,159 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if platform and storyboards %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
{% set isAppKit %}{% if platform == "macOS" %}true{% endif %}{% endset %}
|
||||
{% set prefix %}{% if isAppKit %}NS{% else %}UI{% endif %}{% endset %}
|
||||
{% set controller %}{% if isAppKit %}Controller{% else %}ViewController{% endif %}{% endset %}
|
||||
// swiftlint:disable sorted_imports
|
||||
import Foundation
|
||||
{% for module in modules where module != env.PRODUCT_MODULE_NAME and module != param.module %}
|
||||
import {{module}}
|
||||
{% endfor %}
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length implicit_return
|
||||
|
||||
// MARK: - Storyboard Scenes
|
||||
|
||||
// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name
|
||||
{% macro moduleName item %}{% filter removeNewlines %}
|
||||
{% if item.moduleIsPlaceholder %}
|
||||
{{ env.PRODUCT_MODULE_NAME|default:param.module }}
|
||||
{% else %}
|
||||
{{ item.module }}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro className item %}{% filter removeNewlines %}
|
||||
{% set module %}{% call moduleName item %}{% endset %}
|
||||
{% if module and ( not param.ignoreTargetModule or module != env.PRODUCT_MODULE_NAME and module != param.module ) %}
|
||||
{{module}}.
|
||||
{% endif %}
|
||||
{{item.type}}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{{accessModifier}} enum {{param.enumName|default:"StoryboardScene"}} {
|
||||
{% for storyboard in storyboards %}
|
||||
{% set storyboardName %}{{storyboard.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}{% endset %}
|
||||
{{accessModifier}} enum {{storyboardName}}: StoryboardType {
|
||||
{{accessModifier}} static let storyboardName = "{{storyboard.name}}"
|
||||
{% if storyboard.initialScene %}
|
||||
|
||||
{% set sceneClass %}{% call className storyboard.initialScene %}{% endset %}
|
||||
{{accessModifier}} static let initialScene = InitialSceneType<{{sceneClass}}>(storyboard: {{storyboardName}}.self)
|
||||
{% endif %}
|
||||
{% for scene in storyboard.scenes %}
|
||||
|
||||
{% set sceneID %}{{scene.identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
|
||||
{% set sceneClass %}{% call className scene %}{% endset %}
|
||||
{{accessModifier}} static let {{sceneID}} = SceneType<{{sceneClass}}>(storyboard: {{storyboardName}}.self, identifier: "{{scene.identifier}}")
|
||||
{% endfor %}
|
||||
}
|
||||
{% endfor %}
|
||||
}
|
||||
// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
{{accessModifier}} protocol StoryboardType {
|
||||
static var storyboardName: String { get }
|
||||
}
|
||||
|
||||
{{accessModifier}} extension StoryboardType {
|
||||
static var storyboard: {{prefix}}Storyboard {
|
||||
let name = {% if isAppKit %}NSStoryboard.Name({% endif %}self.storyboardName{% if isAppKit %}){% endif %}
|
||||
{% if param.lookupFunction %}
|
||||
return {{param.lookupFunction}}(name)
|
||||
{% else %}
|
||||
return {{prefix}}Storyboard(name: name, bundle: {{param.bundle|default:"BundleToken.bundle"}})
|
||||
{% endif %}
|
||||
}
|
||||
}
|
||||
|
||||
{{accessModifier}} struct SceneType<T{% if not isAppKit %}: UIViewController{% endif %}> {
|
||||
{{accessModifier}} let storyboard: StoryboardType.Type
|
||||
{{accessModifier}} let identifier: String
|
||||
|
||||
{{accessModifier}} func instantiate() -> T {
|
||||
let identifier = {% if isAppKit %}NSStoryboard.SceneIdentifier({% endif %}self.identifier{% if isAppKit %}){% endif %}
|
||||
guard let controller = storyboard.storyboard.instantiate{{controller}}(withIdentifier: identifier) as? T else {
|
||||
fatalError("{{controller}} '\(identifier)' is not of the expected class \(T.self).")
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
{% if isAppKit %}
|
||||
@available(macOS 10.15, *)
|
||||
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController {
|
||||
let identifier = NSStoryboard.SceneIdentifier(self.identifier)
|
||||
return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block)
|
||||
}
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController {
|
||||
let identifier = NSStoryboard.SceneIdentifier(self.identifier)
|
||||
return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block)
|
||||
}
|
||||
{% else %}
|
||||
@available(iOS 13.0, tvOS 13.0, *)
|
||||
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T {
|
||||
return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block)
|
||||
}
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
{{accessModifier}} struct InitialSceneType<T{% if not isAppKit %}: UIViewController{% endif %}> {
|
||||
{{accessModifier}} let storyboard: StoryboardType.Type
|
||||
|
||||
{{accessModifier}} func instantiate() -> T {
|
||||
guard let controller = storyboard.storyboard.instantiateInitial{{controller}}() as? T else {
|
||||
fatalError("{{controller}} is not of the expected class \(T.self).")
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
{% if isAppKit %}
|
||||
@available(macOS 10.15, *)
|
||||
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController {
|
||||
guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else {
|
||||
fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.")
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController {
|
||||
guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else {
|
||||
fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.")
|
||||
}
|
||||
return controller
|
||||
}
|
||||
{% else %}
|
||||
@available(iOS 13.0, tvOS 13.0, *)
|
||||
{{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T {
|
||||
guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else {
|
||||
fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.")
|
||||
}
|
||||
return controller
|
||||
}
|
||||
{% endif %}
|
||||
}
|
||||
{% if not param.bundle and not param.lookupFunction %}
|
||||
|
||||
// swiftlint:disable convenience_type
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type
|
||||
{% endif %}
|
||||
{% elif storyboards %}
|
||||
// Mixed AppKit and UIKit storyboard files found, please invoke swiftgen with these separately
|
||||
{% else %}
|
||||
// No storyboard found
|
||||
{% endif %}
|
|
@ -0,0 +1,60 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if platform and storyboards %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
{% set isAppKit %}{% if platform == "macOS" %}true{% endif %}{% endset %}
|
||||
// swiftlint:disable sorted_imports
|
||||
import Foundation
|
||||
{% for module in modules where module != env.PRODUCT_MODULE_NAME and module != param.module %}
|
||||
import {{module}}
|
||||
{% endfor %}
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length
|
||||
|
||||
// MARK: - Storyboard Segues
|
||||
|
||||
// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name
|
||||
{{accessModifier}} enum {{param.enumName|default:"StoryboardSegue"}} {
|
||||
{% for storyboard in storyboards where storyboard.segues %}
|
||||
{{accessModifier}} enum {{storyboard.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}: String, SegueType {
|
||||
{% for segue in storyboard.segues %}
|
||||
{% set segueID %}{{segue.identifier|swiftIdentifier:"pretty"|lowerFirstWord}}{% endset %}
|
||||
case {{segueID|escapeReservedKeywords}}{% if segueID != segue.identifier %} = "{{segue.identifier}}"{% endif %}
|
||||
{% endfor %}
|
||||
}
|
||||
{% endfor %}
|
||||
}
|
||||
// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
{{accessModifier}} protocol SegueType: RawRepresentable {}
|
||||
|
||||
{{accessModifier}} extension {% if isAppKit %}NSSeguePerforming{% else %}UIViewController{% endif %} {
|
||||
func perform<S: SegueType>(segue: S, sender: Any? = nil) where S.RawValue == String {
|
||||
let identifier = {% if isAppKit %}NSStoryboardSegue.Identifier({% endif %}segue.rawValue{% if isAppKit %}){% endif %}
|
||||
performSegue{% if isAppKit %}?{% endif %}(withIdentifier: identifier, sender: sender)
|
||||
}
|
||||
}
|
||||
|
||||
{{accessModifier}} extension SegueType where RawValue == String {
|
||||
init?(_ segue: {% if isAppKit %}NS{% else %}UI{% endif %}StoryboardSegue) {
|
||||
{% if isAppKit %}
|
||||
#if swift(>=4.2)
|
||||
guard let identifier = segue.identifier else { return nil }
|
||||
#else
|
||||
guard let identifier = segue.identifier?.rawValue else { return nil }
|
||||
#endif
|
||||
{% else %}
|
||||
guard let identifier = segue.identifier else { return nil }
|
||||
{% endif %}
|
||||
self.init(rawValue: identifier)
|
||||
}
|
||||
}
|
||||
{% elif storyboards %}
|
||||
// Mixed AppKit and UIKit storyboard files found, please invoke swiftgen with these separately
|
||||
{% else %}
|
||||
// No storyboard found
|
||||
{% endif %}
|
|
@ -0,0 +1,60 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if platform and storyboards %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
{% set isAppKit %}{% if platform == "macOS" %}true{% endif %}{% endset %}
|
||||
// swiftlint:disable sorted_imports
|
||||
import Foundation
|
||||
{% for module in modules where module != env.PRODUCT_MODULE_NAME and module != param.module %}
|
||||
import {{module}}
|
||||
{% endfor %}
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length
|
||||
|
||||
// MARK: - Storyboard Segues
|
||||
|
||||
// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name
|
||||
{{accessModifier}} enum {{param.enumName|default:"StoryboardSegue"}} {
|
||||
{% for storyboard in storyboards where storyboard.segues %}
|
||||
{{accessModifier}} enum {{storyboard.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}: String, SegueType {
|
||||
{% for segue in storyboard.segues %}
|
||||
{% set segueID %}{{segue.identifier|swiftIdentifier:"pretty"|lowerFirstWord}}{% endset %}
|
||||
case {{segueID|escapeReservedKeywords}}{% if segueID != segue.identifier %} = "{{segue.identifier}}"{% endif %}
|
||||
{% endfor %}
|
||||
}
|
||||
{% endfor %}
|
||||
}
|
||||
// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
{{accessModifier}} protocol SegueType: RawRepresentable {}
|
||||
|
||||
{{accessModifier}} extension {% if isAppKit %}NSSeguePerforming{% else %}UIViewController{% endif %} {
|
||||
func perform<S: SegueType>(segue: S, sender: Any? = nil) where S.RawValue == String {
|
||||
let identifier = {% if isAppKit %}NSStoryboardSegue.Identifier({% endif %}segue.rawValue{% if isAppKit %}){% endif %}
|
||||
performSegue{% if isAppKit %}?{% endif %}(withIdentifier: identifier, sender: sender)
|
||||
}
|
||||
}
|
||||
|
||||
{{accessModifier}} extension SegueType where RawValue == String {
|
||||
init?(_ segue: {% if isAppKit %}NS{% else %}UI{% endif %}StoryboardSegue) {
|
||||
{% if isAppKit %}
|
||||
#if swift(>=4.2)
|
||||
guard let identifier = segue.identifier else { return nil }
|
||||
#else
|
||||
guard let identifier = segue.identifier?.rawValue else { return nil }
|
||||
#endif
|
||||
{% else %}
|
||||
guard let identifier = segue.identifier else { return nil }
|
||||
{% endif %}
|
||||
self.init(rawValue: identifier)
|
||||
}
|
||||
}
|
||||
{% elif storyboards %}
|
||||
// Mixed AppKit and UIKit storyboard files found, please invoke swiftgen with these separately
|
||||
{% else %}
|
||||
// No storyboard found
|
||||
{% endif %}
|
|
@ -0,0 +1,82 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if files %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length
|
||||
|
||||
// MARK: - JSON Files
|
||||
{% macro fileBlock file %}
|
||||
{% call documentBlock file file.document %}
|
||||
{% endmacro %}
|
||||
{% macro documentBlock file document %}
|
||||
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
|
||||
{% if document.metadata.type == "Array" %}
|
||||
{{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %}
|
||||
{% elif document.metadata.type == "Dictionary" %}
|
||||
{% for key,value in document.metadata.properties %}
|
||||
{{accessModifier}} {% call propertyBlock key value document.data %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% if metadata.type == "Array" %}
|
||||
[{% call typeBlock metadata.element %}]
|
||||
{% elif metadata.type == "Dictionary" %}
|
||||
[String: Any]
|
||||
{% elif metadata.type == "Optional" %}
|
||||
Any?
|
||||
{% else %}
|
||||
{{metadata.type}}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %}
|
||||
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
|
||||
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
|
||||
static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% if metadata.type == "String" %}
|
||||
"{{ value }}"
|
||||
{% elif metadata.type == "Optional" %}
|
||||
nil
|
||||
{% elif metadata.type == "Array" and value %}
|
||||
[{% for value in value %}
|
||||
{% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% endfor %}]
|
||||
{% elif metadata.type == "Dictionary" %}
|
||||
[{% for key,value in value %}
|
||||
"{{key}}": {% call valueBlock value metadata.properties[key] %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% empty %}
|
||||
:
|
||||
{% endfor %}]
|
||||
{% elif metadata.type == "Bool" %}
|
||||
{% if value %}true{% else %}false{% endif %}
|
||||
{% else %}
|
||||
{{ value }}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
|
||||
// swiftlint:disable identifier_name line_length number_separator type_body_length
|
||||
{{accessModifier}} enum {{param.enumName|default:"JSONFiles"}} {
|
||||
{% if files.count > 1 or param.forceFileNameEnum %}
|
||||
{% for file in files %}
|
||||
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call fileBlock files.first %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length number_separator type_body_length
|
||||
{% else %}
|
||||
// No files found
|
||||
{% endif %}
|
|
@ -0,0 +1,82 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if files %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length
|
||||
|
||||
// MARK: - JSON Files
|
||||
{% macro fileBlock file %}
|
||||
{% call documentBlock file file.document %}
|
||||
{% endmacro %}
|
||||
{% macro documentBlock file document %}
|
||||
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
|
||||
{% if document.metadata.type == "Array" %}
|
||||
{{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %}
|
||||
{% elif document.metadata.type == "Dictionary" %}
|
||||
{% for key,value in document.metadata.properties %}
|
||||
{{accessModifier}} {% call propertyBlock key value document.data %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% if metadata.type == "Array" %}
|
||||
[{% call typeBlock metadata.element %}]
|
||||
{% elif metadata.type == "Dictionary" %}
|
||||
[String: Any]
|
||||
{% elif metadata.type == "Optional" %}
|
||||
Any?
|
||||
{% else %}
|
||||
{{metadata.type}}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %}
|
||||
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
|
||||
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
|
||||
static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% if metadata.type == "String" %}
|
||||
"{{ value }}"
|
||||
{% elif metadata.type == "Optional" %}
|
||||
nil
|
||||
{% elif metadata.type == "Array" and value %}
|
||||
[{% for value in value %}
|
||||
{% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% endfor %}]
|
||||
{% elif metadata.type == "Dictionary" %}
|
||||
[{% for key,value in value %}
|
||||
"{{key}}": {% call valueBlock value metadata.properties[key] %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% empty %}
|
||||
:
|
||||
{% endfor %}]
|
||||
{% elif metadata.type == "Bool" %}
|
||||
{% if value %}true{% else %}false{% endif %}
|
||||
{% else %}
|
||||
{{ value }}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
|
||||
// swiftlint:disable identifier_name line_length number_separator type_body_length
|
||||
{{accessModifier}} enum {{param.enumName|default:"JSONFiles"}} {
|
||||
{% if files.count > 1 or param.forceFileNameEnum %}
|
||||
{% for file in files %}
|
||||
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call fileBlock files.first %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length number_separator type_body_length
|
||||
{% else %}
|
||||
// No files found
|
||||
{% endif %}
|
|
@ -0,0 +1,112 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if files %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length
|
||||
|
||||
// MARK: - JSON Files
|
||||
{% macro fileBlock file %}
|
||||
{% call documentBlock file file.document %}
|
||||
{% endmacro %}
|
||||
{% macro documentBlock file document %}
|
||||
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
|
||||
{% if document.metadata.type == "Array" %}
|
||||
{{accessModifier}} static let items: {{rootType}} = objectFromJSON(at: "{% call transformPath file.path %}")
|
||||
{% elif document.metadata.type == "Dictionary" %}
|
||||
private static let _document = JSONDocument(path: "{% call transformPath file.path %}")
|
||||
|
||||
{% for key,value in document.metadata.properties %}
|
||||
{{accessModifier}} {% call propertyBlock key value %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{{accessModifier}} static let value: {{rootType}} = objectFromJSON(at: "{% call transformPath file.path %}")
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% if metadata.type == "Array" %}
|
||||
[{% call typeBlock metadata.element %}]
|
||||
{% elif metadata.type == "Dictionary" %}
|
||||
[String: Any]
|
||||
{% elif metadata.type == "Optional" %}
|
||||
Any?
|
||||
{% else %}
|
||||
{{metadata.type}}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro propertyBlock key metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
|
||||
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
|
||||
static let {{propertyName}}: {{propertyType}} = _document["{{key}}"]
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro transformPath path %}{% filter removeNewlines %}
|
||||
{% if param.preservePath %}
|
||||
{{path}}
|
||||
{% else %}
|
||||
{{path|basename}}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
|
||||
// swiftlint:disable identifier_name line_length type_body_length
|
||||
{{accessModifier}} enum {{param.enumName|default:"JSONFiles"}} {
|
||||
{% if files.count > 1 or param.forceFileNameEnum %}
|
||||
{% for file in files %}
|
||||
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call fileBlock files.first %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length type_body_length
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
private func objectFromJSON<T>(at path: String) -> T {
|
||||
{% if param.lookupFunction %}
|
||||
guard let url = {{param.lookupFunction}}(path),
|
||||
{% else %}
|
||||
guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil),
|
||||
{% endif %}
|
||||
let json = try? JSONSerialization.jsonObject(with: Data(contentsOf: url), options: []),
|
||||
let result = json as? T else {
|
||||
fatalError("Unable to load JSON at path: \(path)")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private struct JSONDocument {
|
||||
let data: [String: Any]
|
||||
|
||||
init(path: String) {
|
||||
self.data = objectFromJSON(at: path)
|
||||
}
|
||||
|
||||
subscript<T>(key: String) -> T {
|
||||
guard let result = data[key] as? T else {
|
||||
fatalError("Property '\(key)' is not of type \(T.self)")
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
{% if not param.bundle and not param.lookupFunction %}
|
||||
|
||||
// swiftlint:disable convenience_type
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type
|
||||
{% endif %}
|
||||
{% else %}
|
||||
// No files found
|
||||
{% endif %}
|
|
@ -0,0 +1,112 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if files %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length
|
||||
|
||||
// MARK: - JSON Files
|
||||
{% macro fileBlock file %}
|
||||
{% call documentBlock file file.document %}
|
||||
{% endmacro %}
|
||||
{% macro documentBlock file document %}
|
||||
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
|
||||
{% if document.metadata.type == "Array" %}
|
||||
{{accessModifier}} static let items: {{rootType}} = objectFromJSON(at: "{% call transformPath file.path %}")
|
||||
{% elif document.metadata.type == "Dictionary" %}
|
||||
private static let _document = JSONDocument(path: "{% call transformPath file.path %}")
|
||||
|
||||
{% for key,value in document.metadata.properties %}
|
||||
{{accessModifier}} {% call propertyBlock key value %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{{accessModifier}} static let value: {{rootType}} = objectFromJSON(at: "{% call transformPath file.path %}")
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% if metadata.type == "Array" %}
|
||||
[{% call typeBlock metadata.element %}]
|
||||
{% elif metadata.type == "Dictionary" %}
|
||||
[String: Any]
|
||||
{% elif metadata.type == "Optional" %}
|
||||
Any?
|
||||
{% else %}
|
||||
{{metadata.type}}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro propertyBlock key metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
|
||||
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
|
||||
static let {{propertyName}}: {{propertyType}} = _document["{{key}}"]
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro transformPath path %}{% filter removeNewlines %}
|
||||
{% if param.preservePath %}
|
||||
{{path}}
|
||||
{% else %}
|
||||
{{path|basename}}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
|
||||
// swiftlint:disable identifier_name line_length type_body_length
|
||||
{{accessModifier}} enum {{param.enumName|default:"JSONFiles"}} {
|
||||
{% if files.count > 1 or param.forceFileNameEnum %}
|
||||
{% for file in files %}
|
||||
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call fileBlock files.first %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length type_body_length
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
private func objectFromJSON<T>(at path: String) -> T {
|
||||
{% if param.lookupFunction %}
|
||||
guard let url = {{param.lookupFunction}}(path),
|
||||
{% else %}
|
||||
guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil),
|
||||
{% endif %}
|
||||
let json = try? JSONSerialization.jsonObject(with: Data(contentsOf: url), options: []),
|
||||
let result = json as? T else {
|
||||
fatalError("Unable to load JSON at path: \(path)")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private struct JSONDocument {
|
||||
let data: [String: Any]
|
||||
|
||||
init(path: String) {
|
||||
self.data = objectFromJSON(at: path)
|
||||
}
|
||||
|
||||
subscript<T>(key: String) -> T {
|
||||
guard let result = data[key] as? T else {
|
||||
fatalError("Property '\(key)' is not of type \(T.self)")
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
{% if not param.bundle and not param.lookupFunction %}
|
||||
|
||||
// swiftlint:disable convenience_type
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type
|
||||
{% endif %}
|
||||
{% else %}
|
||||
// No files found
|
||||
{% endif %}
|
|
@ -0,0 +1,82 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if files %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length
|
||||
|
||||
// MARK: - Plist Files
|
||||
{% macro fileBlock file %}
|
||||
{% call documentBlock file file.document %}
|
||||
{% endmacro %}
|
||||
{% macro documentBlock file document %}
|
||||
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
|
||||
{% if document.metadata.type == "Array" %}
|
||||
{{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %}
|
||||
{% elif document.metadata.type == "Dictionary" %}
|
||||
{% for key,value in document.metadata.properties %}
|
||||
{{accessModifier}} {% call propertyBlock key value document.data %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% if metadata.type == "Array" %}
|
||||
[{% call typeBlock metadata.element %}]
|
||||
{% elif metadata.type == "Dictionary" %}
|
||||
[String: Any]
|
||||
{% else %}
|
||||
{{metadata.type}}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %}
|
||||
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
|
||||
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
|
||||
static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% if metadata.type == "String" %}
|
||||
"{{ value }}"
|
||||
{% elif metadata.type == "Date" %}
|
||||
Date(timeIntervalSinceReferenceDate: {{ value.timeIntervalSinceReferenceDate }})
|
||||
{% elif metadata.type == "Optional" %}
|
||||
nil
|
||||
{% elif metadata.type == "Array" and value %}
|
||||
[{% for value in value %}
|
||||
{% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% endfor %}]
|
||||
{% elif metadata.type == "Dictionary" %}
|
||||
[{% for key,value in value %}
|
||||
"{{key}}": {% call valueBlock value metadata.properties[key] %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% empty %}
|
||||
:
|
||||
{% endfor %}]
|
||||
{% elif metadata.type == "Bool" %}
|
||||
{% if value %}true{% else %}false{% endif %}
|
||||
{% else %}
|
||||
{{ value }}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
|
||||
// swiftlint:disable identifier_name line_length number_separator type_body_length
|
||||
{{accessModifier}} enum {{param.enumName|default:"PlistFiles"}} {
|
||||
{% if files.count > 1 or param.forceFileNameEnum %}
|
||||
{% for file in files %}
|
||||
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call fileBlock files.first %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length number_separator type_body_length
|
||||
{% else %}
|
||||
// No files found
|
||||
{% endif %}
|
|
@ -0,0 +1,82 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if files %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length
|
||||
|
||||
// MARK: - Plist Files
|
||||
{% macro fileBlock file %}
|
||||
{% call documentBlock file file.document %}
|
||||
{% endmacro %}
|
||||
{% macro documentBlock file document %}
|
||||
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
|
||||
{% if document.metadata.type == "Array" %}
|
||||
{{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %}
|
||||
{% elif document.metadata.type == "Dictionary" %}
|
||||
{% for key,value in document.metadata.properties %}
|
||||
{{accessModifier}} {% call propertyBlock key value document.data %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% if metadata.type == "Array" %}
|
||||
[{% call typeBlock metadata.element %}]
|
||||
{% elif metadata.type == "Dictionary" %}
|
||||
[String: Any]
|
||||
{% else %}
|
||||
{{metadata.type}}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %}
|
||||
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
|
||||
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
|
||||
static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% if metadata.type == "String" %}
|
||||
"{{ value }}"
|
||||
{% elif metadata.type == "Date" %}
|
||||
Date(timeIntervalSinceReferenceDate: {{ value.timeIntervalSinceReferenceDate }})
|
||||
{% elif metadata.type == "Optional" %}
|
||||
nil
|
||||
{% elif metadata.type == "Array" and value %}
|
||||
[{% for value in value %}
|
||||
{% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% endfor %}]
|
||||
{% elif metadata.type == "Dictionary" %}
|
||||
[{% for key,value in value %}
|
||||
"{{key}}": {% call valueBlock value metadata.properties[key] %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% empty %}
|
||||
:
|
||||
{% endfor %}]
|
||||
{% elif metadata.type == "Bool" %}
|
||||
{% if value %}true{% else %}false{% endif %}
|
||||
{% else %}
|
||||
{{ value }}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
|
||||
// swiftlint:disable identifier_name line_length number_separator type_body_length
|
||||
{{accessModifier}} enum {{param.enumName|default:"PlistFiles"}} {
|
||||
{% if files.count > 1 or param.forceFileNameEnum %}
|
||||
{% for file in files %}
|
||||
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call fileBlock files.first %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length number_separator type_body_length
|
||||
{% else %}
|
||||
// No files found
|
||||
{% endif %}
|
|
@ -0,0 +1,117 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if files %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length
|
||||
|
||||
// MARK: - Plist Files
|
||||
{% macro fileBlock file %}
|
||||
{% call documentBlock file file.document %}
|
||||
{% endmacro %}
|
||||
{% macro documentBlock file document %}
|
||||
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
|
||||
{% if document.metadata.type == "Array" %}
|
||||
{{accessModifier}} static let items: {{rootType}} = arrayFromPlist(at: "{% call transformPath file.path %}")
|
||||
{% elif document.metadata.type == "Dictionary" %}
|
||||
private static let _document = PlistDocument(path: "{% call transformPath file.path %}")
|
||||
|
||||
{% for key,value in document.metadata.properties %}
|
||||
{{accessModifier}} {% call propertyBlock key value %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
// Unsupported root type `{{rootType}}`
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% if metadata.type == "Array" %}
|
||||
[{% call typeBlock metadata.element %}]
|
||||
{% elif metadata.type == "Dictionary" %}
|
||||
[String: Any]
|
||||
{% else %}
|
||||
{{metadata.type}}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro propertyBlock key metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
|
||||
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
|
||||
static let {{propertyName}}: {{propertyType}} = _document["{{key}}"]
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro transformPath path %}{% filter removeNewlines %}
|
||||
{% if param.preservePath %}
|
||||
{{path}}
|
||||
{% else %}
|
||||
{{path|basename}}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
|
||||
// swiftlint:disable identifier_name line_length type_body_length
|
||||
{{accessModifier}} enum {{param.enumName|default:"PlistFiles"}} {
|
||||
{% if files.count > 1 or param.forceFileNameEnum %}
|
||||
{% for file in files %}
|
||||
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call fileBlock files.first %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length type_body_length
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
private func arrayFromPlist<T>(at path: String) -> [T] {
|
||||
{% if param.lookupFunction %}
|
||||
guard let url = {{param.lookupFunction}}(path),
|
||||
{% else %}
|
||||
guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil),
|
||||
{% endif %}
|
||||
let data = NSArray(contentsOf: url) as? [T] else {
|
||||
fatalError("Unable to load PLIST at path: \(path)")
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
private struct PlistDocument {
|
||||
let data: [String: Any]
|
||||
|
||||
init(path: String) {
|
||||
{% if param.lookupFunction %}
|
||||
guard let url = {{param.lookupFunction}}(path),
|
||||
{% else %}
|
||||
guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil),
|
||||
{% endif %}
|
||||
let data = NSDictionary(contentsOf: url) as? [String: Any] else {
|
||||
fatalError("Unable to load PLIST at path: \(path)")
|
||||
}
|
||||
self.data = data
|
||||
}
|
||||
|
||||
subscript<T>(key: String) -> T {
|
||||
guard let result = data[key] as? T else {
|
||||
fatalError("Property '\(key)' is not of type \(T.self)")
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
{% if not param.bundle and not param.lookupFunction %}
|
||||
|
||||
// swiftlint:disable convenience_type
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type
|
||||
{% endif %}
|
||||
{% else %}
|
||||
// No files found
|
||||
{% endif %}
|
|
@ -0,0 +1,117 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if files %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length
|
||||
|
||||
// MARK: - Plist Files
|
||||
{% macro fileBlock file %}
|
||||
{% call documentBlock file file.document %}
|
||||
{% endmacro %}
|
||||
{% macro documentBlock file document %}
|
||||
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
|
||||
{% if document.metadata.type == "Array" %}
|
||||
{{accessModifier}} static let items: {{rootType}} = arrayFromPlist(at: "{% call transformPath file.path %}")
|
||||
{% elif document.metadata.type == "Dictionary" %}
|
||||
private static let _document = PlistDocument(path: "{% call transformPath file.path %}")
|
||||
|
||||
{% for key,value in document.metadata.properties %}
|
||||
{{accessModifier}} {% call propertyBlock key value %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
// Unsupported root type `{{rootType}}`
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% if metadata.type == "Array" %}
|
||||
[{% call typeBlock metadata.element %}]
|
||||
{% elif metadata.type == "Dictionary" %}
|
||||
[String: Any]
|
||||
{% else %}
|
||||
{{metadata.type}}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro propertyBlock key metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
|
||||
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
|
||||
static let {{propertyName}}: {{propertyType}} = _document["{{key}}"]
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro transformPath path %}{% filter removeNewlines %}
|
||||
{% if param.preservePath %}
|
||||
{{path}}
|
||||
{% else %}
|
||||
{{path|basename}}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
|
||||
// swiftlint:disable identifier_name line_length type_body_length
|
||||
{{accessModifier}} enum {{param.enumName|default:"PlistFiles"}} {
|
||||
{% if files.count > 1 or param.forceFileNameEnum %}
|
||||
{% for file in files %}
|
||||
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call fileBlock files.first %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length type_body_length
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
private func arrayFromPlist<T>(at path: String) -> [T] {
|
||||
{% if param.lookupFunction %}
|
||||
guard let url = {{param.lookupFunction}}(path),
|
||||
{% else %}
|
||||
guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil),
|
||||
{% endif %}
|
||||
let data = NSArray(contentsOf: url) as? [T] else {
|
||||
fatalError("Unable to load PLIST at path: \(path)")
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
private struct PlistDocument {
|
||||
let data: [String: Any]
|
||||
|
||||
init(path: String) {
|
||||
{% if param.lookupFunction %}
|
||||
guard let url = {{param.lookupFunction}}(path),
|
||||
{% else %}
|
||||
guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil),
|
||||
{% endif %}
|
||||
let data = NSDictionary(contentsOf: url) as? [String: Any] else {
|
||||
fatalError("Unable to load PLIST at path: \(path)")
|
||||
}
|
||||
self.data = data
|
||||
}
|
||||
|
||||
subscript<T>(key: String) -> T {
|
||||
guard let result = data[key] as? T else {
|
||||
fatalError("Property '\(key)' is not of type \(T.self)")
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
{% if not param.bundle and not param.lookupFunction %}
|
||||
|
||||
// swiftlint:disable convenience_type
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type
|
||||
{% endif %}
|
||||
{% else %}
|
||||
// No files found
|
||||
{% endif %}
|
|
@ -0,0 +1,99 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if tables.count > 0 %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command file_length implicit_return
|
||||
|
||||
// MARK: - Strings
|
||||
|
||||
{% macro parametersBlock types %}{% filter removeNewlines:"leading" %}
|
||||
{% for type in types %}
|
||||
{% if type == "String" %}
|
||||
_ p{{forloop.counter}}: Any
|
||||
{% else %}
|
||||
_ p{{forloop.counter}}: {{type}}
|
||||
{% endif %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% endfor %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %}
|
||||
{% for type in types %}
|
||||
{% if type == "String" %}
|
||||
String(describing: p{{forloop.counter}})
|
||||
{% elif type == "UnsafeRawPointer" %}
|
||||
Int(bitPattern: p{{forloop.counter}})
|
||||
{% else %}
|
||||
p{{forloop.counter}}
|
||||
{% endif %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% endfor %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro recursiveBlock table item %}
|
||||
{% for string in item.strings %}
|
||||
{% if not param.noComments %}
|
||||
{% for line in string.translation|split:"\n" %}
|
||||
/// {{line}}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if string.types %}
|
||||
{{accessModifier}} static func {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String {
|
||||
return {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %})
|
||||
}
|
||||
{% elif param.lookupFunction %}
|
||||
{# custom localization function is mostly used for in-app lang selection, so we want the loc to be recomputed at each call for those (hence the computed var) #}
|
||||
{{accessModifier}} static var {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}") }
|
||||
{% else %}
|
||||
{{accessModifier}} static let {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}")
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for child in item.children %}
|
||||
{% call recursiveBlock table child %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
// swiftlint:disable function_parameter_count identifier_name line_length type_body_length
|
||||
{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %}
|
||||
{{accessModifier}} enum {{enumName}} {
|
||||
{% if tables.count > 1 or param.forceFileNameEnum %}
|
||||
{% for table in tables %}
|
||||
{{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call recursiveBlock tables.first.name tables.first.levels %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable function_parameter_count identifier_name line_length type_body_length
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
extension {{enumName}} {
|
||||
private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
|
||||
{% if param.lookupFunction %}
|
||||
let format = {{ param.lookupFunction }}(key, table)
|
||||
{% else %}
|
||||
let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: nil, table: table)
|
||||
{% endif %}
|
||||
return String(format: format, locale: Locale.current, arguments: args)
|
||||
}
|
||||
}
|
||||
{% if not param.bundle and not param.lookupFunction %}
|
||||
|
||||
// swiftlint:disable convenience_type
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type
|
||||
{% endif %}
|
||||
{% else %}
|
||||
// No string found
|
||||
{% endif %}
|
|
@ -0,0 +1,99 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if tables.count > 0 %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command file_length implicit_return
|
||||
|
||||
// MARK: - Strings
|
||||
|
||||
{% macro parametersBlock types %}{% filter removeNewlines:"leading" %}
|
||||
{% for type in types %}
|
||||
{% if type == "String" %}
|
||||
_ p{{forloop.counter}}: Any
|
||||
{% else %}
|
||||
_ p{{forloop.counter}}: {{type}}
|
||||
{% endif %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% endfor %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %}
|
||||
{% for type in types %}
|
||||
{% if type == "String" %}
|
||||
String(describing: p{{forloop.counter}})
|
||||
{% elif type == "UnsafeRawPointer" %}
|
||||
Int(bitPattern: p{{forloop.counter}})
|
||||
{% else %}
|
||||
p{{forloop.counter}}
|
||||
{% endif %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% endfor %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro recursiveBlock table item %}
|
||||
{% for string in item.strings %}
|
||||
{% if not param.noComments %}
|
||||
{% for line in string.translation|split:"\n" %}
|
||||
/// {{line}}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if string.types %}
|
||||
{{accessModifier}} static func {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String {
|
||||
return {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %})
|
||||
}
|
||||
{% elif param.lookupFunction %}
|
||||
{# custom localization function is mostly used for in-app lang selection, so we want the loc to be recomputed at each call for those (hence the computed var) #}
|
||||
{{accessModifier}} static var {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}") }
|
||||
{% else %}
|
||||
{{accessModifier}} static let {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}")
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for child in item.children %}
|
||||
{% call recursiveBlock table child %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
// swiftlint:disable function_parameter_count identifier_name line_length type_body_length
|
||||
{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %}
|
||||
{{accessModifier}} enum {{enumName}} {
|
||||
{% if tables.count > 1 or param.forceFileNameEnum %}
|
||||
{% for table in tables %}
|
||||
{{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call recursiveBlock tables.first.name tables.first.levels %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable function_parameter_count identifier_name line_length type_body_length
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
extension {{enumName}} {
|
||||
private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
|
||||
{% if param.lookupFunction %}
|
||||
let format = {{ param.lookupFunction }}(key, table)
|
||||
{% else %}
|
||||
let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: nil, table: table)
|
||||
{% endif %}
|
||||
return String(format: format, locale: Locale.current, arguments: args)
|
||||
}
|
||||
}
|
||||
{% if not param.bundle and not param.lookupFunction %}
|
||||
|
||||
// swiftlint:disable convenience_type
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type
|
||||
{% endif %}
|
||||
{% else %}
|
||||
// No string found
|
||||
{% endif %}
|
|
@ -0,0 +1,68 @@
|
|||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if tables.count > 0 %}
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
{% macro parametersBlock types %}{% filter removeNewlines:"leading" %}
|
||||
{% for type in types %}
|
||||
({% call paramTranslate type %})p{{ forloop.counter }}{{ " :" if not forloop.last }}
|
||||
{% endfor %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %}
|
||||
{% for type in types %}
|
||||
p{{forloop.counter}}{{ ", " if not forloop.last }}
|
||||
{% endfor %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro paramTranslate swiftType %}
|
||||
{% if swiftType == "Any" %}
|
||||
id
|
||||
{% elif swiftType == "CChar" %}
|
||||
char
|
||||
{% elif swiftType == "Float" %}
|
||||
float
|
||||
{% elif swiftType == "Int" %}
|
||||
NSInteger
|
||||
{% elif swiftType == "String" %}
|
||||
id
|
||||
{% elif swiftType == "UnsafePointer<CChar>" %}
|
||||
char*
|
||||
{% elif swiftType == "UnsafeRawPointer" %}
|
||||
void*
|
||||
{% else %}
|
||||
objc-h.stencil is missing '{{swiftType}}'
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro emitOneMethod table item %}
|
||||
{% for string in item.strings %}
|
||||
{% if not param.noComments %}
|
||||
{% for line in string.translation|split:"\n" %}
|
||||
/// {{line}}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if string.types %}
|
||||
{% if string.types.count == 1 %}
|
||||
+ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}WithValue:{% call parametersBlock string.types %};
|
||||
{% else %}
|
||||
+ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}WithValues:{% call parametersBlock string.types %};
|
||||
{% endif %}
|
||||
{% else %}
|
||||
+ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}};
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for child in item.children %}
|
||||
{% call emitOneMethod table child %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
{% for table in tables %}
|
||||
@interface {{ table.name }} : NSObject
|
||||
{% call emitOneMethod table.name table.levels %}
|
||||
@end
|
||||
|
||||
{% endfor %}
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
{% else %}
|
||||
// No strings found
|
||||
{% endif %}
|
|
@ -0,0 +1,90 @@
|
|||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if tables.count > 0 %}
|
||||
#import "{{ param.headerName|default:"Localizable.h" }}"
|
||||
{% if not param.bundle %}
|
||||
|
||||
@interface BundleToken : NSObject
|
||||
@end
|
||||
|
||||
@implementation BundleToken
|
||||
@end
|
||||
{% endif %}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wformat-security"
|
||||
|
||||
static NSString* tr(NSString *tableName, NSString *key, ...) {
|
||||
NSBundle *bundle = {{param.bundle|default:"[NSBundle bundleForClass:BundleToken.class]"}};
|
||||
NSString *format = [bundle localizedStringForKey:key value:nil table:tableName];
|
||||
NSLocale *locale = [NSLocale currentLocale];
|
||||
|
||||
va_list args;
|
||||
va_start(args, key);
|
||||
NSString *result = [[NSString alloc] initWithFormat:format locale:locale arguments:args];
|
||||
va_end(args);
|
||||
|
||||
return result;
|
||||
};
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
{% macro parametersBlock types %}{% filter removeNewlines:"leading" %}
|
||||
{% for type in types %}
|
||||
({% call paramTranslate type %})p{{ forloop.counter }}{{ " :" if not forloop.last }}
|
||||
{% endfor %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %}
|
||||
{% for type in types %}
|
||||
p{{forloop.counter}}{{ ", " if not forloop.last }}
|
||||
{% endfor %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro paramTranslate swiftType %}
|
||||
{% if swiftType == "Any" %}
|
||||
id
|
||||
{% elif swiftType == "CChar" %}
|
||||
char
|
||||
{% elif swiftType == "Float" %}
|
||||
float
|
||||
{% elif swiftType == "Int" %}
|
||||
NSInteger
|
||||
{% elif swiftType == "String" %}
|
||||
id
|
||||
{% elif swiftType == "UnsafePointer<CChar>" %}
|
||||
char*
|
||||
{% elif swiftType == "UnsafeRawPointer" %}
|
||||
void*
|
||||
{% else %}
|
||||
objc-m.stencil is missing '{{swiftType}}'
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro tableContents table item %}
|
||||
{% for string in item.strings %}
|
||||
{% if string.types %}
|
||||
{% if string.types.count == 1 %}
|
||||
+ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}WithValue:{% call parametersBlock string.types %}
|
||||
{% else %}
|
||||
+ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}WithValues:{% call parametersBlock string.types %}
|
||||
{% endif %}
|
||||
{
|
||||
return tr(@"{{table}}", @"{{string.key}}", {% call argumentsBlock string.types %});
|
||||
}
|
||||
{% else %}
|
||||
+ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}} {
|
||||
return tr(@"{{table}}", @"{{string.key}}");
|
||||
}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for child in item.children %}
|
||||
{% call tableContents table child %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
{% for table in tables %}
|
||||
{% set tableName %}{{table.name|default:"Localized"}}{% endset %}
|
||||
@implementation {{ tableName }} : NSObject
|
||||
{% call tableContents table.name table.levels %}
|
||||
@end
|
||||
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
// No strings found
|
||||
{% endif %}
|
|
@ -0,0 +1,104 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if tables.count > 0 %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command file_length implicit_return
|
||||
|
||||
// MARK: - Strings
|
||||
|
||||
{% macro parametersBlock types %}{% filter removeNewlines:"leading" %}
|
||||
{% for type in types %}
|
||||
{% if type == "String" %}
|
||||
_ p{{forloop.counter}}: Any
|
||||
{% else %}
|
||||
_ p{{forloop.counter}}: {{type}}
|
||||
{% endif %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% endfor %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %}
|
||||
{% for type in types %}
|
||||
{% if type == "String" %}
|
||||
String(describing: p{{forloop.counter}})
|
||||
{% elif type == "UnsafeRawPointer" %}
|
||||
Int(bitPattern: p{{forloop.counter}})
|
||||
{% else %}
|
||||
p{{forloop.counter}}
|
||||
{% endif %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% endfor %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro recursiveBlock table item %}
|
||||
{% for string in item.strings %}
|
||||
{% if not param.noComments %}
|
||||
{% for line in string.translation|split:"\n" %}
|
||||
/// {{line}}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if string.types %}
|
||||
{{accessModifier}} static func {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String {
|
||||
return {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %})
|
||||
}
|
||||
{% elif param.lookupFunction %}
|
||||
{# custom localization function is mostly used for in-app lang selection, so we want the loc to be recomputed at each call for those (hence the computed var) #}
|
||||
{{accessModifier}} static var {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}") }
|
||||
{% else %}
|
||||
{{accessModifier}} static let {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}")
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for child in item.children %}
|
||||
|
||||
{{accessModifier}} enum {{child.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call recursiveBlock table child %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
|
||||
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %}
|
||||
{{accessModifier}} enum {{enumName}} {
|
||||
{% if tables.count > 1 or param.forceFileNameEnum %}
|
||||
{% for table in tables %}
|
||||
{{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call recursiveBlock tables.first.name tables.first.levels %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
|
||||
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
extension {{enumName}} {
|
||||
private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
|
||||
{% if param.lookupFunction %}
|
||||
let format = {{ param.lookupFunction }}(key, table)
|
||||
{% else %}
|
||||
let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: nil, table: table)
|
||||
{% endif %}
|
||||
return String(format: format, locale: Locale.current, arguments: args)
|
||||
}
|
||||
}
|
||||
{% if not param.bundle and not param.lookupFunction %}
|
||||
|
||||
// swiftlint:disable convenience_type
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type
|
||||
{% endif %}
|
||||
{% else %}
|
||||
// No string found
|
||||
{% endif %}
|
|
@ -0,0 +1,104 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if tables.count > 0 %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command file_length implicit_return
|
||||
|
||||
// MARK: - Strings
|
||||
|
||||
{% macro parametersBlock types %}{% filter removeNewlines:"leading" %}
|
||||
{% for type in types %}
|
||||
{% if type == "String" %}
|
||||
_ p{{forloop.counter}}: Any
|
||||
{% else %}
|
||||
_ p{{forloop.counter}}: {{type}}
|
||||
{% endif %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% endfor %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %}
|
||||
{% for type in types %}
|
||||
{% if type == "String" %}
|
||||
String(describing: p{{forloop.counter}})
|
||||
{% elif type == "UnsafeRawPointer" %}
|
||||
Int(bitPattern: p{{forloop.counter}})
|
||||
{% else %}
|
||||
p{{forloop.counter}}
|
||||
{% endif %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% endfor %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro recursiveBlock table item %}
|
||||
{% for string in item.strings %}
|
||||
{% if not param.noComments %}
|
||||
{% for line in string.translation|split:"\n" %}
|
||||
/// {{line}}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if string.types %}
|
||||
{{accessModifier}} static func {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String {
|
||||
return {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %})
|
||||
}
|
||||
{% elif param.lookupFunction %}
|
||||
{# custom localization function is mostly used for in-app lang selection, so we want the loc to be recomputed at each call for those (hence the computed var) #}
|
||||
{{accessModifier}} static var {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}") }
|
||||
{% else %}
|
||||
{{accessModifier}} static let {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}")
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for child in item.children %}
|
||||
|
||||
{{accessModifier}} enum {{child.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call recursiveBlock table child %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
|
||||
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %}
|
||||
{{accessModifier}} enum {{enumName}} {
|
||||
{% if tables.count > 1 or param.forceFileNameEnum %}
|
||||
{% for table in tables %}
|
||||
{{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call recursiveBlock tables.first.name tables.first.levels %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
|
||||
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
extension {{enumName}} {
|
||||
private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
|
||||
{% if param.lookupFunction %}
|
||||
let format = {{ param.lookupFunction }}(key, table)
|
||||
{% else %}
|
||||
let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: nil, table: table)
|
||||
{% endif %}
|
||||
return String(format: format, locale: Locale.current, arguments: args)
|
||||
}
|
||||
}
|
||||
{% if not param.bundle and not param.lookupFunction %}
|
||||
|
||||
// swiftlint:disable convenience_type
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type
|
||||
{% endif %}
|
||||
{% else %}
|
||||
// No string found
|
||||
{% endif %}
|
|
@ -0,0 +1,329 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if catalogs %}
|
||||
{% set enumName %}{{param.enumName|default:"Asset"}}{% endset %}
|
||||
{% set arResourceGroupType %}{{param.arResourceGroupTypeName|default:"ARResourceGroupAsset"}}{% endset %}
|
||||
{% set colorType %}{{param.colorTypeName|default:"ColorAsset"}}{% endset %}
|
||||
{% set dataType %}{{param.dataTypeName|default:"DataAsset"}}{% endset %}
|
||||
{% set imageType %}{{param.imageTypeName|default:"ImageAsset"}}{% endset %}
|
||||
{% set symbolType %}{{param.symbolTypeName|default:"SymbolAsset"}}{% endset %}
|
||||
{% set forceNamespaces %}{{param.forceProvidesNamespaces|default:"false"}}{% endset %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
#elseif os(iOS)
|
||||
{% if resourceCount.arresourcegroup > 0 %}
|
||||
import ARKit
|
||||
{% endif %}
|
||||
import UIKit
|
||||
#elseif os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
// Deprecated typealiases
|
||||
{% if resourceCount.color > 0 %}
|
||||
@available(*, deprecated, renamed: "{{colorType}}.Color", message: "This typealias will be removed in SwiftGen 7.0")
|
||||
{{accessModifier}} typealias {{param.colorAliasName|default:"AssetColorTypeAlias"}} = {{colorType}}.Color
|
||||
{% endif %}
|
||||
{% if resourceCount.image > 0 %}
|
||||
@available(*, deprecated, renamed: "{{imageType}}.Image", message: "This typealias will be removed in SwiftGen 7.0")
|
||||
{{accessModifier}} typealias {{param.imageAliasName|default:"AssetImageTypeAlias"}} = {{imageType}}.Image
|
||||
{% endif %}
|
||||
|
||||
// swiftlint:disable superfluous_disable_command file_length implicit_return
|
||||
|
||||
// MARK: - Asset Catalogs
|
||||
|
||||
{% macro enumBlock assets %}
|
||||
{% call casesBlock assets %}
|
||||
{% if param.allValues %}
|
||||
|
||||
// swiftlint:disable trailing_comma
|
||||
{% if resourceCount.arresourcegroup > 0 %}
|
||||
{{accessModifier}} static let allResourceGroups: [{{arResourceGroupType}}] = [
|
||||
{% filter indent:2 %}{% call allValuesBlock assets "arresourcegroup" "" %}{% endfilter %}
|
||||
]
|
||||
{% endif %}
|
||||
{% if resourceCount.color > 0 %}
|
||||
{{accessModifier}} static let allColors: [{{colorType}}] = [
|
||||
{% filter indent:2 %}{% call allValuesBlock assets "color" "" %}{% endfilter %}
|
||||
]
|
||||
{% endif %}
|
||||
{% if resourceCount.data > 0 %}
|
||||
{{accessModifier}} static let allDataAssets: [{{dataType}}] = [
|
||||
{% filter indent:2 %}{% call allValuesBlock assets "data" "" %}{% endfilter %}
|
||||
]
|
||||
{% endif %}
|
||||
{% if resourceCount.image > 0 %}
|
||||
{{accessModifier}} static let allImages: [{{imageType}}] = [
|
||||
{% filter indent:2 %}{% call allValuesBlock assets "image" "" %}{% endfilter %}
|
||||
]
|
||||
{% endif %}
|
||||
{% if resourceCount.symbol > 0 %}
|
||||
{{accessModifier}} static let allSymbols: [{{symbolType}}] = [
|
||||
{% filter indent:2 %}{% call allValuesBlock assets "symbol" "" %}{% endfilter %}
|
||||
]
|
||||
{% endif %}
|
||||
// swiftlint:enable trailing_comma
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro casesBlock assets %}
|
||||
{% for asset in assets %}
|
||||
{% if asset.type == "arresourcegroup" %}
|
||||
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{arResourceGroupType}}(name: "{{asset.value}}")
|
||||
{% elif asset.type == "color" %}
|
||||
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{colorType}}(name: "{{asset.value}}")
|
||||
{% elif asset.type == "data" %}
|
||||
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{dataType}}(name: "{{asset.value}}")
|
||||
{% elif asset.type == "image" %}
|
||||
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{imageType}}(name: "{{asset.value}}")
|
||||
{% elif asset.type == "symbol" %}
|
||||
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{symbolType}}(name: "{{asset.value}}")
|
||||
{% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %}
|
||||
{{accessModifier}} enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %}
|
||||
}
|
||||
{% elif asset.items %}
|
||||
{% call casesBlock asset.items %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
{% macro allValuesBlock assets filter prefix %}
|
||||
{% for asset in assets %}
|
||||
{% if asset.type == filter %}
|
||||
{{prefix}}{{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}},
|
||||
{% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %}
|
||||
{% set prefix2 %}{{prefix}}{{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.{% endset %}
|
||||
{% call allValuesBlock asset.items filter prefix2 %}
|
||||
{% elif asset.items %}
|
||||
{% call allValuesBlock asset.items filter prefix %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
// swiftlint:disable identifier_name line_length nesting type_body_length type_name
|
||||
{{accessModifier}} enum {{enumName}} {
|
||||
{% if catalogs.count > 1 or param.forceFileNameEnum %}
|
||||
{% for catalog in catalogs %}
|
||||
{{accessModifier}} enum {{catalog.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call enumBlock catalog.assets %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call enumBlock catalogs.first.assets %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length nesting type_body_length type_name
|
||||
|
||||
// MARK: - Implementation Details
|
||||
{% if resourceCount.arresourcegroup > 0 %}
|
||||
|
||||
{{accessModifier}} struct {{arResourceGroupType}} {
|
||||
{{accessModifier}} fileprivate(set) var name: String
|
||||
|
||||
#if os(iOS)
|
||||
@available(iOS 11.3, *)
|
||||
{{accessModifier}} var referenceImages: Set<ARReferenceImage> {
|
||||
return ARReferenceImage.referenceImages(in: self)
|
||||
}
|
||||
|
||||
@available(iOS 12.0, *)
|
||||
{{accessModifier}} var referenceObjects: Set<ARReferenceObject> {
|
||||
return ARReferenceObject.referenceObjects(in: self)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
@available(iOS 11.3, *)
|
||||
{{accessModifier}} extension ARReferenceImage {
|
||||
static func referenceImages(in asset: {{arResourceGroupType}}) -> Set<ARReferenceImage> {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
return referenceImages(inGroupNamed: asset.name, bundle: bundle) ?? Set()
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 12.0, *)
|
||||
{{accessModifier}} extension ARReferenceObject {
|
||||
static func referenceObjects(in asset: {{arResourceGroupType}}) -> Set<ARReferenceObject> {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
return referenceObjects(inGroupNamed: asset.name, bundle: bundle) ?? Set()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
{% endif %}
|
||||
{% if resourceCount.color > 0 %}
|
||||
|
||||
{{accessModifier}} final class {{colorType}} {
|
||||
{{accessModifier}} fileprivate(set) var name: String
|
||||
|
||||
#if os(macOS)
|
||||
{{accessModifier}} typealias Color = NSColor
|
||||
#elseif os(iOS) || os(tvOS) || os(watchOS)
|
||||
{{accessModifier}} typealias Color = UIColor
|
||||
#endif
|
||||
|
||||
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)
|
||||
{{accessModifier}} private(set) lazy var color: Color = Color(asset: self)
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
@available(iOS 11.0, tvOS 11.0, *)
|
||||
{{accessModifier}} func color(compatibleWith traitCollection: UITraitCollection) -> Color {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else {
|
||||
fatalError("Unable to load color asset named \(name).")
|
||||
}
|
||||
return color
|
||||
}
|
||||
#endif
|
||||
|
||||
fileprivate init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
{{accessModifier}} extension {{colorType}}.Color {
|
||||
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)
|
||||
convenience init!(asset: {{colorType}}) {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
#if os(iOS) || os(tvOS)
|
||||
self.init(named: asset.name, in: bundle, compatibleWith: nil)
|
||||
#elseif os(macOS)
|
||||
self.init(named: NSColor.Name(asset.name), bundle: bundle)
|
||||
#elseif os(watchOS)
|
||||
self.init(named: asset.name)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
{% if resourceCount.data > 0 %}
|
||||
|
||||
{{accessModifier}} struct {{dataType}} {
|
||||
{{accessModifier}} fileprivate(set) var name: String
|
||||
|
||||
@available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *)
|
||||
{{accessModifier}} var data: NSDataAsset {
|
||||
return NSDataAsset(asset: self)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *)
|
||||
{{accessModifier}} extension NSDataAsset {
|
||||
convenience init!(asset: {{dataType}}) {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
self.init(name: asset.name, bundle: bundle)
|
||||
#elseif os(macOS)
|
||||
self.init(name: NSDataAsset.Name(asset.name), bundle: bundle)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
{% if resourceCount.image > 0 %}
|
||||
|
||||
{{accessModifier}} struct {{imageType}} {
|
||||
{{accessModifier}} fileprivate(set) var name: String
|
||||
|
||||
#if os(macOS)
|
||||
{{accessModifier}} typealias Image = NSImage
|
||||
#elseif os(iOS) || os(tvOS) || os(watchOS)
|
||||
{{accessModifier}} typealias Image = UIImage
|
||||
#endif
|
||||
|
||||
@available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *)
|
||||
{{accessModifier}} var image: Image {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
#if os(iOS) || os(tvOS)
|
||||
let image = Image(named: name, in: bundle, compatibleWith: nil)
|
||||
#elseif os(macOS)
|
||||
let name = NSImage.Name(self.name)
|
||||
let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name)
|
||||
#elseif os(watchOS)
|
||||
let image = Image(named: name)
|
||||
#endif
|
||||
guard let result = image else {
|
||||
fatalError("Unable to load image asset named \(name).")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
@available(iOS 8.0, tvOS 9.0, *)
|
||||
{{accessModifier}} func image(compatibleWith traitCollection: UITraitCollection) -> Image {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else {
|
||||
fatalError("Unable to load image asset named \(name).")
|
||||
}
|
||||
return result
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
{{accessModifier}} extension {{imageType}}.Image {
|
||||
@available(iOS 8.0, tvOS 9.0, watchOS 2.0, *)
|
||||
@available(macOS, deprecated,
|
||||
message: "This initializer is unsafe on macOS, please use the {{imageType}}.image property")
|
||||
convenience init!(asset: {{imageType}}) {
|
||||
#if os(iOS) || os(tvOS)
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
self.init(named: asset.name, in: bundle, compatibleWith: nil)
|
||||
#elseif os(macOS)
|
||||
self.init(named: NSImage.Name(asset.name))
|
||||
#elseif os(watchOS)
|
||||
self.init(named: asset.name)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
{% if resourceCount.symbol > 0 %}
|
||||
|
||||
{{accessModifier}} struct {{symbolType}} {
|
||||
{{accessModifier}} fileprivate(set) var name: String
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||
{{accessModifier}} typealias Configuration = UIImage.SymbolConfiguration
|
||||
{{accessModifier}} typealias Image = UIImage
|
||||
|
||||
@available(iOS 12.0, tvOS 12.0, watchOS 5.0, *)
|
||||
{{accessModifier}} var image: Image {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
#if os(iOS) || os(tvOS)
|
||||
let image = Image(named: name, in: bundle, compatibleWith: nil)
|
||||
#elseif os(watchOS)
|
||||
let image = Image(named: name)
|
||||
#endif
|
||||
guard let result = image else {
|
||||
fatalError("Unable to load symbol asset named \(name).")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||
{{accessModifier}} func image(with configuration: Configuration) -> Image {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
guard let result = Image(named: name, in: bundle, with: configuration) else {
|
||||
fatalError("Unable to load symbol asset named \(name).")
|
||||
}
|
||||
return result
|
||||
}
|
||||
#endif
|
||||
}
|
||||
{% endif %}
|
||||
{% if not param.bundle %}
|
||||
|
||||
// swiftlint:disable convenience_type
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type
|
||||
{% endif %}
|
||||
{% else %}
|
||||
// No assets found
|
||||
{% endif %}
|
|
@ -0,0 +1,337 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if catalogs %}
|
||||
{% set enumName %}{{param.enumName|default:"Asset"}}{% endset %}
|
||||
{% set arResourceGroupType %}{{param.arResourceGroupTypeName|default:"ARResourceGroupAsset"}}{% endset %}
|
||||
{% set colorType %}{{param.colorTypeName|default:"ColorAsset"}}{% endset %}
|
||||
{% set dataType %}{{param.dataTypeName|default:"DataAsset"}}{% endset %}
|
||||
{% set imageType %}{{param.imageTypeName|default:"ImageAsset"}}{% endset %}
|
||||
{% set symbolType %}{{param.symbolTypeName|default:"SymbolAsset"}}{% endset %}
|
||||
{% set forceNamespaces %}{{param.forceProvidesNamespaces|default:"false"}}{% endset %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
#elseif os(iOS)
|
||||
{% if resourceCount.arresourcegroup > 0 %}
|
||||
import ARKit
|
||||
{% endif %}
|
||||
import UIKit
|
||||
#elseif os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
// Deprecated typealiases
|
||||
{% if resourceCount.color > 0 %}
|
||||
@available(*, deprecated, renamed: "{{colorType}}.Color", message: "This typealias will be removed in SwiftGen 7.0")
|
||||
{{accessModifier}} typealias {{param.colorAliasName|default:"AssetColorTypeAlias"}} = {{colorType}}.Color
|
||||
{% endif %}
|
||||
{% if resourceCount.image > 0 %}
|
||||
@available(*, deprecated, renamed: "{{imageType}}.Image", message: "This typealias will be removed in SwiftGen 7.0")
|
||||
{{accessModifier}} typealias {{param.imageAliasName|default:"AssetImageTypeAlias"}} = {{imageType}}.Image
|
||||
{% endif %}
|
||||
|
||||
// swiftlint:disable superfluous_disable_command file_length implicit_return
|
||||
|
||||
// MARK: - Asset Catalogs
|
||||
|
||||
{% macro enumBlock assets %}
|
||||
{% call casesBlock assets %}
|
||||
{% if param.allValues %}
|
||||
|
||||
// swiftlint:disable trailing_comma
|
||||
{% if resourceCount.arresourcegroup > 0 %}
|
||||
{{accessModifier}} static let allResourceGroups: [{{arResourceGroupType}}] = [
|
||||
{% filter indent:2 %}{% call allValuesBlock assets "arresourcegroup" "" %}{% endfilter %}
|
||||
]
|
||||
{% endif %}
|
||||
{% if resourceCount.color > 0 %}
|
||||
{{accessModifier}} static let allColors: [{{colorType}}] = [
|
||||
{% filter indent:2 %}{% call allValuesBlock assets "color" "" %}{% endfilter %}
|
||||
]
|
||||
{% endif %}
|
||||
{% if resourceCount.data > 0 %}
|
||||
{{accessModifier}} static let allDataAssets: [{{dataType}}] = [
|
||||
{% filter indent:2 %}{% call allValuesBlock assets "data" "" %}{% endfilter %}
|
||||
]
|
||||
{% endif %}
|
||||
{% if resourceCount.image > 0 %}
|
||||
{{accessModifier}} static let allImages: [{{imageType}}] = [
|
||||
{% filter indent:2 %}{% call allValuesBlock assets "image" "" %}{% endfilter %}
|
||||
]
|
||||
{% endif %}
|
||||
{% if resourceCount.symbol > 0 %}
|
||||
{{accessModifier}} static let allSymbols: [{{symbolType}}] = [
|
||||
{% filter indent:2 %}{% call allValuesBlock assets "symbol" "" %}{% endfilter %}
|
||||
]
|
||||
{% endif %}
|
||||
// swiftlint:enable trailing_comma
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro casesBlock assets %}
|
||||
{% for asset in assets %}
|
||||
{% if asset.type == "arresourcegroup" %}
|
||||
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{arResourceGroupType}}(name: "{{asset.value}}")
|
||||
{% elif asset.type == "color" %}
|
||||
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{colorType}}(name: "{{asset.value}}")
|
||||
{% elif asset.type == "data" %}
|
||||
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{dataType}}(name: "{{asset.value}}")
|
||||
{% elif asset.type == "image" %}
|
||||
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{imageType}}(name: "{{asset.value}}")
|
||||
{% elif asset.type == "symbol" %}
|
||||
{{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{symbolType}}(name: "{{asset.value}}")
|
||||
{% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %}
|
||||
{{accessModifier}} enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %}
|
||||
}
|
||||
{% elif asset.items %}
|
||||
{% call casesBlock asset.items %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
{% macro allValuesBlock assets filter prefix %}
|
||||
{% for asset in assets %}
|
||||
{% if asset.type == filter %}
|
||||
{{prefix}}{{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}},
|
||||
{% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %}
|
||||
{% set prefix2 %}{{prefix}}{{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.{% endset %}
|
||||
{% call allValuesBlock asset.items filter prefix2 %}
|
||||
{% elif asset.items %}
|
||||
{% call allValuesBlock asset.items filter prefix %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
// swiftlint:disable identifier_name line_length nesting type_body_length type_name
|
||||
{{accessModifier}} enum {{enumName}} {
|
||||
{% if catalogs.count > 1 or param.forceFileNameEnum %}
|
||||
{% for catalog in catalogs %}
|
||||
{{accessModifier}} enum {{catalog.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call enumBlock catalog.assets %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call enumBlock catalogs.first.assets %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length nesting type_body_length type_name
|
||||
|
||||
// MARK: - Implementation Details
|
||||
{% if resourceCount.arresourcegroup > 0 %}
|
||||
|
||||
{{accessModifier}} struct {{arResourceGroupType}} {
|
||||
{{accessModifier}} fileprivate(set) var name: String
|
||||
|
||||
#if os(iOS)
|
||||
@available(iOS 11.3, *)
|
||||
{{accessModifier}} var referenceImages: Set<ARReferenceImage> {
|
||||
return ARReferenceImage.referenceImages(in: self)
|
||||
}
|
||||
|
||||
@available(iOS 12.0, *)
|
||||
{{accessModifier}} var referenceObjects: Set<ARReferenceObject> {
|
||||
return ARReferenceObject.referenceObjects(in: self)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
@available(iOS 11.3, *)
|
||||
{{accessModifier}} extension ARReferenceImage {
|
||||
static func referenceImages(in asset: {{arResourceGroupType}}) -> Set<ARReferenceImage> {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
return referenceImages(inGroupNamed: asset.name, bundle: bundle) ?? Set()
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 12.0, *)
|
||||
{{accessModifier}} extension ARReferenceObject {
|
||||
static func referenceObjects(in asset: {{arResourceGroupType}}) -> Set<ARReferenceObject> {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
return referenceObjects(inGroupNamed: asset.name, bundle: bundle) ?? Set()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
{% endif %}
|
||||
{% if resourceCount.color > 0 %}
|
||||
|
||||
{{accessModifier}} final class {{colorType}} {
|
||||
{{accessModifier}} fileprivate(set) var name: String
|
||||
|
||||
#if os(macOS)
|
||||
{{accessModifier}} typealias Color = NSColor
|
||||
#elseif os(iOS) || os(tvOS) || os(watchOS)
|
||||
{{accessModifier}} typealias Color = UIColor
|
||||
#endif
|
||||
|
||||
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)
|
||||
{{accessModifier}} private(set) lazy var color: Color = {
|
||||
guard let color = Color(asset: self) else {
|
||||
fatalError("Unable to load color asset named \(name).")
|
||||
}
|
||||
return color
|
||||
}()
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
@available(iOS 11.0, tvOS 11.0, *)
|
||||
{{accessModifier}} func color(compatibleWith traitCollection: UITraitCollection) -> Color {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else {
|
||||
fatalError("Unable to load color asset named \(name).")
|
||||
}
|
||||
return color
|
||||
}
|
||||
#endif
|
||||
|
||||
fileprivate init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
{{accessModifier}} extension {{colorType}}.Color {
|
||||
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)
|
||||
convenience init?(asset: {{colorType}}) {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
#if os(iOS) || os(tvOS)
|
||||
self.init(named: asset.name, in: bundle, compatibleWith: nil)
|
||||
#elseif os(macOS)
|
||||
self.init(named: NSColor.Name(asset.name), bundle: bundle)
|
||||
#elseif os(watchOS)
|
||||
self.init(named: asset.name)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
{% if resourceCount.data > 0 %}
|
||||
|
||||
{{accessModifier}} struct {{dataType}} {
|
||||
{{accessModifier}} fileprivate(set) var name: String
|
||||
|
||||
@available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *)
|
||||
{{accessModifier}} var data: NSDataAsset {
|
||||
guard let data = NSDataAsset(asset: self) else {
|
||||
fatalError("Unable to load data asset named \(name).")
|
||||
}
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *)
|
||||
{{accessModifier}} extension NSDataAsset {
|
||||
convenience init?(asset: {{dataType}}) {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
self.init(name: asset.name, bundle: bundle)
|
||||
#elseif os(macOS)
|
||||
self.init(name: NSDataAsset.Name(asset.name), bundle: bundle)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
{% if resourceCount.image > 0 %}
|
||||
|
||||
{{accessModifier}} struct {{imageType}} {
|
||||
{{accessModifier}} fileprivate(set) var name: String
|
||||
|
||||
#if os(macOS)
|
||||
{{accessModifier}} typealias Image = NSImage
|
||||
#elseif os(iOS) || os(tvOS) || os(watchOS)
|
||||
{{accessModifier}} typealias Image = UIImage
|
||||
#endif
|
||||
|
||||
@available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *)
|
||||
{{accessModifier}} var image: Image {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
#if os(iOS) || os(tvOS)
|
||||
let image = Image(named: name, in: bundle, compatibleWith: nil)
|
||||
#elseif os(macOS)
|
||||
let name = NSImage.Name(self.name)
|
||||
let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name)
|
||||
#elseif os(watchOS)
|
||||
let image = Image(named: name)
|
||||
#endif
|
||||
guard let result = image else {
|
||||
fatalError("Unable to load image asset named \(name).")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
@available(iOS 8.0, tvOS 9.0, *)
|
||||
{{accessModifier}} func image(compatibleWith traitCollection: UITraitCollection) -> Image {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else {
|
||||
fatalError("Unable to load image asset named \(name).")
|
||||
}
|
||||
return result
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
{{accessModifier}} extension {{imageType}}.Image {
|
||||
@available(iOS 8.0, tvOS 9.0, watchOS 2.0, *)
|
||||
@available(macOS, deprecated,
|
||||
message: "This initializer is unsafe on macOS, please use the {{imageType}}.image property")
|
||||
convenience init?(asset: {{imageType}}) {
|
||||
#if os(iOS) || os(tvOS)
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
self.init(named: asset.name, in: bundle, compatibleWith: nil)
|
||||
#elseif os(macOS)
|
||||
self.init(named: NSImage.Name(asset.name))
|
||||
#elseif os(watchOS)
|
||||
self.init(named: asset.name)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
{% if resourceCount.symbol > 0 %}
|
||||
|
||||
{{accessModifier}} struct {{symbolType}} {
|
||||
{{accessModifier}} fileprivate(set) var name: String
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||
{{accessModifier}} typealias Configuration = UIImage.SymbolConfiguration
|
||||
{{accessModifier}} typealias Image = UIImage
|
||||
|
||||
@available(iOS 12.0, tvOS 12.0, watchOS 5.0, *)
|
||||
{{accessModifier}} var image: Image {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
#if os(iOS) || os(tvOS)
|
||||
let image = Image(named: name, in: bundle, compatibleWith: nil)
|
||||
#elseif os(watchOS)
|
||||
let image = Image(named: name)
|
||||
#endif
|
||||
guard let result = image else {
|
||||
fatalError("Unable to load symbol asset named \(name).")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||
{{accessModifier}} func image(with configuration: Configuration) -> Image {
|
||||
let bundle = {{param.bundle|default:"BundleToken.bundle"}}
|
||||
guard let result = Image(named: name, in: bundle, with: configuration) else {
|
||||
fatalError("Unable to load symbol asset named \(name).")
|
||||
}
|
||||
return result
|
||||
}
|
||||
#endif
|
||||
}
|
||||
{% endif %}
|
||||
{% if not param.bundle %}
|
||||
|
||||
// swiftlint:disable convenience_type
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type
|
||||
{% endif %}
|
||||
{% else %}
|
||||
// No assets found
|
||||
{% endif %}
|
|
@ -0,0 +1,92 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if files %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
{% set documentPrefix %}{{param.documentName|default:"Document"}}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length
|
||||
|
||||
// MARK: - YAML Files
|
||||
{% macro fileBlock file %}
|
||||
{% if file.documents.count > 1 %}
|
||||
{% for document in file.documents %}
|
||||
{% set documentName %}{{documentPrefix}}{{forloop.counter}}{% endset %}
|
||||
{{accessModifier}} enum {{documentName|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call documentBlock file document %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call documentBlock file file.documents.first %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro documentBlock file document %}
|
||||
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
|
||||
{% if document.metadata.type == "Array" %}
|
||||
{{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %}
|
||||
{% elif document.metadata.type == "Dictionary" %}
|
||||
{% for key,value in document.metadata.properties %}
|
||||
{{accessModifier}} {% call propertyBlock key value document.data %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% if metadata.type == "Array" %}
|
||||
[{% call typeBlock metadata.element %}]
|
||||
{% elif metadata.type == "Dictionary" %}
|
||||
[String: Any]
|
||||
{% elif metadata.type == "Optional" %}
|
||||
Any?
|
||||
{% else %}
|
||||
{{metadata.type}}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %}
|
||||
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
|
||||
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
|
||||
static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% if metadata.type == "String" %}
|
||||
"{{ value }}"
|
||||
{% elif metadata.type == "Optional" %}
|
||||
nil
|
||||
{% elif metadata.type == "Array" and value %}
|
||||
[{% for value in value %}
|
||||
{% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% endfor %}]
|
||||
{% elif metadata.type == "Dictionary" %}
|
||||
[{% for key,value in value %}
|
||||
"{{key}}": {% call valueBlock value metadata.properties[key] %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% empty %}
|
||||
:
|
||||
{% endfor %}]
|
||||
{% elif metadata.type == "Bool" %}
|
||||
{% if value %}true{% else %}false{% endif %}
|
||||
{% else %}
|
||||
{{ value }}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
|
||||
// swiftlint:disable identifier_name line_length number_separator type_body_length
|
||||
{{accessModifier}} enum {{param.enumName|default:"YAMLFiles"}} {
|
||||
{% if files.count > 1 or param.forceFileNameEnum %}
|
||||
{% for file in files %}
|
||||
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call fileBlock files.first %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length number_separator type_body_length
|
||||
{% else %}
|
||||
// No files found
|
||||
{% endif %}
|
|
@ -0,0 +1,92 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if files %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
{% set documentPrefix %}{{param.documentName|default:"Document"}}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command
|
||||
// swiftlint:disable file_length
|
||||
|
||||
// MARK: - YAML Files
|
||||
{% macro fileBlock file %}
|
||||
{% if file.documents.count > 1 %}
|
||||
{% for document in file.documents %}
|
||||
{% set documentName %}{{documentPrefix}}{{forloop.counter}}{% endset %}
|
||||
{{accessModifier}} enum {{documentName|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call documentBlock file document %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call documentBlock file file.documents.first %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro documentBlock file document %}
|
||||
{% set rootType %}{% call typeBlock document.metadata %}{% endset %}
|
||||
{% if document.metadata.type == "Array" %}
|
||||
{{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %}
|
||||
{% elif document.metadata.type == "Dictionary" %}
|
||||
{% for key,value in document.metadata.properties %}
|
||||
{{accessModifier}} {% call propertyBlock key value document.data %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% if metadata.type == "Array" %}
|
||||
[{% call typeBlock metadata.element %}]
|
||||
{% elif metadata.type == "Dictionary" %}
|
||||
[String: Any]
|
||||
{% elif metadata.type == "Optional" %}
|
||||
Any?
|
||||
{% else %}
|
||||
{{metadata.type}}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %}
|
||||
{% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
|
||||
{% set propertyType %}{% call typeBlock metadata %}{% endset %}
|
||||
static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %}
|
||||
{% if metadata.type == "String" %}
|
||||
"{{ value }}"
|
||||
{% elif metadata.type == "Optional" %}
|
||||
nil
|
||||
{% elif metadata.type == "Array" and value %}
|
||||
[{% for value in value %}
|
||||
{% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% endfor %}]
|
||||
{% elif metadata.type == "Dictionary" %}
|
||||
[{% for key,value in value %}
|
||||
"{{key}}": {% call valueBlock value metadata.properties[key] %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% empty %}
|
||||
:
|
||||
{% endfor %}]
|
||||
{% elif metadata.type == "Bool" %}
|
||||
{% if value %}true{% else %}false{% endif %}
|
||||
{% else %}
|
||||
{{ value }}
|
||||
{% endif %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
|
||||
// swiftlint:disable identifier_name line_length number_separator type_body_length
|
||||
{{accessModifier}} enum {{param.enumName|default:"YAMLFiles"}} {
|
||||
{% if files.count > 1 or param.forceFileNameEnum %}
|
||||
{% for file in files %}
|
||||
{{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call fileBlock file %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call fileBlock files.first %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length number_separator type_body_length
|
||||
{% else %}
|
||||
// No files found
|
||||
{% endif %}
|
BIN
.swiftgen/bin/swiftgen
Executable file
29
.swiftgen/templates/fonts.stencil
Normal file
|
@ -0,0 +1,29 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if families %}
|
||||
import SwiftUI
|
||||
{% for family in families %}
|
||||
{% set identifierName %}{{family.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %}
|
||||
{% set styleTypeName %}{{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}Style{% endset %}
|
||||
|
||||
extension Font {
|
||||
public static func {{identifierName}}(_ style: {{styleTypeName}}, fixedSize: CGFloat) -> Font {
|
||||
return Font.custom(style.rawValue, fixedSize: fixedSize)
|
||||
}
|
||||
|
||||
public static func {{identifierName}}(_ style: {{styleTypeName}}, size: CGFloat, relativeTo textStyle: TextStyle = .body) -> Font {
|
||||
return Font.custom(style.rawValue, size: size, relativeTo: textStyle)
|
||||
}
|
||||
|
||||
public enum {{styleTypeName}}: String {
|
||||
{% for font in family.fonts %}
|
||||
case {{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = "{{font.name}}"
|
||||
{% endfor %}
|
||||
}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
// No fonts found
|
||||
{% endif %}
|
||||
// swiftlint:enable all
|
85
.swiftgen/templates/strings.stencil
Normal file
|
@ -0,0 +1,85 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if tables.count > 0 %}
|
||||
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command file_length implicit_return
|
||||
|
||||
// MARK: - Strings
|
||||
|
||||
{% macro parametersBlock types %}{% filter removeNewlines:"leading" %}
|
||||
{% for type in types %}
|
||||
{% if type == "String" %}
|
||||
_ p{{forloop.counter}}: Any
|
||||
{% else %}
|
||||
_ p{{forloop.counter}}: {{type}}
|
||||
{% endif %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% endfor %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %}
|
||||
{% for type in types %}
|
||||
{% if type == "String" %}
|
||||
String(describing: p{{forloop.counter}})
|
||||
{% elif type == "UnsafeRawPointer" %}
|
||||
Int(bitPattern: p{{forloop.counter}})
|
||||
{% else %}
|
||||
p{{forloop.counter}}
|
||||
{% endif %}
|
||||
{{ ", " if not forloop.last }}
|
||||
{% endfor %}
|
||||
{% endfilter %}{% endmacro %}
|
||||
{% macro recursiveBlock table item %}
|
||||
{% for string in item.strings %}
|
||||
{% if not param.noComments %}
|
||||
{% for line in string.translation|split:"\n" %}
|
||||
/// {{line}}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if string.types %}
|
||||
{{accessModifier}} static func {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String {
|
||||
{{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %})
|
||||
}
|
||||
{% else %}
|
||||
{{accessModifier}} static var {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { {{enumName}}.tr("{{table}}", "{{string.key}}") }
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for child in item.children %}
|
||||
|
||||
{{accessModifier}} enum {{child.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call recursiveBlock table child %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
|
||||
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %}
|
||||
{{accessModifier}} enum {{enumName}} {
|
||||
{% if tables.count > 1 or param.forceFileNameEnum %}
|
||||
{% for table in tables %}
|
||||
{{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% call recursiveBlock tables.first.name tables.first.levels %}
|
||||
{% endif %}
|
||||
}
|
||||
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
|
||||
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
|
||||
// MARK: - Implementation Details
|
||||
import Localize_Swift
|
||||
extension {{enumName}} {
|
||||
static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
|
||||
let selectedLanguage = Localize.currentLanguage()
|
||||
guard let path = Bundle.main.path(forResource: selectedLanguage, ofType: "lproj"),
|
||||
let bundle = Bundle(path: path) else { return "Setup language error" }
|
||||
return NSLocalizedString(key, tableName: table, bundle: bundle, comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
// swiftlint: enable all
|
48
.swiftgen/templates/xcassets.stencil
Normal file
|
@ -0,0 +1,48 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if catalogs %}
|
||||
import SwiftUI
|
||||
{% macro casesBlock assets %}
|
||||
{% for asset in assets %}
|
||||
{% if asset.items and asset.isNamespaced == "true" %}
|
||||
public enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %}
|
||||
}
|
||||
{% elif asset.items %}
|
||||
{% call casesBlock asset.items %}
|
||||
{% elif asset.type == "color" %}
|
||||
public static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = Color("{{asset.value}}")
|
||||
{% elif asset.type == "image" %}
|
||||
public static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = Image("{{asset.value}}")
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
{% for catalog in catalogs %}
|
||||
{% if catalog.name == "Colors" %}
|
||||
|
||||
extension Color {
|
||||
{% for catalog in catalogs %}
|
||||
{% if catalog.name == "Colors" %}
|
||||
{% call casesBlock catalog.assets %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for catalog in catalogs %}
|
||||
{% if catalog.name == "Images" %}
|
||||
|
||||
extension Image {
|
||||
{% for catalog in catalogs %}
|
||||
{% if catalog.name == "Images" %}
|
||||
{% call casesBlock catalog.assets %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
// No assets found
|
||||
{% endif %}
|
||||
// swiftlint: enable all
|
36
.swiftgen/templates/xcassets_strings.stencil
Normal file
|
@ -0,0 +1,36 @@
|
|||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
{% if catalogs %}
|
||||
import Foundation
|
||||
|
||||
typealias AssetStrings = String
|
||||
{% macro casesBlock assets %}
|
||||
{% for asset in assets %}
|
||||
{% if asset.items and asset.isNamespaced == "true" %}
|
||||
public enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
|
||||
{% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %}
|
||||
}
|
||||
{% elif asset.items %}
|
||||
{% call casesBlock asset.items %}
|
||||
{% elif asset.type == "image" %}
|
||||
public static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = String("{{asset.value}}")
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
{% for catalog in catalogs %}
|
||||
{% if catalog.name == "Images" %}
|
||||
|
||||
extension String {
|
||||
{% for catalog in catalogs %}
|
||||
{% if catalog.name == "Images" %}
|
||||
{% call casesBlock catalog.assets %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
// No assets found
|
||||
{% endif %}
|
||||
// swiftlint: enable all
|
115
.swiftlint.yml
Normal file
|
@ -0,0 +1,115 @@
|
|||
---
|
||||
colon:
|
||||
severity: error
|
||||
|
||||
line_length:
|
||||
ignores_comments: true
|
||||
warning: 260
|
||||
error: 300
|
||||
|
||||
type_body_length:
|
||||
warning: 300
|
||||
error: 500
|
||||
|
||||
file_length:
|
||||
warning: 800
|
||||
error: 1000
|
||||
|
||||
function_parameter_count:
|
||||
warning: 20
|
||||
error: 30
|
||||
|
||||
function_body_length:
|
||||
warning: 120
|
||||
error: 150
|
||||
|
||||
cyclomatic_complexity:
|
||||
warning: 40
|
||||
error: 50
|
||||
|
||||
nesting:
|
||||
type_level:
|
||||
warning: 3
|
||||
error: 6
|
||||
function_level:
|
||||
warning: 500
|
||||
error: 10
|
||||
|
||||
vertical_parameter_alignment:
|
||||
severity: warning
|
||||
|
||||
implicitly_unwrapped_optional:
|
||||
severity: warning
|
||||
|
||||
force_unwrapping:
|
||||
severity: error
|
||||
|
||||
vertical_whitespace:
|
||||
severity: error
|
||||
|
||||
force_try:
|
||||
severity: error
|
||||
|
||||
trailing_semicolon:
|
||||
severity: error
|
||||
|
||||
type_name:
|
||||
min_length: 3
|
||||
severity: warning
|
||||
|
||||
identifier_name:
|
||||
min_length: 3
|
||||
max_length: 60
|
||||
# validates_start_with_lowercase: true
|
||||
allowed_symbols: "_"
|
||||
excluded:
|
||||
- iv
|
||||
- id
|
||||
- ip
|
||||
- on
|
||||
- ui
|
||||
- x
|
||||
- y
|
||||
- tz
|
||||
- to
|
||||
- db
|
||||
|
||||
# Disable rules from the default enabled set.
|
||||
disabled_rules:
|
||||
- trailing_whitespace
|
||||
- implicit_getter
|
||||
- redundant_string_enum_value
|
||||
- switch_case_alignment
|
||||
|
||||
# Enable rules not from the default set.
|
||||
opt_in_rules:
|
||||
# - function_default_parameter_at_end
|
||||
- empty_count
|
||||
- indentation_width
|
||||
# - index_at_zero
|
||||
- legacy_constant
|
||||
# - implicitly_unwrapped_optional
|
||||
- force_unwrapping
|
||||
# - no header
|
||||
- file_header
|
||||
# - for force unwrapping
|
||||
- implicitly_unwrapped_optional
|
||||
- vertical_parameter_alignment_on_call
|
||||
- vertical_whitespace_between_cases
|
||||
- vertical_whitespace_closing_braces
|
||||
- vertical_whitespace_opening_braces
|
||||
|
||||
# Acts as a whitelist, only the rules specified in this list will be enabled. Can not be specified alongside disabled_rules or opt_in_rules.
|
||||
only_rules:
|
||||
|
||||
# This is an entirely separate list of rules that are only run by the analyze command. All analyzer rules are opt-in, so this is the only configurable rule list (there is no disabled/whitelist equivalent).
|
||||
analyzer_rules:
|
||||
- unused_import
|
||||
- unused_declaration
|
||||
|
||||
unused_declaration:
|
||||
include_public_and_open: true
|
||||
|
||||
# paths to ignore during linting. Takes precedence over `included`.
|
||||
excluded:
|
||||
- SomePathHere
|
674
COPYING
|
@ -1,674 +0,0 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>NotificationService</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.usernotifications.service</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.snikket.shared</string>
|
||||
<string>group.snikket.notifications</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,237 +0,0 @@
|
|||
//
|
||||
// NotificationService.swift
|
||||
//
|
||||
// Siskin IM
|
||||
// Copyright (C) 2019 "Tigase, Inc." <office@tigase.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. Look for COPYING file in the top folder.
|
||||
// If not, see https://www.gnu.org/licenses/.
|
||||
//
|
||||
|
||||
import BackgroundTasks
|
||||
import UserNotifications
|
||||
import Shared
|
||||
import TigaseSwift
|
||||
import os.log
|
||||
|
||||
class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
var contentHandler: ((UNNotificationContent) -> Void)? {
|
||||
didSet {
|
||||
debug("content handler set!");
|
||||
}
|
||||
}
|
||||
var bestAttemptContent: UNMutableNotificationContent?
|
||||
|
||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||
self.contentHandler = contentHandler
|
||||
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
|
||||
|
||||
debug("Received push!");
|
||||
if let bestAttemptContent = bestAttemptContent {
|
||||
bestAttemptContent.sound = UNNotificationSound.default;
|
||||
bestAttemptContent.categoryIdentifier = "MESSAGE";
|
||||
|
||||
if let account = BareJID(bestAttemptContent.userInfo["account"] as? String) {
|
||||
DispatchQueue.main.async {
|
||||
NotificationManager.instance.initialize(provider: ExtensionNotificationManagerProvider());
|
||||
self.debug("push for account:", account);
|
||||
if let encryped = bestAttemptContent.userInfo["encrypted"] as? String, let ivStr = bestAttemptContent.userInfo["iv"] as? String {
|
||||
if let key = NotificationEncryptionKeys.key(for: account), let data = Data(base64Encoded: encryped), let iv = Data(base64Encoded: ivStr) {
|
||||
self.debug("got encrypted push with known key");
|
||||
let cipher = Cipher.AES_GCM();
|
||||
var decoded = Data();
|
||||
if cipher.decrypt(iv: iv, key: key, encoded: data, auth: nil, output: &decoded) {
|
||||
self.debug("got decrypted data:", String(data: decoded, encoding: .utf8) as Any);
|
||||
if let payload = try? JSONDecoder().decode(Payload.self, from: decoded) {
|
||||
self.debug("decoded payload successfully!");
|
||||
NotificationManager.instance.prepareNewMessageNotification(content: bestAttemptContent, account: account, sender: payload.sender.bareJid, type: payload.type, nickname: payload.nickname, body: payload.message, completionHandler: { content in
|
||||
DispatchQueue.main.async {
|
||||
contentHandler(content);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
contentHandler(bestAttemptContent)
|
||||
} else {
|
||||
self.debug("got plain push with", bestAttemptContent.userInfo[AnyHashable("sender")] as? String as Any, bestAttemptContent.userInfo[AnyHashable("body")] as? String as Any, bestAttemptContent.userInfo[AnyHashable("unread-messages")] as? Int as Any, bestAttemptContent.userInfo[AnyHashable("nickname")] as? String as Any);
|
||||
NotificationManager.instance.prepareNewMessageNotification(content: bestAttemptContent, account: account, sender: JID(bestAttemptContent.userInfo[AnyHashable("sender")] as? String)?.bareJid, type: .unknown, nickname: bestAttemptContent.userInfo[AnyHashable("nickname")] as? String, body: bestAttemptContent.userInfo[AnyHashable("body")] as? String, completionHandler: { content in
|
||||
DispatchQueue.main.async {
|
||||
contentHandler(content);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
contentHandler(bestAttemptContent);
|
||||
}
|
||||
} else {
|
||||
contentHandler(request.content);
|
||||
}
|
||||
// if #available(iOS 13.0, *) {
|
||||
// let taskRequest = BGAppRefreshTaskRequest(identifier: "org.tigase.messenger.mobile.refresh");
|
||||
// taskRequest.earliestBeginDate = nil
|
||||
// do {
|
||||
// debug("scheduling background app refresh")
|
||||
// BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: "org.tigase.messenger.mobile.refresh")
|
||||
// try BGTaskScheduler.shared.submit(taskRequest);
|
||||
// } catch {
|
||||
// debug("Could not schedule app refresh: \(error)")
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// func updateNotification(content: UNMutableNotificationContent, account: BareJID, unread: Int, sender: JID, type kind: Payload.Kind, nickname: String?, body: String) {
|
||||
// let tmp = try! DBConnection.main.prepareStatement(NotificationService.GET_NAME_QUERY).findFirst(["account": account, "jid": sender.bareJid] as [String: Any?], map: { (cursor) -> (String?, Int)? in
|
||||
// return (cursor["name"], cursor["type"]!);
|
||||
// });
|
||||
// let name = tmp?.0;
|
||||
// let type: Payload.Kind = tmp?.1 == 1 ? .groupchat : .chat;
|
||||
// switch type {
|
||||
// case .chat:
|
||||
// content.title = name ?? sender.stringValue;
|
||||
// content.body = body;
|
||||
// content.userInfo = ["account": account.stringValue, "sender": sender.bareJid.stringValue];
|
||||
// case .groupchat:
|
||||
// if let nickname = nickname {
|
||||
// content.title = "\(nickname) mentioned you in \(name ?? sender.bareJid.stringValue)";
|
||||
// } else {
|
||||
// content.title = "\(name ?? sender.bareJid.stringValue)";
|
||||
// }
|
||||
// content.body = body;
|
||||
// content.userInfo = ["account": account.stringValue, "sender": sender.bareJid.stringValue];
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// content.categoryIdentifier = NotificationCategory.MESSAGE.rawValue;
|
||||
// //content.badge = 2;
|
||||
//
|
||||
// }
|
||||
|
||||
func debug(_ data: Any...) {
|
||||
os_log("%{public}@", log: OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "SiskinPush"), "\(Date()): \(data)");
|
||||
}
|
||||
|
||||
override func serviceExtensionTimeWillExpire() {
|
||||
// Called just before the extension will be terminated by the system.
|
||||
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
|
||||
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
|
||||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension DBConnection {
|
||||
static func main<T>(execute: @escaping (DBConnection) throws ->T) throws -> T {
|
||||
let dbURL = mainDbURL();
|
||||
let connection = try DBConnection.init(dbPath: dbURL.path);
|
||||
return try execute(connection);
|
||||
}
|
||||
}
|
||||
|
||||
class ExtensionNotificationManagerProvider: NotificationManagerProvider {
|
||||
|
||||
static let GET_NAME_QUERY = "select name, 0 as type from roster_items where account = :account and jid = :jid union select name, 1 as type from chats where account = :account and jid = :jid and type > 0 order by type desc";
|
||||
|
||||
static let GET_UNREAD_CHATS = "select c.account, c.jid from chats c inner join chat_history ch where ch.account = c.account and ch.jid = c.jid and ch.state in (2,6,7) group by c.account, c.jid";
|
||||
|
||||
func getChatNameAndType(for account: BareJID, with jid: BareJID, completionHandler: @escaping (String?, Payload.Kind) -> Void) {
|
||||
let tmp = try? DBConnection.main(execute: { conn in
|
||||
return try conn.prepareStatement(ExtensionNotificationManagerProvider.GET_NAME_QUERY).findFirst(["account": account, "jid": jid] as [String: Any?], map: { (cursor) -> (String?, Int)? in
|
||||
return (cursor["name"], cursor["type"]!);
|
||||
});
|
||||
});
|
||||
completionHandler(tmp?.0, tmp?.1 == 0 ? .chat : .groupchat);
|
||||
}
|
||||
|
||||
func countBadge(withThreadId: String?, completionHandler: @escaping (Int) -> Void) {
|
||||
NotificationManager.unreadChatsThreadIds { (result) in
|
||||
var unreadChats = result;
|
||||
let activeAccounts = self.getActiveAccounts()
|
||||
|
||||
try? DBConnection.main(execute: { conn in
|
||||
try conn.prepareStatement(ExtensionNotificationManagerProvider.GET_UNREAD_CHATS).query(forEach: { cursor in
|
||||
if let account: BareJID = cursor["account"], let jid: BareJID = cursor["jid"] {
|
||||
if activeAccounts.contains(account) {
|
||||
unreadChats.insert("account=\(account.stringValue)|sender=\(jid.stringValue)")
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if let threadId = withThreadId {
|
||||
unreadChats.insert(threadId);
|
||||
}
|
||||
|
||||
completionHandler(unreadChats.count);
|
||||
}
|
||||
}
|
||||
|
||||
func shouldShowNotification(account: BareJID, sender: BareJID?, body: String?, completionHandler: @escaping (Bool)->Void) {
|
||||
completionHandler(true);
|
||||
}
|
||||
|
||||
func getActiveAccounts() -> [BareJID] {
|
||||
let query = [ String(kSecClass) : kSecClassGenericPassword, String(kSecMatchLimit) : kSecMatchLimitAll, String(kSecReturnAttributes) : kCFBooleanTrue as Any, String(kSecAttrService) : "xmpp" ] as [String : Any];
|
||||
var result: CFTypeRef?;
|
||||
|
||||
guard SecItemCopyMatching(query as CFDictionary, &result) == noErr else {
|
||||
return [];
|
||||
}
|
||||
|
||||
guard let results = result as? [[String: NSObject]] else {
|
||||
return [];
|
||||
}
|
||||
|
||||
let accounts = results.map { item -> BareJID in
|
||||
return BareJID(item[kSecAttrAccount as String] as! String);
|
||||
}.sorted(by: { (j1, j2) -> Bool in
|
||||
j1.stringValue.compare(j2.stringValue) == .orderedAscending
|
||||
})
|
||||
|
||||
return accounts.filter { account in
|
||||
let query = getAccountQuery(account.stringValue)
|
||||
var result: CFTypeRef?
|
||||
|
||||
guard SecItemCopyMatching(query as CFDictionary, &result) == noErr else { return false }
|
||||
|
||||
guard let r = result as? [String: NSObject] else { return false }
|
||||
|
||||
var dict: [String: Any]? = nil;
|
||||
if let data = r[String(kSecAttrGeneric)] as? NSData {
|
||||
do {
|
||||
dict = try NSKeyedUnarchiver.unarchivedObject(ofClass: NSDictionary.self, from: data as Data) as? [String : Any]
|
||||
} catch {
|
||||
// failed to get account object
|
||||
}
|
||||
}
|
||||
|
||||
if (dict?["active"] as? Bool) ?? false {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getAccountQuery(_ name:String, withData:CFString = kSecReturnAttributes) -> [String: Any] {
|
||||
return [ String(kSecClass) : kSecClassGenericPassword, String(kSecMatchLimit) : kSecMatchLimitOne, String(withData) : kCFBooleanTrue!, String(kSecAttrService) : "xmpp" as NSObject, String(kSecAttrAccount) : name as NSObject ];
|
||||
}
|
||||
}
|
||||
|
||||
|
15
README.md
|
@ -1,15 +0,0 @@
|
|||
# Snikket iOS client
|
||||
|
||||
This is the source code for the Snikket iOS client.
|
||||
|
||||
# License
|
||||
|
||||
Snikket for iOS is based on [Siskin IM](https://siskin.im/) by <a href="https://tigase.net/"><img alt="Tigase Tigase Logo" src="https://github.com/tigase/website-assets/blob/master/tigase/images/tigase-logo.png?raw=true" width="25"/> Tigase</a>.
|
||||
|
||||
The official Siskin IM repository is available at: https://github.com/tigase/siskin-im/
|
||||
|
||||
Copyright (c) 2004 Tigase, Inc. and Snikket Community Interest Company.
|
||||
|
||||
Snikket and the Snikket logo are trademarks of Snikket Community Interest Company.
|
||||
|
||||
Licensed under GPL License Version 3.
|
|
@ -1,22 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,39 +0,0 @@
|
|||
//
|
||||
// NotificationCategory.swift
|
||||
//
|
||||
// Siskin IM
|
||||
// Copyright (C) 2019 "Tigase, Inc." <office@tigase.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. Look for COPYING file in the top folder.
|
||||
// If not, see https://www.gnu.org/licenses/.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum NotificationCategory: String {
|
||||
case UNKNOWN
|
||||
case ERROR
|
||||
case MESSAGE
|
||||
case SUBSCRIPTION_REQUEST
|
||||
case MUC_ROOM_INVITATION
|
||||
case CALL
|
||||
case UNSENT_MESSAGES
|
||||
|
||||
public static func from(identifier: String?) -> NotificationCategory {
|
||||
guard let str = identifier else {
|
||||
return .UNKNOWN;
|
||||
}
|
||||
return NotificationCategory(rawValue: str) ?? .UNKNOWN;
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
//
|
||||
// Shared.h
|
||||
//
|
||||
// Siskin IM
|
||||
// Copyright (C) 2019 "Tigase, Inc." <office@tigase.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. Look for COPYING file in the top folder.
|
||||
// If not, see https://www.gnu.org/licenses/.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
//! Project version number for Shared.
|
||||
//FOUNDATION_EXPORT double SharedVersionNumber;
|
||||
|
||||
//! Project version string for Shared.
|
||||
//FOUNDATION_EXPORT const unsigned char SharedVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <Shared/PublicHeader.h>
|
||||
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
//
|
||||
// DBConnection_main.swift
|
||||
//
|
||||
// Siskin IM
|
||||
// Copyright (C) 2019 "Tigase, Inc." <office@tigase.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. Look for COPYING file in the top folder.
|
||||
// If not, see https://www.gnu.org/licenses/.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension DBConnection {
|
||||
|
||||
public static func mainDbURL() -> URL {
|
||||
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.snikket.shared")!;
|
||||
return containerURL.appendingPathComponent("snikket_main.db");
|
||||
}
|
||||
|
||||
public private(set) static var createIfNotExist: Bool = false;
|
||||
|
||||
public static func migrateToGroupIfNeeded() throws {
|
||||
let dbURL = mainDbURL();
|
||||
|
||||
if !FileManager.default.fileExists(atPath: dbURL.path) {
|
||||
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true);
|
||||
let documentDirectory = paths[0];
|
||||
let path = documentDirectory.appending("/mobile_messenger1.db");
|
||||
if FileManager.default.fileExists(atPath: path) {
|
||||
try FileManager.default.moveItem(atPath: path, toPath: dbURL.path);
|
||||
}
|
||||
}
|
||||
|
||||
createIfNotExist = true;
|
||||
}
|
||||
|
||||
public static func initialize(dbPath: String) throws -> DBConnection {
|
||||
let conn = try DBConnection(dbPath: dbPath);
|
||||
try? conn.execute("PRAGMA busy_timeout = 2")
|
||||
try DBSchemaManager(dbConnection: conn).upgradeSchema();
|
||||
return conn;
|
||||
}
|
||||
}
|
|
@ -1,590 +0,0 @@
|
|||
//
|
||||
// DBManager.swift
|
||||
//
|
||||
// Siskin IM
|
||||
// Copyright (C) 2016 "Tigase, Inc." <office@tigase.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. Look for COPYING file in the top folder.
|
||||
// If not, see https://www.gnu.org/licenses/.
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import TigaseSwift
|
||||
import SQLite3
|
||||
|
||||
let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
|
||||
|
||||
open class DBConnection {
|
||||
|
||||
fileprivate var handle_:OpaquePointer? = nil;
|
||||
open var handle:OpaquePointer {
|
||||
get {
|
||||
return handle_!;
|
||||
}
|
||||
}
|
||||
|
||||
public let dispatcher: QueueDispatcher;
|
||||
|
||||
open var lastInsertRowId: Int? {
|
||||
let rowid = sqlite3_last_insert_rowid(handle);
|
||||
return rowid > 0 ? Int(rowid) : nil;
|
||||
}
|
||||
|
||||
open var changesCount: Int {
|
||||
return Int(sqlite3_changes(handle));
|
||||
}
|
||||
|
||||
convenience public init(dbUrl: URL) throws {
|
||||
try self.init(dbPath: dbUrl.path);
|
||||
}
|
||||
|
||||
public init(dbPath: String) throws {
|
||||
dispatcher = QueueDispatcher(label: "db_queue");
|
||||
try dispatcher.sync {
|
||||
let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE;
|
||||
|
||||
_ = try self.check(sqlite3_open_v2(dbPath, &self.handle_, flags | SQLITE_OPEN_FULLMUTEX, nil));
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
sqlite3_close(handle);
|
||||
}
|
||||
|
||||
open func execute(_ query: String) throws {
|
||||
try dispatcher.sync {
|
||||
_ = try self.check(sqlite3_exec(self.handle, query, nil, nil, nil));
|
||||
}
|
||||
}
|
||||
|
||||
open func prepareStatement(_ query: String) throws -> DBStatement {
|
||||
return try dispatcher.sync {
|
||||
return try DBStatement(connection: self, query: query);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fileprivate func check(_ result:Int32, statement:DBStatement? = nil) throws -> Int32 {
|
||||
guard let error = DBResult(errorCode: result, connection: self, statement: statement) else {
|
||||
return result;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum DBResult: Error {
|
||||
|
||||
fileprivate static let successCodes = [ SQLITE_OK, SQLITE_ROW, SQLITE_DONE ];
|
||||
|
||||
case error(message:String, code: Int32, statement:DBStatement?)
|
||||
|
||||
init?(errorCode: Int32, connection: DBConnection, statement:DBStatement?) {
|
||||
guard !DBResult.successCodes.contains(errorCode) else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
let tmp = sqlite3_errmsg(connection.handle);
|
||||
let message = String(cString: tmp!);
|
||||
self = .error(message: message, code: errorCode, statement: statement);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
open class DBStatement {
|
||||
|
||||
fileprivate var handle:OpaquePointer? = nil;
|
||||
fileprivate let connection:DBConnection;
|
||||
|
||||
open lazy var columnCount:Int = Int(sqlite3_column_count(self.handle));
|
||||
|
||||
open lazy var columnNames:[String] = (0..<Int32(self.columnCount)).map { (idx:Int32) -> String in
|
||||
return String(cString: sqlite3_column_name(self.handle, idx)!);
|
||||
}
|
||||
|
||||
open lazy var cursor:DBCursor = DBCursor(statement: self);
|
||||
|
||||
public let dispatcher: QueueDispatcher;
|
||||
|
||||
open var lastInsertRowId: Int? {
|
||||
return connection.lastInsertRowId;
|
||||
}
|
||||
|
||||
open var changesCount: Int {
|
||||
return connection.changesCount;
|
||||
}
|
||||
|
||||
init(connection:DBConnection, query:String, dispatcher: QueueDispatcher = QueueDispatcher(label: "DBStatementDispatcher")) throws {
|
||||
self.connection = connection;
|
||||
self.dispatcher = dispatcher;
|
||||
_ = try connection.check(sqlite3_prepare_v2(connection.handle, query, -1, &handle, nil));
|
||||
}
|
||||
|
||||
deinit {
|
||||
sqlite3_finalize(handle);
|
||||
}
|
||||
|
||||
fileprivate func step(_ expect: Int32 = SQLITE_ROW) throws -> Bool {
|
||||
return try connection.dispatcher.sync() {
|
||||
let result = try self.connection.check(sqlite3_step(self.handle));
|
||||
return result == expect;
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func bind(_ params:Any?...) throws -> DBStatement {
|
||||
_ = try bind(params);
|
||||
return self;
|
||||
}
|
||||
|
||||
fileprivate func bind(_ params:[String:Any?]) throws -> DBStatement {
|
||||
reset()
|
||||
for (k,v) in params {
|
||||
let pos = sqlite3_bind_parameter_index(handle, ":"+k);
|
||||
if pos == 0 {
|
||||
print("got pos = 0, while parameter count = ", sqlite3_bind_parameter_count(handle));
|
||||
}
|
||||
try bind(v, pos: pos);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
fileprivate func bind(_ params:[Any?]) throws -> DBStatement {
|
||||
reset()
|
||||
for pos in 1...params.count {
|
||||
_ = try bind(params[pos-1], atIndex: pos);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
fileprivate func bind(_ value:Any?, atIndex:Int) throws -> DBStatement {
|
||||
try bind(value, pos: Int32(atIndex));
|
||||
return self;
|
||||
}
|
||||
|
||||
fileprivate func bind(_ value_:Any?, pos:Int32) throws {
|
||||
var r:Int32 = SQLITE_OK;
|
||||
if value_ == nil {
|
||||
r = sqlite3_bind_null(handle, pos);
|
||||
} else if let value:Any = value_ {
|
||||
switch value {
|
||||
case let v as [UInt8]:
|
||||
r = sqlite3_bind_blob(handle, pos, v, Int32(v.count), SQLITE_TRANSIENT);
|
||||
case let v as Data:
|
||||
r = v.withUnsafeBytes() { (bytes) -> Int32 in
|
||||
return sqlite3_bind_blob(handle, pos, bytes.baseAddress!, Int32(v.count), SQLITE_TRANSIENT);
|
||||
}
|
||||
case let v as Double:
|
||||
r = sqlite3_bind_double(handle, pos, v);
|
||||
case let v as UInt32:
|
||||
r = sqlite3_bind_int(handle, pos, Int32(bitPattern: v));
|
||||
case let v as Int32:
|
||||
r = sqlite3_bind_int(handle, pos, v);
|
||||
case let v as Int:
|
||||
r = sqlite3_bind_int64(handle, pos, Int64(v));
|
||||
case let v as Bool:
|
||||
r = sqlite3_bind_int(handle, pos, Int32(v ? 1 : 0));
|
||||
case let v as String:
|
||||
r = sqlite3_bind_text(handle, pos, v, -1, SQLITE_TRANSIENT);
|
||||
case let v as Date:
|
||||
let timestamp = Int64(v.timeIntervalSince1970 * 1000);
|
||||
r = sqlite3_bind_int64(handle, pos, timestamp);
|
||||
case let v as StringValue:
|
||||
r = sqlite3_bind_text(handle, pos, v.stringValue, -1, SQLITE_TRANSIENT);
|
||||
default:
|
||||
throw DBResult.error(message: "Unsupported type \(value.self) for parameter \(pos)", code: SQLITE_FAIL, statement: self);
|
||||
}
|
||||
} else {
|
||||
sqlite3_bind_null(handle, pos)
|
||||
}
|
||||
_ = try check(r);
|
||||
}
|
||||
|
||||
fileprivate func execute(_ params:[String:Any?]) throws -> DBStatement? {
|
||||
_ = try bind(params);
|
||||
return try execute();
|
||||
}
|
||||
|
||||
fileprivate func execute(_ params:Any?...) throws -> DBStatement? {
|
||||
return try execute(params);
|
||||
}
|
||||
|
||||
fileprivate func execute(_ params:[Any?]) throws -> DBStatement? {
|
||||
if params.count > 0 {
|
||||
_ = try bind(params);
|
||||
}
|
||||
reset(false);
|
||||
return try step() ? self : nil;
|
||||
}
|
||||
|
||||
// open func query(_ params:[String:Any?]) throws -> DBCursor? {
|
||||
// return try execute(params)?.cursor;
|
||||
// }
|
||||
//
|
||||
// open func query(_ params:Any?...) throws -> DBCursor? {
|
||||
// return try execute(params)?.cursor;
|
||||
// }
|
||||
|
||||
open func findFirst<T>(_ params: [String:Any?], map: (DBCursor)-> T?) throws -> T? {
|
||||
return try dispatcher.sync {
|
||||
guard let cursor = try execute(params)?.cursor else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return map(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
open func findFirst<T>(_ params: Any?..., map: (DBCursor)-> T?) throws -> T? {
|
||||
return try dispatcher.sync {
|
||||
guard let cursor = try execute(params)?.cursor else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return map(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
open func query(_ params:[String:Any?], forEach: (DBCursor)->Void) throws {
|
||||
try dispatcher.sync {
|
||||
if let cursor = try execute(params)?.cursor {
|
||||
repeat {
|
||||
forEach(cursor);
|
||||
} while cursor.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open func query(_ params:Any?..., forEach: (DBCursor)->Void) throws {
|
||||
try dispatcher.sync {
|
||||
if let cursor = try execute(params)?.cursor {
|
||||
repeat {
|
||||
forEach(cursor);
|
||||
} while cursor.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open func queryFirstMatching<T>(_ params:[String:Any?], forEachRowUntil: (DBCursor)->T?) throws -> T? {
|
||||
return try dispatcher.sync {
|
||||
if let cursor = try execute(params)?.cursor {
|
||||
var result: T?;
|
||||
repeat {
|
||||
result = forEachRowUntil(cursor);
|
||||
} while result == nil && cursor.next();
|
||||
return result;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
open func queryFirstMatching<T>(_ params:Any?..., forEachRowUntil: (DBCursor)->T?) throws -> T? {
|
||||
var result: T? = nil;
|
||||
try dispatcher.sync {
|
||||
if let cursor = try execute(params)?.cursor {
|
||||
repeat {
|
||||
result = forEachRowUntil(cursor);
|
||||
} while result == nil && cursor.next();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
open func query<T>(_ params:[String:Any?], map: (DBCursor)->T?) throws -> [T] {
|
||||
var result = [T]();
|
||||
try dispatcher.sync {
|
||||
var tmp: T? = nil;
|
||||
if let cursor = try execute(params)?.cursor {
|
||||
repeat {
|
||||
tmp = map(cursor);
|
||||
if tmp != nil {
|
||||
result.append(tmp!);
|
||||
}
|
||||
} while cursor.next();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
open func query<T>(_ params:Any?..., map: (DBCursor)->T?) throws -> [T] {
|
||||
var result = [T]();
|
||||
try dispatcher.sync {
|
||||
var tmp: T? = nil;
|
||||
if let cursor = try execute(params)?.cursor {
|
||||
repeat {
|
||||
tmp = map(cursor);
|
||||
if tmp != nil {
|
||||
result.append(tmp!);
|
||||
}
|
||||
} while cursor.next();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
open func insert(_ params:Any?...) throws -> Int? {
|
||||
return try connection.dispatcher.sync {
|
||||
if params.count > 0 {
|
||||
_ = try self.bind(params);
|
||||
}
|
||||
self.reset(false);
|
||||
if try self.step(SQLITE_DONE) {
|
||||
return self.lastInsertRowId;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
open func insert(_ params:[String:Any?]) throws -> Int? {
|
||||
return try connection.dispatcher.sync {
|
||||
_ = try self.bind(params);
|
||||
self.reset(false);
|
||||
if try self.step(SQLITE_DONE) {
|
||||
return self.lastInsertRowId;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
open func update(_ params:Any?...) throws -> Int {
|
||||
return try connection.dispatcher.sync {
|
||||
_ = try self.execute(params);
|
||||
return self.changesCount;
|
||||
}
|
||||
}
|
||||
|
||||
open func update(_ params:[String:Any?]) throws -> Int {
|
||||
return try connection.dispatcher.sync {
|
||||
_ = try self.execute(params);
|
||||
return self.changesCount;
|
||||
}
|
||||
}
|
||||
|
||||
open func scalar(_ params:Any?...) throws -> Int? {
|
||||
return try dispatcher.sync {
|
||||
let cursor = try self.execute(params)?.cursor;
|
||||
return cursor?[0];
|
||||
}
|
||||
}
|
||||
|
||||
open func scalar(_ params:[String:Any?]) throws -> Int? {
|
||||
return try dispatcher.sync {
|
||||
let cursor = try self.execute(params)?.cursor;
|
||||
return cursor?[0];
|
||||
}
|
||||
}
|
||||
|
||||
open func scalar(_ params:Any?..., columnName: String) throws -> Int? {
|
||||
return try dispatcher.sync {
|
||||
let cursor = try self.execute(params)?.cursor;
|
||||
return cursor?[columnName];
|
||||
}
|
||||
}
|
||||
|
||||
open func scalar(_ params:[String:Any?], columnName: String) throws -> Int? {
|
||||
return try dispatcher.sync {
|
||||
let cursor = try self.execute(params)?.cursor;
|
||||
return cursor?[columnName];
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func reset(_ bindings:Bool=true) {
|
||||
sqlite3_reset(handle);
|
||||
if bindings {
|
||||
sqlite3_clear_bindings(handle);
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func check(_ result:Int32) throws -> Int32 {
|
||||
return try connection.check(result, statement: self);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
open class DBCursor {
|
||||
|
||||
fileprivate let connection: DBConnection;
|
||||
fileprivate let handle:OpaquePointer;
|
||||
|
||||
open lazy var columnCount:Int = Int(sqlite3_column_count(self.handle));
|
||||
|
||||
open lazy var columnNames:[String] = (0..<Int32(self.columnCount)).map { (idx:Int32) -> String in
|
||||
return String(cString: sqlite3_column_name(self.handle, idx)!);
|
||||
}
|
||||
|
||||
public init(statement:DBStatement) {
|
||||
self.connection = statement.connection;
|
||||
self.handle = statement.handle!;
|
||||
}
|
||||
|
||||
open subscript(index: Int) -> Double {
|
||||
return sqlite3_column_double(handle, Int32(index));
|
||||
}
|
||||
|
||||
open subscript(index: Int) -> Int {
|
||||
return Int(sqlite3_column_int64(handle, Int32(index)));
|
||||
}
|
||||
|
||||
open subscript(index: Int) -> Int32 {
|
||||
return sqlite3_column_int(handle, Int32(index));
|
||||
}
|
||||
|
||||
open subscript(index: Int) -> String? {
|
||||
let ptr = sqlite3_column_text(handle, Int32(index));
|
||||
if ptr == nil {
|
||||
return nil;
|
||||
}
|
||||
return String(cString: UnsafePointer(ptr!));
|
||||
}
|
||||
|
||||
open subscript(index: Int) -> Bool {
|
||||
return sqlite3_column_int64(handle, Int32(index)) != 0;
|
||||
}
|
||||
|
||||
open subscript(index: Int) -> [UInt8]? {
|
||||
let idx = Int32(index);
|
||||
let origPtr = sqlite3_column_blob(handle, idx);
|
||||
if origPtr == nil {
|
||||
return nil;
|
||||
}
|
||||
let count = Int(sqlite3_column_bytes(handle, idx));
|
||||
let ptr = origPtr?.assumingMemoryBound(to: UInt8.self)
|
||||
return DBCursor.convert(count, data: ptr!);
|
||||
}
|
||||
|
||||
open subscript(index: Int) -> Data? {
|
||||
let idx = Int32(index);
|
||||
let origPtr = sqlite3_column_blob(handle, idx);
|
||||
if origPtr == nil {
|
||||
return nil;
|
||||
}
|
||||
let count = Int(sqlite3_column_bytes(handle, idx));
|
||||
return Data(bytes: origPtr!, count: count);
|
||||
}
|
||||
|
||||
open subscript(index: Int) -> Date {
|
||||
let timestamp = Double(sqlite3_column_int64(handle, Int32(index))) / 1000;
|
||||
return Date(timeIntervalSince1970: timestamp);
|
||||
}
|
||||
|
||||
open subscript(index: Int) -> JID? {
|
||||
if let str:String = self[index] {
|
||||
return JID(str);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
open subscript(index: Int) -> BareJID? {
|
||||
if let str:String = self[index] {
|
||||
return BareJID(str);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
open subscript(column: String) -> Double? {
|
||||
return forColumn(column) {
|
||||
return self[$0];
|
||||
}
|
||||
}
|
||||
|
||||
open subscript(column: String) -> Int? {
|
||||
// return forColumn(column) {
|
||||
// let v:Int? = self[$0];
|
||||
// print("for \(column), position \($0) got \(v)")
|
||||
// return v;
|
||||
// }
|
||||
if let idx = columnNames.firstIndex(of: column) {
|
||||
return self[idx];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
open subscript(column: String) -> Int32? {
|
||||
// return forColumn(column) {
|
||||
// let v:Int? = self[$0];
|
||||
// print("for \(column), position \($0) got \(v)")
|
||||
// return v;
|
||||
// }
|
||||
if let idx = columnNames.firstIndex(of: column) {
|
||||
return self[idx];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
open subscript(column: String) -> String? {
|
||||
return forColumn(column) {
|
||||
return self[$0];
|
||||
}
|
||||
}
|
||||
|
||||
open subscript(column: String) -> Bool? {
|
||||
return forColumn(column) {
|
||||
return self[$0];
|
||||
}
|
||||
}
|
||||
|
||||
open subscript(column: String) -> [UInt8]? {
|
||||
return forColumn(column) {
|
||||
return self[$0];
|
||||
}
|
||||
}
|
||||
|
||||
open subscript(column: String) -> Data? {
|
||||
return forColumn(column) {
|
||||
return self[$0];
|
||||
}
|
||||
}
|
||||
|
||||
open subscript(column: String) -> Date? {
|
||||
return forColumn(column) {
|
||||
return self[$0];
|
||||
}
|
||||
}
|
||||
|
||||
open subscript(column: String) -> JID? {
|
||||
return forColumn(column) {
|
||||
return self[$0];
|
||||
}
|
||||
}
|
||||
|
||||
open subscript(column: String) -> BareJID? {
|
||||
return forColumn(column) {
|
||||
return self[$0];
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func forColumn<T>(_ column:String, exec:(Int)->T?) -> T? {
|
||||
if let idx = columnNames.firstIndex(of: column) {
|
||||
return exec(idx);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
fileprivate static func convert<T>(_ count: Int, data: UnsafePointer<T>) -> [T] {
|
||||
let buffer = UnsafeBufferPointer(start: data, count: count);
|
||||
return Array(buffer)
|
||||
}
|
||||
|
||||
open func next() -> Bool {
|
||||
return connection.dispatcher.sync {
|
||||
return sqlite3_step(self.handle) == SQLITE_ROW;
|
||||
}
|
||||
}
|
||||
|
||||
open func next() -> DBCursor? {
|
||||
return next() ? self : nil;
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
//
|
||||
// DBSchemaManager.swift
|
||||
//
|
||||
// Siskin IM
|
||||
// Copyright (C) 2017 "Tigase, Inc." <office@tigase.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. Look for COPYING file in the top folder.
|
||||
// If not, see https://www.gnu.org/licenses/.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import TigaseSwift
|
||||
|
||||
public class DBSchemaManager {
|
||||
|
||||
static let CURRENT_VERSION = 15;
|
||||
|
||||
fileprivate let dbConnection: DBConnection;
|
||||
|
||||
public init(dbConnection: DBConnection) {
|
||||
self.dbConnection = dbConnection;
|
||||
}
|
||||
|
||||
open func upgradeSchema() throws {
|
||||
var version = try! getSchemaVersion();
|
||||
while (version < DBSchemaManager.CURRENT_VERSION) {
|
||||
switch version {
|
||||
case 0:
|
||||
try loadSchemaFile(fileName: "/db-schema-1.sql");
|
||||
do {
|
||||
try dbConnection.execute("select preview from chat_history");
|
||||
} catch {
|
||||
try dbConnection.execute("ALTER TABLE chat_history ADD COLUMN preview TEXT");
|
||||
}
|
||||
try cleanUpDuplicatedChats();
|
||||
case 1:
|
||||
try loadSchemaFile(fileName: "/db-schema-2.sql");
|
||||
try cleanUpDuplicatedChats();
|
||||
default:
|
||||
try loadSchemaFile(fileName: "/db-schema-\(version + 1).sql");
|
||||
break;
|
||||
}
|
||||
version = try! getSchemaVersion();
|
||||
}
|
||||
|
||||
let journalMode = try dbConnection.prepareStatement("pragma journal_mode").findFirst(map: { cursor -> String? in
|
||||
return cursor["journal_mode"];
|
||||
})!;
|
||||
if journalMode != "wal" {
|
||||
try dbConnection.execute("PRAGMA journal_mode=WAL");
|
||||
}
|
||||
|
||||
// need to make sure that "error" column exists as there was an issue with db-schema-2.sql
|
||||
// which did not create this column
|
||||
do {
|
||||
try dbConnection.execute("select error from chat_history");
|
||||
} catch {
|
||||
try dbConnection.execute("ALTER TABLE chat_history ADD COLUMN error TEXT;");
|
||||
}
|
||||
|
||||
let toRemove: [(String,String,Int32)] = try dbConnection.prepareStatement("SELECT sess.account as account, sess.name as name, sess.device_id as deviceId FROM omemo_sessions sess WHERE NOT EXISTS (select 1 FROM omemo_identities i WHERE i.account = sess.account and i.name = sess.name and i.device_id = sess.device_id)").query([:] as [String: Any?], map: { (cursor:DBCursor) -> (String, String, Int32)? in
|
||||
return (cursor["account"]!, cursor["name"]!, cursor["deviceId"]!);
|
||||
});
|
||||
|
||||
try toRemove.forEach { tuple in
|
||||
let (account, name, device) = tuple;
|
||||
_ = try dbConnection.prepareStatement("DELETE FROM omemo_sessions WHERE account = :account AND name = :name AND device_id = :deviceId").update(["account": account, "name": name, "deviceId": device] as [String: Any?]);
|
||||
}
|
||||
}
|
||||
|
||||
open func getSchemaVersion() throws -> Int {
|
||||
return try self.dbConnection.prepareStatement("PRAGMA user_version").scalar() ?? 0;
|
||||
}
|
||||
|
||||
fileprivate func loadSchemaFile(fileName: String) throws {
|
||||
guard let bundle = Bundle.allFrameworks.first(where: { (bundle) -> Bool in
|
||||
guard let resourcePath = bundle.resourcePath else {
|
||||
return false;
|
||||
}
|
||||
return FileManager.default.fileExists(atPath: resourcePath.appending(fileName));
|
||||
}) else {
|
||||
return;
|
||||
}
|
||||
let resourcePath = bundle.resourcePath! + fileName;
|
||||
print("loading SQL from file", resourcePath);
|
||||
let dbSchema = try String(contentsOfFile: resourcePath, encoding: String.Encoding.utf8);
|
||||
print("read schema:", dbSchema);
|
||||
try dbConnection.execute(dbSchema);
|
||||
print("loaded schema from file", fileName);
|
||||
}
|
||||
|
||||
fileprivate func cleanUpDuplicatedChats() throws {
|
||||
// deal with duplicated chats for the same bare jid
|
||||
print("looking for duplicated chats...");
|
||||
let duplicates: [(String, String, Int)] = try dbConnection.prepareStatement("select min(c.id) as id, c.account, c.jid from (select count(id) as count, account, jid from chats group by account, jid) x inner join chats c on c.account = x.account and c.jid = x.jid where count > 1 group by c.account, c.jid").query() { (cursor) -> (String, String, Int) in
|
||||
let account: String = cursor["account"]!;
|
||||
let jid: String = cursor["jid"]!;
|
||||
let id: Int = cursor["id"] ?? 0;
|
||||
print("account", account, "jid", jid, "id", id);
|
||||
return (account, jid, id);
|
||||
}
|
||||
print("found duplicates", duplicates);
|
||||
try duplicates.forEach({ (account, jid, idToLeave) in
|
||||
let removed = try dbConnection.prepareStatement("delete from chats where account = ? and jid = ? and id <> :id").scalar(account, jid, idToLeave)!;
|
||||
print("for account", account, "and jid", jid, "removed", removed, "duplicated chats");
|
||||
});
|
||||
print("duplicated chats cleanup finished!");
|
||||
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
CREATE TABLE IF NOT EXISTS chats (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account TEXT NOT NULL,
|
||||
jid TEXT NOT NULL,
|
||||
type INTEGER NOT NULL,
|
||||
timestamp INTEGER,
|
||||
thread_id TEXT,
|
||||
resource TEXT,
|
||||
nickname TEXT,
|
||||
password TEXT,
|
||||
room_state INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS chat_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account TEXT NOT NULL,
|
||||
jid TEXT NOT NULL,
|
||||
author_jid TEXT,
|
||||
author_nickname TEXT,
|
||||
timestamp INTEGER,
|
||||
item_type INTEGER,
|
||||
data TEXT,
|
||||
stanza_id TEXT,
|
||||
state INTEGER,
|
||||
preview TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS chat_history_jid_idx on chats (
|
||||
jid, account
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS roster_items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account TEXT NOT NULL,
|
||||
jid TEXT NOT NULL,
|
||||
name TEXT,
|
||||
subscription TEXT,
|
||||
timestamp INTEGER,
|
||||
ask INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS roster_groups (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS roster_items_groups (
|
||||
item_id INTEGER NOT NULL,
|
||||
group_id INTEGER NOT NULL,
|
||||
FOREIGN KEY(item_id) REFERENCES roster_items(id),
|
||||
FOREIGN KEY(group_id) REFERENCES roster_groups(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS vcards_cache (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
jid TEXT NOT NULL,
|
||||
data TEXT,
|
||||
timestamp INTEGER
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS vcards_cache_jid_idx on vcards_cache (
|
||||
jid
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS caps_features (
|
||||
node TEXT NOT NULL,
|
||||
feature TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS caps_features_node_idx on caps_features (
|
||||
node
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS caps_features_feature_idx on caps_features (
|
||||
feature
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS caps_identities (
|
||||
node TEXT NOT NULL,
|
||||
name TEXT,
|
||||
type TEXT,
|
||||
category TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS caps_indentities_node_idx on caps_identities (
|
||||
node
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS avatars_cache (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
jid TEXT NOT NULL,
|
||||
account TEXT NOT NULL,
|
||||
hash TEXT NOT NULL,
|
||||
type TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS avatars_cache_jid_idx on avatars_cache (
|
||||
jid
|
||||
);
|
||||
|
||||
PRAGMA user_version = 1;
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE chat_history ADD COLUMN recipient_nickname TEXT;
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA user_version = 10;
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
BEGIN;
|
||||
|
||||
ALTER TABLE roster_items ADD COLUMN annotations TEXT;
|
||||
|
||||
ALTER TABLE chat_history ADD COLUMN server_msg_id TEXT;
|
||||
ALTER TABLE chat_history ADD COLUMN remote_msg_id TEXT;
|
||||
ALTER TABLE chat_history ADD COLUMN participant_id TEXT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS chat_history_account_jid_server_msg_id_idx on chat_history (
|
||||
account, server_msg_id
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA user_version = 11;
|
|
@ -1,9 +0,0 @@
|
|||
BEGIN;
|
||||
|
||||
ALTER TABLE chat_history ADD COLUMN master_id INT;
|
||||
ALTER TABLE chat_history ADD COLUMN correction_stanza_id TEXT;
|
||||
ALTER TABLE chat_history ADD COLUMN correction_timestamp INTEGER;
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA user_version = 12;
|
|
@ -1,14 +0,0 @@
|
|||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS chat_history_sync (
|
||||
id TEXT NOT NULL COLLATE NOCASE,
|
||||
account TEXT NOT NULL COLLATE NOCASE,
|
||||
component TEXT COLLATE NOCASE,
|
||||
from_timestamp INTEGER NOT NULL,
|
||||
from_id TEXT,
|
||||
to_timestamp INTEGER NOT NULL
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA user_version = 13;
|
|
@ -1,7 +0,0 @@
|
|||
BEGIN;
|
||||
|
||||
ALTER TABLE roster_items ADD COLUMN nickname TEXT;
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA user_version = 14;
|
|
@ -1,13 +0,0 @@
|
|||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS last_message_sync (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account TEXT NOT NULL COLLATE NOCASE,
|
||||
jid TEXT NOT NULL COLLATE NOCASE,
|
||||
received_id TEXT,
|
||||
read_id TEXT
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA user_version = 15;
|
|
@ -1,153 +0,0 @@
|
|||
BEGIN;
|
||||
|
||||
ALTER TABLE chats RENAME TO chats_old;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS chats (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account TEXT NOT NULL COLLATE NOCASE,
|
||||
jid TEXT NOT NULL COLLATE NOCASE,
|
||||
type INTEGER NOT NULL,
|
||||
timestamp INTEGER,
|
||||
thread_id TEXT,
|
||||
resource TEXT,
|
||||
nickname TEXT,
|
||||
password TEXT,
|
||||
room_state INTEGER
|
||||
);
|
||||
|
||||
INSERT INTO chats (
|
||||
account, jid, type, timestamp, thread_id, resource, nickname, password, room_state
|
||||
)
|
||||
SELECT account, jid, type, timestamp, thread_id, resource, nickname, password, room_state
|
||||
FROM chats_old;
|
||||
|
||||
DROP TABLE chats_old;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS chat_jid_idx on chats (
|
||||
jid, account
|
||||
);
|
||||
|
||||
ALTER TABLE chat_history RENAME TO chat_history_old;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS chat_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account TEXT NOT NULL COLLATE NOCASE,
|
||||
jid TEXT NOT NULL COLLATE NOCASE,
|
||||
author_jid TEXT COLLATE NOCASE,
|
||||
author_nickname TEXT,
|
||||
timestamp INTEGER,
|
||||
item_type INTEGER,
|
||||
data TEXT,
|
||||
stanza_id TEXT,
|
||||
state INTEGER,
|
||||
preview TEXT,
|
||||
error TEXT
|
||||
);
|
||||
|
||||
INSERT INTO chat_history (
|
||||
account, jid, author_jid, author_nickname, timestamp, item_type, data, stanza_id, state, preview
|
||||
)
|
||||
SELECT account, jid, author_jid, author_nickname, timestamp, item_type, data, stanza_id, state, preview
|
||||
FROM chat_history_old;
|
||||
|
||||
DROP TABLE chat_history_old;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS chat_history_account_jid_timestamp_idx on chat_history (
|
||||
account, jid, timestamp
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS chat_history_account_jid_state_idx on chat_history (
|
||||
account, jid, state
|
||||
);
|
||||
|
||||
ALTER TABLE roster_items RENAME TO roster_items_old;
|
||||
ALTER TABLE roster_items_groups RENAME TO roster_items_groups_old;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS roster_items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account TEXT NOT NULL COLLATE NOCASE,
|
||||
jid TEXT NOT NULL COLLATE NOCASE,
|
||||
name TEXT,
|
||||
subscription TEXT,
|
||||
timestamp INTEGER,
|
||||
ask INTEGER
|
||||
);
|
||||
|
||||
INSERT INTO roster_items (
|
||||
account, jid, name, subscription, timestamp, ask
|
||||
)
|
||||
SELECT account, jid, name, subscription, timestamp, ask
|
||||
FROM roster_items_old;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS roster_items_groups (
|
||||
item_id INTEGER NOT NULL,
|
||||
group_id INTEGER NOT NULL,
|
||||
FOREIGN KEY(item_id) REFERENCES roster_items(id),
|
||||
FOREIGN KEY(group_id) REFERENCES roster_groups(id)
|
||||
);
|
||||
|
||||
INSERT INTO roster_items_groups (
|
||||
item_id, group_id
|
||||
)
|
||||
SELECT i.id, go.group_id
|
||||
FROM
|
||||
roster_items_groups_old go
|
||||
INNER JOIN roster_items_old io on io.id = go.item_id
|
||||
INNER JOIN roster_items i on i.jid = io.jid;
|
||||
|
||||
DROP TABLE roster_items_groups_old;
|
||||
DROP TABLE roster_items_old;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS roster_item_jid_idx on roster_items (
|
||||
jid, account
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS roster_item_groups_item_id_idx ON roster_items_groups (item_id);
|
||||
CREATE INDEX IF NOT EXISTS roster_item_groups_group_id_idx ON roster_items_groups (group_id);
|
||||
|
||||
ALTER TABLE vcards_cache RENAME TO vcards_cache_old;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS vcards_cache (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
jid TEXT NOT NULL COLLATE NOCASE,
|
||||
data TEXT,
|
||||
timestamp INTEGER
|
||||
);
|
||||
|
||||
INSERT INTO vcards_cache (
|
||||
jid, data, timestamp
|
||||
)
|
||||
SELECT jid, data, timestamp
|
||||
FROM vcards_cache_old;
|
||||
|
||||
DROP TABLE vcards_cache_old;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS vcards_cache_jid_idx on vcards_cache (
|
||||
jid
|
||||
);
|
||||
|
||||
ALTER TABLE avatars_cache RENAME TO avatars_cache_old;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS avatars_cache (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
jid TEXT NOT NULL COLLATE NOCASE,
|
||||
account TEXT NOT NULL COLLATE NOCASE,
|
||||
hash TEXT NOT NULL,
|
||||
type TEXT NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO avatars_cache (
|
||||
jid, account, hash, type
|
||||
)
|
||||
SELECT jid, account, hash, type
|
||||
FROM avatars_cache_old;
|
||||
|
||||
DROP TABLE avatars_cache_old;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS avatars_cache_jid_idx on avatars_cache (
|
||||
jid
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA user_version = 2;
|
|
@ -1,7 +0,0 @@
|
|||
BEGIN;
|
||||
|
||||
ALTER TABLE chats ADD COLUMN message_draft TEXT;
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA user_version = 3;
|
|
@ -1,7 +0,0 @@
|
|||
BEGIN;
|
||||
|
||||
ALTER TABLE chats ADD COLUMN name TEXT;
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA user_version = 4;
|
|
@ -1,47 +0,0 @@
|
|||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS omemo_sessions (
|
||||
account TEXT NOT NULL COLLATE NOCASE,
|
||||
name TEXT NOT NULL,
|
||||
device_id INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
|
||||
UNIQUE (account, name, device_id) ON CONFLICT REPLACE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS omemo_identities (
|
||||
account TEXT NOT NULL COLLATE NOCASE,
|
||||
name TEXT NOT NULL,
|
||||
device_id INTEGER NOT NULL,
|
||||
fingerprint TEXT NOT NULL,
|
||||
key BLOB NOT NULL,
|
||||
own INTEGER NOT NULL,
|
||||
status INTEGER NOT NULL,
|
||||
|
||||
UNIQUE (account, name, fingerprint) ON CONFLICT IGNORE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS omemo_pre_keys (
|
||||
account TEXT NOT NULL COLLATE NOCASE,
|
||||
id INTEGER NOT NULL,
|
||||
key BLOB NOT NULL,
|
||||
|
||||
UNIQUE (account, id) ON CONFLICT REPLACE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS omemo_signed_pre_keys (
|
||||
account TEXT NOT NULL COLLATE NOCASE,
|
||||
id INTEGER NOT NULL,
|
||||
key BLOB NOT NULL,
|
||||
|
||||
UNIQUE (account, id) ON CONFLICT REPLACE
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE chats ADD COLUMN encryption TEXT;
|
||||
ALTER TABLE chat_history ADD COLUMN encryption int;
|
||||
ALTER TABLE chat_history ADD COLUMN fingerprint text;
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA user_version = 5;
|
|
@ -1,7 +0,0 @@
|
|||
BEGIN;
|
||||
|
||||
ALTER TABLE chats ADD COLUMN options TEXT;
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA user_version = 6;
|
|
@ -1,12 +0,0 @@
|
|||
BEGIN;
|
||||
|
||||
UPDATE chat_history SET state = 11 WHERE state = 5;
|
||||
UPDATE chat_history SET state = 5 WHERE state = 9;
|
||||
UPDATE chat_history SET state = 9 WHERE state = 4;
|
||||
UPDATE chat_history SET state = 4 WHERE state = 7;
|
||||
UPDATE chat_history SET state = 7 WHERE state = 6;
|
||||
UPDATE chat_history SET state = 6 WHERE state = 8;
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA user_version = 7;
|
|
@ -1,13 +0,0 @@
|
|||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS chats_read (
|
||||
account TEXT NOT NULL COLLATE NOCASE,
|
||||
jid TEXT NOT NULL COLLATE NOCASE,
|
||||
timestamp INTEGER,
|
||||
|
||||
UNIQUE (account, jid)
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA user_version = 8;
|
|
@ -1,7 +0,0 @@
|
|||
BEGIN;
|
||||
|
||||
ALTER TABLE chat_history ADD COLUMN appendix TEXT;
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA user_version = 9;
|
|
@ -1,39 +0,0 @@
|
|||
//
|
||||
// NotificationEncryptionKeys.swift
|
||||
//
|
||||
// Siskin IM
|
||||
// Copyright (C) 2019 "Tigase, Inc." <office@tigase.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. Look for COPYING file in the top folder.
|
||||
// If not, see https://www.gnu.org/licenses/.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import TigaseSwift
|
||||
|
||||
public class NotificationEncryptionKeys {
|
||||
|
||||
private static let storage = UserDefaults(suiteName: "group.snikket.notifications")!;
|
||||
|
||||
public static func key(for account: BareJID) -> Data? {
|
||||
storage.data(forKey: account.stringValue)
|
||||
}
|
||||
|
||||
public static func set(key: Data?, for account: BareJID) {
|
||||
storage.setValue(key, forKey: account.stringValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,200 +0,0 @@
|
|||
//
|
||||
// NotificationManager.swift
|
||||
//
|
||||
// Siskin IM
|
||||
// Copyright (C) 2019 "Tigase, Inc." <office@tigase.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. Look for COPYING file in the top folder.
|
||||
// If not, see https://www.gnu.org/licenses/.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import TigaseSwift
|
||||
import UserNotifications
|
||||
import os
|
||||
|
||||
public class NotificationManager {
|
||||
|
||||
public static let instance: NotificationManager = NotificationManager();
|
||||
|
||||
public private(set) var provider: NotificationManagerProvider!;
|
||||
|
||||
public func initialize(provider: NotificationManagerProvider) {
|
||||
self.provider = provider;
|
||||
}
|
||||
|
||||
public static func unreadChatsThreadIds(completionHandler: @escaping (Set<String>)->Void) {
|
||||
unreadThreadIds(for: [.MESSAGE], completionHandler: completionHandler);
|
||||
}
|
||||
|
||||
public static func unreadThreadIds(for categories: [NotificationCategory], completionHandler: @escaping (Set<String>)->Void) {
|
||||
UNUserNotificationCenter.current().getDeliveredNotifications { (notifications) in
|
||||
let unreadChats = Set(notifications.filter({(notification) in
|
||||
let category = NotificationCategory.from(identifier: notification.request.content.categoryIdentifier);
|
||||
return categories.contains(category);
|
||||
}).map({ (notification) in
|
||||
return notification.request.content.threadIdentifier;
|
||||
}));
|
||||
|
||||
completionHandler(unreadChats);
|
||||
}
|
||||
}
|
||||
|
||||
public func notifyNewMessage(account: BareJID, sender: BareJID?, type kind: Payload.Kind, nickname: String?, body: String) {
|
||||
|
||||
shouldShowNotification(account: account, sender: sender, body: body, completionHandler: { (result) in
|
||||
guard result else {
|
||||
return;
|
||||
}
|
||||
self.intNotifyNewMessage(account: account, sender: sender, type: kind, nickname: nickname, body: body);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public func shouldShowNotification(account: BareJID, sender: BareJID?, body: String?, completionHandler: @escaping (Bool)->Void) {
|
||||
provider.shouldShowNotification(account: account, sender: sender, body: body) { (result) in
|
||||
if result {
|
||||
if let uid = self.generateMessageUID(account: account, sender: sender, body: body) {
|
||||
UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in
|
||||
let should = !notifications.contains(where: { (notification) -> Bool in
|
||||
guard let nuid = notification.request.content.userInfo["uid"] as? String else {
|
||||
return false;
|
||||
}
|
||||
return nuid == uid;
|
||||
});
|
||||
completionHandler(should);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
completionHandler(result);
|
||||
}
|
||||
}
|
||||
|
||||
private func intNotifyNewMessage(account: BareJID, sender: BareJID?, type kind: Payload.Kind, nickname: String?, body: String) {
|
||||
let id = UUID().uuidString;
|
||||
let content = UNMutableNotificationContent();
|
||||
prepareNewMessageNotification(content: content, account: account, sender: sender, type: kind, nickname: nickname, body: body) { (content) in
|
||||
UNUserNotificationCenter.current().add(UNNotificationRequest(identifier: id, content: content, trigger: nil)) { (error) in
|
||||
print("message notification error", error as Any);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func prepareNewMessageNotification(content: UNMutableNotificationContent, account: BareJID, sender jid: BareJID?, type kind: Payload.Kind, nickname: String?, body msg: String?, completionHandler: @escaping (UNMutableNotificationContent)->Void) {
|
||||
content.sound = .default;
|
||||
content.categoryIdentifier = NotificationCategory.MESSAGE.rawValue;
|
||||
if let sender = jid, let body = msg {
|
||||
let uid = generateMessageUID(account: account, sender: sender, body: body)!;
|
||||
content.threadIdentifier = "account=\(account.stringValue)|sender=\(sender.stringValue)";
|
||||
self.provider.getChatNameAndType(for: account, with: sender, completionHandler: { (name, type) in
|
||||
os_log("%{public}@", log: OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "SiskinPush"), "Found: name: \(name ?? ""), type: \(type.rawValue)");
|
||||
switch type {
|
||||
case .chat:
|
||||
content.title = name ?? sender.stringValue;
|
||||
if body.starts(with: "/me ") {
|
||||
content.body = String(body.dropFirst(4));
|
||||
} else {
|
||||
content.body = body;
|
||||
}
|
||||
content.userInfo = ["account": account.stringValue, "sender": sender.stringValue, "uid": uid];
|
||||
case .groupchat:
|
||||
content.title = "\(name ?? sender.stringValue)";
|
||||
if body.starts(with: "/me ") {
|
||||
if let nickname = nickname {
|
||||
content.body = "\(nickname) \(body.dropFirst(4))";
|
||||
} else {
|
||||
content.body = String(body.dropFirst(4));
|
||||
}
|
||||
} else {
|
||||
content.body = body;
|
||||
if let nickname = nickname {
|
||||
content.subtitle = nickname;
|
||||
}
|
||||
}
|
||||
content.userInfo = ["account": account.stringValue, "sender": sender.stringValue, "uid": uid];
|
||||
default:
|
||||
break;
|
||||
}
|
||||
self.provider.countBadge(withThreadId: content.threadIdentifier, completionHandler: { count in
|
||||
content.badge = count as NSNumber;
|
||||
completionHandler(content);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
content.threadIdentifier = "account=\(account.stringValue)";
|
||||
content.body = "New message!";
|
||||
self.provider.countBadge(withThreadId: content.threadIdentifier, completionHandler: { count in
|
||||
content.badge = count as NSNumber;
|
||||
completionHandler(content);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
func generateMessageUID(account: BareJID, sender: BareJID?, body: String?) -> String? {
|
||||
if let sender = sender, let body = body {
|
||||
return Digest.sha256.digest(toHex: "\(account)|\(sender)|\(body)".data(using: .utf8));
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
public protocol NotificationManagerProvider {
|
||||
|
||||
func getChatNameAndType(for account: BareJID, with jid: BareJID, completionHandler: @escaping (String?, Payload.Kind)->Void);
|
||||
|
||||
func countBadge(withThreadId: String?, completionHandler: @escaping (Int)->Void);
|
||||
|
||||
func shouldShowNotification(account: BareJID, sender: BareJID?, body: String?, completionHandler: @escaping (Bool)->Void);
|
||||
|
||||
}
|
||||
|
||||
public class Payload: Decodable {
|
||||
public var unread: Int;
|
||||
public var sender: JID;
|
||||
public var type: Kind;
|
||||
public var nickname: String?;
|
||||
public var message: String?;
|
||||
public var sid: String?;
|
||||
public var media: [String]?;
|
||||
|
||||
required public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self);
|
||||
unread = try container.decode(Int.self, forKey: .unread);
|
||||
sender = try container.decode(JID.self, forKey: .sender);
|
||||
type = Kind(rawValue: (try container.decodeIfPresent(String.self, forKey: .type)) ?? Kind.unknown.rawValue)!;
|
||||
nickname = try container.decodeIfPresent(String.self, forKey: .nickname);
|
||||
message = try container.decodeIfPresent(String.self, forKey: .message);
|
||||
sid = try container.decodeIfPresent(String.self, forKey: .sid)
|
||||
media = try container.decodeIfPresent([String].self, forKey: .media);
|
||||
// -- and so on...
|
||||
}
|
||||
|
||||
public enum Kind: String {
|
||||
case unknown
|
||||
case groupchat
|
||||
case chat
|
||||
case call
|
||||
}
|
||||
|
||||
public enum CodingKeys: String, CodingKey {
|
||||
case unread
|
||||
case sender
|
||||
case type
|
||||
case nickname
|
||||
case message
|
||||
case sid
|
||||
case media
|
||||
}
|
||||
}
|
|
@ -1,352 +0,0 @@
|
|||
//
|
||||
// Cipher+AES.swift
|
||||
//
|
||||
// Siskin IM
|
||||
// Copyright (C) 2019 "Tigase, Inc." <office@tigase.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. Look for COPYING file in the top folder.
|
||||
// If not, see https://www.gnu.org/licenses/.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import OpenSSL
|
||||
|
||||
open class Cipher {
|
||||
|
||||
}
|
||||
|
||||
extension Cipher {
|
||||
|
||||
open class AES_GCM {
|
||||
|
||||
public init() {
|
||||
|
||||
}
|
||||
|
||||
public static func generateKey(ofSize: Int) -> Data? {
|
||||
var key = Data(count: ofSize/8);
|
||||
let result = key.withUnsafeMutableBytes({ (ptr: UnsafeMutableRawBufferPointer) -> Int32 in
|
||||
return SecRandomCopyBytes(kSecRandomDefault, ofSize/8, ptr.baseAddress!);
|
||||
});
|
||||
guard result == errSecSuccess else {
|
||||
print("failed to generated AES encryption key:", result)
|
||||
return nil;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
open func encrypt(iv: Data, key: Data, message data: Data, output: UnsafeMutablePointer<Data>?, tag: UnsafeMutablePointer<Data>?) -> Bool {
|
||||
let ctx = EVP_CIPHER_CTX_new();
|
||||
|
||||
EVP_EncryptInit_ex(ctx, key.count == 32 ? EVP_aes_256_gcm() : EVP_aes_128_gcm(), nil, nil, nil);
|
||||
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, Int32(iv.count), nil);
|
||||
iv.withUnsafeBytes({ (ivBytes: UnsafeRawBufferPointer) -> Void in
|
||||
key.withUnsafeBytes({ (keyBytes: UnsafeRawBufferPointer) -> Void in
|
||||
EVP_EncryptInit_ex(ctx, nil, nil, keyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self), ivBytes.baseAddress!.assumingMemoryBound(to: UInt8.self));
|
||||
})
|
||||
});
|
||||
EVP_CIPHER_CTX_set_padding(ctx, 1);
|
||||
|
||||
var outbuf = Array(repeating: UInt8(0), count: data.count);
|
||||
var outbufLen: Int32 = 0;
|
||||
|
||||
let encryptedBody = data.withUnsafeBytes { ( bytes) -> Data in
|
||||
EVP_EncryptUpdate(ctx, &outbuf, &outbufLen, bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), Int32(data.count));
|
||||
return Data(bytes: &outbuf, count: Int(outbufLen));
|
||||
}
|
||||
|
||||
EVP_EncryptFinal_ex(ctx, &outbuf, &outbufLen);
|
||||
|
||||
var tagData = Data(count: 16);
|
||||
tagData.withUnsafeMutableBytes({ (bytes) -> Void in
|
||||
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, bytes.baseAddress!.assumingMemoryBound(to: UInt8.self));
|
||||
});
|
||||
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
tag?.initialize(to: tagData);
|
||||
output?.initialize(to: encryptedBody);
|
||||
return true;
|
||||
}
|
||||
|
||||
open func encrypt(iv: Data, key: Data, provider: CipherDataProvider, consumer: CipherDataConsumer, chunkSize: Int = 512*1024) -> Data {
|
||||
let ctx = EVP_CIPHER_CTX_new();
|
||||
|
||||
EVP_EncryptInit_ex(ctx, key.count == 32 ? EVP_aes_256_gcm() : EVP_aes_128_gcm(), nil, nil, nil);
|
||||
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, Int32(iv.count), nil);
|
||||
iv.withUnsafeBytes({ (ivBytes: UnsafeRawBufferPointer) -> Void in
|
||||
key.withUnsafeBytes({ (keyBytes: UnsafeRawBufferPointer) -> Void in
|
||||
EVP_EncryptInit_ex(ctx, nil, nil, keyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self), ivBytes.baseAddress!.assumingMemoryBound(to: UInt8.self));
|
||||
})
|
||||
});
|
||||
EVP_CIPHER_CTX_set_padding(ctx, 1);
|
||||
|
||||
var ended: Bool = false;
|
||||
var buffer = Data(count: chunkSize * 2);
|
||||
repeat {
|
||||
switch provider.chunk(size: chunkSize) {
|
||||
case .data(let data):
|
||||
let result = data.withUnsafeBytes { ( bytes) -> Data in
|
||||
let wrote = buffer.withUnsafeMutableBytes { (outbuf) -> Int in
|
||||
var outbufLen: Int32 = 0;
|
||||
EVP_EncryptUpdate(ctx, outbuf.baseAddress!.assumingMemoryBound(to: UInt8.self), &outbufLen, bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), Int32(data.count));
|
||||
return Int(outbufLen)
|
||||
}
|
||||
return buffer.subdata(in: 0..<wrote);
|
||||
}
|
||||
_ = consumer.consume(data: result);
|
||||
case .ended:
|
||||
buffer.withUnsafeMutableBytes { (outbuf) -> Void in
|
||||
var outbufLen: Int32 = 0;
|
||||
EVP_EncryptFinal_ex(ctx, outbuf.baseAddress!.assumingMemoryBound(to: UInt8.self), &outbufLen);
|
||||
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, outbuf.baseAddress!.assumingMemoryBound(to: UInt8.self));
|
||||
}
|
||||
ended = true;
|
||||
}
|
||||
} while !ended;
|
||||
|
||||
return buffer.subdata(in: 0..<16);
|
||||
}
|
||||
|
||||
open func decrypt(iv: Data, key: Data, encoded payload: Data, auth tag: Data?, output: UnsafeMutablePointer<Data>?) -> Bool {
|
||||
|
||||
let ctx = EVP_CIPHER_CTX_new();
|
||||
EVP_DecryptInit_ex(ctx, key.count == 32 ? EVP_aes_256_gcm() : EVP_aes_128_gcm(), nil, nil, nil);
|
||||
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, Int32(iv.count), nil);
|
||||
key.withUnsafeBytes({ (keyBytes) -> Void in
|
||||
iv.withUnsafeBytes({ (ivBytes) -> Void in
|
||||
EVP_DecryptInit_ex(ctx, nil, nil, keyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self), ivBytes.baseAddress!.assumingMemoryBound(to: UInt8.self));
|
||||
})
|
||||
})
|
||||
EVP_CIPHER_CTX_set_padding(ctx, 1);
|
||||
|
||||
var auth = tag;
|
||||
var encoded = payload;
|
||||
if auth == nil {
|
||||
auth = payload.subdata(in: (payload.count - 16)..<payload.count);
|
||||
encoded = payload.subdata(in: 0..<(payload.count-16));
|
||||
}
|
||||
|
||||
var outbuf = Array(repeating: UInt8(0), count: encoded.count);
|
||||
var outbufLen: Int32 = 0;
|
||||
let decoded = encoded.withUnsafeBytes({ (bytes) -> Data in
|
||||
EVP_DecryptUpdate(ctx, &outbuf, &outbufLen, bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), Int32(encoded.count));
|
||||
return Data(bytes: &outbuf, count: Int(outbufLen));
|
||||
});
|
||||
|
||||
if auth != nil {
|
||||
auth!.withUnsafeMutableBytes({ [count = auth!.count] (bytes: UnsafeMutableRawBufferPointer) -> Void in
|
||||
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, Int32(count), bytes.baseAddress!.assumingMemoryBound(to: UInt8.self));
|
||||
});
|
||||
}
|
||||
|
||||
let ret = EVP_DecryptFinal_ex(ctx, &outbuf, &outbufLen);
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
guard ret >= 0 else {
|
||||
print("authentication of encrypted message failed:", ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
output?.initialize(to: decoded);
|
||||
return true;
|
||||
}
|
||||
|
||||
open func decrypt(iv: Data, key: Data, provider: CipherDataProvider, consumer: CipherDataConsumer, chunkSize: Int = 512 * 1024) -> Bool {
|
||||
let ctx = EVP_CIPHER_CTX_new();
|
||||
EVP_DecryptInit_ex(ctx, key.count == 32 ? EVP_aes_256_gcm() : EVP_aes_128_gcm(), nil, nil, nil);
|
||||
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, Int32(iv.count), nil);
|
||||
key.withUnsafeBytes({ (keyBytes) -> Void in
|
||||
iv.withUnsafeBytes({ (ivBytes) -> Void in
|
||||
EVP_DecryptInit_ex(ctx, nil, nil, keyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self), ivBytes.baseAddress!.assumingMemoryBound(to: UInt8.self));
|
||||
})
|
||||
})
|
||||
EVP_CIPHER_CTX_set_padding(ctx, 1);
|
||||
|
||||
var ended: Bool = false;
|
||||
var buffer = Data(count: chunkSize * 2);
|
||||
var result = false;
|
||||
repeat {
|
||||
switch provider.chunk(size: chunkSize) {
|
||||
case .data(let data):
|
||||
let result = data.withUnsafeBytes { ( bytes) -> Data in
|
||||
let wrote = buffer.withUnsafeMutableBytes { (outbuf) -> Int in
|
||||
var outbufLen: Int32 = 0;
|
||||
EVP_DecryptUpdate(ctx, outbuf.baseAddress!.assumingMemoryBound(to: UInt8.self), &outbufLen, bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), Int32(data.count));
|
||||
return Int(outbufLen)
|
||||
}
|
||||
return buffer.subdata(in: 0..<wrote);
|
||||
}
|
||||
_ = consumer.consume(data: result);
|
||||
case .ended:
|
||||
if var auth = (provider as? CipherDataProviderWithAuth)?.authTag() {
|
||||
auth.withUnsafeMutableBytes({ [count = auth.count] (bytes: UnsafeMutableRawBufferPointer) -> Void in
|
||||
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, Int32(count), bytes.baseAddress!.assumingMemoryBound(to: UInt8.self));
|
||||
});
|
||||
}
|
||||
result = buffer.withUnsafeMutableBytes { (outbuf) -> Bool in
|
||||
var outbufLen: Int32 = 0;
|
||||
return EVP_DecryptFinal_ex(ctx, outbuf.baseAddress!.assumingMemoryBound(to: UInt8.self), &outbufLen) >= 0;
|
||||
}
|
||||
ended = true;
|
||||
}
|
||||
} while !ended;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class DataDataProvider: CipherDataProvider {
|
||||
|
||||
let data: Data;
|
||||
private(set) var offset: Int = 0;
|
||||
|
||||
public init(data: Data) {
|
||||
self.data = data;
|
||||
}
|
||||
|
||||
public func chunk(size chunkSize: Int) -> Cipher.DataProviderResult {
|
||||
guard offset < data.count else {
|
||||
return .ended;
|
||||
}
|
||||
let size = min(chunkSize, data.count - offset);
|
||||
defer {
|
||||
offset = offset + size;
|
||||
}
|
||||
return .data(data.subdata(in: offset..<(offset + size)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class FileDataProvider: CipherDataProviderWithAuth {
|
||||
|
||||
let inputStream: InputStream;
|
||||
|
||||
var count: Int = 0;
|
||||
var limit: Int = 0;
|
||||
|
||||
public convenience init(inputStream: InputStream, fileSize: Int, hasAuthTag: Bool) {
|
||||
if hasAuthTag {
|
||||
self.init(inputStream: inputStream, limit: fileSize - 16);
|
||||
} else {
|
||||
self.init(inputStream: inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
public init(inputStream: InputStream, limit: Int = Int.max) {
|
||||
self.limit = limit;
|
||||
self.inputStream = inputStream;
|
||||
self.inputStream.open();
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.inputStream.close();
|
||||
}
|
||||
|
||||
public func chunk(size: Int) -> Cipher.DataProviderResult {
|
||||
guard inputStream.hasBytesAvailable else {
|
||||
inputStream.close();
|
||||
return .ended;
|
||||
}
|
||||
|
||||
let limit = min(size, self.limit - self.count);
|
||||
guard limit > 0 else {
|
||||
return .ended;
|
||||
}
|
||||
|
||||
var buf = Array(repeating: UInt8(0), count: limit);
|
||||
let read = inputStream.read(&buf, maxLength: limit);
|
||||
guard read > 0 else {
|
||||
return .ended;
|
||||
}
|
||||
count = count + read;
|
||||
return .data(Data(bytes: &buf, count: read));
|
||||
}
|
||||
|
||||
public func authTag() -> Data? {
|
||||
guard inputStream.hasBytesAvailable else {
|
||||
return nil;
|
||||
}
|
||||
var buf = Array(repeating: UInt8(0), count: 16);
|
||||
let read = inputStream.read(&buf, maxLength: 16);
|
||||
guard read > 0 else {
|
||||
return nil;
|
||||
}
|
||||
return Data(bytes: &buf, count: read);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class TempFileConsumer: CipherDataConsumer {
|
||||
|
||||
public let url: URL;
|
||||
private var outputStream: OutputStream?;
|
||||
public private(set) var size: Int = 0;
|
||||
|
||||
public init?() {
|
||||
self.url = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString);
|
||||
guard let outputStream = OutputStream(url: url, append: true) else {
|
||||
return nil;
|
||||
}
|
||||
self.outputStream = outputStream;
|
||||
self.outputStream?.open();
|
||||
guard self.outputStream != nil, self.outputStream!.hasSpaceAvailable else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
public func consume(data: Data) -> Int {
|
||||
let wrote = data.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> Int in
|
||||
return outputStream!.write(ptr.bindMemory(to: UInt8.self).baseAddress!, maxLength: data.count);
|
||||
}
|
||||
size = size + wrote;
|
||||
return wrote;
|
||||
}
|
||||
|
||||
public func close() {
|
||||
self.outputStream!.close();
|
||||
}
|
||||
|
||||
deinit {
|
||||
if outputStream != nil {
|
||||
outputStream?.close();
|
||||
}
|
||||
try? FileManager.default.removeItem(at: url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum DataProviderResult {
|
||||
case data(Data)
|
||||
case ended
|
||||
}
|
||||
}
|
||||
|
||||
public protocol CipherDataProvider {
|
||||
|
||||
func chunk(size: Int) -> Cipher.DataProviderResult;
|
||||
|
||||
}
|
||||
|
||||
public protocol CipherDataProviderWithAuth: CipherDataProvider {
|
||||
|
||||
func authTag() -> Data?;
|
||||
|
||||
}
|
||||
|
||||
public protocol CipherDataConsumer {
|
||||
|
||||
func consume(data: Data) -> Int;
|
||||
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
//
|
||||
// AccountTableViewCell.swift
|
||||
//
|
||||
// Siskin IM
|
||||
// Copyright (C) 2017 "Tigase, Inc." <office@tigase.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. Look for COPYING file in the top folder.
|
||||
// If not, see https://www.gnu.org/licenses/.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class AccountTableViewCell: UITableViewCell {
|
||||
|
||||
@IBOutlet var accountLabel: UILabel!;
|
||||
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
//
|
||||
// AccountsTableViewController.swift
|
||||
//
|
||||
// Siskin IM
|
||||
// Copyright (C) 2017 "Tigase, Inc." <office@tigase.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. Look for COPYING file in the top folder.
|
||||
// If not, see https://www.gnu.org/licenses/.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class AccountsTableViewController: UITableViewController {
|
||||
|
||||
var accounts: [String] = [];
|
||||
|
||||
var delegate: ShareViewController?;
|
||||
|
||||
var selected: String? = nil;
|
||||
|
||||
override func viewDidLoad() {
|
||||
accounts = getAccounts();
|
||||
super.viewDidLoad();
|
||||
//tableView.register(AccountTableViewCell.self, forCellReuseIdentifier: "accountTableViewCell");
|
||||
}
|
||||
|
||||
func getAccounts() -> [String] {
|
||||
var accounts = [String]();
|
||||
let query = [ String(kSecClass) : kSecClassGenericPassword, String(kSecMatchLimit) : kSecMatchLimitAll, String(kSecReturnAttributes) : kCFBooleanTrue as Any, String(kSecAttrService) : "xmpp" ] as [String : Any];
|
||||
var result:AnyObject?;
|
||||
|
||||
let lastResultCode: OSStatus = withUnsafeMutablePointer(to: &result) {
|
||||
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0));
|
||||
}
|
||||
|
||||
if lastResultCode == noErr {
|
||||
if let results = result as? [[String:NSObject]] {
|
||||
for r in results {
|
||||
let name = r[String(kSecAttrAccount)] as! String;
|
||||
if let data = r[String(kSecAttrGeneric)] as? NSData {
|
||||
let dict = NSKeyedUnarchiver.unarchiveObject(with: data as Data) as? [String:AnyObject];
|
||||
if dict!["active"] as? Bool ?? false {
|
||||
accounts.append(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return accounts;
|
||||
}
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return 1;
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return accounts.count;
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "accountTableViewCell", for: indexPath) as! AccountTableViewCell;
|
||||
let name = accounts[indexPath.row];
|
||||
cell.accountLabel.text = name;
|
||||
if selected != nil && selected! == name {
|
||||
cell.accessoryType = .checkmark;
|
||||
} else {
|
||||
cell.accessoryType = .none;
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let account = accounts[indexPath.row];
|
||||
selected = account;
|
||||
delegate!.accountSelection(account: account);
|
||||
navigationController?.popViewController(animated: true);
|
||||
}
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x",
|
||||
"size" : "57x57"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "57x57"
|
||||
},
|
||||
{
|
||||
"filename" : "logo-120px.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "50x50"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "50x50"
|
||||
},
|
||||
{
|
||||
"filename" : "logo-72px.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "72x72"
|
||||
},
|
||||
{
|
||||
"filename" : "logo-144px.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "72x72"
|
||||
},
|
||||
{
|
||||
"filename" : "logo-76px.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "logo-152px.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "logo-167px.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"filename" : "logo-1024px.png",
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 4.1 KiB |
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Share View Controller-->
|
||||
<scene sceneID="ceB-am-kn3">
|
||||
<objects>
|
||||
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModule="Tigase_Messenger___Share" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="8bI-gs-bmD"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="d5i-Ba-RvD"/>
|
||||
</layoutGuides>
|
||||
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
<extendedEdge key="edgesForExtendedLayout"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="136.80000000000001" y="137.18140929535232"/>
|
||||
</scene>
|
||||
<!--Accounts-->
|
||||
<scene sceneID="taW-D3-3Yx">
|
||||
<objects>
|
||||
<tableViewController storyboardIdentifier="accountSelectionViewController" title="Accounts" useStoryboardIdentifierAsRestorationIdentifier="YES" id="6l2-wd-vXh" customClass="AccountsTableViewController" customModule="Tigase_Messenger___Share" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" id="977-Qx-KPe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" restorationIdentifier="accountTableViewCell" selectionStyle="default" indentationWidth="10" reuseIdentifier="accountTableViewCell" id="SZs-Vf-W54" customClass="AccountTableViewCell" customModule="Tigase_Messenger___Share" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="28" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="SZs-Vf-W54" id="iwn-zZ-n1r">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="95D-JK-fLM">
|
||||
<rect key="frame" x="8" y="8" width="359" height="27.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="95D-JK-fLM" firstAttribute="top" secondItem="iwn-zZ-n1r" secondAttribute="topMargin" id="1uK-gH-Usi"/>
|
||||
<constraint firstItem="95D-JK-fLM" firstAttribute="leading" secondItem="iwn-zZ-n1r" secondAttribute="leadingMargin" id="5IP-c3-dAl"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="95D-JK-fLM" secondAttribute="bottom" id="Gso-BC-0A3"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="95D-JK-fLM" secondAttribute="trailing" id="WJZ-yT-t97"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="accountLabel" destination="95D-JK-fLM" id="Y8d-XC-iCV"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<sections/>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="6l2-wd-vXh" id="2cP-jZ-k4K"/>
|
||||
<outlet property="delegate" destination="6l2-wd-vXh" id="MdV-Xt-gOz"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="NGG-wN-6ep" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1606" y="186"/>
|
||||
</scene>
|
||||
<!--Recipients-->
|
||||
<scene sceneID="bWt-BT-C6m">
|
||||
<objects>
|
||||
<tableViewController storyboardIdentifier="recipientsSelectionViewController" title="Recipients" useStoryboardIdentifierAsRestorationIdentifier="YES" id="wpy-g7-270" customClass="RecipientsSelectionViewController" customModule="Tigase_Messenger___Share" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" id="SH7-od-nvj">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" restorationIdentifier="accountTableViewCell" selectionStyle="default" indentationWidth="10" reuseIdentifier="recipientTableViewCell" id="lwl-dC-64B" customClass="RecipientTableViewCell" customModule="Tigase_Messenger___Share" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="28" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="lwl-dC-64B" id="0s3-Da-XsL">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Geq-xy-Tr2">
|
||||
<rect key="frame" x="8" y="8" width="359" height="27.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="Geq-xy-Tr2" firstAttribute="leading" secondItem="0s3-Da-XsL" secondAttribute="leadingMargin" id="9uo-QN-8Au"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="Geq-xy-Tr2" secondAttribute="trailing" id="L9E-QP-QXL"/>
|
||||
<constraint firstItem="Geq-xy-Tr2" firstAttribute="top" secondItem="0s3-Da-XsL" secondAttribute="topMargin" id="ZB0-2w-ydq"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="Geq-xy-Tr2" secondAttribute="bottom" id="tnB-2B-bZs"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="name" destination="Geq-xy-Tr2" id="zJE-Qp-ccD"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<sections/>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="wpy-g7-270" id="0GI-B2-E3d"/>
|
||||
<outlet property="delegate" destination="wpy-g7-270" id="wbe-ir-gee"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="AYL-Qg-6cV" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="884" y="-202"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
|
@ -1,56 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Share with Snikket</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>NSExtensionActivationRule</key>
|
||||
<dict>
|
||||
<key>NSExtensionActivationDictionaryVersion</key>
|
||||
<integer>2</integer>
|
||||
<key>NSExtensionActivationSupportsAttachmentsWithMaxCount</key>
|
||||
<integer>1</integer>
|
||||
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
|
||||
<integer>1</integer>
|
||||
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
|
||||
<integer>1</integer>
|
||||
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
|
||||
<integer>1</integer>
|
||||
<key>NSExtensionActivationSupportsText</key>
|
||||
<true/>
|
||||
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSExtensionMainStoryboard</key>
|
||||
<string>MainInterface</string>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.share-services</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,29 +0,0 @@
|
|||
//
|
||||
// RecipientTableViewCell.swift
|
||||
//
|
||||
// Siskin IM
|
||||
// Copyright (C) 2017 "Tigase, Inc." <office@tigase.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. Look for COPYING file in the top folder.
|
||||
// If not, see https://www.gnu.org/licenses/.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class RecipientTableViewCell: UITableViewCell {
|
||||
|
||||
@IBOutlet var name: UILabel!;
|
||||
@IBOutlet var jid: UILabel!;
|
||||
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
//
|
||||
// RecipientsSelectionViewController.swift
|
||||
//
|
||||
// Siskin IM
|
||||
// Copyright (C) 2017 "Tigase, Inc." <office@tigase.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. Look for COPYING file in the top folder.
|
||||
// If not, see https://www.gnu.org/licenses/.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TigaseSwift
|
||||
|
||||
class RecipientsSelectionViewController: UITableViewController {
|
||||
|
||||
var selected: [JID] = [];
|
||||
|
||||
var allItems = [JID:RosterItem]();
|
||||
var items: [RosterItem] = [];
|
||||
|
||||
var xmppClient: XMPPClient! {
|
||||
didSet {
|
||||
let store = RosterModule.getRosterStore(xmppClient.sessionObject) as! DefaultRosterStore;
|
||||
self.allItems.removeAll();
|
||||
store.getJids().forEach({(jid) in
|
||||
if let item = store.get(for: jid) {
|
||||
allItems[jid] = item;
|
||||
}
|
||||
});
|
||||
updateItem(item: nil);
|
||||
}
|
||||
}
|
||||
|
||||
var delegate: ShareViewController?;
|
||||
|
||||
var sharedDefaults = UserDefaults(suiteName: "group.snikket.share");
|
||||
fileprivate let indicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 80, height: 80));
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad();
|
||||
indicator.style = .gray;
|
||||
indicator.backgroundColor = UIColor.white;
|
||||
indicator.hidesWhenStopped = true;
|
||||
self.view.addSubview(indicator);
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
if xmppClient.state == .connecting {
|
||||
indicator.startAnimating();
|
||||
}
|
||||
super.viewWillAppear(animated);
|
||||
let view = self.parent!.view!;
|
||||
indicator.center = CGPoint(x: view.bounds.width / 2, y: view.bounds.height / 2);
|
||||
}
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return 1;
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return items.count;
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "recipientTableViewCell", for: indexPath) as! RecipientTableViewCell;
|
||||
let item = items[indexPath.row];
|
||||
cell.name.text = item.name ?? item.jid.stringValue;
|
||||
if selected.contains(item.jid) {
|
||||
cell.accessoryType = .checkmark;
|
||||
} else {
|
||||
cell.accessoryType = .none;
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let jid = items[indexPath.row].jid;
|
||||
if let idx = selected.firstIndex(of: jid) {
|
||||
selected.remove(at: idx);
|
||||
} else {
|
||||
selected.append(jid);
|
||||
}
|
||||
delegate?.recipientsChanged(selected);
|
||||
tableView.reloadData();
|
||||
}
|
||||
|
||||
func hideIndicator() {
|
||||
DispatchQueue.main.async {
|
||||
if self.indicator.isAnimating {
|
||||
self.indicator.stopAnimating();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateItem(item: RosterItem?) {
|
||||
if item != nil {
|
||||
allItems[item!.jid] = item!;
|
||||
}
|
||||
let showHidden = sharedDefaults!.bool(forKey: "RosterDisplayHiddenGroup");
|
||||
let tmp: [RosterItem] = allItems.values.filter({ (ri) -> Bool in
|
||||
return showHidden || !ri.groups.contains("Hidden");
|
||||
});
|
||||
items = tmp.sorted { (r1, r2) -> Bool in
|
||||
return (r1.name ?? r1.jid.stringValue).compare(r2.name ?? r2.jid.stringValue) == .orderedAscending;
|
||||
}
|
||||
tableView.reloadData();
|
||||
if item != nil {
|
||||
hideIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,383 +0,0 @@
|
|||
//
|
||||
// ShareViewController.swift
|
||||
//
|
||||
// Siskin IM
|
||||
// Copyright (C) 2017 "Tigase, Inc." <office@tigase.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. Look for COPYING file in the top folder.
|
||||
// If not, see https://www.gnu.org/licenses/.
|
||||
//
|
||||
import UIKit
|
||||
import Social
|
||||
import Shared
|
||||
import TigaseSwift
|
||||
import MobileCoreServices
|
||||
|
||||
class ShareViewController: SLComposeServiceViewController {
|
||||
|
||||
var account: String? = nil;
|
||||
var recipients: [JID] = [];
|
||||
|
||||
weak var handler: EventHandler?;
|
||||
|
||||
lazy var xmppClient: XMPPClient = {
|
||||
let client = XMPPClient();
|
||||
let sslHandler: ((SessionObject, SecTrust)->Bool) = {(sessionObject,secTrust) -> Bool in
|
||||
return true;
|
||||
};
|
||||
client.sessionObject.setProperty(SocketConnector.SSL_CERTIFICATE_VALIDATOR, value: sslHandler);
|
||||
_ = client.modulesManager.register(AuthModule());
|
||||
_ = client.modulesManager.register(StreamFeaturesModule());
|
||||
_ = client.modulesManager.register(SaslModule());
|
||||
_ = client.modulesManager.register(ResourceBinderModule());
|
||||
_ = client.modulesManager.register(SessionEstablishmentModule());
|
||||
_ = client.modulesManager.register(DiscoveryModule());
|
||||
client.modulesManager.register(PresenceModule()).initialPresence = false;
|
||||
let messageModule = client.modulesManager.register(MessageModule());
|
||||
let rosterModule = client.modulesManager.register(RosterModule());
|
||||
_ = client.modulesManager.register(HttpFileUploadModule());
|
||||
|
||||
let handler = ShareEventHandler();
|
||||
handler.controller = self;
|
||||
self.handler = handler;
|
||||
|
||||
client.eventBus.register(handler: handler, for: RosterModule.ItemUpdatedEvent.TYPE)
|
||||
return client;
|
||||
}();
|
||||
|
||||
lazy var accountConfigurationItem: SLComposeSheetConfigurationItem = {
|
||||
let item = SLComposeSheetConfigurationItem()!;
|
||||
item.title = "Account";
|
||||
item.tapHandler = self.showAccountSelection;
|
||||
return item;
|
||||
}();
|
||||
|
||||
lazy var buddiesConfigurationItem: SLComposeSheetConfigurationItem = {
|
||||
let item = SLComposeSheetConfigurationItem()!;
|
||||
item.title = "Recipients";
|
||||
item.tapHandler = self.showRecipientsSelection;
|
||||
return item;
|
||||
}();
|
||||
|
||||
weak var rosterController: RecipientsSelectionViewController?;
|
||||
|
||||
var webUrl: URL?;
|
||||
|
||||
var sharedDefaults = UserDefaults(suiteName: "group.snikket.share");
|
||||
|
||||
override func isContentValid() -> Bool {
|
||||
return account != nil && xmppClient.state == .connected && recipients.count > 0;
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad();
|
||||
let dbURL = DBConnection.mainDbURL();
|
||||
if !FileManager.default.fileExists(atPath: dbURL.path) {
|
||||
let controller = UIAlertController(title: "Please launch application from the home screen before continuing.", message: nil, preferredStyle: .alert);
|
||||
controller.addAction(UIAlertAction(title: "OK", style: .destructive, handler: { (action) in
|
||||
self.extensionContext?.cancelRequest(withError: ShareError.firstRun);
|
||||
}))
|
||||
self.present(controller, animated: true, completion: nil);
|
||||
}
|
||||
}
|
||||
|
||||
override func presentationAnimationDidFinish() {
|
||||
if !sharedDefaults!.bool(forKey: "SharingViaHttpUpload") {
|
||||
var error = true;
|
||||
if let provider = (self.extensionContext!.inputItems.first as? NSExtensionItem)?.attachments?.first {
|
||||
error = !provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String);
|
||||
}
|
||||
if error {
|
||||
self.showAlert(title: "Failure", message: "Sharing feature with HTTP upload is disabled within application. To use this feature you need to enable sharing with HTTP upload in application");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func didSelectPost() {
|
||||
if let provider = (self.extensionContext!.inputItems.first as? NSExtensionItem)?.attachments?.first {
|
||||
if provider.hasItemConformingToTypeIdentifier(kUTTypeFileURL as String) {
|
||||
provider.loadItem(forTypeIdentifier: kUTTypeFileURL as String, options: nil, completionHandler: { (item, error) in
|
||||
if let localUrl = item as? URL {
|
||||
let uti = try? localUrl.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier;
|
||||
let mimeType = uti != nil ? (UTTypeCopyPreferredTagWithClass(uti! as CFString, kUTTagClassMIMEType)?.takeRetainedValue() as String?) : nil;
|
||||
let size = try? FileManager.default.attributesOfItem(atPath: localUrl.path)[FileAttributeKey.size] as? UInt64;
|
||||
self.upload(localUrl: localUrl, type: mimeType, handler: {(remoteUrl) in
|
||||
guard remoteUrl != nil else {
|
||||
self.showAlert(title: "Failure", message: "Please try again later.");
|
||||
return;
|
||||
}
|
||||
|
||||
if self.sharedDefaults!.integer(forKey: "fileDownloadSizeLimit") > 0 {
|
||||
let hash = Digest.sha1.digest(toHex: remoteUrl!.absoluteString.data(using: .utf8)!)!;
|
||||
|
||||
var params: [String: Any] = [
|
||||
"jids": self.recipients.map({ $0.bareJid.stringValue }),
|
||||
"name": localUrl.lastPathComponent,
|
||||
"timestamp": Date()
|
||||
];
|
||||
if mimeType != nil {
|
||||
params["mimeType"] = mimeType;
|
||||
}
|
||||
if size != nil {
|
||||
params["size"] = Int(size!);
|
||||
}
|
||||
|
||||
let localUploadDirUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.snikket.shared")!.appendingPathComponent("upload", isDirectory: true);
|
||||
if !FileManager.default.fileExists(atPath: localUploadDirUrl.path) {
|
||||
try? FileManager.default.createDirectory(at: localUploadDirUrl, withIntermediateDirectories: true, attributes: nil);
|
||||
}
|
||||
do {
|
||||
try FileManager.default.copyItem(at: localUrl, to: localUploadDirUrl.appendingPathComponent(hash, isDirectory: false));
|
||||
self.sharedDefaults!.set(params as Any?, forKey: "upload-\(hash)");
|
||||
} catch {
|
||||
print("could not copy a file from:", localUrl, "to:", localUploadDirUrl)
|
||||
}
|
||||
}
|
||||
self.share(url: nil, uploadedFileURL: remoteUrl);
|
||||
});
|
||||
} else {
|
||||
self.showAlert(title: "Failure", message: "Please try again later.");
|
||||
}
|
||||
})
|
||||
} else if provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
|
||||
provider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil, completionHandler: { (value, error) in
|
||||
self.share(url: (value as! URL), uploadedFileURL: nil);
|
||||
})
|
||||
// } else if provider.hasItemConformingToTypeIdentifier(kUTTypePlainText as String) {
|
||||
// provider.loadItem(forTypeIdentifier: kUTTypePlainText as String, options: nil, completionHandler: { (item, error) in
|
||||
// self.share(text: item as! String);
|
||||
// });
|
||||
// } else {
|
||||
// self.showAlert(title: "Failure", message: "Please try again later.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func didSelectCancel() {
|
||||
xmppClient.disconnect(true);
|
||||
super.didSelectCancel();
|
||||
}
|
||||
|
||||
override func configurationItems() -> [Any]! {
|
||||
return [accountConfigurationItem, buddiesConfigurationItem];
|
||||
}
|
||||
|
||||
func showAlert(title: String, message: String) {
|
||||
DispatchQueue.main.async {
|
||||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert);
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {(action) in
|
||||
self.extensionContext?.cancelRequest(withError: ShareError.failure);
|
||||
}));
|
||||
self.present(alert, animated: true, completion: nil);
|
||||
}
|
||||
}
|
||||
|
||||
func showAccountSelection() {
|
||||
if xmppClient.state != .disconnected {
|
||||
xmppClient.disconnect(true);
|
||||
}
|
||||
let controller = storyboard?.instantiateViewController(withIdentifier: "accountSelectionViewController") as! AccountsTableViewController;
|
||||
// let controller = AccountsTableViewController(style: .plain);
|
||||
controller.selected = account;
|
||||
controller.delegate = self;
|
||||
pushConfigurationViewController(controller);
|
||||
}
|
||||
|
||||
func accountSelection(account: String) {
|
||||
self.account = account;
|
||||
self.recipients = [];
|
||||
validateContent();
|
||||
self.buddiesConfigurationItem.value = "";
|
||||
accountConfigurationItem.value = account;
|
||||
xmppClient.connectionConfiguration.setUserJID(BareJID(account)!);
|
||||
|
||||
if let password = getAccountPassword() {
|
||||
xmppClient.connectionConfiguration.setUserPassword(password);
|
||||
if let rosterStore: RosterStore = xmppClient.sessionObject.getProperty(RosterModule.ROSTER_STORE_KEY) {
|
||||
rosterStore.cleared();
|
||||
}
|
||||
xmppClient.login();
|
||||
}
|
||||
}
|
||||
|
||||
func showRecipientsSelection() {
|
||||
guard account != nil else {
|
||||
return;
|
||||
}
|
||||
let controller = storyboard?.instantiateViewController(withIdentifier: "recipientsSelectionViewController") as! RecipientsSelectionViewController;
|
||||
controller.selected = recipients;
|
||||
controller.xmppClient = xmppClient;
|
||||
controller.delegate = self;
|
||||
self.rosterController = controller;
|
||||
pushConfigurationViewController(controller);
|
||||
}
|
||||
|
||||
func recipientsChanged(_ recipients: [JID]) {
|
||||
self.recipients = recipients;
|
||||
buddiesConfigurationItem.value = String(recipients.count);
|
||||
validateContent();
|
||||
}
|
||||
|
||||
func getAccountPassword() -> String? {
|
||||
guard account != nil else {
|
||||
return nil;
|
||||
}
|
||||
let query: [String: NSObject] = [ String(kSecClass) : kSecClassGenericPassword, String(kSecMatchLimit) : kSecMatchLimitOne, String(kSecReturnData) : kCFBooleanTrue, String(kSecAttrService) : "xmpp" as NSObject, String(kSecAttrAccount) : account! as NSObject ];
|
||||
|
||||
var result:AnyObject?;
|
||||
|
||||
let lastResultCode: OSStatus = withUnsafeMutablePointer(to: &result) {
|
||||
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0));
|
||||
}
|
||||
|
||||
if lastResultCode == noErr {
|
||||
if let data = result as? NSData {
|
||||
return String(data: data as Data, encoding: String.Encoding.utf8);
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
func upload(localUrl: URL, type: String?, handler: @escaping (URL?)->Void) {
|
||||
let size = try! FileManager.default.attributesOfItem(atPath: localUrl.path)[FileAttributeKey.size] as! UInt64;
|
||||
print("trying to upload", localUrl, "size", size, "type", type as Any);
|
||||
if let httpModule: HttpFileUploadModule = self.xmppClient.modulesManager.getModule(HttpFileUploadModule.ID) {
|
||||
httpModule.findHttpUploadComponent(onSuccess: { (results) in
|
||||
guard !results.isEmpty else {
|
||||
self.showAlert(title: "Upload failed", message: "Feature not supported by XMPP server");
|
||||
return;
|
||||
}
|
||||
|
||||
let compJid = results.filter({ (k,v) -> Bool in
|
||||
return v == nil || v! >= Int(size);
|
||||
}).first?.key;
|
||||
|
||||
guard compJid != nil else {
|
||||
self.showAlert(title: "Upload failed", message: "Selected object is too big!");
|
||||
return;
|
||||
}
|
||||
|
||||
httpModule.requestUploadSlot(componentJid: compJid!, filename: localUrl.pathComponents.last!, size: Int(size), contentType: type ?? "application/octet-stream", onSuccess: {(slot) in
|
||||
print("allocated slot", slot.getUri, slot.putUri);
|
||||
var request = URLRequest(url: slot.putUri);
|
||||
slot.putHeaders.forEach({ (k,v) in
|
||||
request.addValue(v, forHTTPHeaderField: k);
|
||||
});
|
||||
request.httpMethod = "PUT";
|
||||
// let inputStream = InputStream(url: localUrl);
|
||||
// request.httpBodyStream = inputStream;
|
||||
request.addValue(type ?? "application/octet-stream", forHTTPHeaderField: "Content-Type");
|
||||
|
||||
URLSession.shared.uploadTask(with: request, fromFile: localUrl) { (data, response, error) in
|
||||
guard error == nil && ((response as? HTTPURLResponse)?.statusCode ?? 500) == 201 else {
|
||||
print(data as Any, error as Any, response as Any);
|
||||
self.showAlert(title: "Upload failed", message: "Upload to HTTP server failed.");
|
||||
return;
|
||||
}
|
||||
handler(slot.getUri);
|
||||
}.resume();
|
||||
}, onError: {(errorCondition, message) in
|
||||
self.showAlert(title: "Upload failed", message: message ?? "Please try again later.");
|
||||
});
|
||||
}, onError: { (error) in
|
||||
if error != nil && error! == ErrorCondition.item_not_found {
|
||||
self.showAlert(title: "Upload failed", message: "Feature not supported by XMPP server");
|
||||
} else {
|
||||
self.showAlert(title: "Upload failed", message: "Please try again later.");
|
||||
}
|
||||
})
|
||||
} else {
|
||||
showAlert(title: "Upload failure", message: "Upload module not available!");
|
||||
}
|
||||
}
|
||||
|
||||
func share(text: String? = nil, url: URL? = nil, uploadedFileURL: URL? = nil) {
|
||||
recipients.forEach { (recipient) in
|
||||
if !contentText.isEmpty || url != nil {
|
||||
let message = Message();
|
||||
message.type = StanzaType.chat;
|
||||
message.to = recipient;
|
||||
|
||||
if let text = text {
|
||||
message.body = contentText.isEmpty ? text : "\(contentText!) - \(text)";
|
||||
} else if let url = url {
|
||||
message.body = contentText.isEmpty ? url.description : "\(contentText!) - \(url.description)";
|
||||
} else {
|
||||
message.body = contentText;
|
||||
}
|
||||
xmppClient.context.writer?.write(message);
|
||||
}
|
||||
|
||||
if let url = uploadedFileURL {
|
||||
let message = Message();
|
||||
message.type = .chat;
|
||||
message.to = recipient;
|
||||
message.oob = url.description;
|
||||
xmppClient.context.writer?.write(message);
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: {
|
||||
self.xmppClient.disconnect();
|
||||
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// func shareText(url: URL?) {
|
||||
// print("sharing", contentText as Any, url);
|
||||
//
|
||||
// recipients.forEach { (recipient) in
|
||||
// let message = Message();
|
||||
// message.type = StanzaType.chat;
|
||||
// message.to = recipient;
|
||||
// if let url = url {
|
||||
// message.body = contentText.isEmpty ? url.description : "\(contentText!) - \(url.description)";
|
||||
// message.oob = url.description;
|
||||
// }
|
||||
// xmppClient.context.writer?.write(message);
|
||||
// }
|
||||
//
|
||||
// DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: {
|
||||
// self.xmppClient.disconnect();
|
||||
// self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil);
|
||||
// });
|
||||
// }
|
||||
|
||||
class ShareEventHandler: EventHandler {
|
||||
|
||||
weak var controller: ShareViewController?;
|
||||
|
||||
func handle(event: Event) {
|
||||
switch event {
|
||||
case let e as RosterModule.ItemUpdatedEvent:
|
||||
DispatchQueue.main.async {
|
||||
self.controller?.rosterController?.updateItem(item: e.rosterItem!);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum ShareError: Error {
|
||||
case firstRun
|
||||
case featureNotAvailable
|
||||
case tooBig
|
||||
case failure
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.snikket.shared</string>
|
||||
<string>group.snikket.share</string>
|
||||
</array>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)org.snikket.ios.Share</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,11 +0,0 @@
|
|||
/* Class = "UITableViewController"; title = "Accounts"; ObjectID = "6l2-wd-vXh"; */
|
||||
"6l2-wd-vXh.title" = "Accounts";
|
||||
|
||||
/* Class = "UILabel"; text = "Label"; ObjectID = "95D-JK-fLM"; */
|
||||
"95D-JK-fLM.text" = "Label";
|
||||
|
||||
/* Class = "UILabel"; text = "Label"; ObjectID = "Geq-xy-Tr2"; */
|
||||
"Geq-xy-Tr2.text" = "Label";
|
||||
|
||||
/* Class = "UITableViewController"; title = "Recipients"; ObjectID = "wpy-g7-270"; */
|
||||
"wpy-g7-270.title" = "Recipients";
|
|
@ -1,11 +0,0 @@
|
|||
/* Class = "UITableViewController"; title = "Accounts"; ObjectID = "6l2-wd-vXh"; */
|
||||
"6l2-wd-vXh.title" = "Accounts";
|
||||
|
||||
/* Class = "UILabel"; text = "Label"; ObjectID = "95D-JK-fLM"; */
|
||||
"95D-JK-fLM.text" = "Label";
|
||||
|
||||
/* Class = "UILabel"; text = "Label"; ObjectID = "Geq-xy-Tr2"; */
|
||||
"Geq-xy-Tr2.text" = "Label";
|
||||
|
||||
/* Class = "UITableViewController"; title = "Recipients"; ObjectID = "wpy-g7-270"; */
|
||||
"wpy-g7-270.title" = "Recipients";
|
|
@ -1,11 +0,0 @@
|
|||
/* Class = "UITableViewController"; title = "Accounts"; ObjectID = "6l2-wd-vXh"; */
|
||||
"6l2-wd-vXh.title" = "Accounts";
|
||||
|
||||
/* Class = "UILabel"; text = "Label"; ObjectID = "95D-JK-fLM"; */
|
||||
"95D-JK-fLM.text" = "Label";
|
||||
|
||||
/* Class = "UILabel"; text = "Label"; ObjectID = "Geq-xy-Tr2"; */
|
||||
"Geq-xy-Tr2.text" = "Label";
|
||||
|
||||
/* Class = "UITableViewController"; title = "Recipients"; ObjectID = "wpy-g7-270"; */
|
||||
"wpy-g7-270.title" = "Recipients";
|
|
@ -1,11 +0,0 @@
|
|||
/* Class = "UITableViewController"; title = "Accounts"; ObjectID = "6l2-wd-vXh"; */
|
||||
"6l2-wd-vXh.title" = "Accounts";
|
||||
|
||||
/* Class = "UILabel"; text = "Label"; ObjectID = "95D-JK-fLM"; */
|
||||
"95D-JK-fLM.text" = "Label";
|
||||
|
||||
/* Class = "UILabel"; text = "Label"; ObjectID = "Geq-xy-Tr2"; */
|
||||
"Geq-xy-Tr2.text" = "Label";
|
||||
|
||||
/* Class = "UITableViewController"; title = "Recipients"; ObjectID = "wpy-g7-270"; */
|
||||
"wpy-g7-270.title" = "Recipients";
|
|
@ -1,11 +0,0 @@
|
|||
/* Class = "UITableViewController"; title = "Accounts"; ObjectID = "6l2-wd-vXh"; */
|
||||
"6l2-wd-vXh.title" = "Accounts";
|
||||
|
||||
/* Class = "UILabel"; text = "Label"; ObjectID = "95D-JK-fLM"; */
|
||||
"95D-JK-fLM.text" = "Label";
|
||||
|
||||
/* Class = "UILabel"; text = "Label"; ObjectID = "Geq-xy-Tr2"; */
|
||||
"Geq-xy-Tr2.text" = "Label";
|
||||
|
||||
/* Class = "UITableViewController"; title = "Recipients"; ObjectID = "wpy-g7-270"; */
|
||||
"wpy-g7-270.title" = "Recipients";
|
|
@ -1,11 +0,0 @@
|
|||
/* Class = "UITableViewController"; title = "Accounts"; ObjectID = "6l2-wd-vXh"; */
|
||||
"6l2-wd-vXh.title" = "Accounts";
|
||||
|
||||
/* Class = "UILabel"; text = "Label"; ObjectID = "95D-JK-fLM"; */
|
||||
"95D-JK-fLM.text" = "Label";
|
||||
|
||||
/* Class = "UILabel"; text = "Label"; ObjectID = "Geq-xy-Tr2"; */
|
||||
"Geq-xy-Tr2.text" = "Label";
|
||||
|
||||
/* Class = "UITableViewController"; title = "Recipients"; ObjectID = "wpy-g7-270"; */
|
||||
"wpy-g7-270.title" = "Recipients";
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,43 +0,0 @@
|
|||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "HSLuvSwift",
|
||||
"repositoryURL": "https://github.com/hsluv/hsluv-swift.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "9dee4378180f2a9a33c96bc20284cf64158dbe1b",
|
||||
"version": "2.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "libsignal",
|
||||
"repositoryURL": "https://github.com/tigase/libsignal",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "d23d5af0d729cf66b93cea607f3f84a34b9fddfd",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "TigaseSwift",
|
||||
"repositoryURL": "https://github.com/tigase/tigase-swift",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "d3953dcea80010ad433fa96d7ca6b989bf58850c",
|
||||
"version": "2.1.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "TigaseSwiftOMEMO",
|
||||
"repositoryURL": "https://github.com/tigase/tigase-swift-omemo/",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "4c997df9a3a686b7e522ddffa4d2f2c070a94fa0",
|
||||
"version": "1.1.3"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|