Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions Rewind.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 30;
CURRENT_PROJECT_VERSION = 31;
DEVELOPMENT_ASSET_PATHS = "\"Rewind/Preview Content\"";
DEVELOPMENT_TEAM = AGFUBHFQD6;
ENABLE_PREVIEWS = YES;
Expand All @@ -475,7 +475,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
PRODUCT_BUNDLE_IDENTIFIER = chizberg.Rewind;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand All @@ -493,7 +493,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 30;
CURRENT_PROJECT_VERSION = 31;
DEVELOPMENT_ASSET_PATHS = "\"Rewind/Preview Content\"";
DEVELOPMENT_TEAM = AGFUBHFQD6;
ENABLE_PREVIEWS = YES;
Expand All @@ -511,7 +511,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
PRODUCT_BUNDLE_IDENTIFIER = chizberg.Rewind;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand Down
75 changes: 75 additions & 0 deletions Rewind.xcodeproj/xcshareddata/xcschemes/RewindTests.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2640"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "22291C972D50106800F2FEBE"
BuildableName = "RewindTests.xctest"
BlueprintName = "RewindTests"
ReferencedContainer = "container:Rewind.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
queueDebuggingEnableBacktraceRecording = "Yes">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "22291C872D50106600F2FEBE"
BuildableName = "Rewind.app"
BlueprintName = "Rewind"
ReferencedContainer = "container:Rewind.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "22291C872D50106600F2FEBE"
BuildableName = "Rewind.app"
BlueprintName = "Rewind"
ReferencedContainer = "container:Rewind.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
19 changes: 14 additions & 5 deletions Rewind/App/AppGraph.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ import VGSL
final class AppGraph {
let mapControlsStore: MapControlsStore
let floatingMenuStore: FloatingMenu.Store
let rootViewMapStore: RootView.Map.Store
let appStore: AppModel.Store

let onMapLoaded: () -> Void
let selectedImageKind: ObservedVariable<ImageRequestFilters.ImageKind>

let map: Lazy<RewindMap>
let urlOpener: UrlOpener
let imageLoader: ImageLoader
Expand Down Expand Up @@ -83,8 +81,7 @@ final class AppGraph {
action: { .external(.ui($0)) },
)
mapControlsStore = mapStore.makeControlsStore()
onMapLoaded = { mapModelRef?(.external(.ui(.mapViewLoaded))) }
selectedImageKind = mapModel.$state.filters.imageKind.skipRepeats().asObservedVariable()
rootViewMapStore = makeRootMapStore(mapStore: mapStore)
let imageDetailsFactory = { image, source in
makeImageDetailsModel(
modelImage: image,
Expand Down Expand Up @@ -158,6 +155,13 @@ final class AppGraph {
settings.gradientScheme.asObservableVariable().onChange {
appModelRef?(.setGradientScheme($0))
}.dispose(in: disposePool)
ObservableVariable.combineLatest(
mapModel.$state.controls.minimization.skipRepeats(),
mapModel.$state.controls.size.skipRepeats()
).newValues.addObserver { minimization, size in
let hiddenPart = minimization.isMinimized ? mapControlsMinimizedOffset : 0
map.value.updateBottomInset(size.height - hiddenPart)
}.dispose(in: disposePool)
filters.current = mapModel.$state.filters.skipRepeats()

// React to memory warnings by clearing image cache
Expand All @@ -171,6 +175,11 @@ final class AppGraph {
}
}
storeReview.appLaunched()

assert(
map.currentValue == nil,
"the map is loaded too early, this can result in a runtime crash"
)
}

deinit {
Expand Down
3 changes: 1 addition & 2 deletions Rewind/App/RewindApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ struct RewindApp: App {
mapControlsStore: graph.mapControlsStore,
floatingMenuStore: graph.floatingMenuStore,
appStore: graph.appStore,
selectedImageKind: graph.selectedImageKind,
onMapLoaded: graph.onMapLoaded,
mapStore: graph.rootViewMapStore,
)
.environment(\.openURL, OpenURLAction {
graph.urlOpener($0)
Expand Down
51 changes: 41 additions & 10 deletions Rewind/App/RootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,25 @@ import MapKit
import SwiftUI

struct RootView: View {
let rawMap: UIView
enum Map {
struct State {
var selectedImageKind: ImageRequestFilters.ImageKind
}

enum Action {
case mapViewLoaded
case controlsSizeChanged(CGSize)
}

typealias Store = ViewStore<State, Action>
}

let rawMap: UIView
let mapControlsStore: MapControlsStore
let floatingMenuStore: FloatingMenu.Store
let appStore: AppModel.Store

@ObservedVariable
var selectedImageKind: ImageRequestFilters.ImageKind
var onMapLoaded: () -> Void
let appStore: AppModel.Store
let mapStore: Map.Store

enum TransitionSource {
static let settings = "settings"
Expand All @@ -34,7 +44,7 @@ struct RootView: View {
var body: some View {
content
.environment(\.gradientScheme, appStore.gradientScheme)
.environment(\.maxRange, selectedImageKind.maxRange)
.environment(\.maxRange, mapStore.selectedImageKind.maxRange)
}

private var content: some View {
Expand All @@ -45,7 +55,7 @@ struct RootView: View {
.ignoresSafeArea()
.task {
if appStore.onboardingStore == nil {
onMapLoaded()
mapStore(.mapViewLoaded)
}
}

Expand All @@ -58,7 +68,9 @@ struct RootView: View {
namespace: rootView,
hasBottomSafeAreaInset: geometry.safeAreaInsets.bottom > 0,
floatingMenu: { floatingMenu }
)
).readSize {
mapStore(.controlsSizeChanged($0))
}
}.ignoresSafeArea(edges: .bottom)
}
}
Expand Down Expand Up @@ -135,6 +147,26 @@ struct RootView: View {
}
}

func makeRootMapStore(
mapStore: MapViewModel.Store
) -> RootView.Map.Store {
mapStore.bimap(
state: { mapState in
RootView.Map.State(
selectedImageKind: mapState.filters.imageKind
)
},
action: { action in
switch action {
case .mapViewLoaded:
.mapViewLoaded
case let .controlsSizeChanged(size):
.controls(.sizeChanged(size))
}
}
)
}

private let screenRadius = DeviceModel.getCurrent().screenRadius()

extension AppState {
Expand All @@ -156,8 +188,7 @@ extension AppState {
mapControlsStore: graph.mapControlsStore,
floatingMenuStore: graph.floatingMenuStore,
appStore: graph.appStore,
selectedImageKind: graph.selectedImageKind,
onMapLoaded: graph.onMapLoaded,
mapStore: graph.rootViewMapStore
)
}
#endif
2 changes: 1 addition & 1 deletion Rewind/Screens/Map/Controls/FloatingMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ private struct FloatingMenuImpl<Content: View>: View {
view
}
}
.animation(.smooth, value: expandedItems)
.animation(mapControlsAnimation, value: expandedItems)
}
}

Expand Down
11 changes: 9 additions & 2 deletions Rewind/Screens/Map/Controls/MapControls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ struct MapControls<Menu: View>: View {
\.minimization, send: { .controls(.setMinimization($0)) },
),
offset: $offset,
glimpseHeight: 100,
glimpseHeight: glimpseHeight,
pullingProgress: $pullingProgress,
minPullLength: 300,
onPull: { appAction(.imageList(.presentCurrentRegionImages(
Expand All @@ -73,7 +73,7 @@ struct MapControls<Menu: View>: View {
)
}
.animation(
.interactiveSpring(duration: 0.3, extraBounce: 0.1, blendDuration: 1),
mapControlsAnimation,
value: offset,
)
}
Expand Down Expand Up @@ -206,6 +206,12 @@ struct MapControls<Menu: View>: View {

let mapControlsTouchBlockingHeight = glassCardHeight +
makeBottomPadding(hasBottomSafeAreaInset: true)
let mapControlsMinimizedOffset = glassCardHeight - glimpseHeight
let mapControlsAnimation = Animation.interactiveSpring(
duration: 0.5,
extraBounce: 0.1,
blendDuration: 1
)

extension MapViewModel.Store {
func makeControlsStore() -> MapControlsStore {
Expand Down Expand Up @@ -342,6 +348,7 @@ private let containerPadding: CGFloat = 8
private let mapControlRadius: CGFloat = 25
private let glassCardPadding: CGFloat = 20
private let glassCardHeight = thumbnailSize.height + glassCardPadding * 2
private let glimpseHeight: CGFloat = 100
private let glassCardRadius = max(
DeviceModel.getCurrent().screenRadius() - containerPadding,
32,
Expand Down
5 changes: 5 additions & 0 deletions Rewind/Screens/Map/MapModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ struct MapState {
struct ControlsState {
var expandedItems: [FloatingMenu.Item]
var minimization: MinimizationState
var size: CGSize
}

var mapType: MapType
Expand Down Expand Up @@ -47,6 +48,7 @@ enum MapAction {
enum Controls {
case setMinimization(MinimizationState)
case setExpandedItems([FloatingMenu.Item])
case sizeChanged(CGSize)
}

case mapViewLoaded
Expand Down Expand Up @@ -189,6 +191,8 @@ func makeMapModel(
state.controls.minimization = minimization
case let .setExpandedItems(items):
state.controls.expandedItems = items
case let .sizeChanged(size):
state.controls.size = size
}
case .locationButtonTapped:
let locationZoom = 17
Expand Down Expand Up @@ -372,6 +376,7 @@ extension MapState {
controls: ControlsState(
expandedItems: [],
minimization: .normal,
size: .zero,
),
)
}
Expand Down
9 changes: 9 additions & 0 deletions Rewind/Screens/Map/RewindMap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import MapKit
import SwiftUI
import VGSL
Comment on lines 8 to 10

@MainActor
Expand Down Expand Up @@ -77,6 +78,13 @@ final class RewindMap {
map.mapType = mapType.mkMapType
}

func updateBottomInset(_ inset: CGFloat) {
UIView.animate(mapControlsAnimation) {
map.layoutMargins.bottom = inset
map.layoutIfNeeded()
}
}
Comment on lines +81 to +86

private func remove(annotations: [MKAnnotation], completion: @escaping Action) {
animateRemoval(
annotations.compactMap { map.view(for: $0) },
Expand All @@ -103,6 +111,7 @@ private final class RewindMapView: MKMapView, UIGestureRecognizerDelegate {
showsUserLocation = false
isPitchEnabled = false
isRotateEnabled = false
insetsLayoutMarginsFromSafeArea = false

register(
ImageAnnotationView.self,
Expand Down
Loading
Loading