feat: Tab(role: .search) with context-dependent action per tab
Single separated circle in tab bar (like Photos search icon): - Home/Fitness: shows + icon, taps opens food assistant - Reader idle: shows play icon, taps starts auto-scroll - Reader playing: shows pause icon, taps stops auto-scroll Icon updates dynamically via computed actionIcon property. handleActionTap() routes the tap based on selectedTab. After action, selectedTab returns to the previous tab (doesn't stay on the invisible "action" tab). Speed controls still appear as glass capsule overlay when playing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,14 @@ struct MainTabView: View {
|
||||
auth.currentUser?.id != 4
|
||||
}
|
||||
|
||||
// Dynamic icon for the search-role tab based on context
|
||||
private var actionIcon: String {
|
||||
if selectedTab == 2 {
|
||||
return isAutoScrolling ? "pause.fill" : "play.fill"
|
||||
}
|
||||
return "plus"
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
TabView(selection: $selectedTab) {
|
||||
@@ -51,55 +59,37 @@ struct MainTabView: View {
|
||||
ReaderTabView(vm: readerVM, isAutoScrolling: $isAutoScrolling, scrollSpeed: $scrollSpeed)
|
||||
}
|
||||
}
|
||||
|
||||
// Action button — separated circle on trailing side of tab bar
|
||||
// Home/Fitness: quick add food (+)
|
||||
// Reader: play/pause auto-scroll
|
||||
Tab(value: 3, role: .search) {
|
||||
// This view shows briefly when tapped — immediately redirect
|
||||
Color.clear
|
||||
.onAppear {
|
||||
handleActionTap()
|
||||
}
|
||||
} label: {
|
||||
Label("Action", systemImage: actionIcon)
|
||||
}
|
||||
}
|
||||
.tint(Color.accentWarm)
|
||||
.tabBarMinimizeBehavior(.onScrollDown)
|
||||
|
||||
// Floating action button — context-dependent
|
||||
// Feedback button (not on Reader)
|
||||
if selectedTab != 2 {
|
||||
VStack {
|
||||
Spacer()
|
||||
HStack(alignment: .bottom) {
|
||||
if selectedTab != 2 {
|
||||
HStack {
|
||||
FeedbackButton()
|
||||
.padding(.leading, 20)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if selectedTab == 2 && showReader {
|
||||
// Reader: play/pause auto-scroll
|
||||
Button {
|
||||
withAnimation(.spring(duration: 0.3)) {
|
||||
isAutoScrolling.toggle()
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: isAutoScrolling ? "pause.fill" : "play.fill")
|
||||
.font(.system(size: 18))
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 56, height: 56)
|
||||
.background(isAutoScrolling ? Color.red.opacity(0.85) : Color.accentWarm)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 8, y: 4)
|
||||
}
|
||||
.padding(.trailing, 20)
|
||||
} else {
|
||||
// Home/Fitness: FAB (+) for food
|
||||
Button { showAssistant = true } label: {
|
||||
Image(systemName: "plus")
|
||||
.font(.title2.weight(.semibold))
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 56, height: 56)
|
||||
.background(Color.accentWarm)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 8, y: 4)
|
||||
}
|
||||
.padding(.trailing, 20)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 70)
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-scroll speed controls (overlay when playing)
|
||||
// Auto-scroll speed controls (overlay when playing on Reader)
|
||||
if isAutoScrolling && selectedTab == 2 {
|
||||
VStack {
|
||||
Spacer()
|
||||
@@ -130,7 +120,7 @@ struct MainTabView: View {
|
||||
.padding(.vertical, 8)
|
||||
.background(.ultraThinMaterial, in: Capsule())
|
||||
.shadow(color: .black.opacity(0.1), radius: 8, y: 2)
|
||||
.padding(.bottom, 140)
|
||||
.padding(.bottom, 90)
|
||||
.transition(.move(edge: .bottom).combined(with: .opacity))
|
||||
}
|
||||
.animation(.spring(duration: 0.3), value: isAutoScrolling)
|
||||
@@ -157,11 +147,25 @@ struct MainTabView: View {
|
||||
await readerVM.loadInitial()
|
||||
}
|
||||
.onChange(of: selectedTab) { _, newTab in
|
||||
// Stop auto-scroll when leaving Reader
|
||||
if newTab != 2 { isAutoScrolling = false }
|
||||
}
|
||||
}
|
||||
|
||||
private func handleActionTap() {
|
||||
if selectedTab == 2 {
|
||||
// Reader: toggle auto-scroll, stay on Reader
|
||||
withAnimation(.spring(duration: 0.3)) {
|
||||
isAutoScrolling.toggle()
|
||||
}
|
||||
selectedTab = 2 // stay on Reader (don't switch to the "action" tab)
|
||||
} else {
|
||||
// Home/Fitness: open food assistant, return to previous tab
|
||||
let returnTab = selectedTab
|
||||
showAssistant = true
|
||||
selectedTab = returnTab
|
||||
}
|
||||
}
|
||||
|
||||
private func foodAdded() {
|
||||
showAssistant = false
|
||||
selectedTab = 1
|
||||
|
||||
Reference in New Issue
Block a user