mirror of
https://github.com/twofas/2fas-ios.git
synced 2024-11-22 02:10:19 +01:00
TF-250 Service view [WIP]
This commit is contained in:
parent
2921002f33
commit
1b43a4431f
@ -396,6 +396,8 @@
|
||||
C2625F9228BBB87700D84C5C /* AboutModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2625F9128BBB87700D84C5C /* AboutModels.swift */; };
|
||||
C2625F9428BBC01900D84C5C /* AboutPresenter+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2625F9328BBC01900D84C5C /* AboutPresenter+Menu.swift */; };
|
||||
C2625F9628BBC86B00D84C5C /* AboutFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2625F9528BBC86B00D84C5C /* AboutFooter.swift */; };
|
||||
C2627F3A2BC72E96009F93A9 /* ServicePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2627F392BC72E96009F93A9 /* ServicePresenter.swift */; };
|
||||
C2627F3C2BC72EA0009F93A9 /* ServiceInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2627F3B2BC72EA0009F93A9 /* ServiceInteractor.swift */; };
|
||||
C2633F1F265B045F0034B836 /* UIApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2633F1E265B045F0034B836 /* UIApplication.swift */; };
|
||||
C263F45A29900DED009B0837 /* MainMenuModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = C263F45929900DED009B0837 /* MainMenuModels.swift */; };
|
||||
C263F45E29901C4F009B0837 /* MainSplitFlowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C263F45D29901C4F009B0837 /* MainSplitFlowController.swift */; };
|
||||
@ -981,8 +983,6 @@
|
||||
C2D07EE626A45C0400AF8E7B /* IntentsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2C3AE31269E274700506ACF /* IntentsUI.framework */; };
|
||||
C2D07EEB26A47B8100AF8E7B /* CodeEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D07EE926A475F400AF8E7B /* CodeEntry.swift */; };
|
||||
C2D291DC2955FC9E0084FE1E /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = C2D291DB2955FC9E0084FE1E /* Settings.bundle */; };
|
||||
C2D2AFF82BB8C8EE00B91435 /* TokensList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D2AFF72BB8C8EE00B91435 /* TokensList.swift */; };
|
||||
C2D2AFFA2BB8C93C00B91435 /* TokensListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D2AFF92BB8C93C00B91435 /* TokensListPresenter.swift */; };
|
||||
C2D39DDD28232A4E00E864E9 /* NewsEntity+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D39DDB28232A4E00E864E9 /* NewsEntity+CoreDataClass.swift */; };
|
||||
C2D39DDE28232A4E00E864E9 /* NewsEntity+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D39DDC28232A4E00E864E9 /* NewsEntity+CoreDataProperties.swift */; };
|
||||
C2D39DE32823302200E864E9 /* StorageRepositoryImpl+News.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D39DE22823302200E864E9 /* StorageRepositoryImpl+News.swift */; };
|
||||
@ -2282,6 +2282,8 @@
|
||||
C2625F9128BBB87700D84C5C /* AboutModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutModels.swift; sourceTree = "<group>"; };
|
||||
C2625F9328BBC01900D84C5C /* AboutPresenter+Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AboutPresenter+Menu.swift"; sourceTree = "<group>"; };
|
||||
C2625F9528BBC86B00D84C5C /* AboutFooter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutFooter.swift; sourceTree = "<group>"; };
|
||||
C2627F392BC72E96009F93A9 /* ServicePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicePresenter.swift; sourceTree = "<group>"; };
|
||||
C2627F3B2BC72EA0009F93A9 /* ServiceInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceInteractor.swift; sourceTree = "<group>"; };
|
||||
C262E83529E9AA96008D148E /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/TwoFASWidget.strings; sourceTree = "<group>"; };
|
||||
C262E83629E9AA96008D148E /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
C262E83729E9AA97008D148E /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
@ -2772,8 +2774,6 @@
|
||||
C2D07EE726A468D000AF8E7B /* LabelImageRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelImageRenderer.swift; sourceTree = "<group>"; };
|
||||
C2D07EE926A475F400AF8E7B /* CodeEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEntry.swift; sourceTree = "<group>"; };
|
||||
C2D291DB2955FC9E0084FE1E /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
||||
C2D2AFF72BB8C8EE00B91435 /* TokensList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokensList.swift; sourceTree = "<group>"; };
|
||||
C2D2AFF92BB8C93C00B91435 /* TokensListPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokensListPresenter.swift; sourceTree = "<group>"; };
|
||||
C2D39DD92823184500E864E9 /* ListNewsNetworkInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListNewsNetworkInteractor.swift; sourceTree = "<group>"; };
|
||||
C2D39DDB28232A4E00E864E9 /* NewsEntity+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NewsEntity+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
C2D39DDC28232A4E00E864E9 /* NewsEntity+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NewsEntity+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
@ -5492,6 +5492,16 @@
|
||||
path = Types;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C2627F382BC72E19009F93A9 /* Service */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C2627F3B2BC72EA0009F93A9 /* ServiceInteractor.swift */,
|
||||
C2627F392BC72E96009F93A9 /* ServicePresenter.swift */,
|
||||
C2B86D332BC3571E00AAAC63 /* ServiceView.swift */,
|
||||
);
|
||||
path = Service;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C26764A628723CFD00D468B2 /* CategorySelection */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -5657,7 +5667,6 @@
|
||||
C274C9CE2BAB8ABB008E7212 /* TwoFASWatch Watch App */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C2D2AFF62BB8B8CD00B91435 /* TokensList */,
|
||||
C274CAA02BAB98A5008E7212 /* TwoFASWatch Watch App.entitlements */,
|
||||
C274C9CF2BAB8ABB008E7212 /* TwoFASWatchApp.swift */,
|
||||
C274C9D12BAB8ABB008E7212 /* ContentView.swift */,
|
||||
@ -5676,8 +5685,8 @@
|
||||
C2B86D2B2BC3492A00AAAC63 /* Service.swift */,
|
||||
C268918E2BC4974800713078 /* Category.swift */,
|
||||
C268918B2BC4960C00713078 /* ServiceList */,
|
||||
C2627F382BC72E19009F93A9 /* Service */,
|
||||
C2B86D352BC3574900AAAC63 /* SettingsView.swift */,
|
||||
C2B86D332BC3571E00AAAC63 /* ServiceView.swift */,
|
||||
C2B86D372BC35B8300AAAC63 /* IconRenderer.swift */,
|
||||
);
|
||||
path = "TwoFASWatch Watch App";
|
||||
@ -7261,15 +7270,6 @@
|
||||
path = Generated;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C2D2AFF62BB8B8CD00B91435 /* TokensList */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C2D2AFF72BB8C8EE00B91435 /* TokensList.swift */,
|
||||
C2D2AFF92BB8C93C00B91435 /* TokensListPresenter.swift */,
|
||||
);
|
||||
path = TokensList;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C2D39DDF28232A5600E864E9 /* News */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -10066,12 +10066,12 @@
|
||||
C2A4D3152BC097F80001587C /* MainRepository.swift in Sources */,
|
||||
C2B86D2A2BC33D3000AAAC63 /* AppDelegateInteractor.swift in Sources */,
|
||||
C2A4D32E2BC0B1B20001587C /* Token.swift in Sources */,
|
||||
C2D2AFFA2BB8C93C00B91435 /* TokensListPresenter.swift in Sources */,
|
||||
C268918A2BC4960600713078 /* ServiceListPresenter.swift in Sources */,
|
||||
C2A1B3B82BB608C900D6B923 /* PINKeyboardLayout.swift in Sources */,
|
||||
C274C9D22BAB8ABB008E7212 /* ContentView.swift in Sources */,
|
||||
C274C9D02BAB8ABB008E7212 /* TwoFASWatchApp.swift in Sources */,
|
||||
C2B86D362BC3574900AAAC63 /* SettingsView.swift in Sources */,
|
||||
C2627F3A2BC72E96009F93A9 /* ServicePresenter.swift in Sources */,
|
||||
C2B86D282BC32FA300AAAC63 /* InteractorFactory.swift in Sources */,
|
||||
C2A4D32F2BC0B1B50001587C /* Generator.swift in Sources */,
|
||||
C2A1B3B42BB6071C00D6B923 /* PINKeyboard.swift in Sources */,
|
||||
@ -10082,7 +10082,6 @@
|
||||
C2B86D222BC3058A00AAAC63 /* UserDefaultsRepository.swift in Sources */,
|
||||
C2B86D342BC3571E00AAAC63 /* ServiceView.swift in Sources */,
|
||||
C2A4D3322BC0B2B30001587C /* String+.swift in Sources */,
|
||||
C2D2AFF82BB8C8EE00B91435 /* TokensList.swift in Sources */,
|
||||
C2B86D2C2BC3492A00AAAC63 /* Service.swift in Sources */,
|
||||
C2B86D382BC35B8300AAAC63 /* IconRenderer.swift in Sources */,
|
||||
C2B86D322BC356BA00AAAC63 /* ServiceListView.swift in Sources */,
|
||||
@ -10090,6 +10089,7 @@
|
||||
C2B86D302BC349AE00AAAC63 /* MainPresenter.swift in Sources */,
|
||||
C2B86D262BC32F9200AAAC63 /* MainInteractor.swift in Sources */,
|
||||
C2B86D242BC3070F00AAAC63 /* AppPIN.swift in Sources */,
|
||||
C2627F3C2BC72EA0009F93A9 /* ServiceInteractor.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -31,4 +31,11 @@ final class InteractorFactory {
|
||||
func serviceListInteractor() -> ServiceListInteracting {
|
||||
ServiceListInteractor(mainRepository: MainRepositoryImpl.shared)
|
||||
}
|
||||
|
||||
func serviceInteractor(service: Service) -> ServiceInteracting {
|
||||
ServiceInteractor(
|
||||
mainRepository: MainRepositoryImpl.shared,
|
||||
service: service
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,12 @@ struct MainView: View {
|
||||
if !presenter.favoriteList.isEmpty {
|
||||
Section(header: Text("Favorite Services")) {
|
||||
ForEach(presenter.favoriteList, id: \.self) { service in
|
||||
NavigationLink(destination: ServiceView(service: service)) {
|
||||
NavigationLink(destination: ServiceView(
|
||||
presenter: ServicePresenter(
|
||||
interactor: InteractorFactory.shared.serviceInteractor(service: service)
|
||||
)
|
||||
)
|
||||
) {
|
||||
VStack(alignment: .leading) {
|
||||
Text(service.name)
|
||||
.font(.callout)
|
||||
|
109
TwoFAS/TwoFASWatch Watch App/Service/ServiceInteractor.swift
Normal file
109
TwoFAS/TwoFASWatch Watch App/Service/ServiceInteractor.swift
Normal file
@ -0,0 +1,109 @@
|
||||
//
|
||||
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
|
||||
// Copyright © 2024 Two Factor Authentication Service, Inc.
|
||||
// Contributed by Zbigniew Cisiński. 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 Foundation
|
||||
import CommonWatch
|
||||
|
||||
protocol ServiceInteracting: AnyObject {
|
||||
var service: Service { get }
|
||||
|
||||
func initialize()
|
||||
func token(for date: Date) -> TokenValue
|
||||
func timelineEntries(for date: Date) -> [Date]
|
||||
func timeToNextDate(for date: Date) -> Date
|
||||
}
|
||||
|
||||
final class ServiceInteractor {
|
||||
private let mainRepository: MainRepository
|
||||
let service: Service
|
||||
|
||||
private let calendar = Calendar.current
|
||||
|
||||
private var serviceData: ServiceData?
|
||||
|
||||
init(mainRepository: MainRepository, service: Service) {
|
||||
self.mainRepository = mainRepository
|
||||
self.service = service
|
||||
}
|
||||
}
|
||||
|
||||
extension ServiceInteractor: ServiceInteracting {
|
||||
func initialize() {
|
||||
serviceData = mainRepository.service(for: service.id)
|
||||
}
|
||||
|
||||
func token(for date: Date) -> TokenValue {
|
||||
guard let serviceData else { return "" }
|
||||
return mainRepository.token(
|
||||
secret: serviceData.secret,
|
||||
time: date,
|
||||
digits: Digits.create(serviceData.digits),
|
||||
period: Period.create(serviceData.period),
|
||||
algorithm: serviceData.algorithm,
|
||||
counter: 0,
|
||||
tokenType: serviceData.tokenType
|
||||
)
|
||||
}
|
||||
|
||||
func timeToNextDate(for date: Date) -> Date {
|
||||
guard let serviceData else { return Date.now }
|
||||
|
||||
let secondsToNewOne: Int = {
|
||||
let period = serviceData.period
|
||||
let currentSeconds: Int = calendar.component(.second, from: date)
|
||||
if currentSeconds >= period {
|
||||
let times = (currentSeconds / period) + 1
|
||||
return times * period - currentSeconds
|
||||
}
|
||||
return period - currentSeconds
|
||||
}()
|
||||
|
||||
return calendar.date(byAdding: .second, value: secondsToNewOne, to: date)!
|
||||
}
|
||||
|
||||
func timelineEntries(for date: Date) -> [Date] {
|
||||
guard let serviceData else { return [] }
|
||||
var entries: [Date] = []
|
||||
|
||||
let smallestIncrement = serviceData.period
|
||||
|
||||
let seconds = calendar.component(.second, from: date)
|
||||
let offset: Int = {
|
||||
let rest = seconds % smallestIncrement
|
||||
return smallestIncrement - rest
|
||||
}()
|
||||
let upTo = 256
|
||||
|
||||
for i in 0 ..< upTo {
|
||||
let currentOffset: Int = {
|
||||
if i == 0 {
|
||||
return 0
|
||||
} else if i == 1 {
|
||||
return offset
|
||||
}
|
||||
return offset + smallestIncrement * (i - 1)
|
||||
}()
|
||||
let entryDate = calendar.date(byAdding: .second, value: currentOffset, to: date)!
|
||||
|
||||
entries.append(entryDate)
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
}
|
@ -17,27 +17,35 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Foundation
|
||||
import CommonWatch
|
||||
|
||||
struct TokensList: View {
|
||||
@ObservedObject
|
||||
private var presenter: TokensListPresenter
|
||||
final class ServicePresenter: ObservableObject {
|
||||
@Published var name = ""
|
||||
@Published var additionalInfo: String?
|
||||
@Published var service: Service
|
||||
|
||||
// @State
|
||||
// private var selectedItem: TokenEntry
|
||||
private let interactor: ServiceInteracting
|
||||
|
||||
init(presenter: TokensListPresenter) {
|
||||
self.presenter = presenter
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(presenter.list, id: \.self) { item in
|
||||
VStack {
|
||||
Text(item.name)
|
||||
Text(item.additionalInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
init(interactor: ServiceInteracting) {
|
||||
self.interactor = interactor
|
||||
|
||||
name = interactor.service.name
|
||||
additionalInfo = interactor.service.additionalInfo
|
||||
service = interactor.service
|
||||
}
|
||||
}
|
||||
|
||||
extension ServicePresenter {
|
||||
func calculateToken(for date: Date) -> TokenValue {
|
||||
interactor.token(for: date)
|
||||
}
|
||||
|
||||
func timelineEntries() -> [Date] {
|
||||
interactor.timelineEntries(for: Date())
|
||||
}
|
||||
|
||||
func timeToNextDate(for date: Date) -> Date {
|
||||
interactor.timeToNextDate(for: date)
|
||||
}
|
||||
}
|
@ -23,40 +23,41 @@ import CommonWatch
|
||||
struct ServiceView: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
@ObservedObject
|
||||
var presenter: ServicePresenter
|
||||
|
||||
private let spacing: CGFloat = 8
|
||||
|
||||
let service: Service
|
||||
|
||||
|
||||
@ViewBuilder
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: nil) {
|
||||
Spacer()
|
||||
HStack(alignment: .center, spacing: nil) {
|
||||
IconRenderer(service: service)
|
||||
Spacer()
|
||||
Text("0:00")
|
||||
//counterText(for: service.countdownTo)
|
||||
.multilineTextAlignment(.trailing)
|
||||
.font(Font.body.monospacedDigit())
|
||||
.lineLimit(1)
|
||||
.contentTransition(.numericText(countsDown: true))
|
||||
}
|
||||
Spacer(minLength: spacing * 3)
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
Text(service.name)
|
||||
.font(.caption)
|
||||
.multilineTextAlignment(.leading)
|
||||
//Text(service.code)
|
||||
Text("666666")
|
||||
.font(Font.system(.title).weight(.light).monospacedDigit())
|
||||
.multilineTextAlignment(.leading)
|
||||
.minimumScaleFactor(0.2)
|
||||
.contentTransition(.numericText())
|
||||
if let info = service.additionalInfo {
|
||||
Text(info)
|
||||
TimelineView(.explicit(presenter.timelineEntries())) { context in
|
||||
HStack(alignment: .center, spacing: nil) {
|
||||
IconRenderer(service: presenter.service)
|
||||
Spacer()
|
||||
counterText(for: presenter.timeToNextDate(for: context.date))
|
||||
.multilineTextAlignment(.trailing)
|
||||
.font(Font.body.monospacedDigit())
|
||||
.lineLimit(1)
|
||||
.contentTransition(.numericText(countsDown: true))
|
||||
}
|
||||
Spacer(minLength: spacing * 3)
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
Text(presenter.name)
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
.multilineTextAlignment(.leading)
|
||||
Text(presenter.calculateToken(for: context.date))
|
||||
.font(Font.system(.title).weight(.light).monospacedDigit())
|
||||
.multilineTextAlignment(.leading)
|
||||
.minimumScaleFactor(0.2)
|
||||
.contentTransition(.numericText())
|
||||
if let info = presenter.additionalInfo {
|
||||
Text(info)
|
||||
.lineLimit(1)
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer()
|
@ -28,7 +28,13 @@ struct ServiceListView: View {
|
||||
ForEach(presenter.list, id: \.self) { category in
|
||||
Section(header: Text(category.name)) {
|
||||
ForEach(category.services, id: \.self) { service in
|
||||
NavigationLink(destination: ServiceView(service: service)) {
|
||||
NavigationLink(
|
||||
destination: ServiceView(
|
||||
presenter: ServicePresenter(
|
||||
interactor: InteractorFactory.shared.serviceInteractor(service: service)
|
||||
)
|
||||
)
|
||||
) {
|
||||
VStack(alignment: .leading) {
|
||||
Text(service.name)
|
||||
.font(.callout)
|
||||
|
@ -1,33 +0,0 @@
|
||||
//
|
||||
// This file is part of the 2FAS iOS app (https://github.com/twofas/2fas-ios)
|
||||
// Copyright © 2024 Two Factor Authentication Service, Inc.
|
||||
// Contributed by Zbigniew Cisiński. 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
|
||||
|
||||
struct TokenEntry: Identifiable, Hashable {
|
||||
var id: String { secret }
|
||||
let name: String
|
||||
let additionalInfo: String
|
||||
let secret: String
|
||||
let labelColor: Color
|
||||
// let icon: UIImage
|
||||
}
|
||||
|
||||
final class TokensListPresenter: ObservableObject {
|
||||
@Published var list: [TokenEntry] = []
|
||||
}
|
Loading…
Reference in New Issue
Block a user