forked from mirrors/linphone-iphone
Fix chatRoom refresh in background
This commit is contained in:
parent
a9eb5caad4
commit
c441e2cb43
10 changed files with 169 additions and 215 deletions
|
|
@ -120,6 +120,7 @@ final class CoreContext: ObservableObject {
|
|||
self.mCore.videoDisplayEnabled = true
|
||||
self.mCore.videoPreviewEnabled = false
|
||||
self.mCore.fecEnabled = true
|
||||
self.mCore.friendListSubscriptionEnabled = true
|
||||
|
||||
self.mCoreSuscriptions.insert(self.mCore.publisher?.onGlobalStateChanged?.postOnCoreQueue { (cbVal: (core: Core, state: GlobalState, message: String)) in
|
||||
if cbVal.state == GlobalState.On {
|
||||
|
|
|
|||
|
|
@ -154,6 +154,12 @@ struct LinphoneApp: App {
|
|||
if newPhase == .active {
|
||||
Log.info("Entering foreground")
|
||||
coreContext.onEnterForeground()
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
if conversationViewModel != nil && conversationViewModel!.displayedConversation != nil && conversationsListViewModel != nil {
|
||||
conversationViewModel!.resetDisplayedChatRoom(conversationsList: conversationsListViewModel!.conversationsList)
|
||||
}
|
||||
}
|
||||
} else if newPhase == .inactive {
|
||||
} else if newPhase == .background {
|
||||
Log.info("Entering background")
|
||||
|
|
|
|||
|
|
@ -881,10 +881,12 @@ class CallViewModel: ObservableObject {
|
|||
coreContext.doOnCoreQueue { core in
|
||||
if core.currentCall != nil {
|
||||
let tokens = core.currentCall!.remoteAuthenticationTokens
|
||||
self.letters1 = tokens[0]
|
||||
self.letters2 = tokens[1]
|
||||
self.letters3 = tokens[2]
|
||||
self.letters4 = tokens[3]
|
||||
DispatchQueue.main.async {
|
||||
self.letters1 = tokens[0]
|
||||
self.letters2 = tokens[1]
|
||||
self.letters3 = tokens[2]
|
||||
self.letters4 = tokens[3]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ struct ConversationFragment: View {
|
|||
UIList(viewModel: viewModel,
|
||||
paginationState: paginationState,
|
||||
conversationViewModel: conversationViewModel,
|
||||
conversationsListViewModel: conversationsListViewModel,
|
||||
isScrolledToBottom: $isScrolledToBottom,
|
||||
showMessageMenuOnLongPress: showMessageMenuOnLongPress,
|
||||
geometryProxy: geometry,
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ struct ConversationsListBottomSheet: View {
|
|||
Button {
|
||||
if conversationsListViewModel.selectedConversation != nil {
|
||||
conversationsListViewModel.objectWillChange.send()
|
||||
conversationsListViewModel.selectedConversation!.markAsRead()
|
||||
conversationsListViewModel.markAsReadSelectedConversation()
|
||||
conversationsListViewModel.updateUnreadMessagesCount()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@ struct ConversationsListFragment: View {
|
|||
|
||||
@Binding var showingSheet: Bool
|
||||
|
||||
@State var canChangeChatRoom: Bool = true
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
|
|
@ -136,34 +134,16 @@ struct ConversationsListFragment: View {
|
|||
.listRowSeparator(.hidden)
|
||||
.background(.white)
|
||||
.onTapGesture {
|
||||
if canChangeChatRoom {
|
||||
if conversationViewModel.displayedConversation != nil {
|
||||
conversationViewModel.displayedConversation = nil
|
||||
conversationViewModel.resetMessage()
|
||||
if conversationViewModel.displayedConversation != nil {
|
||||
conversationViewModel.displayedConversation = nil
|
||||
conversationViewModel.resetMessage()
|
||||
conversationViewModel.changeDisplayedChatRoom(conversationModel: conversationsListViewModel.conversationsList[index])
|
||||
|
||||
conversationViewModel.getMessages()
|
||||
} else {
|
||||
withAnimation {
|
||||
conversationViewModel.changeDisplayedChatRoom(conversationModel: conversationsListViewModel.conversationsList[index])
|
||||
|
||||
let firstScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
|
||||
if firstScene != nil {
|
||||
let firstWindow = firstScene!.windows.first
|
||||
if firstWindow != nil {
|
||||
firstWindow!.layer.speed = 20.0
|
||||
canChangeChatRoom = false
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||||
firstWindow!.layer.speed = 1.0
|
||||
canChangeChatRoom = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conversationViewModel.getMessages()
|
||||
} else {
|
||||
withAnimation {
|
||||
conversationViewModel.changeDisplayedChatRoom(conversationModel: conversationsListViewModel.conversationsList[index])
|
||||
}
|
||||
}
|
||||
conversationsListViewModel.conversationsList[index].markAsRead()
|
||||
conversationsListViewModel.updateUnreadMessagesCount()
|
||||
}
|
||||
}
|
||||
.onLongPressGesture(minimumDuration: 0.2) {
|
||||
|
|
|
|||
|
|
@ -29,19 +29,16 @@ struct UIList: UIViewRepresentable {
|
|||
@ObservedObject var viewModel: ChatViewModel
|
||||
@ObservedObject var paginationState: PaginationState
|
||||
@ObservedObject var conversationViewModel: ConversationViewModel
|
||||
|
||||
@ObservedObject var conversationsListViewModel: ConversationsListViewModel
|
||||
|
||||
@Binding var isScrolledToBottom: Bool
|
||||
|
||||
let showMessageMenuOnLongPress: Bool
|
||||
let geometryProxy: GeometryProxy
|
||||
let sections: [MessagesSection]
|
||||
|
||||
|
||||
@State private var isScrolledToTop = false
|
||||
|
||||
private let updatesQueue = DispatchQueue(label: "updatesQueue", qos: .utility)
|
||||
@State private var updateSemaphore = DispatchSemaphore(value: 1)
|
||||
@State private var tableSemaphore = DispatchSemaphore(value: 0)
|
||||
|
||||
|
||||
func makeUIView(context: Context) -> UITableView {
|
||||
let tableView = UITableView(frame: .zero, style: .grouped)
|
||||
tableView.contentInset = UIEdgeInsets(top: -10, left: 0, bottom: -20, right: 0)
|
||||
|
|
@ -57,7 +54,7 @@ struct UIList: UIViewRepresentable {
|
|||
tableView.estimatedSectionFooterHeight = UITableView.automaticDimension
|
||||
tableView.backgroundColor = UIColor(.white)
|
||||
tableView.scrollsToTop = true
|
||||
|
||||
|
||||
NotificationCenter.default.addObserver(forName: .onScrollToBottom, object: nil, queue: nil) { _ in
|
||||
DispatchQueue.main.async {
|
||||
if !context.coordinator.sections.isEmpty {
|
||||
|
|
@ -65,127 +62,77 @@ struct UIList: UIViewRepresentable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return tableView
|
||||
}
|
||||
|
||||
|
||||
func updateUIView(_ tableView: UITableView, context: Context) {
|
||||
if context.coordinator.sections == sections {
|
||||
return
|
||||
}
|
||||
updatesQueue.async {
|
||||
updateSemaphore.wait()
|
||||
|
||||
if context.coordinator.sections == sections {
|
||||
updateSemaphore.signal()
|
||||
return
|
||||
}
|
||||
|
||||
let prevSections = context.coordinator.sections
|
||||
let (appliedDeletes, appliedDeletesSwapsAndEdits, deleteOperations, swapOperations, editOperations, insertOperations) = operationsSplit(oldSections: prevSections, newSections: sections)
|
||||
|
||||
// step 1
|
||||
// preapare intermediate sections and operations
|
||||
//print("1 updateUIView sections:", "\n")
|
||||
//print("whole previous:\n", formatSections(prevSections), "\n")
|
||||
//print("whole appliedDeletes:\n", formatSections(appliedDeletes), "\n")
|
||||
//print("whole appliedDeletesSwapsAndEdits:\n", formatSections(appliedDeletesSwapsAndEdits), "\n")
|
||||
//print("whole final sections:\n", formatSections(sections), "\n")
|
||||
|
||||
//print("operations delete:\n", deleteOperations)
|
||||
//print("operations swap:\n", swapOperations)
|
||||
//print("operations edit:\n", editOperations)
|
||||
//print("operations insert:\n", insertOperations)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
tableView.performBatchUpdates {
|
||||
// step 2
|
||||
// delete sections and rows if necessary
|
||||
//print("2 apply delete")
|
||||
context.coordinator.sections = appliedDeletes
|
||||
for operation in deleteOperations {
|
||||
applyOperation(operation, tableView: tableView)
|
||||
}
|
||||
} completion: { _ in
|
||||
tableSemaphore.signal()
|
||||
//print("2 finished delete")
|
||||
}
|
||||
}
|
||||
tableSemaphore.wait()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
tableView.performBatchUpdates {
|
||||
// step 3
|
||||
// swap places for rows that moved inside the table
|
||||
// (example of how this happens. send two messages: first m1, then m2. if m2 is delivered to server faster, then it should jump above m1 even though it was sent later)
|
||||
//print("3 apply swaps")
|
||||
context.coordinator.sections = appliedDeletesSwapsAndEdits // NOTE: this array already contains necessary edits, but won't be a problem for appplying swaps
|
||||
for operation in swapOperations {
|
||||
applyOperation(operation, tableView: tableView)
|
||||
}
|
||||
} completion: { _ in
|
||||
tableSemaphore.signal()
|
||||
//print("3 finished swaps")
|
||||
}
|
||||
}
|
||||
tableSemaphore.wait()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
tableView.performBatchUpdates {
|
||||
// step 4
|
||||
// check only sections that are already in the table for existing rows that changed and apply only them to table's dataSource without animation
|
||||
//print("4 apply edits")
|
||||
context.coordinator.sections = appliedDeletesSwapsAndEdits
|
||||
for operation in editOperations {
|
||||
applyOperation(operation, tableView: tableView)
|
||||
}
|
||||
} completion: { _ in
|
||||
tableSemaphore.signal()
|
||||
//print("4 finished edits")
|
||||
}
|
||||
}
|
||||
tableSemaphore.wait()
|
||||
|
||||
if isScrolledToBottom || isScrolledToTop {
|
||||
DispatchQueue.main.sync {
|
||||
// step 5
|
||||
// apply the rest of the changes to table's dataSource, i.e. inserts
|
||||
//print("5 apply inserts")
|
||||
context.coordinator.sections = sections
|
||||
|
||||
tableView.beginUpdates()
|
||||
for operation in insertOperations {
|
||||
applyOperation(operation, tableView: tableView)
|
||||
}
|
||||
tableView.endUpdates()
|
||||
|
||||
updateSemaphore.signal()
|
||||
}
|
||||
} else {
|
||||
updateSemaphore.signal()
|
||||
if context.coordinator.sections == sections {
|
||||
return
|
||||
}
|
||||
|
||||
let prevSections = context.coordinator.sections
|
||||
let (appliedDeletes, appliedDeletesSwapsAndEdits, deleteOperations, swapOperations, editOperations, insertOperations) = operationsSplit(oldSections: prevSections, newSections: sections)
|
||||
|
||||
tableView.performBatchUpdates {
|
||||
context.coordinator.sections = appliedDeletes
|
||||
for operation in deleteOperations {
|
||||
applyOperation(operation, tableView: tableView)
|
||||
}
|
||||
}
|
||||
|
||||
tableView.performBatchUpdates {
|
||||
context.coordinator.sections = appliedDeletesSwapsAndEdits // NOTE: this array already contains necessary edits, but won't be a problem for appplying swaps
|
||||
for operation in swapOperations {
|
||||
applyOperation(operation, tableView: tableView)
|
||||
}
|
||||
}
|
||||
|
||||
tableView.performBatchUpdates {
|
||||
context.coordinator.sections = appliedDeletesSwapsAndEdits
|
||||
for operation in editOperations {
|
||||
applyOperation(operation, tableView: tableView)
|
||||
}
|
||||
}
|
||||
|
||||
if isScrolledToBottom || isScrolledToTop {
|
||||
context.coordinator.sections = sections
|
||||
|
||||
tableView.beginUpdates()
|
||||
for operation in insertOperations {
|
||||
applyOperation(operation, tableView: tableView)
|
||||
}
|
||||
tableView.endUpdates()
|
||||
}
|
||||
|
||||
if isScrolledToBottom {
|
||||
conversationViewModel.markAsRead()
|
||||
conversationsListViewModel.computeChatRoomsList(filter: "")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Operations
|
||||
|
||||
|
||||
enum Operation {
|
||||
case deleteSection(Int)
|
||||
case insertSection(Int)
|
||||
|
||||
|
||||
case delete(Int, Int) // delete with animation
|
||||
case insert(Int, Int) // insert with animation
|
||||
case swap(Int, Int, Int) // delete first with animation, then insert it into new position with animation. do not do anything with the second for now
|
||||
case edit(Int, Int) // reload the element without animation
|
||||
}
|
||||
|
||||
|
||||
func applyOperation(_ operation: Operation, tableView: UITableView) {
|
||||
switch operation {
|
||||
case .deleteSection(let section):
|
||||
tableView.deleteSections([section], with: .top)
|
||||
case .insertSection(let section):
|
||||
tableView.insertSections([section], with: .top)
|
||||
|
||||
|
||||
case .delete(let section, let row):
|
||||
tableView.deleteRows(at: [IndexPath(row: row, section: section)], with: .top)
|
||||
case .insert(let section, let row):
|
||||
|
|
@ -197,19 +144,16 @@ struct UIList: UIViewRepresentable {
|
|||
tableView.insertRows(at: [IndexPath(row: rowTo, section: section)], with: .top)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func operationsSplit(oldSections: [MessagesSection], newSections: [MessagesSection]) -> ([MessagesSection], [MessagesSection], [Operation], [Operation], [Operation], [Operation]) {
|
||||
var appliedDeletes = oldSections // start with old sections, remove rows that need to be deleted
|
||||
var appliedDeletesSwapsAndEdits = newSections // take new sections and remove rows that need to be inserted for now, then we'll get array with all the changes except for inserts
|
||||
// appliedDeletesSwapsEditsAndInserts == newSection
|
||||
|
||||
var appliedDeletes = oldSections
|
||||
var appliedDeletesSwapsAndEdits = newSections
|
||||
|
||||
var deleteOperations = [Operation]()
|
||||
var swapOperations = [Operation]()
|
||||
var editOperations = [Operation]()
|
||||
var insertOperations = [Operation]()
|
||||
|
||||
// 1 compare sections
|
||||
|
||||
|
||||
let oldDates = oldSections.map { $0.date }
|
||||
let newDates = newSections.map { $0.date }
|
||||
let commonDates = Array(Set(oldDates + newDates)).sorted(by: >)
|
||||
|
|
@ -217,7 +161,6 @@ struct UIList: UIViewRepresentable {
|
|||
let oldIndex = appliedDeletes.firstIndex(where: { $0.date == date } )
|
||||
let newIndex = appliedDeletesSwapsAndEdits.firstIndex(where: { $0.date == date } )
|
||||
if oldIndex == nil, let newIndex {
|
||||
// operationIndex is not the same as newIndex because appliedDeletesSwapsAndEdits is being changed as we go, but to apply changes to UITableView we should have initial index
|
||||
if let operationIndex = newSections.firstIndex(where: { $0.date == date } ) {
|
||||
appliedDeletesSwapsAndEdits.remove(at: newIndex)
|
||||
insertOperations.append(.insertSection(operationIndex))
|
||||
|
|
@ -232,63 +175,53 @@ struct UIList: UIViewRepresentable {
|
|||
continue
|
||||
}
|
||||
guard let newIndex, let oldIndex else { continue }
|
||||
|
||||
// 2 compare section rows
|
||||
// isolate deletes and inserts, and remove them from row arrays, leaving only rows that are in both arrays: 'duplicates'
|
||||
// this will allow to compare relative position changes of rows - swaps
|
||||
|
||||
|
||||
var oldRows = appliedDeletes[oldIndex].rows
|
||||
var newRows = appliedDeletesSwapsAndEdits[newIndex].rows
|
||||
let oldRowIDs = Set(oldRows.map { $0.id })
|
||||
let newRowIDs = Set(newRows.map { $0.id })
|
||||
let rowIDsToDelete = oldRowIDs.subtracting(newRowIDs)
|
||||
let rowIDsToInsert = newRowIDs.subtracting(oldRowIDs) // TODO is order important?
|
||||
let rowIDsToInsert = newRowIDs.subtracting(oldRowIDs)
|
||||
for rowId in rowIDsToDelete {
|
||||
if let index = oldRows.firstIndex(where: { $0.id == rowId }) {
|
||||
oldRows.remove(at: index)
|
||||
deleteOperations.append(.delete(oldIndex, index)) // this row was in old section, should not be in final result
|
||||
deleteOperations.append(.delete(oldIndex, index))
|
||||
}
|
||||
}
|
||||
for rowId in rowIDsToInsert {
|
||||
if let index = newRows.firstIndex(where: { $0.id == rowId }) {
|
||||
// this row was not in old section, should add it to final result
|
||||
insertOperations.append(.insert(newIndex, index))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for rowId in rowIDsToInsert {
|
||||
if let index = newRows.firstIndex(where: { $0.id == rowId }) {
|
||||
// remove for now, leaving only 'duplicates'
|
||||
newRows.remove(at: index)
|
||||
}
|
||||
}
|
||||
|
||||
// 3 isolate swaps and edits
|
||||
|
||||
|
||||
for row in 0..<oldRows.count {
|
||||
let oldRow = oldRows[row]
|
||||
let newRow = newRows[row]
|
||||
if oldRow.id != newRow.id { // a swap: rows in same position are not actually the same rows
|
||||
if oldRow.id != newRow.id {
|
||||
if let index = newRows.firstIndex(where: { $0.id == oldRow.id }) {
|
||||
if !swapsContain(swaps: swapOperations, section: oldIndex, index: row) ||
|
||||
!swapsContain(swaps: swapOperations, section: oldIndex, index: index) {
|
||||
swapOperations.append(.swap(oldIndex, row, index))
|
||||
}
|
||||
}
|
||||
} else if oldRow != newRow { // same ids om same positions but something changed - reload rows without animation
|
||||
} else if oldRow != newRow {
|
||||
editOperations.append(.edit(oldIndex, row))
|
||||
}
|
||||
}
|
||||
|
||||
// 4 store row changes in sections
|
||||
|
||||
|
||||
appliedDeletes[oldIndex].rows = oldRows
|
||||
appliedDeletesSwapsAndEdits[newIndex].rows = newRows
|
||||
}
|
||||
|
||||
|
||||
return (appliedDeletes, appliedDeletesSwapsAndEdits, deleteOperations, swapOperations, editOperations, insertOperations)
|
||||
}
|
||||
|
||||
|
||||
func swapsContain(swaps: [Operation], section: Int, index: Int) -> Bool {
|
||||
!swaps.filter {
|
||||
if case let .swap(section, rowFrom, rowTo) = $0 {
|
||||
|
|
@ -297,12 +230,13 @@ struct UIList: UIViewRepresentable {
|
|||
return false
|
||||
}.isEmpty
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Coordinator
|
||||
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(
|
||||
conversationViewModel: conversationViewModel,
|
||||
conversationsListViewModel: conversationsListViewModel,
|
||||
viewModel: viewModel,
|
||||
paginationState: paginationState,
|
||||
isScrolledToBottom: $isScrolledToBottom,
|
||||
|
|
@ -314,20 +248,22 @@ struct UIList: UIViewRepresentable {
|
|||
}
|
||||
|
||||
class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
|
||||
@ObservedObject var viewModel: ChatViewModel
|
||||
@ObservedObject var paginationState: PaginationState
|
||||
@ObservedObject var conversationViewModel: ConversationViewModel
|
||||
|
||||
@ObservedObject var conversationsListViewModel: ConversationsListViewModel
|
||||
|
||||
@Binding var isScrolledToBottom: Bool
|
||||
@Binding var isScrolledToTop: Bool
|
||||
|
||||
let showMessageMenuOnLongPress: Bool
|
||||
let geometryProxy: GeometryProxy
|
||||
var sections: [MessagesSection]
|
||||
|
||||
init(conversationViewModel: ConversationViewModel, viewModel: ChatViewModel, paginationState: PaginationState, isScrolledToBottom: Binding<Bool>, isScrolledToTop: Binding<Bool>, showMessageMenuOnLongPress: Bool, geometryProxy: GeometryProxy, sections: [MessagesSection]) {
|
||||
|
||||
init(conversationViewModel: ConversationViewModel, conversationsListViewModel: ConversationsListViewModel, viewModel: ChatViewModel, paginationState: PaginationState, isScrolledToBottom: Binding<Bool>, isScrolledToTop: Binding<Bool>, showMessageMenuOnLongPress: Bool, geometryProxy: GeometryProxy, sections: [MessagesSection]) {
|
||||
self.conversationViewModel = conversationViewModel
|
||||
self.conversationsListViewModel = conversationsListViewModel
|
||||
self.viewModel = viewModel
|
||||
self.paginationState = paginationState
|
||||
self._isScrolledToBottom = isScrolledToBottom
|
||||
|
|
@ -336,15 +272,15 @@ struct UIList: UIViewRepresentable {
|
|||
self.geometryProxy = geometryProxy
|
||||
self.sections = sections
|
||||
}
|
||||
|
||||
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
sections.count
|
||||
}
|
||||
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
sections[section].rows.count
|
||||
}
|
||||
|
||||
|
||||
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
||||
return progressView(section)
|
||||
}
|
||||
|
|
@ -361,13 +297,13 @@ struct UIList: UIViewRepresentable {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
|
||||
let tableViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
|
||||
tableViewCell.selectionStyle = .none
|
||||
tableViewCell.backgroundColor = UIColor(.white)
|
||||
|
||||
|
||||
let row = sections[indexPath.section].rows[indexPath.row]
|
||||
if #available(iOS 16.0, *) {
|
||||
tableViewCell.contentConfiguration = UIHostingConfiguration {
|
||||
|
|
@ -381,20 +317,20 @@ struct UIList: UIViewRepresentable {
|
|||
}
|
||||
|
||||
tableViewCell.transform = CGAffineTransformMakeScale(1, -1)
|
||||
|
||||
|
||||
return tableViewCell
|
||||
}
|
||||
|
||||
|
||||
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||
let row = sections[indexPath.section].rows[indexPath.row]
|
||||
paginationState.handle(row)
|
||||
}
|
||||
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
isScrolledToBottom = scrollView.contentOffset.y <= 10
|
||||
|
||||
if isScrolledToBottom && conversationViewModel.displayedConversationUnreadMessagesCount > 0 {
|
||||
conversationViewModel.markAsRead()
|
||||
conversationsListViewModel.computeChatRoomsList(filter: "")
|
||||
}
|
||||
|
||||
if !isScrolledToTop && scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.frame.height - 200 {
|
||||
|
|
@ -406,44 +342,44 @@ struct UIList: UIViewRepresentable {
|
|||
}
|
||||
|
||||
struct MessagesSection: Equatable {
|
||||
|
||||
|
||||
let date: Date
|
||||
var rows: [Message]
|
||||
|
||||
|
||||
static var formatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "EEEE, MMMM d"
|
||||
return formatter
|
||||
}()
|
||||
|
||||
|
||||
init(date: Date, rows: [Message]) {
|
||||
self.date = date
|
||||
self.rows = rows
|
||||
}
|
||||
|
||||
|
||||
var formattedDate: String {
|
||||
MessagesSection.formatter.string(from: date)
|
||||
}
|
||||
|
||||
|
||||
static func == (lhs: MessagesSection, rhs: MessagesSection) -> Bool {
|
||||
lhs.date == rhs.date && lhs.rows == rhs.rows
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
final class PaginationState: ObservableObject {
|
||||
var onEvent: ChatPaginationClosure?
|
||||
var offset: Int
|
||||
|
||||
|
||||
var shouldHandlePagination: Bool {
|
||||
onEvent != nil
|
||||
}
|
||||
|
||||
|
||||
init(onEvent: ChatPaginationClosure? = nil, offset: Int = 0) {
|
||||
self.onEvent = onEvent
|
||||
self.offset = offset
|
||||
}
|
||||
|
||||
|
||||
func handle(_ message: Message) {
|
||||
guard shouldHandlePagination else {
|
||||
return
|
||||
|
|
@ -457,9 +393,9 @@ final class ChatViewModel: ObservableObject {
|
|||
|
||||
@Published private(set) var fullscreenAttachmentItem: Optional<Attachment> = nil
|
||||
@Published var fullscreenAttachmentPresented = false
|
||||
|
||||
|
||||
@Published var messageMenuRow: Message?
|
||||
|
||||
|
||||
public var didSendMessage: (DraftMessage) -> Void = {_ in}
|
||||
|
||||
func presentAttachmentFullScreen(_ attachment: Attachment) {
|
||||
|
|
@ -471,7 +407,7 @@ final class ChatViewModel: ObservableObject {
|
|||
fullscreenAttachmentPresented = false
|
||||
fullscreenAttachmentItem = nil
|
||||
}
|
||||
|
||||
|
||||
func sendMessage(_ message: DraftMessage) {
|
||||
didSendMessage(message)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,19 +94,6 @@ class ConversationModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func markAsRead() {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
let unreadMessagesCountTmp = self.chatRoom.unreadMessagesCount
|
||||
if unreadMessagesCountTmp > 0 {
|
||||
self.chatRoom.markAsRead()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.unreadMessagesCount = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toggleMute() {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
self.chatRoom.muted.toggle()
|
||||
|
|
|
|||
|
|
@ -400,6 +400,8 @@ class ConversationViewModel: ObservableObject {
|
|||
|
||||
let isFirstMessageTmp = (eventLog.chatMessage?.isOutgoing ?? false) ? isFirstMessageOutgoingTmp : isFirstMessageIncomingTmp
|
||||
|
||||
let unreadMessagesCount = self.displayedConversation!.chatRoom.unreadMessagesCount
|
||||
|
||||
var statusTmp: Message.Status? = .sending
|
||||
switch eventLog.chatMessage?.state {
|
||||
case .InProgress:
|
||||
|
|
@ -441,14 +443,10 @@ class ConversationViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
if !message.isOutgoing {
|
||||
self.displayedConversationUnreadMessagesCount += 1
|
||||
self.displayedConversationUnreadMessagesCount = unreadMessagesCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.displayedConversation != nil {
|
||||
self.displayedConversation!.markAsRead()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -581,13 +579,35 @@ class ConversationViewModel: ObservableObject {
|
|||
self.displayedConversation = conversationModel
|
||||
}
|
||||
|
||||
func resetDisplayedChatRoom(conversationsList: [ConversationModel]) {
|
||||
removeConversationDelegate()
|
||||
|
||||
if self.displayedConversation != nil {
|
||||
conversationsList.forEach { conversation in
|
||||
if conversation.id == self.displayedConversation!.id {
|
||||
self.displayedConversation = conversation
|
||||
|
||||
self.chatRoomSuscriptions.insert(conversation.chatRoom.publisher?.onChatMessageSending?.postOnCoreQueue { (cbValue: (chatRoom: ChatRoom, eventLog: EventLog)) in
|
||||
self.getNewMessages(eventLogs: [cbValue.eventLog])
|
||||
})
|
||||
|
||||
self.chatRoomSuscriptions.insert(conversation.chatRoom.publisher?.onChatMessagesReceived?.postOnCoreQueue { (cbValue: (chatRoom: ChatRoom, eventLogs: [EventLog])) in
|
||||
self.getNewMessages(eventLogs: cbValue.eventLogs)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func downloadContent(chatMessage: ChatMessage, content: Content) {
|
||||
//Log.debug("[ConversationViewModel] Starting downloading content for file \(model.fileName)")
|
||||
if content.filePath == nil || content.filePath!.isEmpty {
|
||||
let contentName = content.name
|
||||
if contentName != nil {
|
||||
let isImage = FileUtil.isExtensionImage(path: contentName!)
|
||||
let file = FileUtil.getFileStoragePath(fileName: contentName ?? "", isImage: isImage)
|
||||
let groupName = "group.\(Bundle.main.bundleIdentifier ?? "").linphoneExtension"
|
||||
let file = FileUtil.sharedContainerUrl().appendingPathComponent("Library/Images").absoluteString + (contentName!.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "")
|
||||
//let file = FileUtil.getFileStoragePath(fileName: contentName ?? "", isImage: isImage)
|
||||
content.filePath = String(file.dropFirst(7))
|
||||
Log.info(
|
||||
"[ConversationViewModel] File \(contentName) will be downloaded at \(content.filePath)"
|
||||
|
|
|
|||
|
|
@ -62,11 +62,21 @@ class ConversationsListViewModel: ObservableObject {
|
|||
|
||||
func addConversationDelegate() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
let account = core.defaultAccount
|
||||
let chatRoomsCounter = account?.chatRooms != nil ? account!.chatRooms.count : core.chatRooms.count
|
||||
var counter = 0
|
||||
self.mCoreSuscriptions.insert(core.publisher?.onChatRoomStateChanged?.postOnCoreQueue { (cbValue: (core: Core, chatRoom: ChatRoom, state: ChatRoom.State)) in
|
||||
//Log.info("[ConversationsListViewModel] Conversation [${LinphoneUtils.getChatRoomId(chatRoom)}] state changed [$state]")
|
||||
switch cbValue.state {
|
||||
case ChatRoom.State.Created:
|
||||
self.computeChatRoomsList(filter: "")
|
||||
if !(cbValue.chatRoom.isEmpty && cbValue.chatRoom.hasCapability(mask: ChatRoom.Capabilities.OneToOne.rawValue)) {
|
||||
counter += 1
|
||||
}
|
||||
|
||||
if counter >= chatRoomsCounter {
|
||||
self.computeChatRoomsList(filter: "")
|
||||
counter = 0
|
||||
}
|
||||
case ChatRoom.State.Deleted:
|
||||
self.computeChatRoomsList(filter: "")
|
||||
//ToastViewModel.shared.toastMessage = "toast_conversation_deleted"
|
||||
|
|
@ -177,4 +187,15 @@ class ConversationsListViewModel: ObservableObject {
|
|||
|
||||
reorderChatRooms()
|
||||
}
|
||||
|
||||
func markAsReadSelectedConversation() {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
let unreadMessagesCount = self.selectedConversation!.chatRoom.unreadMessagesCount
|
||||
|
||||
if unreadMessagesCount > 0 {
|
||||
self.selectedConversation!.chatRoom.markAsRead()
|
||||
self.selectedConversation!.unreadMessagesCount = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue