Merge pull request #578 from omnivore-app/feature/horizontal-loading-indicator

Shimmering  loading indicator [iOS]
This commit is contained in:
Satindar Dhillon
2022-05-10 11:08:43 -07:00
committed by GitHub
7 changed files with 98 additions and 21 deletions

View File

@ -13,7 +13,8 @@ struct FeedCardNavigationLink: View {
var body: some View {
let destination = LinkItemDetailView(viewModel: LinkItemDetailViewModel(item: item, homeFeedViewModel: viewModel))
#if os(iOS)
let modifiedDestination = destination.navigationBarHidden(true)
let modifiedDestination = destination
.navigationTitle("")
#else
let modifiedDestination = destination
#endif
@ -51,7 +52,8 @@ struct GridCardNavigationLink: View {
var body: some View {
let destination = LinkItemDetailView(viewModel: LinkItemDetailViewModel(item: item, homeFeedViewModel: viewModel))
#if os(iOS)
let modifiedDestination = destination.navigationBarHidden(true)
let modifiedDestination = destination
.navigationTitle("")
#else
let modifiedDestination = destination
#endif

View File

@ -27,8 +27,7 @@ private let enableGrid = UIDevice.isIPad || FeatureFlag.enableGridCardsOnPhone
loadItems(isRefresh: true)
}
.searchable(
text: $viewModel.searchTerm,
placement: .navigationBarDrawer
text: $viewModel.searchTerm
) {
if viewModel.searchTerm.isEmpty {
Text("Inbox").searchCompletion("in:inbox ")
@ -136,23 +135,28 @@ private let enableGrid = UIDevice.isIPad || FeatureFlag.enableGridCardsOnPhone
var body: some View {
VStack(spacing: 0) {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
TextChipButton.makeAddLabelButton {
showLabelsSheet = true
ZStack(alignment: .bottom) {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
TextChipButton.makeAddLabelButton {
showLabelsSheet = true
}
ForEach(viewModel.selectedLabels, id: \.self) { label in
TextChipButton.makeRemovableLabelButton(feedItemLabel: label) {
viewModel.selectedLabels.removeAll { $0.id == label.id }
}
}
Spacer()
}
ForEach(viewModel.selectedLabels, id: \.self) { label in
TextChipButton.makeRemovableLabelButton(feedItemLabel: label) {
viewModel.selectedLabels.removeAll { $0.id == label.id }
.padding(.horizontal)
.sheet(isPresented: $showLabelsSheet) {
ApplyLabelsView(mode: .list(viewModel.selectedLabels)) {
viewModel.selectedLabels = $0
}
}
Spacer()
}
.padding(.horizontal)
.sheet(isPresented: $showLabelsSheet) {
ApplyLabelsView(mode: .list(viewModel.selectedLabels)) {
viewModel.selectedLabels = $0
}
if viewModel.showLoadingBar {
ShimmeringLoader()
}
}
if prefersListLayout || !enableGrid {

View File

@ -17,6 +17,7 @@ import Views
@Published var snoozePresented = false
@Published var itemToSnoozeID: String?
@Published var selectedLinkItem: LinkedItem?
@Published var showLoadingBar = false
var cursor: String?
@ -47,6 +48,7 @@ import Views
searchIdx += 1
isLoading = true
showLoadingBar = true
// Cache the viewer
if dataService.currentViewer == nil {
@ -81,6 +83,7 @@ import Views
receivedIdx = thisSearchIdx
cursor = queryResult.cursor
await dataService.prefetchPages(itemIDs: newItems.map(\.unwrappedID))
showLoadingBar = false
} else if searchTermIsEmpty {
await dataService.viewContext.perform {
let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
@ -94,6 +97,7 @@ import Views
self.isLoading = false
}
}
showLoadingBar = false
}
}

View File

@ -9,7 +9,7 @@ struct HomeView: View {
NavigationView {
HomeFeedContainerView(viewModel: viewModel)
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationViewStyle(.stack)
.accentColor(.appGrayTextContrast)
} else {
HomeFeedContainerView(viewModel: viewModel)

View File

@ -136,14 +136,27 @@ struct LinkItemDetailView: View {
)
}
// We always want this hidden but setting it to false initially
// fixes a bug where SwiftUI searchable will always show the nav bar
// if the search field is active when pushing.
@State var hideNavBar = false
var body: some View {
#if os(iOS)
if viewModel.item.isPDF {
fixedNavBarReader
.task { viewModel.trackReadEvent() }
.navigationBarHidden(hideNavBar)
.task {
hideNavBar = true
viewModel.trackReadEvent()
}
} else {
WebReaderContainerView(item: viewModel.item, homeFeedViewModel: viewModel.homeFeedViewModel)
.task { viewModel.trackReadEvent() }
.navigationBarHidden(hideNavBar)
.task {
hideNavBar = true
viewModel.trackReadEvent()
}
}
#else
fixedNavBarReader

View File

@ -50,7 +50,7 @@ public struct FeedCard: View {
if case let AsyncImageStatus.loaded(image) = imageStatus {
image
.resizable()
.aspectRatio(1, contentMode: .fill)
.aspectRatio(contentMode: .fill)
.frame(width: 80, height: 80)
.cornerRadius(6)
} else if case AsyncImageStatus.loading = imageStatus {

View File

@ -0,0 +1,54 @@
import SwiftUI
public struct ShimmeringLoader: View {
@State private var phase: CGFloat = 0
public init() {}
public var body: some View {
ZStack {
Color.systemBackground
Color.appGraySolid
.contentShape(Rectangle())
.modifier(AnimatedMask(phase: phase).animation(
Animation.linear(duration: 2.0)
.repeatForever(autoreverses: false)
))
.onAppear { phase = 0.8 }
}
.frame(height: 2)
.frame(maxWidth: .infinity)
}
/// An animatable modifier to interpolate between `phase` values.
struct AnimatedMask: AnimatableModifier {
var phase: CGFloat = 0
var animatableData: CGFloat {
get { phase }
set { phase = newValue }
}
func body(content: Content) -> some View {
content
.mask(GradientMask(phase: phase).scaleEffect(3))
}
}
/// An animatable gradient between transparent and opaque to use as mask.
/// The `phase` parameter shifts the gradient, moving the opaque band.
struct GradientMask: View {
let phase: CGFloat
let centerColor = Color.appGraySolid
let edgeColor = Color.clear
var body: some View {
LinearGradient(gradient:
Gradient(stops: [
.init(color: edgeColor, location: phase),
.init(color: centerColor, location: phase + 0.1),
.init(color: edgeColor, location: phase + 0.2)
]), startPoint: .leading, endPoint: .trailing)
}
}
}