From 9ef564e16a457abb6b1520642e3ceadd4bbe7925 Mon Sep 17 00:00:00 2001 From: Ladislas de Toldi Date: Fri, 8 Dec 2023 12:56:25 +0100 Subject: [PATCH] :art: (swiftformat): Format melody, xylophone files after rebase --- .../Exercice+MidiRecordingPlayer.swift | 58 ++++++----- .../Sources/Exercise/Exercise.swift | 2 +- .../Sources/Utils/MidiRecording.swift | 58 +++++------ .../Melody/MelodyView+ButtonLabel.swift | 14 +-- .../Melody/MelodyView+LauncherView.swift | 17 ++-- .../Melody/MelodyView+SongSelectorView.swift | 31 +++--- .../Melody/MelodyView+ViewModel.swift | 97 ++++++++++--------- .../Melody/MelodyView+XylophoneView.swift | 66 +++++++------ .../Specialized/Melody/MelodyView.swift | 62 +++++++----- .../Views/Activity/ActivityView.swift | 4 +- .../Buttons/BinaryToggleChoiceStyle.swift | 1 - .../Buttons/XylophoneTileButtonStyle.swift | 38 +++++--- 12 files changed, 249 insertions(+), 199 deletions(-) diff --git a/Modules/ContentKit/Sources/Exercise/Exercice+MidiRecordingPlayer.swift b/Modules/ContentKit/Sources/Exercise/Exercice+MidiRecordingPlayer.swift index 8c1aae1050..ee445500e6 100644 --- a/Modules/ContentKit/Sources/Exercise/Exercice+MidiRecordingPlayer.swift +++ b/Modules/ContentKit/Sources/Exercise/Exercice+MidiRecordingPlayer.swift @@ -4,21 +4,22 @@ // swiftlint:disable nesting public enum MidiRecordingPlayer { - public struct Payload: Codable { + // MARK: Lifecycle - public struct Instructions: Codable { - public let textMusicSelection: String - public let textButtonPlay: String - public let textKeyboardPartial: String - public let textKeyboardFull: String + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.instructions = try container.decode(Instructions.self, forKey: .instructions) + self.instrument = try container.decode(String.self, forKey: .instrument) - enum CodingKeys: String, CodingKey { - case textMusicSelection = "text_music_selection" - case textButtonPlay = "text_button_play" - case textKeyboardPartial = "text_keyboard_partial" - case textKeyboardFull = "text_keyboard_full" - } + let midiRecordingSongs = try container.decode([MidiRecording.Song].self, forKey: .songs) + self.songs = midiRecordingSongs.map { MidiRecording($0) } + } + + // MARK: Public + + public struct Instructions: Codable { + // MARK: Lifecycle public init( textMusicSelection: String, textButtonPlay: String, textKeyboardPartial: String, @@ -38,25 +39,36 @@ public enum MidiRecordingPlayer { self.textKeyboardPartial = try container.decode(String.self, forKey: .textKeyboardPartial) self.textKeyboardFull = try container.decode(String.self, forKey: .textKeyboardFull) } + + // MARK: Public + + public let textMusicSelection: String + public let textButtonPlay: String + public let textKeyboardPartial: String + public let textKeyboardFull: String + + // MARK: Internal + + enum CodingKeys: String, CodingKey { + case textMusicSelection = "text_music_selection" + case textButtonPlay = "text_button_play" + case textKeyboardPartial = "text_keyboard_partial" + case textKeyboardFull = "text_keyboard_full" + } } public let instructions: Instructions public let instrument: String public let songs: [MidiRecording] - enum CodingKeys: String, CodingKey { - case instructions, instrument, songs - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.instructions = try container.decode(Instructions.self, forKey: .instructions) - self.instrument = try container.decode(String.self, forKey: .instrument) + // MARK: Internal - let midiRecordingSongs = try container.decode([MidiRecording.Song].self, forKey: .songs) - self.songs = midiRecordingSongs.map { MidiRecording($0) } + enum CodingKeys: String, CodingKey { + case instructions + case instrument + case songs } } - } + // swiftlint:enable nesting diff --git a/Modules/ContentKit/Sources/Exercise/Exercise.swift b/Modules/ContentKit/Sources/Exercise/Exercise.swift index 200eaf8283..18083be6dc 100644 --- a/Modules/ContentKit/Sources/Exercise/Exercise.swift +++ b/Modules/ContentKit/Sources/Exercise/Exercise.swift @@ -37,7 +37,7 @@ public struct Exercise: Codable { self.payload = try container.decode(MusicalInstrument.Payload.self, forKey: .payload) case (.melody, .none): - payload = try container.decode(MidiRecordingPlayer.Payload.self, forKey: .payload) + self.payload = try container.decode(MidiRecordingPlayer.Payload.self, forKey: .payload) case (.remoteStandard, .none), (.remoteArrow, .none): diff --git a/Modules/ContentKit/Sources/Utils/MidiRecording.swift b/Modules/ContentKit/Sources/Utils/MidiRecording.swift index ad91872a7c..05dd4f4fcf 100644 --- a/Modules/ContentKit/Sources/Utils/MidiRecording.swift +++ b/Modules/ContentKit/Sources/Utils/MidiRecording.swift @@ -5,6 +5,21 @@ import Foundation public struct MidiRecording: Codable, Hashable, Equatable { + // MARK: Lifecycle + + public init(name: String, file: String, scale: [UInt8]) { + self.name = name + self.file = file + self.scale = scale + } + + public init(_ song: Song) { + self.name = song.details.name + self.file = song.details.file + self.scale = song.scale + } + + // MARK: Public public enum Song: String, Codable { case none @@ -15,49 +30,51 @@ public struct MidiRecording: Codable, Hashable, Equatable { case ohTheCrocodiles case happyBirthday + // MARK: Internal + var details: (name: String, file: String) { switch self { case .none: - return (name: "", file: "") + (name: "", file: "") case .underTheMoonlight: - return ( + ( name: "Under The Moonlight", file: "Under_The_Moonlight" ) case .aGreenMouse: - return (name: "A Green Mouse", file: "A_Green_Mouse") + (name: "A Green Mouse", file: "A_Green_Mouse") case .twinkleTwinkleLittleStar: - return ( + ( name: "Twinkle Twinkle Little Star", file: "Twinkle_Twinkle_Little_Star" ) case .londonBridgeIsFallingDown: - return ( + ( name: "London Bridge Is Falling Down", file: "London_Bridge_Is_Falling_Down" ) case .ohTheCrocodiles: - return ( + ( name: "Oh The Crocodiles", file: "Oh_The_Crocodiles" ) case .happyBirthday: - return (name: "Happy Birthday", file: "Happy_Birthday") + (name: "Happy Birthday", file: "Happy_Birthday") } } var scale: [UInt8] { switch self { case .none: - return [] + [] case .underTheMoonlight: - return [24, 26, 28, 29, 31, 33, 35, 36] + [24, 26, 28, 29, 31, 33, 35, 36] case .aGreenMouse: - return [24, 26, 28, 29, 31, 33, 34, 36] + [24, 26, 28, 29, 31, 33, 34, 36] case .twinkleTwinkleLittleStar: - return [24, 26, 28, 29, 31, 33, 35, 36] + [24, 26, 28, 29, 31, 33, 35, 36] case .londonBridgeIsFallingDown: - return [24, 26, 28, 29, 31, 33, 35, 36] + [24, 26, 28, 29, 31, 33, 35, 36] case .ohTheCrocodiles: - return [24, 28, 29, 31, 33, 34, 35, 36] + [24, 28, 29, 31, 33, 34, 35, 36] case .happyBirthday: - return [24, 26, 28, 29, 31, 33, 34, 36] + [24, 26, 28, 29, 31, 33, 34, 36] } } } @@ -65,17 +82,4 @@ public struct MidiRecording: Codable, Hashable, Equatable { public let name: String public let file: String public let scale: [UInt8] - - public init(name: String, file: String, scale: [UInt8]) { - self.name = name - self.file = file - self.scale = scale - } - - public init(_ song: Song) { - self.name = song.details.name - self.file = song.details.file - self.scale = song.scale - } - } diff --git a/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+ButtonLabel.swift b/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+ButtonLabel.swift index 633eb05d6f..692944ce4c 100644 --- a/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+ButtonLabel.swift +++ b/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+ButtonLabel.swift @@ -6,26 +6,28 @@ import SwiftUI // TODO(@hugo/@ladislas): Make a general Button struct extension MelodyView { - struct ButtonLabel: View { - let text: String - let color: Color + // MARK: Lifecycle init(_ text: String, color: Color) { self.text = text self.color = color } + // MARK: Internal + + let text: String + let color: Color + var body: some View { - Text(text) + Text(self.text) .font(.title2) .foregroundColor(.white) .fixedSize(horizontal: false, vertical: true) .multilineTextAlignment(.center) .frame(width: 400, height: 50) .scaledToFit() - .background(Capsule().fill(color).shadow(radius: 3)) + .background(Capsule().fill(self.color).shadow(radius: 3)) } } - } diff --git a/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+LauncherView.swift b/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+LauncherView.swift index 0725f3f619..3cf26ad645 100644 --- a/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+LauncherView.swift +++ b/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+LauncherView.swift @@ -6,7 +6,6 @@ import ContentKit import SwiftUI extension MelodyView { - struct LauncherView: View { @Binding var selectedSong: MidiRecording @Binding var mode: Stage @@ -28,8 +27,8 @@ extension MelodyView { GameEngineKitAsset.Exercises.Melody.iconKeyboardPartial.swiftUIImage .resizable() .scaledToFit() - Text(instructions.textKeyboardPartial) - .foregroundStyle(keyboard == .partial ? .black : .gray.opacity(0.4)) + Text(self.instructions.textKeyboardPartial) + .foregroundStyle(self.keyboard == .partial ? .black : .gray.opacity(0.4)) } Toggle( @@ -45,8 +44,8 @@ extension MelodyView { GameEngineKitAsset.Exercises.Melody.iconKeyboardFull.swiftUIImage .resizable() .scaledToFit() - Text(instructions.textKeyboardFull) - .foregroundStyle(keyboard == .full ? .black : .gray.opacity(0.4)) + Text(self.instructions.textKeyboardFull) + .foregroundStyle(self.keyboard == .full ? .black : .gray.opacity(0.4)) } } .padding(.horizontal) @@ -55,8 +54,8 @@ extension MelodyView { .clipShape(RoundedRectangle(cornerRadius: 10)) SongSelectorView( - songs: songs, selectedMidiRecording: $selectedSong, - textMusicSelection: instructions.textMusicSelection + songs: self.songs, selectedMidiRecording: self.$selectedSong, + textMusicSelection: self.instructions.textMusicSelection ) .frame(maxHeight: 260) } @@ -65,9 +64,9 @@ extension MelodyView { .padding(.horizontal, 100) Button { - mode = .selectionConfirmed + self.mode = .selectionConfirmed } label: { - ButtonLabel(instructions.textButtonPlay, color: .cyan) + ButtonLabel(self.instructions.textButtonPlay, color: .cyan) } } } diff --git a/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+SongSelectorView.swift b/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+SongSelectorView.swift index 9001050bef..73092e2c21 100644 --- a/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+SongSelectorView.swift +++ b/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+SongSelectorView.swift @@ -7,8 +7,17 @@ import DesignKit import SwiftUI extension MelodyView { - struct SongSelectorView: View { + // MARK: Lifecycle + + init(songs: [MidiRecording], selectedMidiRecording: Binding, textMusicSelection: String) { + self.songs = songs + self._selectedMidiRecording = selectedMidiRecording + self.textMusicSelection = textMusicSelection + } + + // MARK: Internal + @Binding var selectedMidiRecording: MidiRecording let songs: [MidiRecording] let textMusicSelection: String @@ -18,37 +27,30 @@ extension MelodyView { GridItem(.flexible()), ] - init(songs: [MidiRecording], selectedMidiRecording: Binding, textMusicSelection: String) { - self.songs = songs - self._selectedMidiRecording = selectedMidiRecording - self.textMusicSelection = textMusicSelection - } - var body: some View { - VStack { HStack { Image(systemName: "music.note.list") - Text(textMusicSelection) + Text(self.textMusicSelection) Image(systemName: "music.note.list") } Divider() ScrollView { - LazyVGrid(columns: columns, alignment: .listRowSeparatorLeading, spacing: 20) { - ForEach(songs, id: \.self) { midiRecording in + LazyVGrid(columns: self.columns, alignment: .listRowSeparatorLeading, spacing: 20) { + ForEach(self.songs, id: \.self) { midiRecording in Button { - selectedMidiRecording = midiRecording + self.selectedMidiRecording = midiRecording } label: { HStack { Image( - systemName: midiRecording == selectedMidiRecording + systemName: midiRecording == self.selectedMidiRecording ? "checkmark.circle.fill" : "circle" ) .imageScale(.large) .foregroundColor( - midiRecording == selectedMidiRecording + midiRecording == self.selectedMidiRecording ? .green : DesignKitAsset.Colors.lekaDarkGray.swiftUIColor ) Text(midiRecording.name) @@ -67,7 +69,6 @@ extension MelodyView { .clipShape(RoundedRectangle(cornerRadius: 10)) } } - } #Preview { diff --git a/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+ViewModel.swift b/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+ViewModel.swift index a3b3a6e015..3147bb8b03 100644 --- a/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+ViewModel.swift +++ b/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+ViewModel.swift @@ -8,8 +8,19 @@ import RobotKit import SwiftUI extension MelodyView { - class ViewModel: Identifiable, ObservableObject { + // MARK: Lifecycle + + init(midiPlayer: MIDIPlayer, selectedSong: MidiRecording, shared: ExerciseSharedData? = nil) { + self.midiPlayer = midiPlayer + self.defaultScale = selectedSong.scale + self.exercicesSharedData = shared ?? ExerciseSharedData() + self.exercicesSharedData.state = .playing + self.setMIDIRecording(midiRecording: selectedSong) + } + + // MARK: Public + @ObservedObject public var exercicesSharedData: ExerciseSharedData @Published public var progress: CGFloat = 0.0 @Published public var isNotTappable: Bool = true @@ -19,29 +30,17 @@ extension MelodyView { public let defaultScale: [MIDINoteNumber] public let tileColors: [Robot.Color] = [.pink, .red, .orange, .yellow, .green, .lightBlue, .blue, .purple] - private let tempo: Double = 100 - private let robot = Robot.shared - private let defaultOctave: UInt8 = 2 - private var midiNotes: [MIDINoteData] = [] - private var currentNoteIndex: Int = 0 - private var octaveGap: MIDINoteNumber = 0 - init(midiPlayer: MIDIPlayer, selectedSong: MidiRecording, shared: ExerciseSharedData? = nil) { - self.midiPlayer = midiPlayer - self.defaultScale = selectedSong.scale - self.exercicesSharedData = shared ?? ExerciseSharedData() - self.exercicesSharedData.state = .playing - self.setMIDIRecording(midiRecording: selectedSong) - } + // MARK: Internal func setMIDIRecording(midiRecording: MidiRecording) { let midiFile = Bundle.module.url(forResource: midiRecording.file, withExtension: "mid")! - self.midiPlayer.loadMIDIFile(fileUrl: midiFile, tempo: tempo) - self.midiNotes = midiPlayer.getMidiNotes() - self.octaveGap = getOctaveGap(midiNotes.first!.noteNumber) - self.scale = getScale(midiNotes) - self.currentNoteNumber = midiNotes.first!.noteNumber - self.octaveGap - setInstrumentCallback() + self.midiPlayer.loadMIDIFile(fileURL: midiFile, tempo: self.tempo) + self.midiNotes = self.midiPlayer.getMidiNotes() + self.octaveGap = self.getOctaveGap(self.midiNotes.first!.noteNumber) + self.scale = self.getScale(self.midiNotes) + self.currentNoteNumber = self.midiNotes.first!.noteNumber - self.octaveGap + self.setInstrumentCallback() } func playMIDIRecording() { @@ -62,22 +61,23 @@ extension MelodyView { } func onTileTapped(noteNumber: MIDINoteNumber) { - guard currentNoteIndex < midiNotes.count else { return } - - if noteNumber == currentNoteNumber { - isNotTappable = true - midiPlayer.noteOn( - number: currentNoteNumber, velocity: midiNotes[currentNoteIndex].velocity) - currentNoteIndex += 1 - robot.stopLights() - if currentNoteIndex < midiNotes.count { + guard self.currentNoteIndex < self.midiNotes.count else { return } + + if noteNumber == self.currentNoteNumber { + self.isNotTappable = true + self.midiPlayer.noteOn( + number: self.currentNoteNumber, velocity: self.midiNotes[self.currentNoteIndex].velocity + ) + self.currentNoteIndex += 1 + self.robot.stopLights() + if self.currentNoteIndex < self.midiNotes.count { DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { self.currentNoteNumber = self.midiNotes[self.currentNoteIndex].noteNumber - self.octaveGap self.showColorFromMIDINote(self.currentNoteNumber) self.isNotTappable = false } } else { - robot.stopLights() + self.robot.stopLights() DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { self.midiPlayer.play() @@ -89,15 +89,34 @@ extension MelodyView { } } } - progress = CGFloat(currentNoteIndex) / CGFloat(midiNotes.count) + self.progress = CGFloat(self.currentNoteIndex) / CGFloat(self.midiNotes.count) + } + } + + func showColorFromMIDINote(_ note: MIDINoteNumber) { + guard let index = defaultScale.firstIndex(where: { + $0 == note + }) + else { + fatalError("Note not found") } + self.robot.shine(.all(in: self.tileColors[index])) } + // MARK: Private + + private let tempo: Double = 100 + private let robot = Robot.shared + private let defaultOctave: UInt8 = 2 + private var midiNotes: [MIDINoteData] = [] + private var currentNoteIndex: Int = 0 + private var octaveGap: MIDINoteNumber = 0 + private func setInstrumentCallback() { self.midiPlayer.setInstrumentCallback(callback: { _, note, velocity in if velocity == 0 || note < self.octaveGap { return } let currentNote = note - self.octaveGap - if 24 <= currentNote && currentNote <= 36 { + if currentNote >= 24, currentNote <= 36 { self.showColorFromMIDINote(currentNote) self.midiPlayer.noteOn(number: currentNote, velocity: velocity) } @@ -106,7 +125,7 @@ extension MelodyView { private func getOctaveGap(_ initialNote: MIDINoteNumber) -> MIDINoteNumber { let initialOctave = initialNote / 12 - return (initialOctave - defaultOctave) * 12 + return (initialOctave - self.defaultOctave) * 12 } private func getScale(_ notes: [MIDINoteData]) -> [MIDINoteNumber] { @@ -118,17 +137,5 @@ extension MelodyView { return Array(uniqueDict.keys).sorted() } - - func showColorFromMIDINote(_ note: MIDINoteNumber) { - guard - let index = defaultScale.firstIndex(where: { - $0 == note - }) - else { - fatalError("Note not found") - } - robot.shine(.all(in: tileColors[index])) - } } - } diff --git a/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+XylophoneView.swift b/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+XylophoneView.swift index 8b6f1d2d22..fc0ca2a820 100644 --- a/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+XylophoneView.swift +++ b/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView+XylophoneView.swift @@ -8,65 +8,66 @@ import RobotKit import SwiftUI // swiftlint:disable nesting -extension MelodyView { - - public struct XylophoneView: View { - @StateObject private var viewModel: ViewModel - - private var scale: [MIDINoteNumber] - private let tilesSpacing: CGFloat = 16 - private let keyboard: Keyboard +public extension MelodyView { + struct XylophoneView: View { + // MARK: Lifecycle init( instrument: MIDIInstrument, selectedSong: MidiRecording, keyboard: Keyboard, data: ExerciseSharedData? = nil ) { self._viewModel = StateObject( wrappedValue: ViewModel( - midiPlayer: MIDIPlayer(instrument: instrument), selectedSong: selectedSong, shared: data)) + midiPlayer: MIDIPlayer(instrument: instrument), selectedSong: selectedSong, shared: data + )) self.keyboard = keyboard self.scale = selectedSong.scale } + // MARK: Public + public var body: some View { VStack(spacing: 50) { - ContinuousProgressBar(progress: viewModel.progress) - .animation(.easeOut, value: viewModel.progress) + ContinuousProgressBar(progress: self.viewModel.progress) + .animation(.easeOut, value: self.viewModel.progress) .padding(.horizontal) - HStack(spacing: tilesSpacing) { - ForEach(scale.enumerated().map { $0 }, id: \.0) { index, note in + HStack(spacing: self.tilesSpacing) { + ForEach(self.scale.enumerated().map { $0 }, id: \.0) { index, note in Button { - viewModel.onTileTapped(noteNumber: note) + self.viewModel.onTileTapped(noteNumber: note) } label: { - viewModel.tileColors[index].screen + self.viewModel.tileColors[index].screen } .buttonStyle( XylophoneTileButtonStyle( index: index, - tileNumber: scale.count, + tileNumber: self.scale.count, tileWidth: 100, - isTappable: viewModel.scale.contains(note) + isTappable: self.viewModel.scale.contains(note) ) ) - .disabled(viewModel.isNotTappable) + .disabled(self.viewModel.isNotTappable) .modifier( KeyboardModeModifier( - isPartial: keyboard == .partial, scaleNote: note, viewModel: viewModel) + isPartial: self.keyboard == .partial, scaleNote: note, viewModel: self.viewModel + ) ) .compositingGroup() } } } .onAppear { - viewModel.playMIDIRecording() + self.viewModel.playMIDIRecording() } .onDisappear { - viewModel.setMIDIRecording( + self.viewModel.setMIDIRecording( midiRecording: MidiRecording(.none)) - viewModel.midiPlayer.stop() + self.viewModel.midiPlayer.stop() } } + // MARK: Internal + struct KeyboardModeModifier: ViewModifier { @Environment(\.colorScheme) var colorScheme var isPartial: Bool @@ -74,13 +75,13 @@ extension MelodyView { var viewModel: ViewModel func body(content: Content) -> some View { - if isPartial { + if self.isPartial { content - .disabled(!viewModel.scale.contains(scaleNote)) + .disabled(!self.viewModel.scale.contains(self.scaleNote)) .overlay { - if !viewModel.scale.contains(scaleNote) { + if !self.viewModel.scale.contains(self.scaleNote) { RoundedRectangle(cornerRadius: 7) - .fill(colorScheme == .light ? Color.white.opacity(0.9) : Color.black.opacity(0.85)) + .fill(self.colorScheme == .light ? Color.white.opacity(0.9) : Color.black.opacity(0.85)) } } } else { @@ -88,12 +89,21 @@ extension MelodyView { } } } - } + // MARK: Private + + @StateObject private var viewModel: ViewModel + + private var scale: [MIDINoteNumber] + private let tilesSpacing: CGFloat = 16 + private let keyboard: Keyboard + } } + // swiftlint:enable nesting #Preview { MelodyView.XylophoneView( - instrument: .xylophone, selectedSong: MidiRecording(.aGreenMouse), keyboard: .full) + instrument: .xylophone, selectedSong: MidiRecording(.aGreenMouse), keyboard: .full + ) } diff --git a/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView.swift b/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView.swift index fc13e05842..fdf1aaec05 100644 --- a/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView.swift +++ b/Modules/GameEngineKit/Sources/_NewSystem/Exercises/Specialized/Melody/MelodyView.swift @@ -6,25 +6,8 @@ import AudioKit import ContentKit import SwiftUI -struct MelodyView: View { - - enum Stage { - case waitingForSelection - case selectionConfirmed - } - - enum Keyboard { - case full - case partial - } - - @State private var mode = Stage.waitingForSelection - @State private var selectedSong: MidiRecording - @State private var keyboard: Keyboard = .partial - let data: ExerciseSharedData? - let instructions: MidiRecordingPlayer.Payload.Instructions - let instrument: MIDIInstrument - let songs: [MidiRecording] +public struct MelodyView: View { + // MARK: Lifecycle init(instructions: MidiRecordingPlayer.Payload.Instructions, instrument: MIDIInstrument, songs: [MidiRecording]) { self.instructions = instructions @@ -47,27 +30,54 @@ struct MelodyView: View { self.instructions = payload.instructions self.instrument = instrument self.songs = payload.songs - self.selectedSong = songs.first! + self.selectedSong = self.songs.first! self.data = data } - var body: some View { + // MARK: Public + + public var body: some View { NavigationStack { - switch mode { + switch self.mode { case .waitingForSelection: LauncherView( - selectedSong: $selectedSong, mode: $mode, keyboard: $keyboard, songs: songs, - instructions: instructions) + selectedSong: self.$selectedSong, mode: self.$mode, keyboard: self.$keyboard, songs: self.songs, + instructions: self.instructions + ) case .selectionConfirmed: - switch instrument { + switch self.instrument { case .xylophone: XylophoneView( - instrument: instrument, selectedSong: selectedSong, keyboard: keyboard, data: data) + instrument: self.instrument, selectedSong: self.selectedSong, keyboard: self.keyboard, data: self.data + ) } } } .navigationViewStyle(StackNavigationViewStyle()) } + + // MARK: Internal + + enum Stage { + case waitingForSelection + case selectionConfirmed + } + + enum Keyboard { + case full + case partial + } + + let data: ExerciseSharedData? + let instructions: MidiRecordingPlayer.Payload.Instructions + let instrument: MIDIInstrument + let songs: [MidiRecording] + + // MARK: Private + + @State private var mode = Stage.waitingForSelection + @State private var selectedSong: MidiRecording + @State private var keyboard: Keyboard = .partial } #Preview { diff --git a/Modules/GameEngineKit/Sources/_NewSystem/Views/Activity/ActivityView.swift b/Modules/GameEngineKit/Sources/_NewSystem/Views/Activity/ActivityView.swift index 4c2d8e565c..991526ade4 100644 --- a/Modules/GameEngineKit/Sources/_NewSystem/Views/Activity/ActivityView.swift +++ b/Modules/GameEngineKit/Sources/_NewSystem/Views/Activity/ActivityView.swift @@ -163,8 +163,8 @@ public struct ActivityView: View { case .melody: MelodyView( - exercise: viewModel.currentExercise, - data: viewModel.currentExerciseSharedData + exercise: self.viewModel.currentExercise, + data: self.viewModel.currentExerciseSharedData ) } } diff --git a/Modules/GameEngineKit/Sources/_NewSystem/Views/Buttons/BinaryToggleChoiceStyle.swift b/Modules/GameEngineKit/Sources/_NewSystem/Views/Buttons/BinaryToggleChoiceStyle.swift index b4420cbf84..0e5f4abbd2 100644 --- a/Modules/GameEngineKit/Sources/_NewSystem/Views/Buttons/BinaryToggleChoiceStyle.swift +++ b/Modules/GameEngineKit/Sources/_NewSystem/Views/Buttons/BinaryToggleChoiceStyle.swift @@ -10,7 +10,6 @@ struct BinaryChoiceToggleStyle: ToggleStyle { configuration.label RoundedRectangle(cornerRadius: 20) .foregroundColor(.teal) - .frame(width: 50, height: 30) .overlay( Circle() diff --git a/Modules/GameEngineKit/Sources/_NewSystem/Views/Buttons/XylophoneTileButtonStyle.swift b/Modules/GameEngineKit/Sources/_NewSystem/Views/Buttons/XylophoneTileButtonStyle.swift index 93f04319e7..ffdcaf4c4b 100644 --- a/Modules/GameEngineKit/Sources/_NewSystem/Views/Buttons/XylophoneTileButtonStyle.swift +++ b/Modules/GameEngineKit/Sources/_NewSystem/Views/Buttons/XylophoneTileButtonStyle.swift @@ -6,6 +6,17 @@ import DesignKit import SwiftUI struct XylophoneTileButtonStyle: ButtonStyle { + // MARK: Lifecycle + + init(index: Int, tileNumber: Int, tileWidth: CGFloat = 100, isTappable: Bool = true) { + self.index = index + self.tileNumber = tileNumber + self.tileWidth = tileWidth + self.isTappable = isTappable + } + + // MARK: Internal + let xyloAttachColor = Color(red: 0.87, green: 0.65, blue: 0.54) let defaultMaxTileHeight: Int = 500 let defaultTileHeightGap: Int = 250 @@ -17,29 +28,22 @@ struct XylophoneTileButtonStyle: ButtonStyle { let tileWidth: CGFloat let isTappable: Bool - init(index: Int, tileNumber: Int, tileWidth: CGFloat = 100, isTappable: Bool = true) { - self.index = index - self.tileNumber = tileNumber - self.tileWidth = tileWidth - self.isTappable = isTappable - } - func makeBody(configuration: Self.Configuration) -> some View { configuration.label .overlay { VStack { Spacer() Circle() - .fill(xyloAttachColor) + .fill(self.xyloAttachColor) .shadow( color: .black.opacity(0.4), radius: 3, x: 0, y: 3 ) Spacer() Circle() - .fill(xyloAttachColor) + .fill(self.xyloAttachColor) .shadow( - color: isTappable ? .black.opacity(0.4) : .clear, + color: self.isTappable ? .black.opacity(0.4) : .clear, radius: 3, x: 0, y: 3 ) Spacer() @@ -51,24 +55,26 @@ struct XylophoneTileButtonStyle: ButtonStyle { .stroke(.black.opacity(configuration.isPressed ? 0.3 : 0), lineWidth: 20) } .clipShape(RoundedRectangle(cornerRadius: 7, style: .circular)) - .frame(width: tileWidth, height: setSizeFromIndex()) + .frame(width: self.tileWidth, height: self.setSizeFromIndex()) .scaleEffect( - configuration.isPressed ? defaultTilesScaleFeedback : 1, + configuration.isPressed ? self.defaultTilesScaleFeedback : 1, anchor: .center ) .rotationEffect( - Angle(degrees: configuration.isPressed ? defaultTilesRotationFeedback : 0), + Angle(degrees: configuration.isPressed ? self.defaultTilesRotationFeedback : 0), anchor: .center ) .shadow( - color: isTappable ? .black.opacity(0.4) : .clear, + color: self.isTappable ? .black.opacity(0.4) : .clear, radius: 3, x: 0, y: 3 ) } + // MARK: Private + private func setSizeFromIndex() -> CGFloat { - let sizeDiff = defaultTileHeightGap / tileNumber - let tileHeight = defaultMaxTileHeight - index * sizeDiff + let sizeDiff = self.defaultTileHeightGap / self.tileNumber + let tileHeight = self.defaultMaxTileHeight - self.index * sizeDiff return CGFloat(tileHeight) }