Refactored creation of UserSessionListItemViewData, added inactive session icon

This commit is contained in:
Aleksandrs Proskurins 2022-10-03 15:45:06 +03:00
parent 0b85beb0f4
commit 899539fbeb
10 changed files with 153 additions and 59 deletions

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "user_session_list_item_inactive_session.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,3 @@
<svg width="8" height="15" viewBox="0 0 8 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.33333 0.833984C0.6 0.833984 0 1.43398 0 2.16732L0.00666682 4.28732C0.00666682 4.64065 0.146667 4.97398 0.393333 5.22732L2.66667 7.50065L0.393333 9.78732C0.146667 10.034 0.00666682 10.374 0.00666682 10.7273L0 12.834C0 13.5673 0.6 14.1673 1.33333 14.1673H6.66667C7.4 14.1673 8 13.5673 8 12.834V10.7273C8 10.374 7.86 10.034 7.61333 9.78732L5.33333 7.50065L7.60667 5.23398C7.86 4.98065 8 4.64065 8 4.28732V2.16732C8 1.43398 7.4 0.833984 6.66667 0.833984H1.33333ZM6.66667 10.774V12.1673C6.66667 12.534 6.36667 12.834 6 12.834H2C1.63333 12.834 1.33333 12.534 1.33333 12.1673V10.774C1.33333 10.594 1.40667 10.4273 1.52667 10.3007L4 7.83398L6.47333 10.3073C6.59333 10.4273 6.66667 10.6007 6.66667 10.774Z" fill="#737D8C"/>
</svg>

After

Width:  |  Height:  |  Size: 828 B

View file

@ -0,0 +1,33 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
class InactiveUserSessionLastActivityFormatter {
private static var dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.current
dateFormatter.dateStyle = .medium
dateFormatter.doesRelativeDateFormatting = true
return dateFormatter
}()
func lastActivityDateString(from lastActivityTimestamp: TimeInterval) -> String {
let date = Date(timeIntervalSince1970: lastActivityTimestamp)
return InactiveUserSessionLastActivityFormatter.dateFormatter.string(from: date)
}
}

View file

@ -40,7 +40,6 @@ struct UserOtherSessions: View {
} header: {
UserOtherSessionsHeaderView(viewData: header)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 16.0)
.padding(.top, 24.0)
}
case .clearFilter:

View file

