Skip to content

Conversation

dmitrysimkin
Copy link
Contributor

@dmitrysimkin dmitrysimkin commented May 15, 2025

TaskWPB-16787 Improve Swipe gesture

Issue

First part of the planned changes. Please take a look at the bigger picture and approach, this is the goal of the PR.
PR is targeting into feature branch. You can find this first PR a bit raw, it has some TODO's and dev code but it's too many things affected, we need to start with smth. I'll address all this dev code and TODO in next PRs

In this PR

  1. Migrated sender name to SwiftUI:
  • show if needed
  • update dynamically if was renamed on other device without refreshing the cell, as self sufficient message view.
  • Also there known issue when sender name updated so line number is different, it does not update UI properly (will address later)
  1. Migrated status view to SwiftUI:
  • show if needed
  • updates dynamically without refreshing a cell through table view
  1. Introduced UI layer model to serve the UI needs only. Also to avoid issues with multiple threads
  2. Removed ConversationCell to be generic, it's not needed as we set cell's content using contetConfigration for which you don't need to reuse cells. Another reason as that you can't create viewModel empty on the fly, we need ViewModels be created before and passed to a cell.
  3. Introduced ViewModels factory to encapsulate initialisation to one place with single responsibility. Similarly to approach in WireAuthenication but without 3rd party DI which can be done as next transition phase
  4. Move some UI related code like date formatters and fonts to WireUI to be able to reuse in main App and WireConversationUI module

What's not in this PR:

  • Sender's avatar
  • Count down timer for self deleting messages
  • message text content
  • some little UI things like images and some formatting logic

Testing

Describe how to test.

Optional: attachments like images, videos, etc.


Checklist

  • Title contains a reference JIRA issue number like [WPB-XXX].
  • Description is filled and free of optional paragraphs.
  • Adds/updates automated tests.

UI accessibility checklist

If your PR includes UI changes, please utilize this checklist:

  • Make sure you use the API for UI elements that support large fonts.
  • All colors are taken from WireDesign.ColorTheme or constructed using WireDesign.BaseColorPalette.
  • New UI elements have Accessibility strings for VoiceOver.

@dmitrysimkin dmitrysimkin changed the title Feature/improve swipe wpb 16787 Feat: improve swipe - WPB-16787 May 15, 2025
# Conflicts:
#	wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ConfigurationMessageCell/ConversationMessageSectionController.swift
@dmitrysimkin dmitrysimkin changed the base branch from develop to feature/base-improve-swipe-WPB-16787 May 16, 2025 12:28

import Foundation

