rubysketch-solitaire 0.1.0 → 0.9.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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +3 -0
  3. data/.gitignore +2 -0
  4. data/.hooks/post-commit +37 -0
  5. data/.hooks/pre-commit +69 -0
  6. data/ChangeLog.md +27 -0
  7. data/Gemfile.lock +10 -10
  8. data/Podfile.lock +33 -33
  9. data/README.md +8 -0
  10. data/Rakefile +107 -10
  11. data/RubySolitaire/Ad.swift +88 -0
  12. data/RubySolitaire/Assets.xcassets/AppIcon.appiconset/AppIcon.png +0 -0
  13. data/RubySolitaire/Assets.xcassets/AppIcon.appiconset/Contents.json +3 -87
  14. data/RubySolitaire/Base.lproj/Localizable.strings +6 -0
  15. data/RubySolitaire/Extensions.swift +5 -0
  16. data/RubySolitaire/GameView.swift +75 -13
  17. data/RubySolitaire/Helper.swift +70 -0
  18. data/RubySolitaire/MenuScreen.swift +141 -0
  19. data/RubySolitaire/RubySolitaireApp.swift +188 -1
  20. data/RubySolitaire/SafariView.swift +16 -0
  21. data/RubySolitaire/Strings.swift +48 -0
  22. data/RubySolitaire/ja.lproj/InfoPlist.strings +4 -0
  23. data/RubySolitaire/ja.lproj/Localizable.strings +6 -0
  24. data/VERSION +1 -1
  25. data/data/card.png +0 -0
  26. data/data/classicPSPWave.glsl +94 -0
  27. data/data/colorfulUnderwaterBubbles2.glsl +113 -0
  28. data/data/cosmic2.glsl +121 -0
  29. data/data/reflectiveHexes.glsl +219 -0
  30. data/fastlane/Fastfile +76 -0
  31. data/fastlane/metadata/copyright.txt +1 -0
  32. data/fastlane/metadata/en-US/apple_tv_privacy_policy.txt +1 -0
  33. data/fastlane/metadata/en-US/description.txt +5 -0
  34. data/fastlane/metadata/en-US/keywords.txt +1 -0
  35. data/fastlane/metadata/en-US/marketing_url.txt +1 -0
  36. data/fastlane/metadata/en-US/name.txt +1 -0
  37. data/fastlane/metadata/en-US/privacy_url.txt +1 -0
  38. data/fastlane/metadata/en-US/promotional_text.txt +1 -0
  39. data/fastlane/metadata/en-US/release_notes.txt +1 -0
  40. data/fastlane/metadata/en-US/subtitle.txt +1 -0
  41. data/fastlane/metadata/en-US/support_url.txt +1 -0
  42. data/fastlane/metadata/ja/apple_tv_privacy_policy.txt +1 -0
  43. data/fastlane/metadata/ja/description.txt +5 -0
  44. data/fastlane/metadata/ja/keywords.txt +1 -0
  45. data/fastlane/metadata/ja/marketing_url.txt +1 -0
  46. data/fastlane/metadata/ja/name.txt +1 -0
  47. data/fastlane/metadata/ja/privacy_url.txt +1 -0
  48. data/fastlane/metadata/ja/promotional_text.txt +1 -0
  49. data/fastlane/metadata/ja/release_notes.txt +1 -0
  50. data/fastlane/metadata/ja/subtitle.txt +1 -0
  51. data/fastlane/metadata/ja/support_url.txt +1 -0
  52. data/fastlane/metadata/primary_category.txt +1 -0
  53. data/fastlane/metadata/primary_first_sub_category.txt +1 -0
  54. data/fastlane/metadata/primary_second_sub_category.txt +1 -0
  55. data/fastlane/metadata/review_information/demo_password.txt +1 -0
  56. data/fastlane/metadata/review_information/demo_user.txt +1 -0
  57. data/fastlane/metadata/review_information/email_address.txt +1 -0
  58. data/fastlane/metadata/review_information/first_name.txt +0 -0
  59. data/fastlane/metadata/review_information/last_name.txt +0 -0
  60. data/fastlane/metadata/review_information/notes.txt +1 -0
  61. data/fastlane/metadata/review_information/phone_number.txt +0 -0
  62. data/fastlane/metadata/secondary_category.txt +1 -0
  63. data/fastlane/metadata/secondary_first_sub_category.txt +1 -0
  64. data/fastlane/metadata/secondary_second_sub_category.txt +1 -0
  65. data/lib/rubysketch/solitaire/background.rb +115 -12
  66. data/lib/rubysketch/solitaire/card.rb +10 -87
  67. data/lib/rubysketch/solitaire/common/animation.rb +34 -34
  68. data/lib/rubysketch/solitaire/common/button.rb +49 -19
  69. data/lib/rubysketch/solitaire/common/dialog.rb +78 -21
  70. data/lib/rubysketch/solitaire/common/scene.rb +15 -4
  71. data/lib/rubysketch/solitaire/common/settings.rb +15 -3
  72. data/lib/rubysketch/solitaire/common/timer.rb +2 -2
  73. data/lib/rubysketch/solitaire/common/transitions.rb +12 -6
  74. data/lib/rubysketch/solitaire/common/utils.rb +15 -0
  75. data/lib/rubysketch/solitaire/klondike.rb +382 -81
  76. data/lib/rubysketch/solitaire/places.rb +12 -102
  77. data/lib/rubysketch/solitaire/skin.rb +151 -0
  78. data/lib/rubysketch/solitaire.rb +33 -9
  79. data/main.rb +1 -0
  80. data/project.yml +85 -4
  81. metadata +55 -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,141 @@
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
+ .navigationBarTitleDisplayMode(.inline)
15
+ .navigationTitle("\(Strings.appName.s) v\(Helper.appVersion)")
16
+ .navigationBarItems(leading: BackButton())
17
+ }
18
+ }
19
+
20
+ private func BackButton() -> some View {
21
+ Button {
22
+ isVisible = false
23
+ } label: {
24
+ Text(Strings.close.s)
25
+ }
26
+ }
27
+
28
+ private func PrivacyPolicy() -> some View {
29
+ Button {
30
+ Helper.openURL(URL(string: "https://xord.org/rubysolitaire/privacy_policy.html")!)
31
+ } label: {
32
+ Text(Strings.menuPrivacyPolicy.s)
33
+ }
34
+ }
35
+
36
+ private func Licenses() -> some View {
37
+ NavigationLink {
38
+ LicensesScreen()
39
+ } label: {
40
+ Text(Strings.menuLicenses.s)
41
+ }
42
+ }
43
+ }
44
+
45
+
46
+ struct LicensesScreen: View {
47
+ var body: some View {
48
+ ScrollView {
49
+ Text("""
50
+ # GLSL Shaders
51
+
52
+ ## Cosmic 2
53
+
54
+ // Started as Star Nest by Pablo Román Andrioli
55
+ // Modifications by Beibei Wang and Huw Bowles.
56
+ // This content is under the MIT License.
57
+
58
+ https://www.shadertoy.com/view/XllGzN
59
+
60
+ ## Classic PSP Wave
61
+
62
+ MIT License
63
+
64
+ Copyright (c) 2023 Parking Lot Studio
65
+
66
+ Permission is hereby granted, free of charge, to any person obtaining a copy
67
+ of this software and associated documentation files (the "Software"), to deal
68
+ in the Software without restriction, including without limitation the rights
69
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
70
+ copies of the Software, and to permit persons to whom the Software is
71
+ furnished to do so, subject to the following conditions:
72
+
73
+ The above copyright notice and this permission notice shall be included in all
74
+ copies or substantial portions of the Software.
75
+
76
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
77
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
78
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
79
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
80
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
81
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
82
+ SOFTWARE.
83
+
84
+ https://www.shadertoy.com/view/ddV3DK
85
+
86
+ ## Reflective hexes
87
+
88
+ // CC0: Reflective hexes
89
+ https://creativecommons.org/publicdomain/zero/1.0/
90
+
91
+ https://www.shadertoy.com/view/ds2XRt
92
+
93
+ ## Colorful underwater bubbles II
94
+
95
+ // CCO: Colorful underwater bubbles II
96
+ https://creativecommons.org/publicdomain/zero/1.0/
97
+
98
+ https://www.shadertoy.com/view/mlBSWc
99
+
100
+
101
+ # Assets
102
+
103
+ ## Bg-patterns
104
+ https://bg-patterns.com/
105
+
106
+ ## 効果音ラボ
107
+ https://soundeffect-lab.info/
108
+
109
+
110
+ # Licenses
111
+
112
+ ## Ruby
113
+
114
+ Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
115
+
116
+ Redistribution and use in source and binary forms, with or without
117
+ modification, are permitted provided that the following conditions
118
+ are met:
119
+ 1. Redistributions of source code must retain the above copyright
120
+ notice, this list of conditions and the following disclaimer.
121
+ 2. Redistributions in binary form must reproduce the above copyright
122
+ notice, this list of conditions and the following disclaimer in the
123
+ documentation and/or other materials provided with the distribution.
124
+
125
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
126
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
127
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
128
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
129
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
130
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
131
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
132
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
133
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
134
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
135
+ SUCH DAMAGE.
136
+ """)
137
+ .padding(10)
138
+ }
139
+ .navigationTitle(Strings.menuLicenses.s)
140
+ }
141
+ }
@@ -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.9.0
data/data/card.png CHANGED
Binary file