ios: fix pull to refresh
This commit is contained in:
@@ -159,10 +159,7 @@ struct SybilSidebarItemList: View {
|
|||||||
.padding(10)
|
.padding(10)
|
||||||
}
|
}
|
||||||
.refreshable {
|
.refreshable {
|
||||||
await viewModel.refreshVisibleContent(
|
await viewModel.refreshSidebarCollectionsFromPullToRefresh()
|
||||||
refreshCollections: true,
|
|
||||||
refreshSelection: false
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -911,6 +911,23 @@ final class SybilViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func refreshSidebarCollectionsFromPullToRefresh() async {
|
||||||
|
guard isAuthenticated, !isCheckingSession else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SybilLog.info(
|
||||||
|
SybilLog.ui,
|
||||||
|
"Sidebar pull-to-refresh requested"
|
||||||
|
)
|
||||||
|
|
||||||
|
let preferredSelection = selectedItem
|
||||||
|
let refreshTask = Task { @MainActor in
|
||||||
|
await refreshCollections(preferredSelection: preferredSelection, refreshSelection: false)
|
||||||
|
}
|
||||||
|
await refreshTask.value
|
||||||
|
}
|
||||||
|
|
||||||
func sendComposer() async {
|
func sendComposer() async {
|
||||||
let content = composer.trimmingCharacters(in: .whitespacesAndNewlines)
|
let content = composer.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
let attachments = composerAttachments
|
let attachments = composerAttachments
|
||||||
@@ -1276,7 +1293,9 @@ final class SybilViewModel {
|
|||||||
attachToVisibleActiveRunIfNeeded()
|
attachToVisibleActiveRunIfNeeded()
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
if shouldSuppressInactiveTransportError(error) {
|
if isCancellation(error) {
|
||||||
|
SybilLog.debug(SybilLog.app, "Collection refresh cancelled")
|
||||||
|
} else if shouldSuppressInactiveTransportError(error) {
|
||||||
SybilLog.info(SybilLog.app, "Suppressing collection refresh transport interruption while app is inactive")
|
SybilLog.info(SybilLog.app, "Suppressing collection refresh transport interruption while app is inactive")
|
||||||
} else {
|
} else {
|
||||||
errorMessage = normalizeAPIError(error)
|
errorMessage = normalizeAPIError(error)
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ private actor MockSybilClient: SybilAPIClienting {
|
|||||||
private var lastCreateChatCall: ChatCreateCallSnapshot?
|
private var lastCreateChatCall: ChatCreateCallSnapshot?
|
||||||
private var lastCompletionStreamBody: CompletionStreamRequest?
|
private var lastCompletionStreamBody: CompletionStreamRequest?
|
||||||
private var completionStreamEvents: [CompletionStreamEvent]?
|
private var completionStreamEvents: [CompletionStreamEvent]?
|
||||||
|
private var listChatsDelayNanoseconds: UInt64 = 0
|
||||||
|
private var listSearchesDelayNanoseconds: UInt64 = 0
|
||||||
private var getChatDelayNanoseconds: UInt64 = 0
|
private var getChatDelayNanoseconds: UInt64 = 0
|
||||||
private var getSearchDelayNanoseconds: UInt64 = 0
|
private var getSearchDelayNanoseconds: UInt64 = 0
|
||||||
private var completionStreamNetworkErrorMessage: String?
|
private var completionStreamNetworkErrorMessage: String?
|
||||||
@@ -85,6 +87,11 @@ private actor MockSybilClient: SybilAPIClienting {
|
|||||||
completionStreamDelayNanoseconds = delayNanoseconds
|
completionStreamDelayNanoseconds = delayNanoseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setListDelays(chats: UInt64 = 0, searches: UInt64 = 0) {
|
||||||
|
listChatsDelayNanoseconds = chats
|
||||||
|
listSearchesDelayNanoseconds = searches
|
||||||
|
}
|
||||||
|
|
||||||
func setGetChatDelay(_ delayNanoseconds: UInt64) {
|
func setGetChatDelay(_ delayNanoseconds: UInt64) {
|
||||||
getChatDelayNanoseconds = delayNanoseconds
|
getChatDelayNanoseconds = delayNanoseconds
|
||||||
}
|
}
|
||||||
@@ -122,6 +129,9 @@ private actor MockSybilClient: SybilAPIClienting {
|
|||||||
|
|
||||||
func listChats() async throws -> [ChatSummary] {
|
func listChats() async throws -> [ChatSummary] {
|
||||||
snapshot.listChats += 1
|
snapshot.listChats += 1
|
||||||
|
if listChatsDelayNanoseconds > 0 {
|
||||||
|
try await Task.sleep(nanoseconds: listChatsDelayNanoseconds)
|
||||||
|
}
|
||||||
return chatsResponse
|
return chatsResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +175,9 @@ private actor MockSybilClient: SybilAPIClienting {
|
|||||||
|
|
||||||
func listSearches() async throws -> [SearchSummary] {
|
func listSearches() async throws -> [SearchSummary] {
|
||||||
snapshot.listSearches += 1
|
snapshot.listSearches += 1
|
||||||
|
if listSearchesDelayNanoseconds > 0 {
|
||||||
|
try await Task.sleep(nanoseconds: listSearchesDelayNanoseconds)
|
||||||
|
}
|
||||||
return searchesResponse
|
return searchesResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,6 +396,33 @@ private func makeSearchDetail(id: String, date: Date, answer: String) -> SearchD
|
|||||||
#expect(viewModel.selectedItem == .chat("chat-1"))
|
#expect(viewModel.selectedItem == .chat("chat-1"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
@Test func pullToRefreshCompletesWhenRefreshableTaskIsCancelled() async throws {
|
||||||
|
let date = Date(timeIntervalSince1970: 1_700_000_050)
|
||||||
|
let chat = makeChatSummary(id: "chat-cancelled", date: date)
|
||||||
|
let search = makeSearchSummary(id: "search-cancelled", date: date)
|
||||||
|
let client = MockSybilClient(
|
||||||
|
chatsResponse: [chat],
|
||||||
|
searchesResponse: [search]
|
||||||
|
)
|
||||||
|
await client.setListDelays(chats: 50_000_000, searches: 50_000_000)
|
||||||
|
let viewModel = SybilViewModel(settings: testSettings(named: #function)) { _ in client }
|
||||||
|
viewModel.isAuthenticated = true
|
||||||
|
viewModel.isCheckingSession = false
|
||||||
|
|
||||||
|
let refreshTask = Task {
|
||||||
|
await viewModel.refreshSidebarCollectionsFromPullToRefresh()
|
||||||
|
}
|
||||||
|
try await Task.sleep(nanoseconds: 10_000_000)
|
||||||
|
refreshTask.cancel()
|
||||||
|
await refreshTask.value
|
||||||
|
|
||||||
|
#expect(viewModel.errorMessage == nil)
|
||||||
|
#expect(!viewModel.isLoadingCollections)
|
||||||
|
#expect(viewModel.chats.map(\.id) == ["chat-cancelled"])
|
||||||
|
#expect(viewModel.searches.map(\.id) == ["search-cancelled"])
|
||||||
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@Test func foregroundChatRefreshReloadsSelectedTranscript() async throws {
|
@Test func foregroundChatRefreshReloadsSelectedTranscript() async throws {
|
||||||
let date = Date(timeIntervalSince1970: 1_700_000_100)
|
let date = Date(timeIntervalSince1970: 1_700_000_100)
|
||||||
|
|||||||
Reference in New Issue
Block a user