ruby_everywhere 0.1.15 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bridge/README.md +57 -2
- data/bridge/everywhere/bridge.js +803 -9
- data/bridge/package.json +1 -1
- data/lib/everywhere/builders/ios.rb +364 -0
- data/lib/everywhere/cli.rb +2 -0
- data/lib/everywhere/commands/build.rb +17 -1
- data/lib/everywhere/commands/clean.rb +19 -10
- data/lib/everywhere/commands/dev.rb +182 -19
- data/lib/everywhere/commands/doctor.rb +45 -3
- data/lib/everywhere/commands/icon.rb +14 -0
- data/lib/everywhere/commands/install.rb +37 -6
- data/lib/everywhere/commands/logs.rb +56 -0
- data/lib/everywhere/commands/platform/build.rb +3 -3
- data/lib/everywhere/commands/platform/runner.rb +1 -1
- data/lib/everywhere/commands/release.rb +1 -1
- data/lib/everywhere/commands/shell_dir.rb +11 -4
- data/lib/everywhere/config.rb +366 -1
- data/lib/everywhere/engine.rb +33 -1
- data/lib/everywhere/icon.rb +50 -0
- data/lib/everywhere/log_filter.rb +28 -2
- data/lib/everywhere/mobile_config_endpoint.rb +75 -0
- data/lib/everywhere/mobile_configs_controller.rb +46 -0
- data/lib/everywhere/native_helper.rb +156 -0
- data/lib/everywhere/paths.rb +41 -0
- data/lib/everywhere/raster.rb +17 -0
- data/lib/everywhere/shellout.rb +3 -1
- data/lib/everywhere/simulator.rb +74 -0
- data/lib/everywhere/ui.rb +18 -0
- data/lib/everywhere/version.rb +1 -1
- data/support/mobile/ios/App/App.xcconfig +6 -0
- data/support/mobile/ios/App/AppDelegate.swift +163 -0
- data/support/mobile/ios/App/Assets.xcassets/AccentColor.colorset/Contents.json +20 -0
- data/support/mobile/ios/App/Assets.xcassets/AppIcon.appiconset/AppIcon.png +0 -0
- data/support/mobile/ios/App/Assets.xcassets/AppIcon.appiconset/Contents.json +14 -0
- data/support/mobile/ios/App/Assets.xcassets/Contents.json +6 -0
- data/support/mobile/ios/App/Assets.xcassets/LaunchBackground.colorset/Contents.json +38 -0
- data/support/mobile/ios/App/Base.lproj/LaunchScreen.storyboard +32 -0
- data/support/mobile/ios/App/Bridge/BiometricsComponent.swift +276 -0
- data/support/mobile/ios/App/Bridge/HapticsComponent.swift +47 -0
- data/support/mobile/ios/App/Bridge/NotificationComponent.swift +56 -0
- data/support/mobile/ios/App/Bridge/PermissionsComponent.swift +142 -0
- data/support/mobile/ios/App/Bridge/StorageComponent.swift +63 -0
- data/support/mobile/ios/App/ErrorViewController.swift +64 -0
- data/support/mobile/ios/App/EverywhereConfig.swift +268 -0
- data/support/mobile/ios/App/EverywhereHost.swift +34 -0
- data/support/mobile/ios/App/Extensions/EverywhereExtensions.swift +32 -0
- data/support/mobile/ios/App/Info.plist +28 -0
- data/support/mobile/ios/App/Resources/everywhere.json +8 -0
- data/support/mobile/ios/App/Resources/path-configuration.json +19 -0
- data/support/mobile/ios/App/SceneDelegate.swift +443 -0
- data/support/mobile/ios/App.xcodeproj/project.pbxproj +454 -0
- data/support/mobile/ios/App.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- data/support/mobile/ios/App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +14 -0
- data/support/mobile/ios/App.xcodeproj/xcshareddata/xcschemes/App.xcscheme +77 -0
- data/support/mobile/ios/NativeExtensions/Package.swift +26 -0
- data/support/mobile/ios/NativeExtensions/Sources/NativeExtensions/Exports.swift +5 -0
- data/support/mobile/ios/README.md +66 -0
- metadata +35 -1
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import HotwireNative
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
/// A color that adapts to light/dark mode. Decodes either a single hex string
|
|
6
|
+
/// (used for both appearances) or a `{"light": "#RRGGBB", "dark": "#RRGGBB"}` object.
|
|
7
|
+
struct ThemedColor: Decodable {
|
|
8
|
+
let light: String
|
|
9
|
+
let dark: String?
|
|
10
|
+
|
|
11
|
+
init(from decoder: Decoder) throws {
|
|
12
|
+
if let container = try? decoder.singleValueContainer(), let hex = try? container.decode(String.self) {
|
|
13
|
+
light = hex
|
|
14
|
+
dark = nil
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
18
|
+
light = try container.decode(String.self, forKey: .light)
|
|
19
|
+
dark = try container.decodeIfPresent(String.self, forKey: .dark)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private enum CodingKeys: String, CodingKey {
|
|
23
|
+
case light, dark
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var uiColor: UIColor {
|
|
27
|
+
let lightColor = UIColor(hex: light) ?? .tintColor
|
|
28
|
+
guard let dark, let darkColor = UIColor(hex: dark) else { return lightColor }
|
|
29
|
+
return UIColor { traits in
|
|
30
|
+
traits.userInterfaceStyle == .dark ? darkColor : lightColor
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// The app's configuration, stamped into the bundle as `everywhere.json` by
|
|
36
|
+
/// `every build`. The raw template ships a placeholder pointing at rubyeverywhere.com.
|
|
37
|
+
struct EverywhereConfig: Decodable {
|
|
38
|
+
let name: String
|
|
39
|
+
let version: String?
|
|
40
|
+
let mode: String?
|
|
41
|
+
let remoteUrl: String?
|
|
42
|
+
let remoteInstances: Bool?
|
|
43
|
+
let entryPath: String?
|
|
44
|
+
let tintColor: ThemedColor?
|
|
45
|
+
let backgroundColor: ThemedColor?
|
|
46
|
+
let permissions: [String]?
|
|
47
|
+
let splashMinSeconds: Double?
|
|
48
|
+
let lazyLoadTabs: Bool?
|
|
49
|
+
|
|
50
|
+
private enum CodingKeys: String, CodingKey {
|
|
51
|
+
case name
|
|
52
|
+
case version
|
|
53
|
+
case mode
|
|
54
|
+
case remoteUrl = "remote_url"
|
|
55
|
+
case remoteInstances = "remote_instances"
|
|
56
|
+
case entryPath = "entry_path"
|
|
57
|
+
case tintColor = "tint_color"
|
|
58
|
+
case backgroundColor = "background_color"
|
|
59
|
+
case permissions
|
|
60
|
+
case splashMinSeconds = "splash_min_seconds"
|
|
61
|
+
case lazyLoadTabs = "lazy_load_tabs"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Whether the tab bar defers each non-selected tab's initial visit until
|
|
65
|
+
/// it is first selected (everywhere.yml `lazy_load_tabs`). Absent in the
|
|
66
|
+
/// stamped config means the app didn't set it, so we match Hotwire Native's
|
|
67
|
+
/// own default: load every tab eagerly.
|
|
68
|
+
var lazyLoadsTabs: Bool {
|
|
69
|
+
lazyLoadTabs ?? false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// Minimum on-screen time for an app-provided splash
|
|
73
|
+
/// (everywhere.yml native.ios.splash_min_seconds).
|
|
74
|
+
var splashMinimumDisplay: TimeInterval {
|
|
75
|
+
splashMinSeconds ?? 1.0
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// Whether everywhere.yml declares this permission. Undeclared camera and
|
|
79
|
+
/// location must never reach the system APIs — their Info.plist usage
|
|
80
|
+
/// strings are only stamped for declared permissions, and requesting
|
|
81
|
+
/// without one crashes the app.
|
|
82
|
+
func declaresPermission(_ name: String) -> Bool {
|
|
83
|
+
permissions?.contains(name) ?? false
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
static let shared: EverywhereConfig = {
|
|
87
|
+
guard let url = Bundle.main.url(forResource: "everywhere", withExtension: "json"),
|
|
88
|
+
let data = try? Data(contentsOf: url)
|
|
89
|
+
else {
|
|
90
|
+
fatalError("everywhere.json is missing from the app bundle — stamp this shell with `every build` before running it.")
|
|
91
|
+
}
|
|
92
|
+
do {
|
|
93
|
+
return try JSONDecoder().decode(EverywhereConfig.self, from: data)
|
|
94
|
+
} catch {
|
|
95
|
+
fatalError("everywhere.json in the app bundle is invalid (\(error)) — re-stamp this shell with `every build`.")
|
|
96
|
+
}
|
|
97
|
+
}()
|
|
98
|
+
|
|
99
|
+
/// The dev-server override, resolved once. `EVERYWHERE_DEV_URL` arrives
|
|
100
|
+
/// via `simctl launch` — but ONLY then. Relaunching from the home screen
|
|
101
|
+
/// (or after iOS force-kills the app for a Settings permission change)
|
|
102
|
+
/// has no env, which would silently fall back to the production
|
|
103
|
+
/// remote.url. Debug builds persist the last dev URL to survive those
|
|
104
|
+
/// relaunches; release builds never read it.
|
|
105
|
+
private static let devOverrideURL: URL? = {
|
|
106
|
+
let key = "EverywhereDevURL"
|
|
107
|
+
if let dev = ProcessInfo.processInfo.environment["EVERYWHERE_DEV_URL"], let url = URL(string: dev) {
|
|
108
|
+
#if DEBUG
|
|
109
|
+
UserDefaults.standard.set(dev, forKey: key)
|
|
110
|
+
#endif
|
|
111
|
+
return url
|
|
112
|
+
}
|
|
113
|
+
#if DEBUG
|
|
114
|
+
if let saved = UserDefaults.standard.string(forKey: key), let url = URL(string: saved) {
|
|
115
|
+
return url
|
|
116
|
+
}
|
|
117
|
+
#endif
|
|
118
|
+
return nil
|
|
119
|
+
}()
|
|
120
|
+
|
|
121
|
+
// MARK: Instance override (multi-instance apps)
|
|
122
|
+
|
|
123
|
+
/// UserDefaults key holding the user-chosen instance root. Multi-instance
|
|
124
|
+
/// apps (`remote.instances: true`) boot into remote.url — the hosted
|
|
125
|
+
/// instance picker — until the picker calls `Everywhere.instance.set`;
|
|
126
|
+
/// from then on every launch boots the chosen instance, until cleared.
|
|
127
|
+
private static let instanceKey = "EverywhereInstanceURL"
|
|
128
|
+
|
|
129
|
+
/// The persisted instance override. Read per-access, never cached:
|
|
130
|
+
/// setInstance changes it mid-run and everything derived from rootURL
|
|
131
|
+
/// (start URL, path-configuration URL) must follow immediately.
|
|
132
|
+
static var instanceURL: URL? {
|
|
133
|
+
guard shared.remoteInstances == true else { return nil }
|
|
134
|
+
return UserDefaults.standard.string(forKey: instanceKey).flatMap { URL(string: $0) }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// Persist (nil clears) the instance override. Only absolute http(s)
|
|
138
|
+
/// roots are accepted — the page posts arbitrary strings at the control
|
|
139
|
+
/// channel, and re-rooting the whole app is the one place that must not
|
|
140
|
+
/// take them on faith. (http stays allowed for LAN-hosted instances.)
|
|
141
|
+
@discardableResult
|
|
142
|
+
static func setInstanceURL(_ url: URL?) -> Bool {
|
|
143
|
+
guard let url else {
|
|
144
|
+
UserDefaults.standard.removeObject(forKey: instanceKey)
|
|
145
|
+
return true
|
|
146
|
+
}
|
|
147
|
+
guard let scheme = url.scheme?.lowercased(), ["https", "http"].contains(scheme),
|
|
148
|
+
url.host != nil
|
|
149
|
+
else { return false }
|
|
150
|
+
UserDefaults.standard.set(url.absoluteString, forKey: instanceKey)
|
|
151
|
+
return true
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// The root URL of the deployed app. A picked instance outranks the dev
|
|
155
|
+
/// override on purpose: it's the more recent, in-app expression of intent,
|
|
156
|
+
/// and dev-testing the picker flow only works if picking takes effect.
|
|
157
|
+
/// `Everywhere.instance.clear()` falls back to the dev/stamped root.
|
|
158
|
+
var rootURL: URL {
|
|
159
|
+
if let instance = Self.instanceURL {
|
|
160
|
+
return instance
|
|
161
|
+
}
|
|
162
|
+
if let dev = Self.devOverrideURL {
|
|
163
|
+
return dev
|
|
164
|
+
}
|
|
165
|
+
guard let remoteUrl, let url = URL(string: remoteUrl) else {
|
|
166
|
+
fatalError("remote.url not set in everywhere.yml — iOS shell is remote-mode only")
|
|
167
|
+
}
|
|
168
|
+
return url
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/// The first location the navigator visits: rootURL joined with entry_path.
|
|
172
|
+
var startURL: URL {
|
|
173
|
+
guard let entryPath, !entryPath.isEmpty, entryPath != "/" else { return rootURL }
|
|
174
|
+
return url(forPath: entryPath)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// The server half of the path configuration (rules + settings.tabs),
|
|
178
|
+
/// generated from everywhere.yml by the gem's MobileConfigEndpoint — so
|
|
179
|
+
/// tab changes deploy with the web app, no app-store release.
|
|
180
|
+
var pathConfigurationURL: URL {
|
|
181
|
+
url(forPath: "/everywhere/ios_v1.json")
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/// The path-configuration sources. At launch we want both: the bundled
|
|
185
|
+
/// copy loads synchronously (instant tabs, works offline) and the live
|
|
186
|
+
/// server copy refines it. After launch — foregrounds and resets — we use
|
|
187
|
+
/// the server copy ONLY: it's the auth-aware source of truth, and skipping
|
|
188
|
+
/// the bundled copy avoids a flash of the baked (all-tabs) config before
|
|
189
|
+
/// the server's answer lands.
|
|
190
|
+
static func pathConfigurationSources(serverOnly: Bool = false) -> [PathConfiguration.Source] {
|
|
191
|
+
let server = PathConfiguration.Source.server(shared.pathConfigurationURL)
|
|
192
|
+
guard !serverOnly else { return [server] }
|
|
193
|
+
|
|
194
|
+
return [
|
|
195
|
+
.file(Bundle.main.url(forResource: "path-configuration", withExtension: "json")!),
|
|
196
|
+
server
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/// rootURL joined with an absolute-style path (leading `/` optional).
|
|
201
|
+
func url(forPath path: String) -> URL {
|
|
202
|
+
let root = rootURL
|
|
203
|
+
guard var components = URLComponents(url: root, resolvingAgainstBaseURL: false) else { return root }
|
|
204
|
+
let basePath = components.path.hasSuffix("/") ? String(components.path.dropLast()) : components.path
|
|
205
|
+
let suffix = path.hasPrefix("/") ? path : "/" + path
|
|
206
|
+
components.path = basePath + suffix
|
|
207
|
+
return components.url ?? root
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
var userAgentPrefix: String {
|
|
211
|
+
if let version {
|
|
212
|
+
return "RubyEverywhere/\(version) (ios)"
|
|
213
|
+
}
|
|
214
|
+
return "RubyEverywhere (ios)"
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/// User-Agent for native URLSession requests (everywhereURLSession): the
|
|
218
|
+
/// shell prefix plus the Hotwire Native marker, so the gem's
|
|
219
|
+
/// native_app?/native_platform/native_version helpers see native-screen
|
|
220
|
+
/// requests exactly like web-view requests.
|
|
221
|
+
var nativeUserAgent: String {
|
|
222
|
+
"\(userAgentPrefix) Hotwire Native iOS"
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
var tint: UIColor? { tintColor?.uiColor }
|
|
226
|
+
var background: UIColor? { backgroundColor?.uiColor }
|
|
227
|
+
|
|
228
|
+
/// A compact JSON object injected into every page as `window.__EVERYWHERE_CONFIG__`.
|
|
229
|
+
/// Built per web view (not once at launch): `instance` must reflect the
|
|
230
|
+
/// override as it stands when the post-reset web views are created.
|
|
231
|
+
var webConfigJSON: String {
|
|
232
|
+
var payload: [String: Any] = ["name": name, "os": "ios"]
|
|
233
|
+
if let version { payload["version"] = version }
|
|
234
|
+
if remoteInstances == true {
|
|
235
|
+
payload["instances"] = true
|
|
236
|
+
if let instance = Self.instanceURL { payload["instance"] = instance.absoluteString }
|
|
237
|
+
}
|
|
238
|
+
guard let data = try? JSONSerialization.data(withJSONObject: payload, options: [.sortedKeys]),
|
|
239
|
+
let json = String(data: data, encoding: .utf8)
|
|
240
|
+
else {
|
|
241
|
+
return #"{"os":"ios"}"#
|
|
242
|
+
}
|
|
243
|
+
return json
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
extension UIColor {
|
|
248
|
+
/// Accepts `#RRGGBB` and `#RRGGBBAA` (leading `#` optional).
|
|
249
|
+
convenience init?(hex: String) {
|
|
250
|
+
var value = hex.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
251
|
+
if value.hasPrefix("#") { value.removeFirst() }
|
|
252
|
+
guard value.count == 6 || value.count == 8, let bits = UInt64(value, radix: 16) else { return nil }
|
|
253
|
+
|
|
254
|
+
let red, green, blue, alpha: CGFloat
|
|
255
|
+
if value.count == 8 {
|
|
256
|
+
red = CGFloat((bits >> 24) & 0xFF) / 255.0
|
|
257
|
+
green = CGFloat((bits >> 16) & 0xFF) / 255.0
|
|
258
|
+
blue = CGFloat((bits >> 8) & 0xFF) / 255.0
|
|
259
|
+
alpha = CGFloat(bits & 0xFF) / 255.0
|
|
260
|
+
} else {
|
|
261
|
+
red = CGFloat((bits >> 16) & 0xFF) / 255.0
|
|
262
|
+
green = CGFloat((bits >> 8) & 0xFF) / 255.0
|
|
263
|
+
blue = CGFloat(bits & 0xFF) / 255.0
|
|
264
|
+
alpha = 1.0
|
|
265
|
+
}
|
|
266
|
+
self.init(red: red, green: green, blue: blue, alpha: alpha)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
/// Called by the generated EverywhereExtensions registry around screen and
|
|
5
|
+
/// splash declarations: UIViewController subclasses pass through untouched,
|
|
6
|
+
/// SwiftUI views get wrapped in a hosting controller — the overload resolves
|
|
7
|
+
/// per declared type at compile time, so everywhere.yml can name either kind.
|
|
8
|
+
func everywhereHost(_ controller: UIViewController) -> UIViewController {
|
|
9
|
+
controller
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
func everywhereHost(_ view: some View) -> UIViewController {
|
|
13
|
+
UIHostingController(rootView: view)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// URLSession for native extension code talking to the app's own server.
|
|
17
|
+
/// Cookies come from HTTPCookieStorage.shared — which the shell mirrors from
|
|
18
|
+
/// the web views, so requests ride the signed-in session — and every request
|
|
19
|
+
/// carries the app's native User-Agent, so server-side native_app? /
|
|
20
|
+
/// native_platform checks hold. Prefer this over URLSession.shared.
|
|
21
|
+
let everywhereURLSession: URLSession = {
|
|
22
|
+
let configuration = URLSessionConfiguration.default
|
|
23
|
+
configuration.httpAdditionalHeaders = ["User-Agent": EverywhereConfig.shared.nativeUserAgent]
|
|
24
|
+
return URLSession(configuration: configuration)
|
|
25
|
+
}()
|
|
26
|
+
|
|
27
|
+
/// Route the app to a path (or absolute URL) from native extension code — the
|
|
28
|
+
/// native counterpart of `Everywhere.visit()`. Relative paths resolve against
|
|
29
|
+
/// the effective root (dev override / picked instance included); web routes
|
|
30
|
+
/// load in the web view, native-screen routes push their screen.
|
|
31
|
+
func everywhereVisit(_ path: String) {
|
|
32
|
+
NotificationCenter.default.post(name: .everywhereNativeVisit, object: nil,
|
|
33
|
+
userInfo: ["path": path])
|
|
34
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import HotwireNative
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
// The app-extension registry. This checked-in copy is the empty default so
|
|
5
|
+
// the raw template builds unchanged; `every build` overwrites it — and drops
|
|
6
|
+
// the app repo's native/ios/*.swift beside it — when config/everywhere.yml
|
|
7
|
+
// declares native code:
|
|
8
|
+
//
|
|
9
|
+
// native:
|
|
10
|
+
// ios:
|
|
11
|
+
// components: [ChartComponent] # BridgeComponent subclasses to register
|
|
12
|
+
// screens: # path rules with view_controller: <id>
|
|
13
|
+
// map: MapScreen # SwiftUI View or UIViewController, init(url:)
|
|
14
|
+
// splash: LaunchSplash # SwiftUI View or UIViewController, init()
|
|
15
|
+
//
|
|
16
|
+
// Extensions/ is a filesystem-synchronized (buildable) folder: files landing
|
|
17
|
+
// here compile without the pbxproj ever being touched.
|
|
18
|
+
enum EverywhereExtensions {
|
|
19
|
+
/// Extra bridge components, registered alongside the built-ins.
|
|
20
|
+
static let components: [BridgeComponent.Type] = []
|
|
21
|
+
|
|
22
|
+
/// The native screen for a path-configuration rule's `view_controller`
|
|
23
|
+
/// identifier; nil falls through to the standard web visit.
|
|
24
|
+
static func screen(for identifier: String, url: URL) -> UIViewController? {
|
|
25
|
+
nil
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Replaces the splash overlay's default spinner when non-nil.
|
|
29
|
+
static func splashViewController() -> UIViewController? {
|
|
30
|
+
nil
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>NSAppTransportSecurity</key>
|
|
6
|
+
<dict>
|
|
7
|
+
<key>NSAllowsLocalNetworking</key>
|
|
8
|
+
<true/>
|
|
9
|
+
</dict>
|
|
10
|
+
<key>UIApplicationSceneManifest</key>
|
|
11
|
+
<dict>
|
|
12
|
+
<key>UIApplicationSupportsMultipleScenes</key>
|
|
13
|
+
<false/>
|
|
14
|
+
<key>UISceneConfigurations</key>
|
|
15
|
+
<dict>
|
|
16
|
+
<key>UIWindowSceneSessionRoleApplication</key>
|
|
17
|
+
<array>
|
|
18
|
+
<dict>
|
|
19
|
+
<key>UISceneConfigurationName</key>
|
|
20
|
+
<string>Default</string>
|
|
21
|
+
<key>UISceneDelegateClassName</key>
|
|
22
|
+
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
|
23
|
+
</dict>
|
|
24
|
+
</array>
|
|
25
|
+
</dict>
|
|
26
|
+
</dict>
|
|
27
|
+
</dict>
|
|
28
|
+
</plist>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"settings": {},
|
|
3
|
+
"rules": [
|
|
4
|
+
{
|
|
5
|
+
"patterns": [".*"],
|
|
6
|
+
"properties": {
|
|
7
|
+
"context": "default",
|
|
8
|
+
"pull_to_refresh_enabled": true
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"patterns": ["/new$", "/edit$"],
|
|
13
|
+
"properties": {
|
|
14
|
+
"context": "modal",
|
|
15
|
+
"pull_to_refresh_enabled": false
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|