Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature 5 & 6 #22

Merged
merged 26 commits into from
Jun 10, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e721aac
Initial TDD for DatabaseService
devahmedshendy Apr 30, 2023
8d8d6d3
Add GRDB dependency using project.yml instead
devahmedshendy Apr 30, 2023
b2d0189
Write test for inserting favorite location
devahmedshendy Apr 30, 2023
21ec3e6
Remove constants of sqlColumn/sqlTable
devahmedshendy Apr 30, 2023
77009ef
Fix: uuid shouldn't be saved, but is string representation
devahmedshendy Apr 30, 2023
9515f49
Clean up
devahmedshendy Apr 30, 2023
e98c6c1
Refactor sql statements to find location by name, lat, long instead
devahmedshendy May 3, 2023
9674263
Use LocationProvider, and make view models as StateObject
devahmedshendy May 3, 2023
5e45f7a
Add created_at to fetch favorites as sorted
devahmedshendy May 3, 2023
6b89edd
Change some 'summary' namings to 'favorites'
devahmedshendy May 3, 2023
7ef1412
Implement Feature 5 & 6
devahmedshendy May 16, 2023
08d2f23
DatabaseError, memory leak, cleanup
devahmedshendy May 17, 2023
4de9a0c
Apple fixes
devahmedshendy May 18, 2023
35c8445
Rename database to databaseService
devahmedshendy May 18, 2023
147f515
refactor LocationAdapterTest
devahmedshendy May 20, 2023
491ce4b
Mentioned ticket #23
devahmedshendy May 20, 2023
fc37985
rewrite/add-more tests for SQLiteDatabaseServiceTests
devahmedshendy May 26, 2023
301e586
Write unit test for toggleLocation of SearchLocationViewModel
devahmedshendy May 26, 2023
be30d6b
Divide unit test for toggleFavorite in SearchLocationViewModel to sea…
devahmedshendy May 26, 2023
07fc7d6
Add tests for FavoritesViewModel
devahmedshendy May 26, 2023
a857cbc
Add test for weather summary for FavoritesViewModel
devahmedshendy May 28, 2023
fe86f10
Use tagged version for GRDB than master branch
devahmedshendy Jun 1, 2023
a71a787
Resolve conversation
devahmedshendy Jun 1, 2023
58a025e
Fix project.yml syntax, 'from' instead of 'branch'
devahmedshendy Jun 1, 2023
148f647
Sync unfavorites with search result
devahmedshendy Jun 7, 2023
726c93a
clean up
devahmedshendy Jun 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions OpenWeather/App/Home/HomeScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@ struct HomeScreen: View {
private let location: CLLocation = Mock.londonLocation

@ObservedObject var settings: AppSettings = AppSettings.shared
@ObservedObject var viewModel: HomeViewModel

init(viewModel: HomeViewModel) {
self.viewModel = viewModel
}
@StateObject var viewModel: HomeViewModel

var body: some View {
viewModel.view { content in
Expand Down
2 changes: 1 addition & 1 deletion OpenWeather/App/OpenWeatherApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import SwiftUI

@main
struct OBOpenWeatherApp: App {
@ObservedObject var navigation: Navigation = Navigation.shared
@StateObject var navigation: Navigation = Navigation.shared

var body: some Scene {
WindowGroup {
Expand Down
24 changes: 18 additions & 6 deletions OpenWeather/App/RootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,10 @@ struct RootView: View {
var body: some View {
TabView(selection: $navigation.tab) {
OpenBytesNavigationView(path: navigation.home) {
// TODO: Update to Production
HomeScreen(
viewModel: HomeViewModel(
capabilities: .init(
locationProviding: MockLocationProvider(),
locationProviding: LocationProvider(),
weatherProviding: OpenWeatherMapWeatherProvider()
),
input: .init()
Expand All @@ -48,8 +47,14 @@ struct RootView: View {
}

OpenBytesNavigationView(path: navigation.search) {
// TODO: Update to Production
SearchLocationScreen(viewModel: .mock)
SearchLocationScreen(
viewModel: .init(
capabilities: .init(
locationProviding: LocationProvider()
),
input: .init()
)
)
}
.tag(Tab.search)
.tabItem {
Expand All @@ -58,8 +63,15 @@ struct RootView: View {
}

OpenBytesNavigationView(path: navigation.summary) {
// TODO: Update to Production
SummaryScreen(viewModel: .mock)
SummaryScreen(
viewModel: .init(
capabilities: .init(
locationProviding: LocationProvider(),
weatherProviding: OpenWeatherMapWeatherProvider()
),
input: .init()
)
)
}
.tag(Tab.summary)
.tabItem {
Expand Down
8 changes: 8 additions & 0 deletions OpenWeather/App/RootViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//
// OpenWeather template generated by OpenBytes on 30/04/2023.
//
// Created by Ahmed Shendy.
// RootViewModel.swift
//

import Foundation
6 changes: 1 addition & 5 deletions OpenWeather/App/Search/SearchLocationScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ import OpenBytesNavigation
import SwiftUI

struct SearchLocationScreen: View {
@ObservedObject var viewModel: SearchLocationViewModel

init(viewModel: SearchLocationViewModel) {
self.viewModel = viewModel
}
@StateObject var viewModel: SearchLocationViewModel

var body: some View {
viewModel.view { content in
Expand Down
29 changes: 17 additions & 12 deletions OpenWeather/App/Search/SearchLocationViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,23 +86,28 @@ final class SearchLocationViewModel: ViewModel<
}
}

private func getLocations(searchText: String) {
searchTask?.cancel()
private func getLocations(searchText: String) {
searchTask?.cancel()

searchTask = Task {
do {
guard searchTask?.isCancelled == false else { return }
guard searchText.isEmpty == false else {
self.result = []
return
}

searchTask = Task {
do {
guard searchTask?.isCancelled == false else { return }

let result = try await capabilities.getLocations(query: searchText)
let result = try await capabilities.getLocations(query: searchText)

guard searchTask?.isCancelled == false else { return }
guard searchTask?.isCancelled == false else { return }

await MainActor.run {
self.result = result
}
} catch {
errorHandler.handle(error: error)
await MainActor.run {
self.result = result
}
} catch {
errorHandler.handle(error: error)
}
}
}
}
devahmedshendy marked this conversation as resolved.
Show resolved Hide resolved
8 changes: 2 additions & 6 deletions OpenWeather/App/Summary/SummaryScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,9 @@ import SwiftUI
import CoreLocation

struct SummaryScreen: View {
private let locations: [CLLocation] = [.london, .cairo, .newYork]
@StateObject var viewModel: SummaryViewModel

@ObservedObject var viewModel: SummaryViewModel

init(viewModel: SummaryViewModel) {
self.viewModel = viewModel
}
private let locations: [CLLocation] = [.london]

var body: some View {
viewModel.view { content in
Expand Down
8 changes: 4 additions & 4 deletions OpenWeather/App/Summary/SummaryViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@ final class SummaryViewModel: ViewModel<
struct Capabilities {
static var mock: Capabilities {
.init(
weatherProviding: MockWeatherProvider(),
locationProviding: MockLocationProvider()
locationProviding: MockLocationProvider(),
weatherProviding: MockWeatherProvider()
)
}

private var weatherProviding: WeatherProviding
private var locationProviding: LocationProviding

init(weatherProviding: WeatherProviding, locationProviding: LocationProviding) {
self.weatherProviding = weatherProviding
init(locationProviding: LocationProviding, weatherProviding: WeatherProviding) {
self.locationProviding = locationProviding
self.weatherProviding = weatherProviding
}

func locationName(for location: CLLocation) async throws -> String {
Expand Down
2 changes: 1 addition & 1 deletion OpenWeather/Data/Adapters/LocationAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation
import CoreLocation

enum LocationAdapter: Adaptable {
static func device(from: NetworkLocation) -> DeviceLocation {
static func device(from: LocationData) -> DeviceLocation {
return DeviceLocation(
name: from.name,
location: from.location
Expand Down
3 changes: 3 additions & 0 deletions OpenWeather/Data/Device/DeviceLocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ struct DeviceLocation: Identifiable {
let id = UUID()
let name: String
let location: CLLocation

var lat: Double { location.coordinate.latitude }
var long: Double { location.coordinate.longitude }
}
43 changes: 43 additions & 0 deletions OpenWeather/Data/Network/LocationData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// OpenWeather template generated by OpenBytes on 22/03/2023.
//
// Created by Ahmed Shendy.
// LocationData.swift
//

import Foundation
import CoreLocation
import CryptoKit

struct LocationData {
let name: String
let location: CLLocation

var lat: Double { location.coordinate.latitude }
var long: Double { location.coordinate.longitude }

init(name: String, location: CLLocation) {
self.name = name
self.location = location
}
}

extension LocationData: CustomStringConvertible {
var description: String {
"""
{
name: \(name),
latitude: \(lat),
longitude: \(long)
}
"""
devahmedshendy marked this conversation as resolved.
Show resolved Hide resolved
}
}

extension LocationData: Equatable {
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.name == rhs.name
&& lhs.lat == rhs.lat
&& lhs.long == rhs.long
}
}
14 changes: 0 additions & 14 deletions OpenWeather/Data/Network/NetworkLocation.swift

This file was deleted.

4 changes: 4 additions & 0 deletions OpenWeather/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app requires access to your location at all times to provide you with accurate weather forecasts and alerts for your area.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app requires access to your location while you are using it to provide you with accurate weather forecasts and alerts for your area.</string>
<key>UILaunchScreen</key>
<array>
<dict>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,41 +18,14 @@ struct LocationProvider: LocationProviding {
}

private func locationName(for location: CLLocation) async throws -> LocationNameResponse {
return try await withCheckedThrowingContinuation { continuation in
locationName(
for: location,
completion: { placemark, error in
if let error = error {
continuation.resume(
throwing: LocationProvidingError.failure(reason: error.localizedDescription)
)
} else {
if let locationName = placemark?.name {
continuation.resume(
returning: LocationNameResponse(name: locationName)
)
} else {
continuation.resume(
throwing: LocationProvidingError.noSuchPlace
)
}
}
}
)
}
}
let placemarks = try await geocoder.reverseGeocodeLocation(location)

private func locationName(
for location: CLLocation,
completion: @escaping (CLPlacemark?, Error?) -> Void
) {
geocoder.reverseGeocodeLocation(location) { placemarks, error in
if let error = error as? NSError {
completion(nil, error)
} else {
completion(placemarks?.first, nil)
}
guard let name = placemarks.first?.name
else {
throw LocationProvidingError.noSuchPlace
}

return LocationNameResponse(name: name)
}

func locations(for address: String) async throws -> [DeviceLocation] {
Expand All @@ -71,14 +44,14 @@ struct LocationProvider: LocationProviding {
throwing: LocationProvidingError.failure(reason: error.localizedDescription)
)
} else {
let result: [NetworkLocation] = placemarks
.compactMap { placemark -> NetworkLocation? in
let result: [LocationData] = placemarks
.compactMap { placemark -> LocationData? in
guard
let name = placemark.name,
let location = placemark.location
else { return nil }

return NetworkLocation(
return LocationData(
name: name,
location: location
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ struct LocationNameResponse {
}

struct LocationSearchResponse {
let result: [NetworkLocation]
let result: [LocationData]
}
Loading