UX Update for user profile screen and room member details (#2822)

This commit is contained in:
Mauro
2024-05-09 17:41:09 +02:00
committed by GitHub
parent 1166400312
commit 783eab2a8a
56 changed files with 243 additions and 147 deletions
@@ -63,6 +63,7 @@
"action_leave_room" = "Leave room"; "action_leave_room" = "Leave room";
"action_manage_account" = "Manage account"; "action_manage_account" = "Manage account";
"action_manage_devices" = "Manage devices"; "action_manage_devices" = "Manage devices";
"action_message" = "Message";
"action_next" = "Next"; "action_next" = "Next";
"action_no" = "No"; "action_no" = "No";
"action_not_now" = "Not now"; "action_not_now" = "Not now";
@@ -176,6 +176,14 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
} }
} }
private func presentCallScreen(roomID: String) async {
guard let roomProxy = await userSession.clientProxy.roomForIdentifier(roomID) else {
return
}
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
}
private func handleRoomRoute(roomID: String, focussedEventID: String? = nil, animated: Bool) async { private func handleRoomRoute(roomID: String, focussedEventID: String? = nil, animated: Bool) async {
guard roomID == self.roomID else { fatalError("Navigation route doesn't belong to this room flow.") } guard roomID == self.roomID else { fatalError("Navigation route doesn't belong to this room flow.") }
@@ -1064,6 +1072,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.tryEvent(.presentUserProfile(userID: userID)) stateMachine.tryEvent(.presentUserProfile(userID: userID))
case .openDirectChat(let roomID): case .openDirectChat(let roomID):
stateMachine.tryEvent(.startChildFlow(roomID: roomID, entryPoint: .room)) stateMachine.tryEvent(.startChildFlow(roomID: roomID, entryPoint: .room))
case .startCall(let roomID):
Task { await self.presentCallScreen(roomID: roomID) }
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
@@ -1087,6 +1097,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
switch action { switch action {
case .openDirectChat(let roomID): case .openDirectChat(let roomID):
stateMachine.tryEvent(.startChildFlow(roomID: roomID, entryPoint: .room)) stateMachine.tryEvent(.startChildFlow(roomID: roomID, entryPoint: .room))
case .startCall(let roomID):
Task { await self.presentCallScreen(roomID: roomID) }
case .dismiss: case .dismiss:
break // Not supported when pushed. break // Not supported when pushed.
} }
@@ -1094,9 +1106,12 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
.store(in: &cancellables) .store(in: &cancellables)
// Replace the RoomMemberDetailsScreen without any animation. // Replace the RoomMemberDetailsScreen without any animation.
navigationStackCoordinator.pop(animated: false) // If this pop and push happens before the previous navigation is completed it might break screen presentation logic
navigationStackCoordinator.push(coordinator, animated: false) { [weak self] in DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
self?.stateMachine.tryEvent(.dismissUserProfile) self.navigationStackCoordinator.pop(animated: false)
self.navigationStackCoordinator.push(coordinator, animated: false) { [weak self] in
self?.stateMachine.tryEvent(.dismissUserProfile)
}
} }
} }
@@ -238,10 +238,10 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.processEvent(.showUserProfileScreen(userID: userID), userInfo: .init(animated: animated)) stateMachine.processEvent(.showUserProfileScreen(userID: userID), userInfo: .init(animated: animated))
case .genericCallLink(let url): case .genericCallLink(let url):
navigationSplitCoordinator.setSheetCoordinator(GenericCallLinkCoordinator(parameters: .init(url: url)), animated: animated) navigationSplitCoordinator.setSheetCoordinator(GenericCallLinkCoordinator(parameters: .init(url: url)), animated: animated)
case .oidcCallback:
break
case .settings, .chatBackupSettings: case .settings, .chatBackupSettings:
settingsFlowCoordinator.handleAppRoute(appRoute, animated: animated) settingsFlowCoordinator.handleAppRoute(appRoute, animated: animated)
case .oidcCallback:
break
} }
} }
@@ -595,6 +595,14 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
navigationSplitCoordinator.setSheetCoordinator(callScreenCoordinator, animated: true) navigationSplitCoordinator.setSheetCoordinator(callScreenCoordinator, animated: true)
} }
private func presentCallScreen(roomID: String) async {
guard let roomProxy = await userSession.clientProxy.roomForIdentifier(roomID) else {
return
}
presentCallScreen(roomProxy: roomProxy)
}
// MARK: Secure backup confirmation // MARK: Secure backup confirmation
private func presentSecureBackupLogoutConfirmationScreen() { private func presentSecureBackupLogoutConfirmationScreen() {
@@ -706,6 +714,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
case .openDirectChat(let roomID): case .openDirectChat(let roomID):
navigationSplitCoordinator.setSheetCoordinator(nil) navigationSplitCoordinator.setSheetCoordinator(nil)
stateMachine.processEvent(.selectRoom(roomID: roomID, entryPoint: .room)) stateMachine.processEvent(.selectRoom(roomID: roomID, entryPoint: .room))
case .startCall(let roomID):
Task { await self.presentCallScreen(roomID: roomID) }
case .dismiss: case .dismiss:
navigationSplitCoordinator.setSheetCoordinator(nil) navigationSplitCoordinator.setSheetCoordinator(nil)
} }
+2
View File
@@ -160,6 +160,8 @@ internal enum L10n {
internal static var actionManageAccount: String { return L10n.tr("Localizable", "action_manage_account") } internal static var actionManageAccount: String { return L10n.tr("Localizable", "action_manage_account") }
/// Manage devices /// Manage devices
internal static var actionManageDevices: String { return L10n.tr("Localizable", "action_manage_devices") } internal static var actionManageDevices: String { return L10n.tr("Localizable", "action_manage_devices") }
/// Message
internal static var actionMessage: String { return L10n.tr("Localizable", "action_message") }
/// Next /// Next
internal static var actionNext: String { return L10n.tr("Localizable", "action_next") } internal static var actionNext: String { return L10n.tr("Localizable", "action_next") }
/// No /// No
@@ -22,19 +22,22 @@ struct FormActionButtonStyle: ButtonStyle {
let title: String let title: String
func makeBody(configuration: Configuration) -> some View { func makeBody(configuration: Configuration) -> some View {
VStack(spacing: 8) { VStack(spacing: 4) {
configuration.label configuration.label
.buttonStyle(.plain) .buttonStyle(.plain)
.foregroundColor(.compound.textPrimary) .foregroundColor(.compound.iconSecondary)
.scaledFrame(size: 54) .scaledFrame(size: 24)
.background {
RoundedRectangle(cornerRadius: 16)
.fill(configuration.isPressed ? Color.compound.bgSubtlePrimary : .compound.bgCanvasDefaultLevel1)
}
Text(title) Text(title)
.foregroundColor(.compound.textSecondary) .foregroundColor(.compound.textPrimary)
.font(.compound.bodyMD) .font(.compound.bodyLG)
}
.padding(.horizontal, 4)
.padding(.vertical, 8)
.frame(maxWidth: .infinity)
.background {
RoundedRectangle(cornerRadius: 14)
.fill(configuration.isPressed ? Color.compound.bgSubtlePrimary : .compound.bgCanvasDefaultLevel1)
} }
} }
} }
@@ -106,6 +106,10 @@ struct AvatarHeaderView<Footer: View>: View {
} }
.frame(maxWidth: .infinity, alignment: .center) .frame(maxWidth: .infinity, alignment: .center)
.listRowBackground(Color.clear) .listRowBackground(Color.clear)
.listRowInsets(EdgeInsets(top: 11,
leading: 0,
bottom: 11,
trailing: 0))
} }
} }
@@ -97,7 +97,7 @@ struct RoomDetailsScreen: View {
@ViewBuilder @ViewBuilder
private var headerSectionShortcuts: some View { private var headerSectionShortcuts: some View {
HStack(spacing: 32) { HStack(spacing: 8) {
ForEach(context.viewState.shortcuts, id: \.self) { shortcut in ForEach(context.viewState.shortcuts, id: \.self) { shortcut in
switch shortcut { switch shortcut {
case .mute: case .mute:
@@ -29,6 +29,7 @@ struct RoomMemberDetailsScreenCoordinatorParameters {
enum RoomMemberDetailsScreenCoordinatorAction { enum RoomMemberDetailsScreenCoordinatorAction {
case openUserProfile case openUserProfile
case openDirectChat(roomID: String) case openDirectChat(roomID: String)
case startCall(roomID: String)
} }
final class RoomMemberDetailsScreenCoordinator: CoordinatorProtocol { final class RoomMemberDetailsScreenCoordinator: CoordinatorProtocol {
@@ -59,6 +60,8 @@ final class RoomMemberDetailsScreenCoordinator: CoordinatorProtocol {
actionsSubject.send(.openUserProfile) actionsSubject.send(.openUserProfile)
case .openDirectChat(let roomID): case .openDirectChat(let roomID):
actionsSubject.send(.openDirectChat(roomID: roomID)) actionsSubject.send(.openDirectChat(roomID: roomID))
case .startCall(let roomID):
actionsSubject.send(.startCall(roomID: roomID))
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
@@ -19,6 +19,7 @@ import Foundation
enum RoomMemberDetailsScreenViewModelAction { enum RoomMemberDetailsScreenViewModelAction {
case openUserProfile case openUserProfile
case openDirectChat(roomID: String) case openDirectChat(roomID: String)
case startCall(roomID: String)
} }
struct RoomMemberDetailsScreenViewState: BindableState { struct RoomMemberDetailsScreenViewState: BindableState {
@@ -26,6 +27,7 @@ struct RoomMemberDetailsScreenViewState: BindableState {
var memberDetails: RoomMemberDetails? var memberDetails: RoomMemberDetails?
var isOwnMemberDetails = false var isOwnMemberDetails = false
var isProcessingIgnoreRequest = false var isProcessingIgnoreRequest = false
var dmRoomID: String?
var bindings: RoomMemberDetailsScreenViewStateBindings var bindings: RoomMemberDetailsScreenViewStateBindings
} }
@@ -83,6 +85,7 @@ enum RoomMemberDetailsScreenViewAction {
case unignoreConfirmed case unignoreConfirmed
case displayAvatar case displayAvatar
case openDirectChat case openDirectChat
case startCall(roomID: String)
} }
enum RoomMemberDetailsScreenError: Hashable { enum RoomMemberDetailsScreenError: Hashable {
@@ -61,6 +61,12 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
roomMemberProxy = member roomMemberProxy = member
state.memberDetails = RoomMemberDetails(withProxy: member) state.memberDetails = RoomMemberDetails(withProxy: member)
state.isOwnMemberDetails = member.userID == roomProxy.ownUserID state.isOwnMemberDetails = member.userID == roomProxy.ownUserID
switch await clientProxy.directRoomForUserID(member.userID) {
case .success(let roomID):
state.dmRoomID = roomID
case .failure:
break
}
case .failure(let error): case .failure(let error):
MXLog.warning("Failed to find member: \(error)") MXLog.warning("Failed to find member: \(error)")
actionsSubject.send(.openUserProfile) actionsSubject.send(.openUserProfile)
@@ -91,6 +97,8 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
Task { await displayFullScreenAvatar() } Task { await displayFullScreenAvatar() }
case .openDirectChat: case .openDirectChat:
Task { await openDirectChat() } Task { await openDirectChat() }
case .startCall(let roomID):
actionsSubject.send(.startCall(roomID: roomID))
} }
} }
@@ -25,7 +25,6 @@ struct RoomMemberDetailsScreen: View {
headerSection headerSection
if context.viewState.memberDetails != nil, !context.viewState.isOwnMemberDetails { if context.viewState.memberDetails != nil, !context.viewState.isOwnMemberDetails {
directChatSection
blockUserSection blockUserSection
} }
} }
@@ -39,6 +38,38 @@ struct RoomMemberDetailsScreen: View {
// MARK: - Private // MARK: - Private
@ViewBuilder
private var otherUserFooter: some View {
HStack(spacing: 8) {
if context.viewState.memberDetails != nil, !context.viewState.isOwnMemberDetails {
Button {
context.send(viewAction: .openDirectChat)
} label: {
CompoundIcon(\.chat)
}
.buttonStyle(FormActionButtonStyle(title: L10n.commonMessage))
.accessibilityIdentifier(A11yIdentifiers.roomMemberDetailsScreen.directChat)
}
if let roomID = context.viewState.dmRoomID {
Button {
context.send(viewAction: .startCall(roomID: roomID))
} label: {
CompoundIcon(\.videoCall)
}
.buttonStyle(FormActionButtonStyle(title: L10n.actionCall))
}
if let permalink = context.viewState.memberDetails?.permalink {
ShareLink(item: permalink) {
CompoundIcon(\.shareIos)
}
.buttonStyle(FormActionButtonStyle(title: L10n.actionShare))
}
}
.padding(.top, 32)
}
@ViewBuilder @ViewBuilder
private var headerSection: some View { private var headerSection: some View {
if let memberDetails = context.viewState.memberDetails { if let memberDetails = context.viewState.memberDetails {
@@ -47,15 +78,7 @@ struct RoomMemberDetailsScreen: View {
imageProvider: context.imageProvider) { imageProvider: context.imageProvider) {
context.send(viewAction: .displayAvatar) context.send(viewAction: .displayAvatar)
} footer: { } footer: {
if let permalink = memberDetails.permalink { otherUserFooter
HStack(spacing: 32) {
ShareLink(item: permalink) {
CompoundIcon(\.shareIos)
}
.buttonStyle(FormActionButtonStyle(title: L10n.actionShare))
}
.padding(.top, 32)
}
} }
} else { } else {
AvatarHeaderView(user: UserProfileProxy(userID: context.viewState.userID), AvatarHeaderView(user: UserProfileProxy(userID: context.viewState.userID),
@@ -65,17 +88,6 @@ struct RoomMemberDetailsScreen: View {
} }
} }
private var directChatSection: some View {
Section {
ListRow(label: .default(title: L10n.commonDirectChat,
icon: \.chat),
kind: .button {
context.send(viewAction: .openDirectChat)
})
.accessibilityIdentifier(A11yIdentifiers.roomMemberDetailsScreen.directChat)
}
}
@ViewBuilder @ViewBuilder
private var blockUserSection: some View { private var blockUserSection: some View {
if let memberDetails = context.viewState.memberDetails { if let memberDetails = context.viewState.memberDetails {
@@ -134,9 +146,16 @@ struct RoomMemberDetailsScreen_Previews: PreviewProvider, TestablePreview {
let roomProxyMock = RoomProxyMock(with: .init(name: "")) let roomProxyMock = RoomProxyMock(with: .init(name: ""))
roomProxyMock.getMemberUserIDReturnValue = .success(member) roomProxyMock.getMemberUserIDReturnValue = .success(member)
let clientProxyMock = ClientProxyMock(.init())
// to avoid mock the call state for the account owner test case
if member.userID != RoomMemberProxyMock.mockMe.userID {
clientProxyMock.directRoomForUserIDReturnValue = .success("roomID")
}
return RoomMemberDetailsScreenViewModel(userID: member.userID, return RoomMemberDetailsScreenViewModel(userID: member.userID,
roomProxy: roomProxyMock, roomProxy: roomProxyMock,
clientProxy: ClientProxyMock(.init()), clientProxy: clientProxyMock,
mediaProvider: MockMediaProvider(), mediaProvider: MockMediaProvider(),
userIndicatorController: ServiceLocator.shared.userIndicatorController, userIndicatorController: ServiceLocator.shared.userIndicatorController,
analytics: ServiceLocator.shared.analytics) analytics: ServiceLocator.shared.analytics)
@@ -28,6 +28,7 @@ struct UserProfileScreenCoordinatorParameters {
enum UserProfileScreenCoordinatorAction { enum UserProfileScreenCoordinatorAction {
case openDirectChat(roomID: String) case openDirectChat(roomID: String)
case startCall(roomID: String)
case dismiss case dismiss
} }
@@ -57,6 +58,8 @@ final class UserProfileScreenCoordinator: CoordinatorProtocol {
switch action { switch action {
case .openDirectChat(let roomID): case .openDirectChat(let roomID):
actionsSubject.send(.openDirectChat(roomID: roomID)) actionsSubject.send(.openDirectChat(roomID: roomID))
case .startCall(let roomID):
actionsSubject.send(.startCall(roomID: roomID))
case .dismiss: case .dismiss:
actionsSubject.send(.dismiss) actionsSubject.send(.dismiss)
} }
@@ -18,6 +18,7 @@ import Foundation
enum UserProfileScreenViewModelAction { enum UserProfileScreenViewModelAction {
case openDirectChat(roomID: String) case openDirectChat(roomID: String)
case startCall(roomID: String)
case dismiss case dismiss
} }
@@ -28,6 +29,7 @@ struct UserProfileScreenViewState: BindableState {
var userProfile: UserProfileProxy? var userProfile: UserProfileProxy?
var permalink: URL? var permalink: URL?
var dmRoomID: String?
var bindings: UserProfileScreenViewStateBindings var bindings: UserProfileScreenViewStateBindings
} }
@@ -42,6 +44,7 @@ struct UserProfileScreenViewStateBindings {
enum UserProfileScreenViewAction { enum UserProfileScreenViewAction {
case displayAvatar case displayAvatar
case openDirectChat case openDirectChat
case startCall(roomID: String)
case dismiss case dismiss
} }
@@ -59,6 +59,12 @@ class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScr
case .success(let userProfile): case .success(let userProfile):
state.userProfile = userProfile state.userProfile = userProfile
state.permalink = (try? matrixToUserPermalink(userId: userID)).flatMap(URL.init(string:)) state.permalink = (try? matrixToUserPermalink(userId: userID)).flatMap(URL.init(string:))
switch await clientProxy.directRoomForUserID(userProfile.userID) {
case .success(let roomID):
state.dmRoomID = roomID
case .failure:
break
}
case .failure(let error): case .failure(let error):
state.bindings.alertInfo = .init(id: .unknown) state.bindings.alertInfo = .init(id: .unknown)
MXLog.error("Failed to find user profile: \(error)") MXLog.error("Failed to find user profile: \(error)")
@@ -81,6 +87,8 @@ class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScr
Task { await displayFullScreenAvatar() } Task { await displayFullScreenAvatar() }
case .openDirectChat: case .openDirectChat:
Task { await openDirectChat() } Task { await openDirectChat() }
case .startCall(let roomID):
actionsSubject.send(.startCall(roomID: roomID))
case .dismiss: case .dismiss:
actionsSubject.send(.dismiss) actionsSubject.send(.dismiss)
} }
@@ -23,10 +23,6 @@ struct UserProfileScreen: View {
var body: some View { var body: some View {
Form { Form {
headerSection headerSection
if context.viewState.userProfile != nil, !context.viewState.isOwnUser {
directChatSection
}
} }
.compoundList() .compoundList()
.navigationTitle(L10n.screenRoomMemberDetailsTitle) .navigationTitle(L10n.screenRoomMemberDetailsTitle)
@@ -39,6 +35,38 @@ struct UserProfileScreen: View {
// MARK: - Private // MARK: - Private
@ViewBuilder
private var otherUserFooter: some View {
HStack(spacing: 8) {
if context.viewState.userProfile != nil, !context.viewState.isOwnUser {
Button {
context.send(viewAction: .openDirectChat)
} label: {
CompoundIcon(\.chat)
}
.buttonStyle(FormActionButtonStyle(title: L10n.commonMessage))
.accessibilityIdentifier(A11yIdentifiers.roomMemberDetailsScreen.directChat)
}
if let roomID = context.viewState.dmRoomID {
Button {
context.send(viewAction: .startCall(roomID: roomID))
} label: {
CompoundIcon(\.videoCall)
}
.buttonStyle(FormActionButtonStyle(title: L10n.actionCall))
}
if let permalink = context.viewState.permalink {
ShareLink(item: permalink) {
CompoundIcon(\.shareIos)
}
.buttonStyle(FormActionButtonStyle(title: L10n.actionShare))
}
}
.padding(.top, 32)
}
@ViewBuilder @ViewBuilder
private var headerSection: some View { private var headerSection: some View {
if let userProfile = context.viewState.userProfile { if let userProfile = context.viewState.userProfile {
@@ -47,15 +75,7 @@ struct UserProfileScreen: View {
imageProvider: context.imageProvider) { imageProvider: context.imageProvider) {
context.send(viewAction: .displayAvatar) context.send(viewAction: .displayAvatar)
} footer: { } footer: {
if let permalink = context.viewState.permalink { otherUserFooter
HStack(spacing: 32) {
ShareLink(item: permalink) {
CompoundIcon(\.shareIos)
}
.buttonStyle(FormActionButtonStyle(title: L10n.actionShare))
}
.padding(.top, 32)
}
} }
} else { } else {
AvatarHeaderView(user: UserProfileProxy(userID: context.viewState.userID), AvatarHeaderView(user: UserProfileProxy(userID: context.viewState.userID),
@@ -65,17 +85,6 @@ struct UserProfileScreen: View {
} }
} }
private var directChatSection: some View {
Section {
ListRow(label: .default(title: L10n.commonDirectChat,
icon: \.chat),
kind: .button {
context.send(viewAction: .openDirectChat)
})
.accessibilityIdentifier(A11yIdentifiers.roomMemberDetailsScreen.directChat)
}
}
@ToolbarContentBuilder @ToolbarContentBuilder
private var toolbar: some ToolbarContent { private var toolbar: some ToolbarContent {
if context.viewState.isPresentedModally { if context.viewState.isPresentedModally {
@@ -104,11 +113,15 @@ struct UserProfileScreen_Previews: PreviewProvider, TestablePreview {
} }
static func makeViewModel(userID: String) -> UserProfileScreenViewModel { static func makeViewModel(userID: String) -> UserProfileScreenViewModel {
UserProfileScreenViewModel(userID: userID, let clientProxyMock = ClientProxyMock(.init())
isPresentedModally: false, if userID != RoomMemberProxyMock.mockMe.userID {
clientProxy: ClientProxyMock(.init()), clientProxyMock.directRoomForUserIDReturnValue = .success("roomID")
mediaProvider: MockMediaProvider(), }
userIndicatorController: ServiceLocator.shared.userIndicatorController, return UserProfileScreenViewModel(userID: userID,
analytics: ServiceLocator.shared.analytics) isPresentedModally: false,
clientProxy: clientProxyMock,
mediaProvider: MockMediaProvider(),
userIndicatorController: ServiceLocator.shared.userIndicatorController,
analytics: ServiceLocator.shared.analytics)
} }
} }
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:c2eddb648a0298d35c9ed697aefaf89ce609f58c0d3ba9fdb0b2c1cd892594fd oid sha256:a5b9f5e0db05f4beda9e4d7cd08c8a353a85c64a21988c6c9ec71956b4444eae
size 123637 size 124255
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:c2eddb648a0298d35c9ed697aefaf89ce609f58c0d3ba9fdb0b2c1cd892594fd oid sha256:a5b9f5e0db05f4beda9e4d7cd08c8a353a85c64a21988c6c9ec71956b4444eae
size 123637 size 124255
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e02beede3f2a35a44b403435c8634d20cc9a120141dd7973f158c3c755914247 oid sha256:8c04d77ffec96df07cbbe034347a267f56c832d1fccac0d8fde0c9a592525349
size 73050 size 73216
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e02beede3f2a35a44b403435c8634d20cc9a120141dd7973f158c3c755914247 oid sha256:8c04d77ffec96df07cbbe034347a267f56c832d1fccac0d8fde0c9a592525349
size 73050 size 73216
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:a70bb5fdd345057464613c8d8d915bb30aeaf340b709299270c100482b4bf4aa oid sha256:0fea52f76951ad2252887444caeb73ca9c8998275cdafca51e0aef97cdf745b9
size 80297 size 81212
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:a70bb5fdd345057464613c8d8d915bb30aeaf340b709299270c100482b4bf4aa oid sha256:0fea52f76951ad2252887444caeb73ca9c8998275cdafca51e0aef97cdf745b9
size 80297 size 81212
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:cf3ba4bb678c7c5c3c5abbef122cf97eef5df819470812f501b76528beb12ddb oid sha256:b07080055d6ff55e0e2d326f9696d5bb05240702251a881052c63fd0595e102c
size 35416 size 36488
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:cf3ba4bb678c7c5c3c5abbef122cf97eef5df819470812f501b76528beb12ddb oid sha256:b07080055d6ff55e0e2d326f9696d5bb05240702251a881052c63fd0595e102c
size 35416 size 36488
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:99457e77405391a3be5b12d4513053bc22ee38146b0a026480206d622b5340cb oid sha256:993bde979b9cf5177f1b04069cd5dbb2058fb699f1dd531aa872d3b413cb810c
size 232976 size 243685
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8cf3d6d752e771fca995ad3d7283fbee60bfd7614d22097f033b3bf1f91ab78b oid sha256:edbb5601a3b3dd48ff43910cad90b1cabff8ea99392d5e406c2ea645801b4b98
size 233824 size 235619
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:b31fa22226801415c912eba75810f02d40ac49a6fd1a4381d626ea1bb2bfee0c oid sha256:53f61afa3672b847e50aee527a24c1cdf45b7892fc1155f5fe35e19459a87ddf
size 174497 size 180791
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:75751372beb8547f0a22636a16f494c48f850e8e2de3e663e3bf8b3c99380265 oid sha256:9308508c97fe4beb9dfede048f625ee56cac2d51dba45eb98770eb55439406bd
size 295815 size 301330
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:dd8485c8079834b2346e203f6ce396ca7c8704d353fb34d9453804d7d72a6a47 oid sha256:05e5b40e37fd79e246442ac554d1c9524aa69d79c5aa3b3f0c38ae083b8aeb52
size 278745 size 282353
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:78a38dcd6b66c838c6c3ec33098a11bc064d2c33f4b296973ad7b9f65c55248a oid sha256:8a84cd11210a1a2ff6ee4b263b6234db0506fa1ddcbf65b313a2a23f4073460d
size 227514 size 238709
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:512606e0e6220ff71d4fce5441021e85f0f30c7ec7f1f29c584700113c8da9d1 oid sha256:962394bd9d8afb3061818822d1745e43bf79e1cce148fe40ec851bc901afe3cf
size 179884 size 181482
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:64fce215cb9b466276f55f01c04c6a32f11c2108c09a96123a6e8ff573900936 oid sha256:3b090ad72c576f703f002b6ae5cbb5b143f491a856eca9b4c02c9bd99b3f2809
size 158911 size 160547
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:51267cc2b70adcbbeb6c5df6d7560c9a49c58437cb04ab31d6fd05b7d3ad7bf9 oid sha256:d1d6105a1c59caf5c6e7edca31faa51667feabde732ef7c9951d2c35937c74cb
size 114025 size 114559
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8f87d0ddaf6ae6b287f75a4963a749e6350c185b850188b52ff4ad7d50cefa77 oid sha256:d70f72e041f7e569fd99f7e8a63e62bbb92a2aaedf03cdbd67a158d9fa885145
size 210205 size 222061
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:58018bea77f7b28dfa0bbfe6bf0b16398bd159af2bd245f3d483eb1e99c68007 oid sha256:eec17eea97add9dec4b238f458905ad40dcdc6f4d302bc3aca6f1f424a30bd3e
size 196330 size 198643
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:c7625aa1976b07cc0759f2431f6a44ada887f3927c816be128a45d14642c25da oid sha256:79a57e83b5432cd57652f444c96af0bff54ab20b8fbca663e38261237acc2f2c
size 152736 size 153273
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:bee936322c7945f72ab11e079e26c367fd782ed883a05ec8c33da82385522ba6 oid sha256:b59dc444120d040baba15dc5322e00acf10c8afeba97fddbae2aea31a88ff7bf
size 119262 size 120302
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8ec35e3ebcd15298f7d4f7e196c593bb7e23f4574c84bcd4c6f12567f1748c9a oid sha256:e69ee9a2b3e21dce64a57d5664173480b49b49e7e70952ccfb8a74837e7e4297
size 124211 size 127058
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f3877939ae180b3a1aae3b972de0d22f0f8eb27e46b23250349873e4090d4a52 oid sha256:00ae153e9298e2171ed34572aa11db1e82faca3a2654c7fd3f76c0cf8509369e
size 139981 size 143045
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8ff200cd13ec2dd401b87a29c8e052d2cbd117abd97580d9c4ea013c8bd53f9a oid sha256:08b1f2e462d2b3fee6ef422fae0552195efb849dab814174d506f98a7a71a9fb
size 122432 size 124405
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:3405f914a5d00764b852d8c5fcbb34b1f0a01adc84e629e1e15892a22425476a oid sha256:f47d3d5d886babe9176b2e1641eedd4102525648fe2e04e33db7db587b31d8d9
size 138854 size 142258
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:654f4ed07206e3b447a9eddd8fdd3656f9c619261656a59abd40cc83d8ccbe93 oid sha256:41d004bf2eead94cd40c0ffbb344bc5b471a5aa811a4164b3c5c7513acbb28ad
size 154414 size 158265
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0a1a9da6128d63577051c4e5f8038f289a663684cbfebaac2b2d0d8cb0c25a9f oid sha256:f6f8727b80f45320b3e11a836115a0ee7236a6cf709833b364718679c574cdc5
size 68084 size 68755
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:c4d8f29bf227dad82d7205b0a1de7cde3ff43f23ea75dd5b3baded488f26e1b9 oid sha256:2c0a42eccfde2736e4ba39ecd0f130a5eea4aa08b824f21eef4d2edc4246b3a0
size 75340 size 75605
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2953ae81a69f0adeb0445b15616e2bf5ff54dac822c3ed2010ae9b4804322b95 oid sha256:e3962a70728c6f3c4fd976e3d7d83badfe618094a13fa5f824b93a0bd9836ad3
size 87984 size 88597
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:380f908b5ea794fcd00d8e2ed30cdbc4e3d8b252e41492e03b0326186e0f049e oid sha256:17b2d906a1f52c2729b3c0f08f0abba2d1aaa188661df2c559ff6256bf2a6714
size 70245 size 71214
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2c0e26cb0231c9abd1d57f4d38d899d93a7a8ab32259b1221991b8ac88b3bcc6 oid sha256:9552cad4fffb2f1ab0855ecfaa7b1f4968cb83a4501961665cb62f3988fc7054
size 87550 size 90543
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:826249c063050e52da83c81e34d0cee9023c536298df7b4db59057ead3f01c23 oid sha256:4404ca231f490c501c7f134a57757fa7822ceb1a627a50316de829e24c72b7dc
size 100501 size 103982
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e42a43a6820048238e353125067d7efc2c32c77dc5633a50a2060b468eceeefb oid sha256:ca1cdb9c0f157fe4869240c986f884eebd0b5205812734e07ee034f392a4ca17
size 102403 size 103673
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:a1cf3ad4bcc2e661971a8019be778757b640a85bed8debf7b6d31003325f2e05 oid sha256:468214e5bb420cc0536b2b913fc3d45c8fcf795b45a4238a9bd54b9d69036b5b
size 112587 size 116265
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:7f9378f6ee8fe77ec5038cf87a77fad3261a79c1addb785e04db12b8a9b59003 oid sha256:248b93a7d9532693c451c0f7ffe0597b580f143ff9cc5e5ed638416d51d1f8e6
size 105673 size 107933
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:b9a78887dd1a44c5d39c92c4e474c9bab5c5a858768bb990300cc40d80501561 oid sha256:624bf89fe59faf35f169a18c06cf8985eda4d332c61dacd294cdcafb3a635b1a
size 121404 size 125279
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:bcb33f529761684c7564df444d2808ea72a1e6eee290c349fce6bda2e239344d oid sha256:9a08d07335e8af83c60875a53eda8da7c2e06591cdbe00a2447841c04eec982d
size 54899 size 55310
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:d555f65dd5a7296e1da5798ed7ff710b6bbd652b2dc801d1aa4decbe3a064ba3 oid sha256:61eebfd560410b170993785e0aae41d0e1560e1d0ee6576aadc873329674b30e
size 62749 size 66231
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:7c7715d26bb104bb82c18ccc6ea4d363e833fe91f84219ecee57f2b66ca857c7 oid sha256:4b86bd4439129b9132ad9a317c775b7758171d9391017fad47bc7e42173d93b8
size 57234 size 58167
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:d0d240b2ab4fbaaa18ea8817dcaf6ac33725cc2f73dea06c372336288bc04a0a oid sha256:759ff727547a456b9d1a11b980351afa0fc0e353a167f90855d03d5c4b676807
size 69921 size 75796
+1
View File
@@ -0,0 +1 @@
The UX of the profile of other users has been updated.