Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions app/Shared/Logic/LogicCall.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,16 @@ func logicCallAsync<Response: SwiftProtobuf.Message>(
requestDispatchQueue: DispatchQueue = .global(qos: .userInitiated),
errorToastModel: ToastModel? = .banner
) async -> Result<Response, LogicError> {
await withCheckedContinuation { (continuation: CheckedContinuation<Result<Response, LogicError>, Never>) in
logicCallAsync(requestValue, requestDispatchQueue: requestDispatchQueue, errorToastModel: errorToastModel) { (res: Response) in
continuation.resume(returning: .success(res))
} onError: { err in
continuation.resume(returning: .failure(err))
await withTaskCancellationHandler {
await withCheckedContinuation { (continuation: CheckedContinuation<Result<Response, LogicError>, Never>) in
logicCallAsync(requestValue, requestDispatchQueue: requestDispatchQueue, errorToastModel: errorToastModel) { (res: Response) in
continuation.resume(returning: .success(res))
} onError: { err in
continuation.resume(returning: .failure(err))
}
}
} onCancel: {
// TODO: cancel the request in rust side
logger.debug("logicCallAsync (async): cancelled")
}
}
137 changes: 70 additions & 67 deletions app/Shared/Models/PagingDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class PagingDataSource<Res: SwiftProtobuf.Message, Item>: ObservableObject {
return pagedItems.sorted { $0.key < $1.key }.map { (page: $0.key, items: $0.value) }
}

@MainActor
private func upsertItems(_ items: some Sequence<Item>, page: Int) {
for item in items {
let id = item[keyPath: id]
Expand All @@ -93,6 +94,7 @@ class PagingDataSource<Res: SwiftProtobuf.Message, Item>: ObservableObject {
}
}

@MainActor
private func replaceItems(_ items: some Sequence<Item>, page: Int) {
if neverRemove == false {
self.items.removeAll()
Expand All @@ -101,16 +103,11 @@ class PagingDataSource<Res: SwiftProtobuf.Message, Item>: ObservableObject {
upsertItems(items, page: page)
}

func loadMore(after: Double = 0.0) {
DispatchQueue.main.asyncAfter(deadline: .now() + after) {
self.loadMore(background: false, alwaysAnimation: true)
}
}

// TODO: `onAppear` works great while `task` seems glitchy
func loadMoreIfNeeded(currentItem: Item) {
if let index = itemToIndexAndPage[currentItem[keyPath: id]]?.index {
let threshold = items.index(items.endIndex, offsetBy: -2)
if index >= threshold { loadMore(background: true) }
let threshold = items.index(items.endIndex, offsetBy: -3)
if index >= threshold { Task { await loadMore(backgroundQueue: true) } }
}
}

Expand All @@ -121,6 +118,7 @@ class PagingDataSource<Res: SwiftProtobuf.Message, Item>: ObservableObject {
latestError = e
}

@MainActor
private func preRefresh(fromPage: Int) -> AsyncRequest.OneOf_Value? {
if isRefreshing || isLoading { return nil }
dataFlowId = UUID()
Expand All @@ -135,6 +133,7 @@ class PagingDataSource<Res: SwiftProtobuf.Message, Item>: ObservableObject {
return request
}

@MainActor
private func onRefreshSuccess(response: Res, animated: Bool, fromPage: Int) {
latestResponse = response
latestError = nil
Expand All @@ -152,6 +151,7 @@ class PagingDataSource<Res: SwiftProtobuf.Message, Item>: ObservableObject {
lastRefreshTime = Date()
}

@MainActor
private func onRefreshError(_ e: LogicError, animated: Bool) {
withAnimation(when: animated) {
self.isRefreshing = false
Expand All @@ -160,99 +160,102 @@ class PagingDataSource<Res: SwiftProtobuf.Message, Item>: ObservableObject {
onError(e)
}

// Sync version for compatibility.
func refresh(animated: Bool = false, silentOnError: Bool = false, fromPage: Int = 1) {
guard let request = preRefresh(fromPage: fromPage) else { return }

logicCallAsync(request, errorToastModel: silentOnError ? nil : .banner) { (response: Res) in
self.onRefreshSuccess(response: response, animated: animated, fromPage: fromPage)
} onError: { e in
self.onRefreshError(e, animated: animated)
}
Task { await refresh(animated: animated, silentOnError: silentOnError, fromPage: fromPage) }
}

func refreshAsync(animated: Bool = false, fromPage: Int = 1) async {
let request = DispatchQueue.main.sync { preRefresh(fromPage: fromPage) }
guard let request else { return }
@MainActor
func refresh(animated: Bool = false, silentOnError: Bool = false, fromPage: Int = 1) async {
guard let request = preRefresh(fromPage: fromPage) else { return }

let response: Result<Res, LogicError> = await logicCallAsync(request)
let response: Result<Res, LogicError> = await logicCallAsync(request, errorToastModel: silentOnError ? nil : .banner)

DispatchQueue.main.sync {
switch response {
case let .success(response):
self.onRefreshSuccess(response: response, animated: animated, fromPage: fromPage)
case let .failure(e):
self.onRefreshError(e, animated: animated)
}
switch response {
case let .success(response):
onRefreshSuccess(response: response, animated: animated, fromPage: fromPage)
case let .failure(e):
onRefreshError(e, animated: animated)
}
}

func initialLoad() {
func initialLoad() async {
if loadedPage == 0, latestError == nil {
refresh(animated: true)
await refresh(animated: true)
}
}

func reloadLastPages(evenIfNotLoaded: Bool) {
func reloadLastPages(evenIfNotLoaded: Bool) async {
for page in [totalPages, totalPages + 1] {
reload(page: page, evenIfNotLoaded: evenIfNotLoaded)
await reload(page: page, evenIfNotLoaded: evenIfNotLoaded)
}
}

func reload(page: Int, evenIfNotLoaded: Bool, animated: Bool = true, after: (() -> Void)? = nil) {
func reload(page: Int, evenIfNotLoaded: Bool, animated: Bool = true) async {
guard page <= loadedPage || evenIfNotLoaded else { return }
let request = buildRequest(page)
let currentId = dataFlowId

logicCallAsync(request) { (response: Res) in
guard currentId == self.dataFlowId else { return }
let response: Result<Res, LogicError> = await logicCallAsync(request)

self.latestResponse = response
self.latestError = nil
let (newItems, newTotalPages) = self.onResponse(response)
await MainActor.run {
switch response {
case let .success(response):
guard currentId == dataFlowId else { return }
latestResponse = response
latestError = nil
let (newItems, newTotalPages) = onResponse(response)

withAnimation(when: animated) {
upsertItems(newItems, page: page)
isLoading = false
}
totalPages = newTotalPages ?? totalPages

withAnimation(when: animated) {
self.upsertItems(newItems, page: page)
self.isLoading = false
}
self.totalPages = newTotalPages ?? self.totalPages
if let after { after() }
} onError: { e in
withAnimation {
self.isLoading = false
case let .failure(e):
withAnimation {
isLoading = false
}
onError(e)
}
self.onError(e)
}
}

private func loadMore(background: Bool = false, alwaysAnimation: Bool = false) {
func loadMore(backgroundQueue: Bool = false, alwaysAnimation: Bool = false) async {
if isLoading || loadedPage >= totalPages { return }
isLoading = true

let page = loadedPage + 1
let request = buildRequest(page)
let currentId = dataFlowId

let queue = DispatchQueue.global(qos: background ? .background : .userInitiated)
let queue = DispatchQueue.global(qos: backgroundQueue ? .background : .userInitiated)

logicCallAsync(request, requestDispatchQueue: queue) { (response: Res) in
guard currentId == self.dataFlowId else { return }
let response: Result<Res, LogicError> = await logicCallAsync(request, requestDispatchQueue: queue)

self.latestResponse = response
self.latestError = nil
let (newItems, newTotalPages) = self.onResponse(response)
logger.debug("page \(self.loadedPage + 1), newItems \(newItems.count)")
await MainActor.run {
switch response {
case let .success(response):
guard currentId == dataFlowId else { return }

withAnimation(when: self.items.isEmpty || alwaysAnimation) {
self.upsertItems(newItems, page: page)
self.isLoading = false
}
self.totalPages = newTotalPages ?? self.totalPages
self.loadedPage += 1
} onError: { e in
withAnimation(when: self.items.isEmpty) {
self.isLoading = false
latestResponse = response
latestError = nil
let (newItems, newTotalPages) = onResponse(response)
logger.debug("page \(loadedPage + 1), newItems \(newItems.count)")

withAnimation(when: items.isEmpty || alwaysAnimation) {
upsertItems(newItems, page: page)
isLoading = false
}
totalPages = newTotalPages ?? totalPages
loadedPage += 1

case let .failure(e):
withAnimation(when: items.isEmpty) {
isLoading = false
}
onError(e)
}
self.onError(e)
}
}
}
Expand All @@ -264,9 +267,9 @@ struct PagingDataSourceRefreshable<Res: SwiftProtobuf.Message, Item>: ViewModifi
@Environment(\.scenePhase) var scenePhase

func doRefresh() async {
try? await Task.sleep(nanoseconds: UInt64(0.25 * Double(NSEC_PER_SEC)))
await dataSource.refreshAsync(animated: true)
try? await Task.sleep(nanoseconds: UInt64(0.25 * Double(NSEC_PER_SEC)))
// try? await Task.sleep(nanoseconds: UInt64(0.25 * Double(NSEC_PER_SEC)))
await dataSource.refresh(animated: true)
// try? await Task.sleep(nanoseconds: UInt64(0.25 * Double(NSEC_PER_SEC)))
}

func body(content: Content) -> some View {
Expand Down
2 changes: 1 addition & 1 deletion app/Shared/Views/FavoriteTopicListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ struct FavoriteTopicListView: View {
Group {
if dataSource.notLoaded {
ProgressView()
.onAppear { dataSource.initialLoad() }
.task { await dataSource.initialLoad() }
} else {
List {
ForEach($dataSource.items, id: \.id) { topic in
Expand Down
4 changes: 2 additions & 2 deletions app/Shared/Views/GlobalSearchView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ struct ForumSearchView: View {
Group {
if dataSource.notLoaded {
ProgressView()
.onAppear { dataSource.initialLoad() }
.task { await dataSource.initialLoad() }
} else {
List {
Section(header: Text("Search Results")) {
Expand All @@ -99,7 +99,7 @@ struct UserSearchView: View {
Group {
if dataSource.notLoaded {
ProgressView()
.onAppear { dataSource.initialLoad() }
.task { await dataSource.initialLoad() }
} else {
List {
Section(header: Text("Search Results")) {
Expand Down
2 changes: 1 addition & 1 deletion app/Shared/Views/HotTopicListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ struct HotTopicListInnerView: View {
Group {
if dataSource.notLoaded {
ProgressView()
.onAppear { dataSource.initialLoad() }
.task { await dataSource.initialLoad() }
} else {
List {
Section(header: Text(range.description)) {
Expand Down
2 changes: 1 addition & 1 deletion app/Shared/Views/NotificationListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ struct NotificationListView: View {
Group {
if dataSource.notLoaded {
ProgressView()
.onAppear { dataSource.initialLoad() }
.task { await dataSource.initialLoad() }
} else {
List {
ForEach($dataSource.items, id: \.id) { notification in
Expand Down
2 changes: 1 addition & 1 deletion app/Shared/Views/RecommendedTopicListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ struct RecommendedTopicListView: View {
Group {
if dataSource.notLoaded {
ProgressView()
.onAppear { dataSource.initialLoad() }
.task { await dataSource.initialLoad() }
} else {
List {
ForEach($dataSource.items, id: \.id) { topic in
Expand Down
4 changes: 2 additions & 2 deletions app/Shared/Views/ShortMessageDetailsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ struct ShortMessageDetailsView: View {
Group {
if dataSource.notLoaded {
ProgressView()
.onAppear { dataSource.initialLoad() }
.task { await dataSource.initialLoad() }
} else {
List {
Section(header: Text("Participants")) {
Expand All @@ -84,7 +84,7 @@ struct ShortMessageDetailsView: View {
.refreshable(dataSource: dataSource)
.withTopicDetailsAction()
.toolbar { toolbar }
.onChange(of: postModel.sent) { dataSource.reloadLastPages(evenIfNotLoaded: false) }
.task(id: postModel.sent) { await dataSource.reloadLastPages(evenIfNotLoaded: false) }
}

func doReply() {
Expand Down
4 changes: 2 additions & 2 deletions app/Shared/Views/ShortMessageListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ struct ShortMessageListView: View {
Group {
if dataSource.notLoaded {
ProgressView()
.onAppear { dataSource.initialLoad() }
.task { await dataSource.initialLoad() }
} else {
List {
ForEach(dataSource.items, id: \.id) { message in
Expand All @@ -65,7 +65,7 @@ struct ShortMessageListView: View {
.mayGroupedListStyle()
.refreshable(dataSource: dataSource)
.toolbar { toolbar }
.onChange(of: postModel.sent) { dataSource.reload(page: 1, evenIfNotLoaded: false) }
.task(id: postModel.sent) { await dataSource.reload(page: 1, evenIfNotLoaded: false) }
}

func newShortMessage() {
Expand Down
Loading