# HG changeset patch # User Dennis Concepción Martín <66180929+denniscm190@users.noreply.github.com> # Date 1616084601 -3600 # Node ID 62f2c675b666797f66f5c87c200c1bb30a620f97 # Parent 61208d7aa71546ea3616030b9de5f1ca32df076f Interactive LineChart implemented diff -r 61208d7aa715 -r 62f2c675b666 LazyBear/Functions/Haptics.swift --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LazyBear/Functions/Haptics.swift Thu Mar 18 17:23:21 2021 +0100 @@ -0,0 +1,20 @@ +// +// Haptics.swift +// LazyBear +// +// Created by Dennis Concepción Martín on 18/3/21. +// + +import SwiftUI + +struct Haptics: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +struct Haptics_Previews: PreviewProvider { + static var previews: some View { + Haptics() + } +} diff -r 61208d7aa715 -r 62f2c675b666 LazyBear/Functions/NormalizeData.swift --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LazyBear/Functions/NormalizeData.swift Thu Mar 18 17:23:21 2021 +0100 @@ -0,0 +1,21 @@ +// +// NormalizeData.swift +// LazyBear +// +// Created by Dennis Concepción Martín on 18/3/21. +// + +import SwiftUI + +func normalize(_ data: [Double]) -> [Double] { + var normalData = [Double]() + let min = data.min()! + let max = data.max()! + + for value in data { + let normal = (value - min) / (max - min) + normalData.append(normal) + } + + return normalData +} diff -r 61208d7aa715 -r 62f2c675b666 LazyBear/UI/ChartView.swift --- a/LazyBear/UI/ChartView.swift Wed Mar 17 20:26:19 2021 +0100 +++ b/LazyBear/UI/ChartView.swift Thu Mar 18 17:23:21 2021 +0100 @@ -10,23 +10,43 @@ struct ChartView: View { var symbol: String @State private var historicalPrices = [HistoricalPriceModel]() + @EnvironmentObject var deviceSize: DeviceSize // Date picker var period = ["1W", "1M", "3M", "6M", "1Y", "2Y", "5Y"] @State var selectedPeriod = 2 + // Line View + @State var showingChartIndicator = false + @State var pointInPath: CGPoint = .zero + @State var indexValue = Int() + var body: some View { + VStack { - DateSelection(period: period, selectedperiod: $selectedPeriod) - .padding(.horizontal) - .onChange(of: selectedPeriod, perform: { (value) in - let url = getUrl(endpoint: .historicalPrices, symbol: symbol, range: period[value]) - request(url: url, model: [HistoricalPriceModel].self) { self.historicalPrices = $0 } - }) + let prices = historicalPrices.map { $0.close } + let dates = historicalPrices.map { $0.date } + ZStack { + if showingChartIndicator { + PriceChartIndicator(prices: prices, dates: dates, indexValue: $indexValue) + } else { + DateSelection(period: period, selectedperiod: $selectedPeriod) + .padding(.horizontal) + .onChange(of: selectedPeriod, perform: { (value) in + let url = getUrl(endpoint: .historicalPrices, symbol: symbol, range: period[value]) + request(url: url, model: [HistoricalPriceModel].self) { self.historicalPrices = $0 } + }) + } + } + .frame(height: 40) - let prices = historicalPrices.map { $0.close } - LineChart(data: prices) - .padding(.vertical) + if !historicalPrices.isEmpty { + LineView(width: deviceSize.width, height: deviceSize.width/3, normalizedData: normalize(prices), showingChartIndicator: $showingChartIndicator, pointInPath: $pointInPath, indexValue: $indexValue) + .frame(width: deviceSize.width, height: deviceSize.width / 3) + .rotationEffect(.degrees(180), anchor: .center) + .rotation3DEffect(.degrees(180), axis: (x: 0.0, y: 1.0, z: 0.0)) + .padding([.top, .bottom]) + } } .onAppear { let url = getUrl(endpoint: .historicalPrices, symbol: symbol, range: "3m") diff -r 61208d7aa715 -r 62f2c675b666 LazyBear/UI/CompanyView.swift --- a/LazyBear/UI/CompanyView.swift Wed Mar 17 20:26:19 2021 +0100 +++ b/LazyBear/UI/CompanyView.swift Thu Mar 18 17:23:21 2021 +0100 @@ -14,6 +14,7 @@ struct CompanyView: View { var name: String var symbol: String + let haptics = Haptics() @EnvironmentObject var hudManager: HudManager @EnvironmentObject var companyOption: CompanyOption @Environment(\.managedObjectContext) private var moc @@ -65,14 +66,13 @@ // Add to watchlist private func addCompany() { - let generator = UINotificationFeedbackGenerator() // Haptic let company = Company(context: moc) company.symbol = symbol company.name = name do { try moc.save() hudManager.selectHud(type: .notification) - generator.notificationOccurred(.success) + haptics.simpleSuccess() print("Company saved") } catch { print(error.localizedDescription) diff -r 61208d7aa715 -r 62f2c675b666 LazyBear/UI/IconPicker.swift --- a/LazyBear/UI/IconPicker.swift Wed Mar 17 20:26:19 2021 +0100 +++ b/LazyBear/UI/IconPicker.swift Thu Mar 18 17:23:21 2021 +0100 @@ -33,9 +33,10 @@ struct IconRow: View { @Environment(\.colorScheme) var colorScheme // Detect dark mode var icon: IconModel + let haptics = Haptics() var body: some View { - Button(action: { changeIcon(key: icon.file) }) { + Button(action: { haptics.simpleSuccess(); changeIcon(key: icon.file) }) { HStack { Image(icon.file) .resizable() diff -r 61208d7aa715 -r 62f2c675b666 LazyBear/UI/IndicatorPoint.swift --- a/LazyBear/UI/IndicatorPoint.swift Wed Mar 17 20:26:19 2021 +0100 +++ b/LazyBear/UI/IndicatorPoint.swift Thu Mar 18 17:23:21 2021 +0100 @@ -10,7 +10,8 @@ struct IndicatorPoint: View { var body: some View { Circle() - .frame(width: 10, height: 10) + .frame(width: 20, height: 20) + .foregroundColor(.blue) } } diff -r 61208d7aa715 -r 62f2c675b666 LazyBear/UI/LanguagePicker.swift --- a/LazyBear/UI/LanguagePicker.swift Wed Mar 17 20:26:19 2021 +0100 +++ b/LazyBear/UI/LanguagePicker.swift Thu Mar 18 17:23:21 2021 +0100 @@ -10,6 +10,7 @@ struct LanguagePicker: View { @Environment(\.managedObjectContext) private var moc @State var language: String + let haptics = Haptics() var body: some View { Picker("News language", selection: $language) { @@ -29,6 +30,7 @@ userSettings.newsLanguage = change as? String do { try moc.save() + haptics.simpleSuccess() print("Settings saved") } catch { print(error.localizedDescription) diff -r 61208d7aa715 -r 62f2c675b666 LazyBear/UI/LineChart.swift --- a/LazyBear/UI/LineChart.swift Wed Mar 17 20:26:19 2021 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -// -// LineChart.swift -// LazyBear -// -// Created by Dennis Concepción Martín on 15/3/21. -// - -import SwiftUI - -struct LineChart: View { - var data: [Double] - @EnvironmentObject var deviceSize: DeviceSize - - var body: some View { - if !data.isEmpty { - let normalizedData = normalize(data) - - VStack { - LineView(width: deviceSize.width, height: deviceSize.width / 3, normalizedData: normalizedData) - .rotationEffect(.degrees(180), anchor: .center) - .rotation3DEffect(.degrees(180), axis: (x: 0.0, y: 1.0, z: 0.0)) - - } - .frame(width: deviceSize.width, height: deviceSize.width / 3) - } - } - - func normalize(_ data: [Double]) -> [Double] { - var normalData = [Double]() - let min = data.min()! - let max = data.max()! - - for value in data { - let normal = (value - min) / (max - min) - normalData.append(normal) - } - - return normalData - } -} - -struct LineChart_Previews: PreviewProvider { - static var previews: some View { - LineChart(data: [50.0, 50.5, 51.0, 50.4, 50.8, 51.3, 51.5, 52, 51.9, 52.4]) - .environmentObject(DeviceSize()) - } -} diff -r 61208d7aa715 -r 62f2c675b666 LazyBear/UI/LineView.swift --- a/LazyBear/UI/LineView.swift Wed Mar 17 20:26:19 2021 +0100 +++ b/LazyBear/UI/LineView.swift Thu Mar 18 17:23:21 2021 +0100 @@ -11,9 +11,12 @@ var width: CGFloat var height: CGFloat var normalizedData: [Double] + let haptics = Haptics() // Drag gesture - @State private var touchLocation: CGPoint = .zero + @Binding var showingChartIndicator: Bool + @Binding var pointInPath: CGPoint + @Binding var indexValue: Int var body: some View { // Substract 2 to skip the first and the last item @@ -24,38 +27,57 @@ ZStack { GeometryReader { geo in - Path { path in - path.move(to: CGPoint(x: x, y: initialPoint)) - for y in normalizedData { - // Skip first item - if normalizedData.firstIndex(of: y) != 0 { - x += widthBetweenPoints - let y = y * Double(height) - path.addLine(to: CGPoint(x: x, y: y)) + Path { path in + path.move(to: CGPoint(x: x, y: initialPoint)) + for y in normalizedData { + // Skip first item + if normalizedData.firstIndex(of: y) != 0 { + x += widthBetweenPoints + let y = y * Double(height) + path.addLine(to: CGPoint(x: x, y: y)) + } + + pathPoints.append(path.currentPoint!) } - - pathPoints.append(path.currentPoint!) } - } - .stroke(Color.green, lineWidth: 2) - .gesture(DragGesture() // Add gesture - .onChanged({ value in // Take value of the gesture - let (closestXPoint, closestYPoint) = getClosestValueFrom(value.location, inData: pathPoints) - self.touchLocation.x = closestXPoint - self.touchLocation.y = closestYPoint + .stroke(self.showingChartIndicator ? Color.blue: Color.green, lineWidth: 2) - })) - - - IndicatorPoint() - .position(x: touchLocation.x, y: touchLocation.y) + if showingChartIndicator { + IndicatorPoint() + .position(x: pointInPath.x, y: pointInPath.y) + } } } + .contentShape(Rectangle()) // Control tappable area + .gesture( + LongPressGesture(minimumDuration: 0.2) + .sequenced(before: DragGesture(minimumDistance: 0, coordinateSpace: .local)) + .onChanged({ value in // Take value of the gesture + switch value { + // Start the second gesture -> Drag() + case .second(true, let drag): + if let longPressLocation = drag?.location { + let (closestXPoint, closestYPoint, yPointIndex) = getClosestValueFrom(longPressLocation, inData: pathPoints) + self.pointInPath.x = closestXPoint + self.pointInPath.y = closestYPoint + self.showingChartIndicator = true + self.indexValue = yPointIndex + } else { + haptics.simpleSuccess() + } + default: + break + } + }) + .onEnded({ value in + self.showingChartIndicator = false + }) + ) } // First search the closest X path point from the touch location. The find the correspondant Y path point // given the X path point - func getClosestValueFrom(_ value: CGPoint, inData: [CGPoint]) -> (CGFloat, CGFloat) { + func getClosestValueFrom(_ value: CGPoint, inData: [CGPoint]) -> (CGFloat, CGFloat, Int) { let touchPoint: (CGFloat, CGFloat) = (value.x, value.y) let xPathPoints = inData.map { $0.x } let yPathPoints = inData.map { $0.y } @@ -65,7 +87,10 @@ let closestYPointIndex = xPathPoints.firstIndex(of: closestXPoint.element)! let closestYPoint = yPathPoints[closestYPointIndex] - return (closestXPoint.element, closestYPoint) + // Index of the closest points in the array + let yPointIndex = yPathPoints.firstIndex(of: closestYPoint)! + + return (closestXPoint.element, closestYPoint, yPointIndex) } } @@ -74,7 +99,7 @@ GeometryReader { geo in VStack { let normalizedData: [Double] = [0, 0.1, 0.15, 0.2, 0.3, 0.4, 0.35, 0.38, 0.5, 0.55, 0.6, 0.57, 0.8, 1] - LineView(width: geo.size.width, height: geo.size.width / 2, normalizedData: normalizedData) + LineView(width: geo.size.width, height: geo.size.width / 2, normalizedData: normalizedData, showingChartIndicator: .constant(false), pointInPath: .constant(.zero), indexValue: .constant(0)) } } } diff -r 61208d7aa715 -r 62f2c675b666 LazyBear/UI/PriceChartIndicator.swift --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LazyBear/UI/PriceChartIndicator.swift Thu Mar 18 17:23:21 2021 +0100 @@ -0,0 +1,31 @@ +// +// PriceChartIndicator.swift +// LazyBear +// +// Created by Dennis Concepción Martín on 18/3/21. +// + +import SwiftUI + +struct PriceChartIndicator: View { + var prices: [Double] + var dates: [String] + @Binding var indexValue: Int + + var body: some View { + HStack { + Group { + Text(dates[indexValue]) + Text("\(prices[indexValue], specifier: "%.2f")") + .foregroundColor(.blue) + } + .font(.subheadline) + } + } +} + +struct PriceChartIndicator_Previews: PreviewProvider { + static var previews: some View { + PriceChartIndicator(prices: [100, 50], dates: ["10-10-2020", "11-10-2020"], indexValue: .constant(0)) + } +}