Skip to content

Commit 3a1f4c3

Browse files
committed
Update circular widget implementation
1 parent 12ff573 commit 3a1f4c3

File tree

3 files changed

+86
-84
lines changed

3 files changed

+86
-84
lines changed

Core/Sources/SuggestionWidget/FeatureReducers/CircularWidgetFeature.swift

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ public struct CircularWidgetFeature: ReducerProtocol {
1616
var isContentEmpty: Bool
1717
var isChatPanelDetached: Bool
1818
var isChatOpen: Bool
19-
var animationProgress: Double = 0
2019
}
2120

2221
public enum Action: Equatable {
@@ -27,7 +26,6 @@ public struct CircularWidgetFeature: ReducerProtocol {
2726
case markIsProcessing
2827
case endIsProcessing
2928
case _forceEndIsProcessing
30-
case _refreshRing
3129
}
3230

3331
struct CancelAutoEndIsProcessKey: Hashable {}
@@ -75,14 +73,6 @@ public struct CircularWidgetFeature: ReducerProtocol {
7573
state.isProcessingCounters.removeAll()
7674
state.isProcessing = false
7775
return .none
78-
79-
case ._refreshRing:
80-
if state.isProcessing {
81-
state.animationProgress = 1 - state.animationProgress
82-
} else {
83-
state.animationProgress = state.isContentEmpty ? 0 : 1
84-
}
85-
return .none
8676
}
8777
}
8878
}

Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ public struct WidgetFeature: ReducerProtocol {
3939
public struct CircularWidgetState: Equatable {
4040
var isProcessingCounters = [CircularWidgetFeature.IsProcessingCounter]()
4141
var isProcessing: Bool = false
42-
var animationProgress: Double = 0
4342
}
4443

4544
public var circularWidgetState = CircularWidgetState()
@@ -67,15 +66,13 @@ public struct WidgetFeature: ReducerProtocol {
6766
isContentEmpty: chatPanelState.chatTabGroup.tabInfo.isEmpty
6867
&& panelState.sharedPanelState.isEmpty,
6968
isChatPanelDetached: chatPanelState.chatPanelInASeparateWindow,
70-
isChatOpen: chatPanelState.isPanelDisplayed,
71-
animationProgress: circularWidgetState.animationProgress
69+
isChatOpen: chatPanelState.isPanelDisplayed
7270
)
7371
}
7472
set {
7573
circularWidgetState = .init(
7674
isProcessingCounters: newValue.isProcessingCounters,
77-
isProcessing: newValue.isProcessing,
78-
animationProgress: newValue.animationProgress
75+
isProcessing: newValue.isProcessing
7976
)
8077
}
8178
}

Core/Sources/SuggestionWidget/WidgetView.swift

