VMReleaseNotesView.swift 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. //
  2. // Copyright © 2023 osy. All rights reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. import SwiftUI
  17. struct VMReleaseNotesView: View {
  18. @ObservedObject var helper: UTMReleaseHelper
  19. @State private var isShowAll: Bool = false
  20. @Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
  21. let ignoreSections = ["Highlights", "Installation"]
  22. var body: some View {
  23. VStack {
  24. if helper.releaseNotes.count > 0 {
  25. ScrollView {
  26. Text("What's New")
  27. .font(.largeTitle)
  28. .padding(.bottom)
  29. VStack(alignment: .leading) {
  30. Notes(section: helper.releaseNotes.first!, isProminent: true)
  31. .padding(.bottom, 0.5)
  32. if isShowAll {
  33. ForEach(helper.releaseNotes) { section in
  34. if !ignoreSections.contains(section.title) {
  35. Notes(section: section)
  36. }
  37. }
  38. }
  39. }
  40. }
  41. } else {
  42. VStack {
  43. Spacer()
  44. HStack {
  45. Spacer()
  46. Text("No release notes found for version \(helper.currentVersion).")
  47. .font(.headline)
  48. Spacer()
  49. }
  50. Spacer()
  51. }
  52. }
  53. Spacer()
  54. Buttons {
  55. if !isShowAll {
  56. Button {
  57. isShowAll = true
  58. } label: {
  59. Text("Show All")
  60. #if os(iOS) || os(visionOS)
  61. .frame(maxWidth: .infinity)
  62. #endif
  63. }.buttonStyle(ReleaseButtonStyle())
  64. }
  65. Button {
  66. presentationMode.wrappedValue.dismiss()
  67. } label: {
  68. Text("Continue")
  69. #if os(iOS) || os(visionOS)
  70. .frame(maxWidth: .infinity)
  71. #endif
  72. }.keyboardShortcut(.defaultAction)
  73. .buttonStyle(ReleaseButtonStyle(isProminent: true))
  74. }
  75. }
  76. #if os(macOS) || os(visionOS)
  77. .frame(width: 450, height: 450)
  78. #endif
  79. .onAppear {
  80. if helper.releaseNotes.count == 0 {
  81. isShowAll = true
  82. } else if helper.releaseNotes.first!.body.count == 0 {
  83. isShowAll = true
  84. }
  85. }
  86. }
  87. }
  88. private struct Notes: View {
  89. let section: UTMReleaseHelper.Section
  90. @State var isProminent: Bool = false
  91. private var hasBullet: Bool {
  92. !isProminent && section.body.count > 1
  93. }
  94. var body: some View {
  95. if !isProminent {
  96. Text(section.title)
  97. .font(.title2)
  98. .padding([.top, .bottom])
  99. }
  100. ForEach(section.body) { description in
  101. HStack(alignment: .top) {
  102. if hasBullet {
  103. Text("\u{2022} ")
  104. }
  105. if #available(iOS 15, macOS 12, *), let attributed = try? AttributedString(markdown: description) {
  106. Text(attributed)
  107. } else {
  108. Text(description)
  109. }
  110. }
  111. }
  112. }
  113. }
  114. private struct Buttons<Content>: View where Content: View {
  115. var content: () -> Content
  116. init(@ViewBuilder content: @escaping () -> Content) {
  117. self.content = content
  118. }
  119. var body: some View {
  120. #if os(macOS)
  121. HStack {
  122. Spacer()
  123. content()
  124. }
  125. #else
  126. VStack {
  127. if #available(iOS 15, *) {
  128. content()
  129. .buttonStyle(.bordered)
  130. } else {
  131. content()
  132. }
  133. }
  134. #endif
  135. }
  136. }
  137. private struct ReleaseButtonStyle: PrimitiveButtonStyle {
  138. private let isProminent: Bool
  139. private let backgroundColor: Color
  140. private let foregroundColor: Color
  141. init(isProminent: Bool = false) {
  142. self.isProminent = isProminent
  143. self.backgroundColor = isProminent ? .accentColor : .gray
  144. self.foregroundColor = isProminent ? .white : .white
  145. }
  146. func makeBody(configuration: Self.Configuration) -> some View {
  147. #if os(macOS) || os(visionOS)
  148. DefaultButtonStyle().makeBody(configuration: configuration)
  149. #else
  150. if #available(iOS 15, *) {
  151. if isProminent {
  152. BorderedProminentButtonStyle().makeBody(configuration: configuration)
  153. } else {
  154. BorderedButtonStyle().makeBody(configuration: configuration)
  155. }
  156. } else {
  157. DefaultButtonStyle().makeBody(configuration: configuration)
  158. .padding()
  159. .foregroundColor(foregroundColor)
  160. .background(backgroundColor)
  161. .cornerRadius(6)
  162. .overlay(
  163. RoundedRectangle(cornerRadius: 6)
  164. .stroke(foregroundColor, lineWidth: 1)
  165. )
  166. }
  167. #endif
  168. }
  169. }
  170. struct VMReleaseNotesView_Previews: PreviewProvider {
  171. static var previews: some View {
  172. VMReleaseNotesView(helper: UTMReleaseHelper())
  173. }
  174. }