rubysketch-solitaire 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +3 -0
- data/.gitignore +2 -0
- data/.hooks/pre-commit +69 -0
- data/ChangeLog.md +21 -0
- data/Gemfile.lock +10 -10
- data/Podfile.lock +33 -33
- data/Rakefile +107 -10
- data/RubySolitaire/Ad.swift +88 -0
- data/RubySolitaire/Assets.xcassets/AppIcon.appiconset/AppIcon.png +0 -0
- data/RubySolitaire/Assets.xcassets/AppIcon.appiconset/Contents.json +3 -87
- data/RubySolitaire/Base.lproj/Localizable.strings +6 -0
- data/RubySolitaire/Extensions.swift +5 -0
- data/RubySolitaire/GameView.swift +75 -13
- data/RubySolitaire/Helper.swift +70 -0
- data/RubySolitaire/MenuScreen.swift +140 -0
- data/RubySolitaire/RubySolitaireApp.swift +188 -1
- data/RubySolitaire/SafariView.swift +16 -0
- data/RubySolitaire/Strings.swift +48 -0
- data/RubySolitaire/ja.lproj/InfoPlist.strings +4 -0
- data/RubySolitaire/ja.lproj/Localizable.strings +6 -0
- data/VERSION +1 -1
- data/data/card.png +0 -0
- data/data/classicPSPWave.glsl +94 -0
- data/data/colorfulUnderwaterBubbles2.glsl +113 -0
- data/data/cosmic2.glsl +121 -0
- data/data/reflectiveHexes.glsl +219 -0
- data/fastlane/Fastfile +76 -0
- data/fastlane/metadata/copyright.txt +1 -0
- data/fastlane/metadata/en-US/apple_tv_privacy_policy.txt +1 -0
- data/fastlane/metadata/en-US/description.txt +5 -0
- data/fastlane/metadata/en-US/keywords.txt +1 -0
- data/fastlane/metadata/en-US/marketing_url.txt +1 -0
- data/fastlane/metadata/en-US/name.txt +1 -0
- data/fastlane/metadata/en-US/privacy_url.txt +1 -0
- data/fastlane/metadata/en-US/promotional_text.txt +1 -0
- data/fastlane/metadata/en-US/release_notes.txt +1 -0
- data/fastlane/metadata/en-US/subtitle.txt +1 -0
- data/fastlane/metadata/en-US/support_url.txt +1 -0
- data/fastlane/metadata/ja/apple_tv_privacy_policy.txt +1 -0
- data/fastlane/metadata/ja/description.txt +5 -0
- data/fastlane/metadata/ja/keywords.txt +1 -0
- data/fastlane/metadata/ja/marketing_url.txt +1 -0
- data/fastlane/metadata/ja/name.txt +1 -0
- data/fastlane/metadata/ja/privacy_url.txt +1 -0
- data/fastlane/metadata/ja/promotional_text.txt +1 -0
- data/fastlane/metadata/ja/release_notes.txt +1 -0
- data/fastlane/metadata/ja/subtitle.txt +1 -0
- data/fastlane/metadata/ja/support_url.txt +1 -0
- data/fastlane/metadata/primary_category.txt +1 -0
- data/fastlane/metadata/primary_first_sub_category.txt +1 -0
- data/fastlane/metadata/primary_second_sub_category.txt +1 -0
- data/fastlane/metadata/review_information/demo_password.txt +1 -0
- data/fastlane/metadata/review_information/demo_user.txt +1 -0
- data/fastlane/metadata/review_information/email_address.txt +1 -0
- data/fastlane/metadata/review_information/first_name.txt +0 -0
- data/fastlane/metadata/review_information/last_name.txt +0 -0
- data/fastlane/metadata/review_information/notes.txt +1 -0
- data/fastlane/metadata/review_information/phone_number.txt +0 -0
- data/fastlane/metadata/secondary_category.txt +1 -0
- data/fastlane/metadata/secondary_first_sub_category.txt +1 -0
- data/fastlane/metadata/secondary_second_sub_category.txt +1 -0
- data/lib/rubysketch/solitaire/background.rb +115 -12
- data/lib/rubysketch/solitaire/card.rb +10 -87
- data/lib/rubysketch/solitaire/common/animation.rb +34 -34
- data/lib/rubysketch/solitaire/common/button.rb +49 -19
- data/lib/rubysketch/solitaire/common/dialog.rb +78 -21
- data/lib/rubysketch/solitaire/common/scene.rb +15 -4
- data/lib/rubysketch/solitaire/common/settings.rb +10 -2
- data/lib/rubysketch/solitaire/common/timer.rb +2 -2
- data/lib/rubysketch/solitaire/common/transitions.rb +12 -6
- data/lib/rubysketch/solitaire/common/utils.rb +15 -0
- data/lib/rubysketch/solitaire/klondike.rb +378 -81
- data/lib/rubysketch/solitaire/places.rb +12 -102
- data/lib/rubysketch/solitaire/skin.rb +151 -0
- data/lib/rubysketch/solitaire.rb +33 -9
- data/main.rb +1 -0
- data/project.yml +85 -4
- metadata +54 -2
@@ -1,15 +1,20 @@
|
|
1
1
|
import SwiftUI
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
|
4
|
+
protocol GameViewControllerDelegate {
|
5
|
+
func run(command: String)
|
5
6
|
}
|
6
7
|
|
7
|
-
|
8
|
+
|
9
|
+
class GameViewController: ReflexViewController {
|
10
|
+
|
11
|
+
var delegate: GameViewControllerDelegate?
|
12
|
+
|
13
|
+
private var started = false
|
14
|
+
|
8
15
|
override func viewDidLoad() {
|
9
16
|
super.viewDidLoad()
|
10
17
|
|
11
|
-
let mainBundleDir = Bundle.main.bundlePath
|
12
|
-
|
13
18
|
CRuby.evaluate("""
|
14
19
|
Encoding.default_internal = Encoding::UTF_8
|
15
20
|
Encoding.default_external = Encoding::UTF_8
|
@@ -18,7 +23,7 @@ class GameViewController : ReflexViewController {
|
|
18
23
|
%w[
|
19
24
|
lib
|
20
25
|
].each do |lib|
|
21
|
-
$LOAD_PATH.unshift File.join '\(
|
26
|
+
$LOAD_PATH.unshift File.join '\(Bundle.main.bundlePath)', lib
|
22
27
|
end
|
23
28
|
|
24
29
|
Dir.chdir '\(getDocumentDir().path)'
|
@@ -26,22 +31,79 @@ class GameViewController : ReflexViewController {
|
|
26
31
|
|
27
32
|
RubySketch.setup()
|
28
33
|
RubySketch.setActiveReflexViewController(self)
|
34
|
+
}
|
29
35
|
|
30
|
-
|
36
|
+
private func getDocumentDir() -> URL {
|
37
|
+
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
38
|
+
}
|
39
|
+
|
40
|
+
override func viewWillDisappear(_ animated: Bool) {
|
41
|
+
RubySketch.resetActiveReflexViewController()
|
42
|
+
super.viewWillDisappear(animated)
|
43
|
+
}
|
44
|
+
|
45
|
+
override func viewDidLayoutSubviews() {
|
46
|
+
super.viewDidLayoutSubviews()
|
47
|
+
|
48
|
+
if started {return}
|
49
|
+
started = true
|
50
|
+
|
51
|
+
let frame = self.reflexView.frame
|
52
|
+
CRuby.evaluate("""
|
53
|
+
ENV['WIDTH'] = \(frame.width).to_s
|
54
|
+
ENV['HEIGHT'] = \(frame.height).to_s
|
55
|
+
""")
|
56
|
+
|
57
|
+
RubySketch.start("\(Bundle.main.bundlePath)/main.rb");
|
31
58
|
}
|
32
|
-
}
|
33
59
|
|
34
|
-
|
35
|
-
|
36
|
-
|
60
|
+
override func update() {
|
61
|
+
if let delegate = delegate, let command = CRuby.evaluate("$command")?.toString() {
|
62
|
+
delegate.run(command: command)
|
63
|
+
} else {
|
64
|
+
super.update()
|
65
|
+
}
|
37
66
|
}
|
38
67
|
}
|
39
68
|
|
40
|
-
|
69
|
+
|
70
|
+
struct GameView: UIViewControllerRepresentable {
|
71
|
+
|
72
|
+
@Binding var command: String
|
73
|
+
|
74
|
+
func makeCoordinator() -> Coordinator {
|
75
|
+
Coordinator(command: $command)
|
76
|
+
}
|
77
|
+
|
41
78
|
func makeUIViewController(context: Context) -> some UIViewController {
|
42
|
-
|
79
|
+
let vc = GameViewController()
|
80
|
+
vc.delegate = context.coordinator
|
81
|
+
return vc
|
43
82
|
}
|
44
83
|
|
45
84
|
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
|
85
|
+
let c = context.coordinator
|
86
|
+
if c.prevCommand != nil && command.isEmpty {
|
87
|
+
CRuby.evaluate("$command = nil")
|
88
|
+
c.prevCommand = nil
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
class Coordinator: NSObject, GameViewControllerDelegate {
|
93
|
+
|
94
|
+
@Binding var command: String
|
95
|
+
|
96
|
+
var prevCommand: String?
|
97
|
+
|
98
|
+
init(command: Binding<String>) {
|
99
|
+
_command = command
|
100
|
+
}
|
101
|
+
|
102
|
+
func run(command: String) {
|
103
|
+
if command != prevCommand {
|
104
|
+
self.command = command
|
105
|
+
prevCommand = command
|
106
|
+
}
|
107
|
+
}
|
46
108
|
}
|
47
109
|
}
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class Helper {
|
2
|
+
|
3
|
+
static var isDebug: Bool {
|
4
|
+
#if DEBUG
|
5
|
+
true
|
6
|
+
#else
|
7
|
+
false
|
8
|
+
#endif
|
9
|
+
}
|
10
|
+
|
11
|
+
static var isRelease: Bool {
|
12
|
+
!isDebug
|
13
|
+
}
|
14
|
+
|
15
|
+
static var isUnitTest: Bool {
|
16
|
+
NSClassFromString("XCTest") != nil
|
17
|
+
}
|
18
|
+
|
19
|
+
static var isTestFlight: Bool {
|
20
|
+
Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
|
21
|
+
}
|
22
|
+
|
23
|
+
static var isPhone: Bool {
|
24
|
+
UIDevice.current.userInterfaceIdiom == .phone
|
25
|
+
}
|
26
|
+
|
27
|
+
static var isPad: Bool {
|
28
|
+
UIDevice.current.userInterfaceIdiom == .pad
|
29
|
+
}
|
30
|
+
|
31
|
+
static var isMac: Bool {
|
32
|
+
ProcessInfo.processInfo.isiOSAppOnMac
|
33
|
+
}
|
34
|
+
|
35
|
+
static var language: String? {
|
36
|
+
guard let lang = Locale.preferredLanguages.first else {
|
37
|
+
return nil
|
38
|
+
}
|
39
|
+
guard let first = lang.split(separator: "-").first else {
|
40
|
+
return lang
|
41
|
+
}
|
42
|
+
return String(first)
|
43
|
+
}
|
44
|
+
|
45
|
+
static var appVersion: String {
|
46
|
+
getInfoString("CFBundleShortVersionString")!
|
47
|
+
}
|
48
|
+
|
49
|
+
static func getInfoString(_ key: String, plistName: String? = nil) -> String? {
|
50
|
+
guard let dic = getPlist(plistName) else {
|
51
|
+
return nil
|
52
|
+
}
|
53
|
+
return dic[key] as? String
|
54
|
+
}
|
55
|
+
|
56
|
+
static func getPlist(_ name: String? = nil) -> [String: Any]? {
|
57
|
+
guard let name = name else {
|
58
|
+
return Bundle.main.infoDictionary
|
59
|
+
}
|
60
|
+
guard let path = Bundle.main.path(forResource: name, ofType: "plist") else {
|
61
|
+
return nil
|
62
|
+
}
|
63
|
+
return NSDictionary(contentsOfFile: path) as? [String: Any]
|
64
|
+
}
|
65
|
+
|
66
|
+
static func openURL(_ url: URL) {
|
67
|
+
UIApplication.shared.open(url)
|
68
|
+
}
|
69
|
+
|
70
|
+
}// Helper
|
@@ -0,0 +1,140 @@
|
|
1
|
+
import SwiftUI
|
2
|
+
|
3
|
+
|
4
|
+
struct MenuScreen: View {
|
5
|
+
|
6
|
+
@Binding var isVisible: Bool
|
7
|
+
|
8
|
+
var body: some View {
|
9
|
+
NavigationView {
|
10
|
+
List {
|
11
|
+
PrivacyPolicy()
|
12
|
+
Licenses()
|
13
|
+
}
|
14
|
+
.navigationTitle("\(Strings.appName.s) v\(Helper.appVersion)")
|
15
|
+
.navigationBarItems(leading: BackButton())
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
private func BackButton() -> some View {
|
20
|
+
Button {
|
21
|
+
isVisible = false
|
22
|
+
} label: {
|
23
|
+
Text(Strings.close.s)
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
private func PrivacyPolicy() -> some View {
|
28
|
+
Button {
|
29
|
+
Helper.openURL(URL(string: "https://xord.org/rubysolitaire/privacy_policy.html")!)
|
30
|
+
} label: {
|
31
|
+
Text(Strings.menuPrivacyPolicy.s)
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
private func Licenses() -> some View {
|
36
|
+
NavigationLink {
|
37
|
+
LicensesScreen()
|
38
|
+
} label: {
|
39
|
+
Text(Strings.menuLicenses.s)
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
|
45
|
+
struct LicensesScreen: View {
|
46
|
+
var body: some View {
|
47
|
+
ScrollView {
|
48
|
+
Text("""
|
49
|
+
# GLSL Shaders
|
50
|
+
|
51
|
+
## Cosmic 2
|
52
|
+
|
53
|
+
// Started as Star Nest by Pablo Román Andrioli
|
54
|
+
// Modifications by Beibei Wang and Huw Bowles.
|
55
|
+
// This content is under the MIT License.
|
56
|
+
|
57
|
+
https://www.shadertoy.com/view/XllGzN
|
58
|
+
|
59
|
+
## Classic PSP Wave
|
60
|
+
|
61
|
+
MIT License
|
62
|
+
|
63
|
+
Copyright (c) 2023 Parking Lot Studio
|
64
|
+
|
65
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
66
|
+
of this software and associated documentation files (the "Software"), to deal
|
67
|
+
in the Software without restriction, including without limitation the rights
|
68
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
69
|
+
copies of the Software, and to permit persons to whom the Software is
|
70
|
+
furnished to do so, subject to the following conditions:
|
71
|
+
|
72
|
+
The above copyright notice and this permission notice shall be included in all
|
73
|
+
copies or substantial portions of the Software.
|
74
|
+
|
75
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
76
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
77
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
78
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
79
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
80
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
81
|
+
SOFTWARE.
|
82
|
+
|
83
|
+
https://www.shadertoy.com/view/ddV3DK
|
84
|
+
|
85
|
+
## Reflective hexes
|
86
|
+
|
87
|
+
// CC0: Reflective hexes
|
88
|
+
https://creativecommons.org/publicdomain/zero/1.0/
|
89
|
+
|
90
|
+
https://www.shadertoy.com/view/ds2XRt
|
91
|
+
|
92
|
+
## Colorful underwater bubbles II
|
93
|
+
|
94
|
+
// CCO: Colorful underwater bubbles II
|
95
|
+
https://creativecommons.org/publicdomain/zero/1.0/
|
96
|
+
|
97
|
+
https://www.shadertoy.com/view/mlBSWc
|
98
|
+
|
99
|
+
|
100
|
+
# Assets
|
101
|
+
|
102
|
+
## Bg-patterns
|
103
|
+
https://bg-patterns.com/
|
104
|
+
|
105
|
+
## 効果音ラボ
|
106
|
+
https://soundeffect-lab.info/
|
107
|
+
|
108
|
+
|
109
|
+
# Licenses
|
110
|
+
|
111
|
+
## Ruby
|
112
|
+
|
113
|
+
Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
|
114
|
+
|
115
|
+
Redistribution and use in source and binary forms, with or without
|
116
|
+
modification, are permitted provided that the following conditions
|
117
|
+
are met:
|
118
|
+
1. Redistributions of source code must retain the above copyright
|
119
|
+
notice, this list of conditions and the following disclaimer.
|
120
|
+
2. Redistributions in binary form must reproduce the above copyright
|
121
|
+
notice, this list of conditions and the following disclaimer in the
|
122
|
+
documentation and/or other materials provided with the distribution.
|
123
|
+
|
124
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
125
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
126
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
127
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
128
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
129
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
130
|
+
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
131
|
+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
132
|
+
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
133
|
+
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
134
|
+
SUCH DAMAGE.
|
135
|
+
""")
|
136
|
+
.padding(10)
|
137
|
+
}
|
138
|
+
.navigationTitle(Strings.menuLicenses.s)
|
139
|
+
}
|
140
|
+
}
|
@@ -1,10 +1,197 @@
|
|
1
1
|
import SwiftUI
|
2
|
+
import FirebaseCore
|
3
|
+
import GoogleMobileAds
|
4
|
+
import AppTrackingTransparency
|
5
|
+
|
6
|
+
|
7
|
+
class AppContext: NSObject, ObservableObject {
|
8
|
+
@Published var ready = false
|
9
|
+
}
|
10
|
+
|
11
|
+
|
12
|
+
class SceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObject {
|
13
|
+
|
14
|
+
var window: UIWindow?
|
15
|
+
|
16
|
+
func scene(
|
17
|
+
_ scene: UIScene,
|
18
|
+
willConnectTo session: UISceneSession,
|
19
|
+
options connectionOptions: UIScene.ConnectionOptions
|
20
|
+
) {
|
21
|
+
guard let windowScene = scene as? UIWindowScene else {
|
22
|
+
return
|
23
|
+
}
|
24
|
+
|
25
|
+
if #available(iOS 15.0, *) {
|
26
|
+
window = windowScene.keyWindow
|
27
|
+
} else {
|
28
|
+
window = windowScene.windows.first
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
func sceneDidBecomeActive(_ scene: UIScene) {
|
33
|
+
showATTDialog()
|
34
|
+
}
|
35
|
+
|
36
|
+
func showATTDialog() {
|
37
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
38
|
+
guard ATTrackingManager.trackingAuthorizationStatus == .notDetermined else {
|
39
|
+
return
|
40
|
+
}
|
41
|
+
ATTrackingManager.requestTrackingAuthorization {_ in}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
|
47
|
+
class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject {
|
48
|
+
|
49
|
+
@Published var appContext = AppContext()
|
50
|
+
|
51
|
+
func application(
|
52
|
+
_ application: UIApplication,
|
53
|
+
configurationForConnecting connectingSceneSession: UISceneSession,
|
54
|
+
options: UIScene.ConnectionOptions
|
55
|
+
) -> UISceneConfiguration {
|
56
|
+
let configuration = UISceneConfiguration(
|
57
|
+
name: nil, sessionRole: connectingSceneSession.role)
|
58
|
+
if (connectingSceneSession.role == .windowApplication) {
|
59
|
+
configuration.delegateClass = SceneDelegate.self
|
60
|
+
}
|
61
|
+
return configuration
|
62
|
+
}
|
63
|
+
|
64
|
+
func application(
|
65
|
+
_ application: UIApplication,
|
66
|
+
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
67
|
+
) -> Bool {
|
68
|
+
if let lang = Helper.language {
|
69
|
+
CRuby.evaluate("$language = '\(lang)'")
|
70
|
+
}
|
71
|
+
|
72
|
+
if Helper.isDebug {
|
73
|
+
CRuby.evaluate("$debug = true")
|
74
|
+
}
|
75
|
+
|
76
|
+
if Helper.isRelease {
|
77
|
+
FirebaseApp.configure()
|
78
|
+
}
|
79
|
+
|
80
|
+
GADMobileAds.sharedInstance().start(completionHandler: { [weak self] _ in
|
81
|
+
self!.appContext.ready = true
|
82
|
+
})
|
83
|
+
|
84
|
+
return true
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
2
88
|
|
3
89
|
@main
|
4
90
|
struct RubySolitaireApp: App {
|
91
|
+
|
92
|
+
@UIApplicationDelegateAdaptor var appDelegate: AppDelegate
|
93
|
+
|
5
94
|
var body: some Scene {
|
6
95
|
WindowGroup {
|
7
|
-
|
96
|
+
GameScreen()
|
8
97
|
}
|
9
98
|
}
|
10
99
|
}
|
100
|
+
|
101
|
+
|
102
|
+
struct GameScreen: View {
|
103
|
+
|
104
|
+
@EnvironmentObject private var sceneDelegate: SceneDelegate
|
105
|
+
|
106
|
+
@StateObject private var interstitialAd = InterstitialAd(
|
107
|
+
adUnitID: Helper.getInfoString("GADGameScreenInterstitial")!)
|
108
|
+
|
109
|
+
@State private var command: String = ""
|
110
|
+
|
111
|
+
@State private var url: URL? = nil
|
112
|
+
|
113
|
+
@State private var isMenuVisible = false
|
114
|
+
@State private var isInterstitialAdVisible = false
|
115
|
+
|
116
|
+
var body: some View {
|
117
|
+
ZStack {
|
118
|
+
Color.black
|
119
|
+
.ignoresSafeArea(.all)
|
120
|
+
GeometryReader { gr in
|
121
|
+
VStack(spacing: 2) {
|
122
|
+
GameView(command: $command)
|
123
|
+
AdBannerView(
|
124
|
+
width: gr.size.width,
|
125
|
+
adUnitID: Helper.getInfoString("GADGameScreenBottomBanner")!,
|
126
|
+
rootViewController: sceneDelegate.window!.rootViewController!
|
127
|
+
)
|
128
|
+
.frame(width: gr.size.width, height: 50)
|
129
|
+
}
|
130
|
+
}
|
131
|
+
}
|
132
|
+
.statusBarHidden()
|
133
|
+
.onChange(of: command) { _ in
|
134
|
+
if command.isEmpty {
|
135
|
+
return
|
136
|
+
}
|
137
|
+
let split = command.split(separator: ":").map {$0.removingPercentEncoding}
|
138
|
+
let commandAndArgs = split.compactMap {$0}
|
139
|
+
if commandAndArgs.count != split.count || commandAndArgs.count == 0 {
|
140
|
+
return clearCommand()
|
141
|
+
}
|
142
|
+
runCommand(command: commandAndArgs.first!, args: Array<String>(commandAndArgs.dropFirst(1)))
|
143
|
+
}
|
144
|
+
.onChange(of: url) { _ in
|
145
|
+
if url == nil {
|
146
|
+
clearCommand()
|
147
|
+
}
|
148
|
+
}
|
149
|
+
.onChange(of: isMenuVisible) { visible in
|
150
|
+
if !visible {
|
151
|
+
clearCommand()
|
152
|
+
}
|
153
|
+
}
|
154
|
+
.onChange(of: isInterstitialAdVisible) { visible in
|
155
|
+
if visible, interstitialAd.ready {
|
156
|
+
interstitialAd.show(
|
157
|
+
rootViewController: sceneDelegate.window!.rootViewController!
|
158
|
+
) { error in
|
159
|
+
isInterstitialAdVisible = false
|
160
|
+
clearCommand()
|
161
|
+
}
|
162
|
+
} else {
|
163
|
+
isInterstitialAdVisible = false
|
164
|
+
clearCommand()
|
165
|
+
}
|
166
|
+
}
|
167
|
+
.sheet(isPresented: $isMenuVisible) {
|
168
|
+
MenuScreen(isVisible: $isMenuVisible)
|
169
|
+
}
|
170
|
+
.fullScreenCover(item: $url) { _ in
|
171
|
+
if let url = url {
|
172
|
+
SafariView(url: url)
|
173
|
+
.ignoresSafeArea()
|
174
|
+
}
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
func runCommand(command: String, args: [String]) {
|
179
|
+
switch command {
|
180
|
+
case "openURL":
|
181
|
+
guard let str = args.first, let url = URL(string: str) else {
|
182
|
+
return clearCommand()
|
183
|
+
}
|
184
|
+
self.url = url
|
185
|
+
case "showMenu":
|
186
|
+
isMenuVisible = true;
|
187
|
+
case "showInterstitialAd":
|
188
|
+
isInterstitialAdVisible = true
|
189
|
+
default:
|
190
|
+
clearCommand()
|
191
|
+
}
|
192
|
+
}
|
193
|
+
|
194
|
+
func clearCommand() {
|
195
|
+
command = ""
|
196
|
+
}
|
197
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import SwiftUI
|
2
|
+
import SafariServices
|
3
|
+
|
4
|
+
|
5
|
+
struct SafariView: UIViewControllerRepresentable {
|
6
|
+
let url: URL
|
7
|
+
|
8
|
+
func makeUIViewController(context: Context) -> some UIViewController {
|
9
|
+
let vc = SFSafariViewController(url: url)
|
10
|
+
vc.dismissButtonStyle = .close
|
11
|
+
return vc
|
12
|
+
}
|
13
|
+
|
14
|
+
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
|
15
|
+
}
|
16
|
+
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
enum Strings : String, CaseIterable {
|
2
|
+
case appName
|
3
|
+
|
4
|
+
case close
|
5
|
+
|
6
|
+
case menuPrivacyPolicy
|
7
|
+
case menuLicenses
|
8
|
+
|
9
|
+
var s: String {
|
10
|
+
return getString(rawValue) ?? getBaseString(rawValue)
|
11
|
+
}
|
12
|
+
|
13
|
+
func format(_ args: CVarArg...) -> String {
|
14
|
+
return String.init(format: s, arguments: args)
|
15
|
+
}
|
16
|
+
|
17
|
+
private func getString(_ key: String) -> String? {
|
18
|
+
let string = NSLocalizedString(key, value: Strings.NOT_FOUND, comment: "")
|
19
|
+
if string == Strings.NOT_FOUND {
|
20
|
+
return nil
|
21
|
+
}
|
22
|
+
|
23
|
+
return string
|
24
|
+
}
|
25
|
+
|
26
|
+
private func getBaseString(_ key: String) -> String {
|
27
|
+
guard let bundle = Strings.BASE_BUNDLE else {
|
28
|
+
fatalError("Base bundle not found")
|
29
|
+
}
|
30
|
+
|
31
|
+
let string = NSLocalizedString(key, bundle: bundle, value: Strings.NOT_FOUND, comment: "")
|
32
|
+
if string == Strings.NOT_FOUND {
|
33
|
+
fatalError("'\(key)' not found")
|
34
|
+
}
|
35
|
+
|
36
|
+
return string
|
37
|
+
}
|
38
|
+
|
39
|
+
static private let NOT_FOUND = "__NOT_FOUND__"
|
40
|
+
|
41
|
+
static private let BASE_BUNDLE: Bundle? = {
|
42
|
+
guard let path = Bundle.main.path(forResource: "Base", ofType: "lproj") else {
|
43
|
+
return nil
|
44
|
+
}
|
45
|
+
|
46
|
+
return Bundle(path: path)
|
47
|
+
}()
|
48
|
+
}
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.2
|
data/data/card.png
CHANGED
Binary file
|