@ -16,7 +16,7 @@
import SwiftUI
struct UserOtherSessionsHeaderViewData {
struct UserOtherSessionsHeaderViewData: Hashable {
var title: String?
var subtitle: String
var iconName: String?
@ -24,6 +24,10 @@ struct UserOtherSessionsHeaderViewData {
struct UserOtherSessionsHeaderView: View {
private var backgroundShape: RoundedRectangle {
RoundedRectangle(cornerRadius: 8)
}
@Environment(\.theme) private var theme
let viewData: UserOtherSessionsHeaderViewData
@ -33,13 +37,15 @@ struct UserOtherSessionsHeaderView: View {
if let iconName = viewData.iconName {
Image(iconName)
.foregroundColor(.red)
.frame(width: 40, height: 40)
.background(theme.colors.background)
.clipShape(backgroundShape)
.shapedBorder(color: theme.colors.quinaryContent, borderWidth: 1.0, shape: backgroundShape)
}
VStack(alignment: .leading, spacing: 0, content: {
if let title = viewData.title {
Text(title)
.font(.callout)
.textCase(.uppercase)
.font(theme.fonts.footnote)
.font(theme.fonts.calloutSB)
.foregroundColor(theme.colors.primaryContent)
.padding(.bottom, 9.0)
}

View file

@ -48,7 +48,7 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
deviceType: .desktop,
isVerified: true,
lastSeenIP: "1.0.0.1",
lastSeenTimestamp: Date().timeIntervalSince1970 - 130_000,
lastSeenTimestamp: Date().timeIntervalSince1970 - 8000000,
isActive: false,
isCurrent: false),
UserSessionInfo(id: "2",

View file

@ -100,6 +100,6 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess
extension Collection where Element == UserSessionInfo {
func asViewData() -> [UserSessionListItemViewData] {
map { UserSessionListItemViewData(session: $0) }
map { UserSessionListItemViewDataFactory().create(from: $0)}
}
}

View file

@ -42,11 +42,16 @@ struct UserSessionListItem: View {
.font(theme.fonts.bodySB)
.foregroundColor(theme.colors.primaryContent)
.multilineTextAlignment(.leading)
Text(viewData.sessionDetails)
.font(theme.fonts.caption1)
.foregroundColor(theme.colors.secondaryContent)
.multilineTextAlignment(.leading)
HStack {
if let sessionDetailsIcon = viewData.sessionDetailsIcon {
Image(sessionDetailsIcon)
.padding(.leading, 2)
}
Text(viewData.sessionDetails)
.font(theme.fonts.caption1)
.foregroundColor(theme.colors.secondaryContent)
.multilineTextAlignment(.leading)
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
@ -69,7 +74,7 @@ struct UserSessionListPreview: View {
var body: some View {
VStack(alignment: .leading, spacing: 0) {
ForEach(userSessionsOverviewService.overviewData.otherSessions) { userSessionInfo in
let viewData = UserSessionListItemViewData(session: userSessionInfo)
let viewData = UserSessionListItemViewDataFactory().create(from: userSessionInfo)
UserSessionListItem(viewData: viewData, onBackgroundTap: { _ in

View file

@ -18,61 +18,17 @@ import Foundation
/// View data for UserSessionListItem
struct UserSessionListItemViewData: Identifiable, Hashable {
private static let userSessionNameFormatter = UserSessionNameFormatter()
private static let lastActivityDateFormatter = UserSessionLastActivityFormatter()
var id: String {
sessionId
}
let sessionId: String
let sessionName: String
let sessionDetails: String
let deviceAvatarViewData: DeviceAvatarViewData
init(sessionId: String,
sessionDisplayName: String?,
deviceType: DeviceType,
isVerified: Bool,
lastActivityDate: TimeInterval?) {
self.sessionId = sessionId
sessionName = Self.userSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName)
sessionDetails = Self.buildSessionDetails(isVerified: isVerified, lastActivityDate: lastActivityDate)
deviceAvatarViewData = DeviceAvatarViewData(deviceType: deviceType, isVerified: isVerified)
}
// MARK: - Private
private static func buildSessionDetails(isVerified: Bool, lastActivityDate: TimeInterval?) -> String {
let sessionDetailsString: String
let sessionStatusText = isVerified ? VectorL10n.userSessionVerifiedShort : VectorL10n.userSessionUnverifiedShort
var lastActivityDateString: String?
if let lastActivityDate = lastActivityDate {
lastActivityDateString = Self.lastActivityDateFormatter.lastActivityDateString(from: lastActivityDate)
}
if let lastActivityDateString = lastActivityDateString, lastActivityDateString.isEmpty == false {
sessionDetailsString = VectorL10n.userSessionItemDetails(sessionStatusText, lastActivityDateString)
} else {
sessionDetailsString = sessionStatusText
}
return sessionDetailsString
}
}
extension UserSessionListItemViewData {
init(session: UserSessionInfo) {
self.init(sessionId: session.id,
sessionDisplayName: session.name,
deviceType: session.deviceType,
isVerified: session.isVerified,
lastActivityDate: session.lastSeenTimestamp)
}
let sessionDetailsIcon: String?
}

View file

@ -0,0 +1,80 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
struct UserSessionListItemViewDataFactory {
private static let userSessionNameFormatter = UserSessionNameFormatter()
private static let lastActivityDateFormatter = UserSessionLastActivityFormatter()
private static let inactiveSessionDateFormatter = InactiveUserSessionLastActivityFormatter()
func create(from session: UserSessionInfo) -> UserSessionListItemViewData {
let sessionName = UserSessionListItemViewDataFactory.userSessionNameFormatter.sessionName(deviceType: session.deviceType,
sessionDisplayName: session.name)
let sessionDetails = buildSessionDetails(isVerified: session.isVerified,
lastActivityDate: session.lastSeenTimestamp,
isActive: session.isActive)
let deviceAvatarViewData = DeviceAvatarViewData(deviceType: session.deviceType,
isVerified: session.isVerified)
return UserSessionListItemViewData(sessionId: session.id,
sessionName: sessionName,
sessionDetails: sessionDetails,
deviceAvatarViewData: deviceAvatarViewData,
sessionDetailsIcon: getSessionDetailsIcon(isActive: session.isActive))
}
private func buildSessionDetails(isVerified: Bool, lastActivityDate: TimeInterval?, isActive: Bool) -> String {
if isActive {
return activeSessionDetails(isVerified: isVerified, lastActivityDate: lastActivityDate)
} else {
return inactiveSessionDetails(lastActivityDate: lastActivityDate)
}
}
private func inactiveSessionDetails(lastActivityDate: TimeInterval?) -> String {
if let lastActivityDate = lastActivityDate {
let lastActivityDateString = Self.inactiveSessionDateFormatter.lastActivityDateString(from: lastActivityDate)
return "Inactive for 90+ days (\(lastActivityDateString))"
}
return "Inactive for 90+ days"
}
private func activeSessionDetails(isVerified: Bool, lastActivityDate: TimeInterval?) -> String {
let sessionDetailsString: String
let sessionStatusText = isVerified ? VectorL10n.userSessionVerifiedShort : VectorL10n.userSessionUnverifiedShort
var lastActivityDateString: String?
if let lastActivityDate = lastActivityDate {
lastActivityDateString = Self.lastActivityDateFormatter.lastActivityDateString(from: lastActivityDate)
}
if let lastActivityDateString = lastActivityDateString, lastActivityDateString.isEmpty == false {
sessionDetailsString = VectorL10n.userSessionItemDetails(sessionStatusText, lastActivityDateString)
} else {
sessionDetailsString = sessionStatusText
}
return sessionDetailsString
}
private func getSessionDetailsIcon(isActive: Bool) -> String? {
isActive ? nil : Asset.Images.userSessionListItemInactiveSession.name
}
}