Lines changed: 84 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ struct WidgetView: View {
2121
store.send(.widgetClicked)
2222
}
2323
}
24-
.overlay { overlayCircle }
24+
.overlay { WidgetAnimatedCircle(store: store) }
2525
.onHover { yes in
2626
withAnimation(.easeInOut(duration: 0.2)) {
2727
isHovering = yes
@@ -40,86 +40,101 @@ struct WidgetView: View {
4040
)
4141
}
4242
}
43+
}
44+
45+
struct WidgetAnimatedCircle: View {
46+
let store: StoreOf<CircularWidgetFeature>
47+
@State var processingProgress: Double = 0
4348

4449
struct OverlayCircleState: Equatable {
4550
var isProcessing: Bool
4651
var isContentEmpty: Bool
4752
}
4853

49-
@ViewBuilder var overlayCircle: some View {
50-
WithViewStore(store, observe: { $0.animationProgress }) { viewStore in
51-
let processingProgress = viewStore.state
52-
let minimumLineWidth: Double = 3
53-
let lineWidth = (1 - processingProgress) *
54-
(Style.widgetWidth - minimumLineWidth / 2) + minimumLineWidth
55-
let scale = max(processingProgress * 1, 0.0001)
56-
ZStack {
57-
Circle()
58-
.stroke(
59-
Color(nsColor: .darkGray),
60-
style: .init(lineWidth: minimumLineWidth)
61-
)
62-
.padding(minimumLineWidth / 2)
54+
var body: some View {
55+
let minimumLineWidth: Double = 3
56+
let lineWidth = (1 - processingProgress) *
57+
(Style.widgetWidth - minimumLineWidth / 2) + minimumLineWidth
58+
let scale = max(processingProgress * 1, 0.0001)
59+
ZStack {
60+
Circle()
61+
.stroke(
62+
Color(nsColor: .darkGray),
63+
style: .init(lineWidth: minimumLineWidth)
64+
)
65+
.padding(minimumLineWidth / 2)
6366

64-
// how do I stop the repeatForever animation without removing the view?
65-
// I tried many solutions found on stackoverflow but non of them works.
66-
WithViewStore(
67-
store,
68-
observe: {
69-
OverlayCircleState(
70-
isProcessing: $0.isProcessing,
71-
isContentEmpty: $0.isContentEmpty
72-
)
73-
}
74-
) { viewStore in
75-
Group {
76-
if viewStore.isProcessing {
77-
Circle()
78-
.stroke(
79-
Color.accentColor,
80-
style: .init(lineWidth: lineWidth)
81-
)
82-
.padding(minimumLineWidth / 2)
83-
.scaleEffect(x: scale, y: scale)
84-
.opacity(
85-
!viewStore.isContentEmpty || viewStore
86-
.isProcessing ? 1 : 0
87-
)
88-
.animation(
89-
featureFlag: \.animationCCrashSuggestion,
90-
.easeInOut(duration: 1)
91-
.repeatForever(autoreverses: true),
92-
value: processingProgress
93-
)
94-
} else {
95-
Circle()
96-
.stroke(
97-
Color.accentColor,
98-
style: .init(lineWidth: lineWidth)
99-
)
100-
.padding(minimumLineWidth / 2)
101-
.scaleEffect(x: scale, y: scale)
102-
.opacity(
103-
!viewStore.isContentEmpty || viewStore
104-
.isProcessing ? 1 : 0
105-
)
106-
.animation(
107-
featureFlag: \.animationCCrashSuggestion,
108-
.easeInOut(duration: 1),
109-
value: processingProgress
110-
)
111-
}
112-
}
113-
.onChange(of: viewStore.isProcessing) { _ in
114-
viewStore.send(._refreshRing)
115-
}
116-
.onChange(of: viewStore.isContentEmpty) { _ in
117-
viewStore.send(._refreshRing)
67+
// how do I stop the repeatForever animation without removing the view?
68+
// I tried many solutions found on stackoverflow but non of them works.
69+
WithViewStore(
70+
store,
71+
observe: {
72+
OverlayCircleState(
73+
isProcessing: $0.isProcessing,
74+
isContentEmpty: $0.isContentEmpty
75+
)
76+
}
77+
) { viewStore in
78+
Group {
79+
if viewStore.isProcessing {
80+
Circle()
81+
.stroke(
82+
Color.accentColor,
83+
style: .init(lineWidth: lineWidth)
84+
)
85+
.padding(minimumLineWidth / 2)
86+
.scaleEffect(x: scale, y: scale)
87+
.opacity(
88+
!viewStore.isContentEmpty || viewStore.isProcessing ? 1 : 0
89+
)
90+
.animation(
91+
featureFlag: \.animationCCrashSuggestion,
92+
.easeInOut(duration: 1)
93+
.repeatForever(autoreverses: true),
94+
value: processingProgress
95+
)
96+
} else {
97+
Circle()
98+
.stroke(
99+
Color.accentColor,
100+
style: .init(lineWidth: lineWidth)
101+
)
102+
.padding(minimumLineWidth / 2)
103+
.scaleEffect(x: scale, y: scale)
104+
.opacity(
105+
!viewStore.isContentEmpty || viewStore
106+
.isProcessing ? 1 : 0
107+
)
108+
.animation(
109+
featureFlag: \.animationCCrashSuggestion,
110+
.easeInOut(duration: 1),
111+
value: processingProgress
112+
)
118113
}
119114
}
115+
.onChange(of: viewStore.isProcessing) { _ in
116+
refreshRing(
117+
isProcessing: viewStore.state.isProcessing,
118+
isContentEmpty: viewStore.state.isContentEmpty
119+
)
120+
}
121+
.onChange(of: viewStore.isContentEmpty) { _ in
122+
refreshRing(
123+
isProcessing: viewStore.state.isProcessing,
124+
isContentEmpty: viewStore.state.isContentEmpty
125+
)
126+
}
120127
}
121128
}
122129
}
130+
131+
func refreshRing(isProcessing: Bool, isContentEmpty: Bool) {
132+
if isProcessing {
133+
processingProgress = 1 - processingProgress
134+
} else {
135+
processingProgress = isContentEmpty ? 0 : 1
136+
}
137+
}
123138
}
124139

125140
struct WidgetContextMenu: View {

0 commit comments

Comments
 (0)