rubysketch-solitaire 0.1.0 → 0.1.2

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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +3 -0
  3. data/.gitignore +2 -0
  4. data/.hooks/pre-commit +69 -0
  5. data/ChangeLog.md +21 -0
  6. data/Gemfile.lock +10 -10
  7. data/Podfile.lock +33 -33
  8. data/Rakefile +107 -10
  9. data/RubySolitaire/Ad.swift +88 -0
  10. data/RubySolitaire/Assets.xcassets/AppIcon.appiconset/AppIcon.png +0 -0
  11. data/RubySolitaire/Assets.xcassets/AppIcon.appiconset/Contents.json +3 -87
  12. data/RubySolitaire/Base.lproj/Localizable.strings +6 -0
  13. data/RubySolitaire/Extensions.swift +5 -0
  14. data/RubySolitaire/GameView.swift +75 -13
  15. data/RubySolitaire/Helper.swift +70 -0
  16. data/RubySolitaire/MenuScreen.swift +140 -0
  17. data/RubySolitaire/RubySolitaireApp.swift +188 -1
  18. data/RubySolitaire/SafariView.swift +16 -0
  19. data/RubySolitaire/Strings.swift +48 -0
  20. data/RubySolitaire/ja.lproj/InfoPlist.strings +4 -0
  21. data/RubySolitaire/ja.lproj/Localizable.strings +6 -0
  22. data/VERSION +1 -1
  23. data/data/card.png +0 -0
  24. data/data/classicPSPWave.glsl +94 -0
  25. data/data/colorfulUnderwaterBubbles2.glsl +113 -0
  26. data/data/cosmic2.glsl +121 -0
  27. data/data/reflectiveHexes.glsl +219 -0
  28. data/fastlane/Fastfile +76 -0
  29. data/fastlane/metadata/copyright.txt +1 -0
  30. data/fastlane/metadata/en-US/apple_tv_privacy_policy.txt +1 -0
  31. data/fastlane/metadata/en-US/description.txt +5 -0
  32. data/fastlane/metadata/en-US/keywords.txt +1 -0
  33. data/fastlane/metadata/en-US/marketing_url.txt +1 -0
  34. data/fastlane/metadata/en-US/name.txt +1 -0
  35. data/fastlane/metadata/en-US/privacy_url.txt +1 -0
  36. data/fastlane/metadata/en-US/promotional_text.txt +1 -0
  37. data/fastlane/metadata/en-US/release_notes.txt +1 -0
  38. data/fastlane/metadata/en-US/subtitle.txt +1 -0
  39. data/fastlane/metadata/en-US/support_url.txt +1 -0
  40. data/fastlane/metadata/ja/apple_tv_privacy_policy.txt +1 -0
  41. data/fastlane/metadata/ja/description.txt +5 -0
  42. data/fastlane/metadata/ja/keywords.txt +1 -0
  43. data/fastlane/metadata/ja/marketing_url.txt +1 -0
  44. data/fastlane/metadata/ja/name.txt +1 -0
  45. data/fastlane/metadata/ja/privacy_url.txt +1 -0
  46. data/fastlane/metadata/ja/promotional_text.txt +1 -0
  47. data/fastlane/metadata/ja/release_notes.txt +1 -0
  48. data/fastlane/metadata/ja/subtitle.txt +1 -0
  49. data/fastlane/metadata/ja/support_url.txt +1 -0
  50. data/fastlane/metadata/primary_category.txt +1 -0
  51. data/fastlane/metadata/primary_first_sub_category.txt +1 -0
  52. data/fastlane/metadata/primary_second_sub_category.txt +1 -0
  53. data/fastlane/metadata/review_information/demo_password.txt +1 -0
  54. data/fastlane/metadata/review_information/demo_user.txt +1 -0
  55. data/fastlane/metadata/review_information/email_address.txt +1 -0
  56. data/fastlane/metadata/review_information/first_name.txt +0 -0
  57. data/fastlane/metadata/review_information/last_name.txt +0 -0
  58. data/fastlane/metadata/review_information/notes.txt +1 -0
  59. data/fastlane/metadata/review_information/phone_number.txt +0 -0
  60. data/fastlane/metadata/secondary_category.txt +1 -0
  61. data/fastlane/metadata/secondary_first_sub_category.txt +1 -0
  62. data/fastlane/metadata/secondary_second_sub_category.txt +1 -0
  63. data/lib/rubysketch/solitaire/background.rb +115 -12
  64. data/lib/rubysketch/solitaire/card.rb +10 -87
  65. data/lib/rubysketch/solitaire/common/animation.rb +34 -34
  66. data/lib/rubysketch/solitaire/common/button.rb +49 -19
  67. data/lib/rubysketch/solitaire/common/dialog.rb +78 -21
  68. data/lib/rubysketch/solitaire/common/scene.rb +15 -4
  69. data/lib/rubysketch/solitaire/common/settings.rb +10 -2
  70. data/lib/rubysketch/solitaire/common/timer.rb +2 -2
  71. data/lib/rubysketch/solitaire/common/transitions.rb +12 -6
  72. data/lib/rubysketch/solitaire/common/utils.rb +15 -0
  73. data/lib/rubysketch/solitaire/klondike.rb +378 -81
  74. data/lib/rubysketch/solitaire/places.rb +12 -102
  75. data/lib/rubysketch/solitaire/skin.rb +151 -0
  76. data/lib/rubysketch/solitaire.rb +33 -9
  77. data/main.rb +1 -0
  78. data/project.yml +85 -4
  79. metadata +54 -2
@@ -1,15 +1,20 @@
1
1
  import SwiftUI
2
2
 
3
- func getDocumentDir() -> URL {
4
- return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
3
+
4
+ protocol GameViewControllerDelegate {
5
+ func run(command: String)
5
6
  }
6
7
 
7
- class GameViewController : ReflexViewController {
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 '\(mainBundleDir)', lib
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
- RubySketch.start("\(mainBundleDir)/main.rb");
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
- struct GameView: View {
35
- var body: some View {
36
- GameViewControllerWrapper()
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
- struct GameViewControllerWrapper : UIViewControllerRepresentable {
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
- return GameViewController()
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
- GameView()
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
+ }
@@ -0,0 +1,4 @@
1
+ CFBundleDisplayName = "ソリティア";
2
+
3
+ NSCameraUsageDescription = "Rubyスクリプトからカメラの画像を取得できるようになります。";
4
+ NSUserTrackingUsageDescription = "許可すると、あなたの好みに合った広告が表示されやすくなります。";
@@ -0,0 +1,6 @@
1
+ appName = "宇宙ソリティア";
2
+
3
+ close = "閉じる";
4
+
5
+ menuPrivacyPolicy = "プライバシーポリシー";
6
+ menuLicenses = "ライセンス";
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.2
data/data/card.png CHANGED
Binary file