Merge branch 'release/5.3.9' into feature/TF-1571

This commit is contained in:
gmc 2024-07-20 21:16:34 +02:00
commit 5e134a9ba6
26 changed files with 298 additions and 28 deletions

View File

@ -20,7 +20,7 @@
import Foundation
public enum SortType: String, CaseIterable, Equatable {
case manual
case az
case za
case manual
}

View File

@ -9,6 +9,9 @@
/* Begin PBXBuildFile section */
8F594C422C3027160066562F /* AppleWatchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F594C412C3027160066562F /* AppleWatchView.swift */; };
8F594C442C3054F90066562F /* AppleWatchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F594C432C3054F90066562F /* AppleWatchViewController.swift */; };
8F00E46A2C23953B001F15AD /* SortTokensView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F00E4692C23953B001F15AD /* SortTokensView.swift */; };
8F872DE92C24EE2600160D14 /* SortTokensPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F872DE82C24EE2600160D14 /* SortTokensPresenter.swift */; };
8F872DEB2C24EE5500160D14 /* SortTokensInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F872DEA2C24EE5500160D14 /* SortTokensInteractor.swift */; };
8F5C0C6C2BFFAC8A00D73ADE /* TwoFAS.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 8F5C0C6A2BFFAC8900D73ADE /* TwoFAS.xcdatamodeld */; };
8F5C0C6F2BFFACA900D73ADE /* Sync.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 8F5C0C6D2BFFACA900D73ADE /* Sync.xcdatamodeld */; };
8F7952532C01D1940053F776 /* KeyboardButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F7952522C01D1940053F776 /* KeyboardButtonStyle.swift */; };
@ -1880,6 +1883,9 @@
/* Begin PBXFileReference section */
8F594C412C3027160066562F /* AppleWatchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleWatchView.swift; sourceTree = "<group>"; };
8F594C432C3054F90066562F /* AppleWatchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleWatchViewController.swift; sourceTree = "<group>"; };
8F00E4692C23953B001F15AD /* SortTokensView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortTokensView.swift; sourceTree = "<group>"; };
8F872DE82C24EE2600160D14 /* SortTokensPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortTokensPresenter.swift; sourceTree = "<group>"; };
8F872DEA2C24EE5500160D14 /* SortTokensInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortTokensInteractor.swift; sourceTree = "<group>"; };
8F5C0C6B2BFFAC8900D73ADE /* TwoFAS.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = TwoFAS.xcdatamodel; sourceTree = "<group>"; };
8F5C0C6E2BFFACA900D73ADE /* Sync.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Sync.xcdatamodel; sourceTree = "<group>"; };
8F7952522C01D1940053F776 /* KeyboardButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardButtonStyle.swift; sourceTree = "<group>"; };
@ -3325,6 +3331,16 @@
path = AppleWatch;
sourceTree = "<group>";
};
8F00E4682C239524001F15AD /* SortTokens */ = {
isa = PBXGroup;
children = (
8F00E4692C23953B001F15AD /* SortTokensView.swift */,
8F872DE82C24EE2600160D14 /* SortTokensPresenter.swift */,
8F872DEA2C24EE5500160D14 /* SortTokensInteractor.swift */,
);
path = SortTokens;
sourceTree = "<group>";
};
8F7952502C01D1770053F776 /* CommonSwiftUI */ = {
isa = PBXGroup;
children = (
@ -5008,6 +5024,7 @@
C244BE492BD07AA000F7E566 /* Settings */ = {
isa = PBXGroup;
children = (
8F00E4682C239524001F15AD /* SortTokens */,
C244BE4B2BD07AFF00F7E566 /* Security */,
C244BE4A2BD07AFA00F7E566 /* About */,
C293782F2BD1A218008D5125 /* SettingsView.swift */,
@ -10235,6 +10252,7 @@
C2B86D2A2BC33D3000AAAC63 /* AppDelegateInteractor.swift in Sources */,
C2A4D32E2BC0B1B20001587C /* Token.swift in Sources */,
C268918A2BC4960600713078 /* ServiceListPresenter.swift in Sources */,
8F00E46A2C23953B001F15AD /* SortTokensView.swift in Sources */,
C26983822BCC8326009B3BE2 /* AppPresenter.swift in Sources */,
C274C9D02BAB8ABB008E7212 /* TwoFASWatchApp.swift in Sources */,
C2627F3A2BC72E96009F93A9 /* ServicePresenter.swift in Sources */,
@ -10249,6 +10267,7 @@
C2A4D3312BC0B2540001587C /* TokenGenerator.swift in Sources */,
C29378382BD1BF15008D5125 /* SecurityPresenter.swift in Sources */,
C2A4D32D2BC0B1AD0001587C /* TokenHandler.swift in Sources */,
8F872DE92C24EE2600160D14 /* SortTokensPresenter.swift in Sources */,
C2B86D222BC3058A00AAAC63 /* UserDefaultsRepository.swift in Sources */,
C2B86D342BC3571E00AAAC63 /* ServiceView.swift in Sources */,
C27D99E12BD53AB30008203F /* LogoView.swift in Sources */,
@ -10257,6 +10276,7 @@
C293783A2BD1BF23008D5125 /* SecurityInteractor.swift in Sources */,
C27D99DF2BD52C8A0008203F /* WatchConsts.swift in Sources */,
C2B86D382BC35B8300AAAC63 /* IconRenderer.swift in Sources */,
8F872DEB2C24EE5500160D14 /* SortTokensInteractor.swift in Sources */,
C2B86D322BC356BA00AAAC63 /* ServiceListView.swift in Sources */,
C281A2732BD47D6C0068451C /* SuccessView.swift in Sources */,
C268918F2BC4974800713078 /* Category.swift in Sources */,

View File

@ -1153,6 +1153,8 @@ internal enum T {
internal static let showNextToken = T.tr("Localizable", "settings__show_next_token", fallback: "Show next token")
/// Show next token when current one is about to expire.
internal static let showNextTokenDesc = T.tr("Localizable", "settings__show_next_token_desc", fallback: "Show next token when current one is about to expire.")
/// Sort tokens
internal static let sortTokens = T.tr("Localizable", "settings__sort_tokens", fallback: "Sort tokens")
/// An SSL error has occurred and a secure connection to the server cannot be made. Ensure you have the latest version of the app or try to change the network.
internal static let sslErrorDescription = T.tr("Localizable", "settings__ssl_error_description", fallback: "An SSL error has occurred and a secure connection to the server cannot be made. Ensure you have the latest version of the app or try to change the network.")
/// SSL Error

View File

@ -388,8 +388,6 @@ internal enum T {
internal static func newServices(_ p1: Int) -> String {
return T.tr("Localizable", "backup__new_services", p1, fallback: "%d new services")
}
/// This file is in a newer format version than the one the app supports
internal static let newerFormatNotSupported = T.tr("Localizable", "backup__newer_format_not_supported", fallback: "This file is in a newer format version than the one the app supports")
/// Nothing to import
internal static let noNewServices = T.tr("Localizable", "backup__no_new_services", fallback: "Nothing to import")
/// Either this file is empty, or all the services within are already available in the app
@ -1155,6 +1153,8 @@ internal enum T {
internal static let showNextToken = T.tr("Localizable", "settings__show_next_token", fallback: "Show next token")
/// Show next token when current one is about to expire.
internal static let showNextTokenDesc = T.tr("Localizable", "settings__show_next_token_desc", fallback: "Show next token when current one is about to expire.")
/// Sort tokens
internal static let sortTokens = T.tr("Localizable", "settings__sort_tokens", fallback: "Sort tokens")
/// An SSL error has occurred and a secure connection to the server cannot be made. Ensure you have the latest version of the app or try to change the network.
internal static let sslErrorDescription = T.tr("Localizable", "settings__ssl_error_description", fallback: "An SSL error has occurred and a secure connection to the server cannot be made. Ensure you have the latest version of the app or try to change the network.")
/// SSL Error

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "NaviIconSort.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@ -48,4 +48,8 @@ final class InteractorFactory {
func pinInteractor(variant: PINKeyboardVariant) -> PINKeyboardInteracting {
PINKeyboardInteractor(mainRepository: MainRepositoryImpl.shared, variant: variant)
}
func sortTokensInteractor() -> SortTokensInteracting {
SortTokensInteractor(mainRepository: MainRepositoryImpl.shared)
}
}

View File

@ -37,6 +37,6 @@ extension ServiceListInteractor: ServiceListInteracting {
var hasServices: Bool { mainRepository.hasServices }
func listAllServices() -> [CategoryData] {
mainRepository.listAllServicesWithingCategories(for: nil, sorting: .manual, ids: [])
mainRepository.listAllServicesWithingCategories(for: nil, sorting: mainRepository.sortType ?? .manual, ids: [])
}
}

View File

@ -19,6 +19,12 @@
import SwiftUI
enum SettingsPath: Hashable {
case security
case about
case sortTokens
}
struct SettingsView: View {
@Binding
var path: NavigationPath
@ -26,34 +32,34 @@ struct SettingsView: View {
var body: some View {
List {
NavigationLink(value: SettingsPath.security) {
HStack {
Image(systemName: "lock.fill")
Text(T.Settings.security)
.font(.callout)
.padding(4)
.foregroundStyle(.primary)
}
settingsRow(image: Image(systemName: "lock.fill"), title: T.Settings.security)
}
NavigationLink(value: SettingsPath.sortTokens) {
settingsRow(image: Image(.naviIconSort), title: T.Settings.sortTokens)
}
NavigationLink(value: SettingsPath.about) {
HStack {
Image(systemName: "info.bubble.fill")
Text(T.Settings.about)
.font(.callout)
.padding(4)
.foregroundStyle(.primary)
}
settingsRow(image: Image(systemName: "info.bubble.fill"), title: T.Settings.about)
}
}
.navigationDestination(for: SettingsPath.self) { route in
switch route {
case .security: SecurityView(
path: $path,
presenter: SecurityPresenter(
interactor: InteractorFactory.shared.securityInteractor()
case .security:
SecurityView(
path: $path,
presenter: SecurityPresenter(
interactor: InteractorFactory.shared.securityInteractor()
)
)
case .about:
AboutView()
case .sortTokens:
SortTokensView(
presenter: SortTokensPresenter(
interactor: InteractorFactory.shared.sortTokensInteractor()
)
)
)
case .about: AboutView()
}
}
.containerBackground(.red.gradient, for: .navigation)
@ -63,9 +69,19 @@ struct SettingsView: View {
.navigationBarTitleDisplayMode(.automatic)
.listItemTint(.clear)
}
private func settingsRow(image: Image, title: String) -> some View {
HStack {
image
.foregroundColor(.primary)
Text(title)
.font(.callout)
.padding(4)
.foregroundStyle(.primary)
}
}
}
enum SettingsPath: Hashable {
case security
case about
#Preview {
SettingsView(path: .constant(NavigationPath()))
}

View File

@ -0,0 +1,43 @@
//
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
// Copyright © 2024 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Machnio. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
import CommonWatch
protocol SortTokensInteracting: AnyObject {
var currentSortType: SortType { get }
func set(_ sortType: SortType)
}
final class SortTokensInteractor: SortTokensInteracting {
var currentSortType: SortType {
mainRepository.sortType ?? .manual
}
private let mainRepository: MainRepository
init(mainRepository: MainRepository) {
self.mainRepository = mainRepository
}
func set(_ sortType: SortType) {
Log("SortInteractor - saving new sort type \(sortType)", module: .interactor)
mainRepository.setSortType(sortType)
}
}

View File

@ -0,0 +1,62 @@
//
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
// Copyright © 2024 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Machnio. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
import CommonWatch
import SwiftUI
protocol SortTokensPresenting: ObservableObject {
var currentSortType: SortType { get }
var sortTypes: [SortType] { get }
func onAppear()
func set(_ sortType: SortType)
func image(for sortType: SortType) -> Image
}
final class SortTokensPresenter: SortTokensPresenting {
@Published
var currentSortType: SortType = .manual
var sortTypes: [SortType] {
SortType.allCases
}
private let interactor: SortTokensInteracting
init(interactor: SortTokensInteracting) {
self.interactor = interactor
}
func onAppear() {
currentSortType = interactor.currentSortType
}
func set(_ sortType: SortType) {
interactor.set(sortType)
currentSortType = sortType
}
func image(for sortType: SortType) -> Image {
let configuration: UIImage.SymbolConfiguration = {
return .init(weight: currentSortType == sortType ? .heavy : .regular)
}()
return Image(uiImage: sortType.image(forSelectedOption: sortType, configuration: configuration))
}
}

View File

@ -0,0 +1,108 @@
//
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
// Copyright © 2024 Two Factor Authentication Service, Inc.
// Contributed by Grzegorz Machnio. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
//
import SwiftUI
import UIKit
import CommonWatch
struct SortTokensView<Presenter: SortTokensPresenting>: View {
@ObservedObject
var presenter: Presenter
var body: some View {
List {
ForEach(presenter.sortTypes, id: \.rawValue) { sortType in
HStack {
presenter.image(for: sortType)
.foregroundColor(.primary)
Text(sortType.localized)
.font(.callout)
.fontWeight(presenter.currentSortType == sortType ? .heavy : .regular)
.padding(4)
.foregroundStyle(.primary)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.contentShape(Rectangle())
.onTapGesture {
presenter.set(sortType)
}
}
}
.containerBackground(.red.gradient, for: .navigation)
.listStyle(.carousel)
.environment(\.defaultMinListRowHeight, WatchConsts.minRowHeight)
.navigationTitle(T.Settings.sortTokens)
.navigationBarTitleDisplayMode(.automatic)
.listItemTint(.clear)
.onAppear {
presenter.onAppear()
}
}
private func sortOptionRow(image: Image, title: String) -> some View {
HStack {
image
.foregroundColor(.primary)
Text(title)
.font(.callout)
.padding(4)
.foregroundStyle(.primary)
}
}
}
extension SortType {
var localized: String {
switch self {
case .az: return T.Tokens.sortByAToZ
case .za: return T.Tokens.sortByZToA
case .manual: return T.Tokens.sortByManual
}
}
func image(forSelectedOption option: Self, configuration: UIImage.SymbolConfiguration) -> UIImage {
var image: UIImage?
switch self {
case .az: image = UIImage(systemName: "arrow.down")
case .za: image = UIImage(systemName: "arrow.up")
case .manual: image = UIImage(systemName: "line.3.horizontal")
}
if self == option {
image = image?.withConfiguration(configuration)
}
return image?.withRenderingMode(.alwaysTemplate) ?? UIImage()
}
}
#Preview {
final class SortTokensPresenterMock: SortTokensPresenting {
var currentSortType: CommonWatch.SortType = .manual
var sortTypes: [CommonWatch.SortType] = CommonWatch.SortType.allCases
func onAppear() {}
func set(_ sortType: SortType) {}
func image(for sortType: SortType) -> Image {
.init(systemName: "arrowDown")
}
}
return SortTokensView(presenter: SortTokensPresenterMock())
}