Skip to content
This repository has been archived by the owner on Aug 11, 2024. It is now read-only.

Commit

Permalink
Implement Modal Editor Sheet (#1141)
Browse files Browse the repository at this point in the history
  • Loading branch information
bfollington authored Mar 27, 2024
1 parent c5761ec commit e64cb55
Show file tree
Hide file tree
Showing 25 changed files with 755 additions and 45 deletions.
2 changes: 2 additions & 0 deletions xcode/Subconscious/Shared/AppTheme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ extension AppTheme {
static let lineHeight: CGFloat = 24
static let fabSize: CGFloat = 56
static let minTouchSize: CGFloat = 44
static let comfortableTouchSize: CGFloat = tightPadding + minTouchSize
static let minGradientMaskSize: CGFloat = 96
static let cornerRadiusSm: Double = 4
static let cornerRadius: Double = 8
static let cornerRadiusLg: Double = 16
Expand Down
96 changes: 89 additions & 7 deletions xcode/Subconscious/Shared/Components/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ struct AppView: View {
)
)
@Environment(\.scenePhase) private var scenePhase: ScenePhase
@Namespace var namespace

var body: some View {
ZStack {
AppTabView(store: store)
.zIndex(0)
.disabled(store.state.editorSheet.presented)

if !store.state.isAppUpgraded {
AppUpgradeView(
Expand All @@ -52,6 +54,18 @@ struct AppView: View {
)
.zIndex(1)
}

if store.state.isModalEditorEnabled {
if let _ = store.state.editorSheet.item,
let namespace = store.state.namespace {
EditorModalSheetView(app: store, namespace: namespace)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(.top, AppTheme.comfortableTouchSize)
.ignoresSafeArea(.all)
.shadow(style: .editorSheet)
.zIndex(3)
}
}
}
.sheet(
isPresented: Binding(
Expand All @@ -78,7 +92,7 @@ struct AppView: View {
}
.onAppear {
SentryIntegration.start()
store.send(.appear)
store.send(.appear(namespace: namespace))
}
// Track changes to scene phase so we know when app gets
// foregrounded/backgrounded.
Expand Down Expand Up @@ -118,13 +132,14 @@ enum AppAction: Hashable {
case gatewayURLField(GatewayUrlFormField.Action)
case recoveryMode(RecoveryModeModel.Action)
case toastStack(ToastStackAction)
case editorSheet(EditorModalSheetAction)

/// Scene phase events
/// See https://developer.apple.com/documentation/swiftui/scenephase
case scenePhaseChange(ScenePhase)

/// On view appear
case appear
case appear(namespace: Namespace.ID)

case setAppUpgraded(_ isUpgraded: Bool)

Expand Down Expand Up @@ -168,6 +183,8 @@ enum AppAction: Hashable {

/// Set and persist experimental block editor enabled
case persistBlockEditorEnabled(Bool)
/// Set and persist experimental modal editor sheet enabled
case persistModalEditorEnabled(Bool)
case persistNoosphereLogLevel(Noosphere.NoosphereLogLevel)
case persistAiFeaturesEnabled(Bool)
case persistPreferredLlm(String)
Expand Down Expand Up @@ -539,6 +556,28 @@ struct ToastStackCursor: CursorProtocol {
}
}

struct EditorModalSheetCursor: CursorProtocol {
typealias Model = AppModel
typealias ViewModel = EditorModalSheetModel

static func get(state: Model) -> ViewModel {
state.editorSheet
}

static func set(state: Model, inner: ViewModel) -> Model {
var model = state
model.editorSheet = inner
return model
}

static func tag(_ action: ViewModel.Action) -> Model.Action {
switch action {
default:
return .editorSheet(action)
}
}
}

enum AppDatabaseState {
case initial
case migrating
Expand Down Expand Up @@ -574,7 +613,8 @@ struct AppModel: ModelProtocol {
var isFirstRunComplete = false
var firstRunPath: [FirstRunStep] = []

var toastStack: ToastStackModel = ToastStackModel()
var toastStack = ToastStackModel()
var editorSheet = EditorModalSheetModel()

/// Should first run show?
var shouldPresentFirstRun: Bool {
Expand All @@ -583,6 +623,7 @@ struct AppModel: ModelProtocol {

/// Is experimental block editor enabled?
var isBlockEditorEnabled = false
var isModalEditorEnabled = false
var noosphereLogLevel: Noosphere.NoosphereLogLevel = .basic
var areAiFeaturesEnabled = false
var openAiApiKey = OpenAIKey(key: "sk-")
Expand Down Expand Up @@ -679,6 +720,7 @@ struct AppModel: ModelProtocol {
}

var selectedAppTab: AppTab = .notebook
var namespace: Namespace.ID? = nil

// Logger for actions
static let logger = Logger(
Expand Down Expand Up @@ -747,16 +789,23 @@ struct AppModel: ModelProtocol {
action: action,
environment: environment
)
case .editorSheet(let action):
return EditorModalSheetCursor.update(
state: state,
action: action,
environment: environment
)
case .scenePhaseChange(let scenePhase):
return scenePhaseChange(
state: state,
environment: environment,
scenePhase: scenePhase
)
case .appear:
case let .appear(namespace):
return appear(
state: state,
environment: environment
environment: environment,
namespace: namespace
)
case let .setFirstRunPath(path):
return setFirstRunPath(
Expand Down Expand Up @@ -942,6 +991,12 @@ struct AppModel: ModelProtocol {
environment: environment,
llm: llm
)
case let .persistModalEditorEnabled(isModalEditorEnabled):
return persistModalEditorEnabled(
state: state,
environment: environment,
isModalEditorEnabled: isModalEditorEnabled
)
case let .persistNoosphereLogLevel(level):
return persistNoosphereLogLevel(
state: state,
Expand Down Expand Up @@ -1481,6 +1536,8 @@ struct AppModel: ModelProtocol {
return .loadOpenAIKey(OpenAIKey(key: key))
}.eraseToAnyPublisher()

model.isModalEditorEnabled = AppDefaults.standard.isModalEditorEnabled

// Update model from app defaults
return update(
state: model,
Expand Down Expand Up @@ -1520,7 +1577,8 @@ struct AppModel: ModelProtocol {

static func appear(
state: AppModel,
environment: AppEnvironment
environment: AppEnvironment,
namespace: Namespace.ID
) -> Update<AppModel> {
let sphereIdentity = state.sphereIdentity?.description ?? "nil"
logger.debug(
Expand All @@ -1531,8 +1589,12 @@ struct AppModel: ModelProtocol {
"sphereIdentity": sphereIdentity
]
)

var model = state
model.namespace = namespace

return update(
state: state,
state: model,
actions: [
.migrateDatabase,
.refreshSphereVersion,
Expand Down Expand Up @@ -1907,6 +1969,18 @@ struct AppModel: ModelProtocol {
return Update(state: state).mergeFx(fx)
}

static func persistModalEditorEnabled(
state: AppModel,
environment: AppEnvironment,
isModalEditorEnabled: Bool
) -> Update<AppModel> {
// Persist value
AppDefaults.standard.isModalEditorEnabled = isModalEditorEnabled
var model = state
model.isModalEditorEnabled = isModalEditorEnabled
return Update(state: model)
}

static func persistNoosphereLogLevel(
state: AppModel,
environment: AppEnvironment,
Expand Down Expand Up @@ -3082,6 +3156,9 @@ struct AppModel: ModelProtocol {
}
.eraseToAnyPublisher()

environment.feedback.prepare()
environment.feedback.impactOccurred()

// MUST be dispatched as an fx so that it will appear on the `store.actions` stream
// Which is consumed and replayed on the FeedStore and NotebookStore etc.
return Update(state: state, fx: fx)
Expand All @@ -3090,6 +3167,8 @@ struct AppModel: ModelProtocol {
var model = state
model.selectedAppTab = tab
AppDefaults.standard.selectedAppTab = tab.rawValue
environment.selectionFeedback.prepare()
environment.selectionFeedback.selectionChanged()

return Update(state: model)
}
Expand Down Expand Up @@ -3413,6 +3492,9 @@ struct AppEnvironment {

/// Service for generating creative prompts and oblique strategies
var prompt = PromptService.default

var feedback = UIImpactFeedbackGenerator()
var selectionFeedback = UISelectionFeedbackGenerator()

/// Create a long polling publisher that never completes
static func poll(every interval: Double) -> AnyPublisher<Date, Never> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import SwiftUI

struct AudienceMenuButtonView: View {
@ScaledMetric(relativeTo: .caption)
private var width: CGFloat = 140
private var width: CGFloat = 100

@Binding var audience: Audience

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ struct MenuButtonView<Icon: View>: View {
.font(.system(size: iconSize))
.foregroundColor(Color.secondary)
}
.foregroundColor(Color.accentColor)
.lineLimit(1)
.frame(height: height)
.padding(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,22 @@ import SwiftUI
/// Sight-matched in Figma to close button from Apple Notes.
struct CloseButtonView: View {
var action: () -> Void

@Environment(\.colorScheme) var colorScheme

var body: some View {
Button(
action: action,
label: {
Image(systemName: "xmark")
.font(.system(size: 16, weight: .semibold))
Circle()
.foregroundStyle(Color.secondaryBackground.opacity(0.7))
.frame(width: 30, height: 30)
.foregroundColor(.secondary)
.background(Color.secondaryBackground)
.clipShape(Circle())
.overlay(
Image(systemName: "xmark")
.font(.system(size: 16, weight: .semibold))
.foregroundColor(.secondary)
)
.blendMode(colorScheme == .light ? .plusDarker : .plusLighter)
.frame(
width: AppTheme.minTouchSize,
height: AppTheme.minTouchSize
Expand All @@ -30,6 +35,7 @@ struct CloseButtonView: View {
.accessibility(hint: Text("Tap to close"))
}
)
.frame(width: 30, height: 30)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ struct EntryListView: View {
var onRefresh: () -> Void

var notify: (EntryNotification) -> Void
var namespace: Namespace.ID
var editingInSheet: Bool = false

@Environment(\.colorScheme) var colorScheme
static let resetScrollTargetId: Int = 0
Expand Down Expand Up @@ -58,6 +60,11 @@ struct EntryListView: View {
color: entry.color
)
)
.matchedGeometryEffect(
id: entry.id,
in: namespace,
isSource: !editingInSheet
)
.modifier(RowViewModifier())
.swipeActions(
edge: .trailing,
Expand Down Expand Up @@ -174,7 +181,8 @@ struct EntryListView_Previews: PreviewProvider {
)
],
onRefresh: {},
notify: { _ in }
notify: { _ in },
namespace: Namespace().wrappedValue
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ struct SearchModel: ModelProtocol {
// MARK: View
struct SearchView: View {
var store: ViewStore<SearchModel>
var suggestionHeight: CGFloat = 56
var suggestionHeight: CGFloat = AppTheme.comfortableTouchSize

var body: some View {
VStack(alignment: .leading, spacing: 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import SwiftUI
/// It sets the basic list styles we use for suggestions.
/// Apply it to the "row" view which is an immediate child of `List`.
struct SuggestionViewModifier: ViewModifier {
var height: CGFloat = 56
var height: CGFloat = AppTheme.comfortableTouchSize
var insets: EdgeInsets = EdgeInsets(
top: 0,
leading: AppTheme.tightPadding,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import SwiftUI
struct TruncateWithGradientViewModifier: ViewModifier {
var maxHeight: CGFloat

private static let maxGradientHeight: CGFloat = 80
private static let maxGradientHeight: CGFloat = AppTheme.minGradientMaskSize

func body(content: Content) -> some View {
content
Expand Down
8 changes: 7 additions & 1 deletion xcode/Subconscious/Shared/Components/Deck/DeckTheme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ extension DeckTheme {
dampingFraction: 0.5,
blendDuration: 0
)
static let friendlySpring: Animation = .spring(
response: 0.3,
dampingFraction: 0.85,
blendDuration: 0.1
)

static let dragTargetSize = CGSize(width: 16, height: 380)

Expand Down Expand Up @@ -143,7 +148,8 @@ extension String {

extension Slashlink {
var themeColor: ThemeColor {
description.themeColor
// by using the slug we ensure colors match across namespaces (AKA users)
slug.description.themeColor
}
}

Expand Down
Loading

0 comments on commit e64cb55

Please sign in to comment.