diff --git a/CubeTime/Sessions/Tools/CalculatorTool.swift b/CubeTime/Sessions/Tools/CalculatorTool.swift index 263033f..88162c2 100644 --- a/CubeTime/Sessions/Tools/CalculatorTool.swift +++ b/CubeTime/Sessions/Tools/CalculatorTool.swift @@ -33,10 +33,11 @@ struct SimpleSolve: Comparable, Hashable { struct CalculatorTool: View { @Environment(\.managedObjectContext) var managedObjectContext - + @Namespace private var namespace @State private var calculatorType: CalculatorType = .average + @State private var numberOfSolves: Int = 5 @State private var currentTime: String = "" @State private var solves: [SimpleSolve] = [] @@ -52,108 +53,119 @@ struct CalculatorTool: View { .ignoresSafeArea() VStack { - ToolHeader(name: tools[3].name, image: tools[3].iconName, content: { - Picker("", selection: $calculatorType) { - ForEach(CalculatorType.allCases, id: \.self) { t in - Text(t.rawValue) - .tag(t) + ToolHeader(name: tools[3].name, image: tools[3].iconName) { + HStack(spacing: 4) { + Picker("", selection: $calculatorType) { + ForEach(CalculatorType.allCases, id: \.self) { t in + Text(t.rawValue) + .tag(t) + } } + + TextField("", value: $numberOfSolves, format: .number) + .fixedSize() + .textFieldStyle(.plain) + .keyboardType(.numberPad) + .foregroundColor(Color("accent")) + .padding(.trailing, 12) } - }) + } .frame(maxWidth: .infinity) VStack { if (solves.count > 0) { - VStack(spacing: 8) { - ForEach(Array(zip(solves.indices, solves)), id: \.0) { index, solve in - HStack { - if (solves.count > 3 && (index == solves.firstIndex(of: solves.min() ?? SimpleSolve(time: 0.00, penalty: .none)) || index == solves.lastIndex(of: solves.max() ?? SimpleSolve(time: 0.00, penalty: .none)))) { - Text("(" + formatSolveTime(secs: solve.time, penalty: solve.penalty) + ")") - .font(.body.weight(.semibold)) - .foregroundColor(Color("grey")) - } else { - Text(formatSolveTime(secs: solve.time, penalty: solve.penalty)) - .font(.body.weight(.semibold)) - .foregroundColor(Color("dark")) - } - - Spacer() - - ZStack { - RoundedRectangle(cornerRadius: 6, style: .continuous) - .fill(Color("overlay0")) - .frame(width: showEditFor != nil && showEditFor == index ? nil : 35, height: 35) + ScrollView { + VStack(spacing: 8) { + ForEach(Array(zip(solves.indices, solves)), id: \.0) { index, solve in + HStack { + if (calculatorType == .average && solves.count > 3 && (solve.timeIncPen <= solves.sorted()[StopwatchManager.getTrimSizeEachEnd(numberOfSolves) - 1].timeIncPen || solve.timeIncPen >= solves.sorted()[solves.count - StopwatchManager.getTrimSizeEachEnd(numberOfSolves)].timeIncPen)) { + Text("(" + formatSolveTime(secs: solve.time, penalty: solve.penalty) + ")") + .font(.body.weight(.semibold)) + .foregroundColor(Color("grey")) + } else { + Text(formatSolveTime(secs: solve.time, penalty: solve.penalty)) + .font(.body.weight(.semibold)) + .foregroundColor(Color("dark")) + } - HStack { - if (showEditFor != nil && showEditFor == index) { - CTButton(type: .mono, size: .medium, square: true, hasShadow: false, onTapRun: { - self.solves[index].penalty = .plustwo - showEditFor = nil - }) { - Image("+2.label") - .imageScale(.medium) - } - - CTButton(type: .mono, size: .medium, square: true, hasShadow: false, onTapRun: { - self.solves[index].penalty = .dnf - showEditFor = nil - }) { - Image(systemName: "xmark.circle") - .imageScale(.medium) + Spacer() + + ZStack { + RoundedRectangle(cornerRadius: 6, style: .continuous) + .fill(Color("overlay0")) + .frame(width: showEditFor != nil && showEditFor == index ? nil : 35, height: 35) + + HStack { + if (showEditFor != nil && showEditFor == index) { + CTButton(type: .mono, size: .medium, square: true, hasShadow: false, onTapRun: { + self.solves[index].penalty = .plustwo + showEditFor = nil + }) { + Image("+2.label") + .imageScale(.medium) + } + + CTButton(type: .mono, size: .medium, square: true, hasShadow: false, onTapRun: { + self.solves[index].penalty = .dnf + showEditFor = nil + }) { + Image(systemName: "xmark.circle") + .imageScale(.medium) + } + + CTButton(type: .mono, size: .medium, square: true, hasShadow: false, onTapRun: { + self.solves[index].penalty = .none + showEditFor = nil + }) { + Image(systemName: "checkmark.circle") + .imageScale(.medium) + } + + CTDivider(isHorizontal: false) + .padding(.vertical, 8) + + CTButton(type: .coloured(Color("red")), size: .medium, square: true, hasShadow: false, hasBackground: false, onTapRun: { + self.solves.remove(at: index) + showEditFor = nil + }) { + Image(systemName: "trash") + .imageScale(.medium) + } } CTButton(type: .mono, size: .medium, square: true, hasShadow: false, onTapRun: { - self.solves[index].penalty = .none - showEditFor = nil - }) { - Image(systemName: "checkmark.circle") - .imageScale(.medium) - } - - CTDivider(isHorizontal: false) - .padding(.vertical, 8) - - CTButton(type: .coloured(Color("red")), size: .medium, square: true, hasShadow: false, hasBackground: false, onTapRun: { - self.solves.remove(at: index) - showEditFor = nil - }) { - Image(systemName: "trash") - .imageScale(.medium) - } - } - - CTButton(type: .mono, size: .medium, square: true, hasShadow: false, onTapRun: { - - if (showEditFor != nil && showEditFor == index) { - editNumber = index - showEditFor = nil - } else { - if (self.showEditFor == index) { - self.showEditFor = nil + if (showEditFor != nil && showEditFor == index) { + editNumber = index + showEditFor = nil } else { - self.showEditFor = index + if (self.showEditFor == index) { + self.showEditFor = nil + } else { + self.showEditFor = index + } } + }) { + Image(systemName: "pencil") + .imageScale(.medium) } - }) { - Image(systemName: "pencil") - .imageScale(.medium) } + .padding(.horizontal, 2) } - .padding(.horizontal, 2) + .clipped() + .animation(.customDampedSpring, value: showEditFor) + .fixedSize() } - .clipped() - .animation(.customDampedSpring, value: showEditFor) - .fixedSize() } + .clipped() } - .clipped() + .padding(10) } - .padding(10) .background( RoundedRectangle(cornerRadius: 8, style: .continuous) .fill(Color("overlay1")) ) .padding(.top) + .padding(.bottom, 4) } Spacer() @@ -163,21 +175,29 @@ struct CalculatorTool: View { if let editNumber = editNumber { Text("EDITING SOLVE \(editNumber + 1)") } else { - if (solves.count < 5) { + if (solves.count < numberOfSolves) { Text("SOLVE \(solves.count + 1)") } else { - Text("= " + formatSolveTime(secs: StopwatchManager.calculateAverage(forSortedSolves: solves.sorted(), count: 5, trim: 1), - penalty: solves.sorted()[3].penalty == .dnf ? Penalty.dnf : Penalty.none )) - .font(.largeTitle.weight(.bold)) + Group { + if (calculatorType == .average) { + Text("= " + formatSolveTime(secs: StopwatchManager.calculateAverage(forSortedSolves: solves.sorted(), count: numberOfSolves, trim: 1), + penalty: solves.sorted()[3].penalty == .dnf ? Penalty.dnf : Penalty.none )) + } else { + let mean: Average = StopwatchManager.calculateMean(of: numberOfSolves, for: solves) + + Text("= " + formatSolveTime(secs: mean.average, penalty: mean.penalty)) + } + } + .font(.title.weight(.bold)) } } } .foregroundStyle(Color("dark")) .font(.callout.weight(.semibold)) .padding(.top, 10) - + Group { - if (solves.count < 5 || editNumber != nil) { + if (solves.count < numberOfSolves || editNumber != nil) { TextField("0.00", text: $currentTime) .focused($focused) .padding(.vertical, 6) diff --git a/CubeTime/StopwatchManager/StopwatchManager+Stats.swift b/CubeTime/StopwatchManager/StopwatchManager+Stats.swift index 57efa80..a97f880 100644 --- a/CubeTime/StopwatchManager/StopwatchManager+Stats.swift +++ b/CubeTime/StopwatchManager/StopwatchManager+Stats.swift @@ -626,13 +626,23 @@ extension StopwatchManager { return nil } - func calculateMean(of count: Int, for solves: [Solve]) -> Average { + static func calculateMean(of count: Int, for solves: [Solve]) -> Average { let penalty: Penalty = solves.contains(where: { Penalty(rawValue: $0.penalty) == .dnf }) ? .dnf : .none let average: Double = solves.reduce(0, { $0 + $1.timeIncPen }) / Double(count) return Average(average: average, penalty: penalty) } + #warning("TODO: get rid of this simplesolve") + + static func calculateMean(of count: Int, for solves: [SimpleSolve]) -> Average { + let penalty: Penalty = solves.contains(where: { $0.penalty == .dnf }) ? .dnf : .none + let average: Double = solves.reduce(0, { $0 + $1.timeIncPen }) / Double(count) + + return Average(average: average, penalty: penalty) + } + + func getBpaWpa() -> (bpa: Average?, wpa: Average?) { if !(currentSession is CompSimSession) { return (nil, nil) } @@ -646,8 +656,8 @@ extension StopwatchManager { if (lastGroupSolves.count == 4) { let sortedGroup = lastGroupSolves.sorted(by: Self.sortWithDNFsLast) - let bpa = calculateMean(of: 3, for: Array(sortedGroup.dropLast())) - let wpa = calculateMean(of: 3, for: Array(sortedGroup.dropFirst())) + let bpa = StopwatchManager.calculateMean(of: 3, for: Array(sortedGroup.dropLast())) + let wpa = StopwatchManager.calculateMean(of: 3, for: Array(sortedGroup.dropFirst())) return (bpa, wpa) } diff --git a/Localizable.xcstrings b/Localizable.xcstrings index b932b66..7f83ed6 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -564,7 +564,14 @@ } }, "Calculator" : { - + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "计算器" + } + } + } }, "Cancel" : { "localizations" : { @@ -983,7 +990,14 @@ } }, "Displays one scramble at a time. A timer is not shown. Tap to generate the next scramble." : { - + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "专门生成单次打乱。没有计时器。点击屏幕更换打乱。" + } + } + } }, "DNF" : { "localizations" : { @@ -1156,7 +1170,14 @@ } }, "Generate multiple scrambles at once, to share, save or use." : { - + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "一次生成多个打乱,以供共享、保存或直接使用。" + } + } + } }, "Generate!" : { "localizations" : { @@ -1349,7 +1370,14 @@ } }, "Just a timer. No scrambles are shown. Your solves are **not** recorded and are not saved to a session." : { - + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "只有计时器。不会显示打乱。 你的还原时间是**不会**被记录的。" + } + } + } }, "L' D R2 B2 D2 F2 R2 B2 D R2 D R2 U B' R F2 R U' F L2 D'" : { "localizations" : { @@ -1912,10 +1940,24 @@ } }, "Scramble Generator" : { - + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打乱生成器" + } + } + } }, "Scramble Only" : { - + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打乱模式" + } + } + } }, "Scramble Size: " : { "localizations" : { @@ -2170,7 +2212,14 @@ } }, "Simple average and mean calculator." : { - + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "计算平均成绩(去头尾和所有平均)" + } + } + } }, "Skewb" : { "localizations" : { @@ -2493,7 +2542,14 @@ } }, "Timer Only" : { - + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "计时器模式" + } + } + } }, "Timer Settings" : { "localizations" : {