public struct MessageModel: Equatable {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created UI model to have it's own model and not depend on other layers Models.
Also it was requirement becasue this is WireConversationUI module where I can't import WireDataModel project.
As well it helps to have just pure struct which helps be safer with background thread calculations

Copy link
Contributor

@jullianm jullianm May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: I wonder if we want to move these models to the WireDomain package and have, as such, kind of a centralized place where we retrieve these models from wherever we need them. just concerned about duplicating WireDataModel objects spread a little bit everywhere in new code.

update: these look like more like UIModels so probably best to keep it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are presentation layer models not domain, for me WireDomain is not a places for them but if it will be used in other modules and not in WireConversationUI only we'd need to find proper place to put

Comment on lines +58 to +72
public enum SystemMessageTypeModel: Int, Equatable {
case performedCall
case missedCall
case messageDeletedForEveryone
}

public enum DeliveryStateModel: Int, Sendable, Equatable, CaseIterable {
case invalid
case pending
case sent
case delivered
case read
case failedToSend
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll split them into its own files later

Comment on lines +56 to +71
// private func startRandomStateTimer() {
// timer = Timer.publish(every: 1.0, on: .main, in: .common)
// .autoconnect()
// .sink { [weak self] _ in
// guard let self else { return }
// let (newText, lines) = self.randomMultilineText()
// if lines >= 2 {
// self.significantChangeSubject.send(())
// } else {
// self.text = newText
// }
// }
// }

// func randomMultilineText() -> (String, Int) {
// let lines = [
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in next PR

Comment on lines -21 to -27
final class ConversationCellsPreview: UITableViewController {

enum SectionIdentifier {
case single
}

typealias ItemIdentifier = ConversationCellModel
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've commented this out as it was not straight forward to make all models support identifiable for diffable data source. Will try to back that in on final stages

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was typo in Resouces instead of Resources

Comment on lines -26 to +28
var message: ZMConversationMessage
var deliveryState: ZMDeliveryState
var message: MessageModel
var deliveryState: DeliveryStateModel
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use UI model which is pure structs so we don't have issues with multithreaded access

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: I don't think we have a convention on that yet however to be more specific about the intent of these models we could rename them with the suffix UIModel

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree, I'll ask in the channel about naming and will rename in next PRs accordingly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will update in next PRs to avoid back and force merging

extension NewTextCellDescription: NewCellDescription {}
extension BurstTimestampSenderMessageCellDescription: NewCellDescription {}

final class NewTextCellDescription: ConversationMessageCellDescription {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New description for first step migration of text message into new way. It will support for now sender, status and just simple message content, without links, previews, search queries, quoted messages

}
}

final class NewTextCell: UIView, ConversationMessageCell {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not used but needed to compile with old approach

Comment on lines +23 to +44
final class SenderObserver: NSObject, UserObserving, SenderObserverProtocol {

var observation: Any?

var author: String?
private let authorChangedSubject = PassthroughSubject<String, Never>()
var authorChangedPublisher: AnyPublisher<String, Never> {
authorChangedSubject
.removeDuplicates()
.eraseToAnyPublisher()
}

init(
messageID: NSManagedObjectID,
viewContext: NSManagedObjectContext
) {
super.init()
viewContext.perform {
let message = try! viewContext.existingObject(with: messageID) as! ZMMessage
self.author = message.senderName
if let sender = message.senderUser {
self.observation = UserChangeInfo.add(observer: self, for: sender, context: viewContext)
Copy link
Contributor Author

@dmitrysimkin dmitrysimkin May 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Object that helps be smarter with what to observe for each specific part of the message.
Also it reduces amount of updates if nothing really changed (.removeDuplicates())

_ controller: ConversationMessageSectionController,
didRequestRefreshForMessage message: ZMConversationMessage
) {
guard !message.isText else { return }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For new approach it would be not needed to call that in case of small changes that affect only cell itself

@dmitrysimkin dmitrysimkin changed the title Feat: improve swipe - WPB-16787 Feat: improve swipe pt.1 - WPB-16787 May 19, 2025
@dmitrysimkin dmitrysimkin requested review from a team, netbe, samwyndham and jullianm and removed request for a team May 19, 2025 09:18
@dmitrysimkin dmitrysimkin changed the title Feat: improve swipe pt.1 - WPB-16787 feat: improve swipe pt.1 - WPB-16787 May 19, 2025
@dmitrysimkin dmitrysimkin marked this pull request as ready for review May 19, 2025 09:24
Copy link
Contributor

Test Results

2 tests   1 ✅  12s ⏱️
1 suites  1 💤
1 files    0 ❌

Results for commit 54d41ad.

@datadog-wireapp
Copy link

Datadog Report

Branch report: feature/improve-swipe-WPB-16787
Commit report: 764ee49
Test service: wire-ios-mono

✅ 0 Failed, 1 Passed, 1 Skipped, 12s Total Time

@dmitrysimkin dmitrysimkin requested a review from KaterinaWire May 19, 2025 09:47
Copy link
Contributor

@jullianm jullianm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work so far, left a couple of comments/questions before approving

public enum State {
case none
case some(MessageSenderViewModel)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Can't we get rid of the wrapper and use the view model directly as @ObservedObject by removing the constraint of having it optional ? The view should always be tightly coupled to its view model so no need for an optional here, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I got the suggestion right, can you pls elaborate @jullianm ?
For some cases we don't need to show sender so we need some way to tell that, nil or some specific state

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a view model wrapper , can't we use a state inside the view model like for the MessageStatusViewModel ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I have case none for MessageStatusViewModel as well for a case when we don't need to show status. It's just happen that status view has 2 more states: fail and callList, 4 in total. So approach is the same, @jullianm hope that makes more clear now?

@ObservedObject var model: MessageSenderViewModelWrapper

var body: some View {
switch model.state {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: I think here instead of relying on the view model state itself (whether it's optional or not) the view model should always be tightly coupled to its view and provide some state inside that the view would check (i.e model.state..)

@ObservedObject var model: MessageStatusViewModel

var body: some View {
switch model.state {
Copy link
Contributor

@jullianm jullianm May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep like this using the view model state property directly 👍


struct MessageStatusView: View {

@ObservedObject var model: MessageStatusViewModel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@ObservedObject var model: MessageStatusViewModel
@ObservedObject var viewModel: MessageStatusViewModel

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will rename in next PRs


import Foundation

public struct MessageModel: Equatable {
Copy link
Contributor

@jullianm jullianm May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: I wonder if we want to move these models to the WireDomain package and have, as such, kind of a centralized place where we retrieve these models from wherever we need them. just concerned about duplicating WireDataModel objects spread a little bit everywhere in new code.

update: these look like more like UIModels so probably best to keep it here.

public typealias ContentView = TextMessageView

@ObservedObject var senderViewModelWrapper: MessageSenderViewModelWrapper
@ObservedObject var statusViewModel: MessageStatusViewModel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: not a big fan of using these view models inside another view model as, for me, a view should have ideally only one view model although I'm assuming the code here will evolve in next PRs and probably these's no strict rule about this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me it's really good way to split things up and reuse. later we'd have another message types like assets and with separate sender and status it would easier to just add it to message content and that's it.
This is similar what we have with cell descriptions, we have each for status, sender, reactions, etc. But with VM's it's more self-sufficient in terms if reactive change


import Foundation

public extension DateFormatter {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: this could go to WireFoundation/Utilities so we could reuse it from any packages

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't move to WireUtilities because it's a project. But I think I can move it to WireFoundation in next PR. However I don't have clear understanding of separation and what should go in e and what in WireDesign or WireReusableComponents

Comment on lines -26 to +28
var message: ZMConversationMessage
var deliveryState: ZMDeliveryState
var message: MessageModel
var deliveryState: DeliveryStateModel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: I don't think we have a convention on that yet however to be more specific about the intent of these models we could rename them with the suffix UIModel

shouldShowSender: Bool,
shouldShowStatus: Bool
) -> TextMessageViewModel
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: nice, I think we could use Needle foundation which acts as factories in next PRs

@dmitrysimkin dmitrysimkin requested a review from jullianm May 22, 2025 06:52
Copy link
Contributor

This PR is stale because it has been open 30 days with no activity. Please update it or close it in case is not relevant anymore.

@github-actions github-actions bot added the stale label Jun 23, 2025
@dmitrysimkin dmitrysimkin marked this pull request as draft July 28, 2025 10:00
@netbe netbe added the WIP label Jul 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants