Merge pull request #13 from rock-n-code/feature/open-files
Feature: Open documents
This commit is contained in:
commit
0438b1ad83
@ -91,6 +91,7 @@ private extension ContentView {
|
|||||||
login: login
|
login: login
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
.tint(.red)
|
||||||
} else {
|
} else {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
|
49
Modules/Sources/Browse/Logic/Adapters/DateAdapter.swift
Normal file
49
Modules/Sources/Browse/Logic/Adapters/DateAdapter.swift
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
//
|
||||||
|
// DateAdapter.swift
|
||||||
|
// Browse
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 15/12/2022.
|
||||||
|
// Copyright © 2022 Röck+Cöde. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct DateAdapter {
|
||||||
|
|
||||||
|
// MARK: Properties
|
||||||
|
|
||||||
|
private let dateFormatter: DateFormatter = .dateTimeFormatter
|
||||||
|
|
||||||
|
// MARK: Functions
|
||||||
|
|
||||||
|
func callAsFunction(value: Date?) -> String {
|
||||||
|
if let value {
|
||||||
|
return dateFormatter.string(from: value)
|
||||||
|
} else {
|
||||||
|
return .Constants.noValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - DateFormatter+Formats
|
||||||
|
|
||||||
|
private extension DateFormatter {
|
||||||
|
static let dateTimeFormatter = {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
|
||||||
|
formatter.dateStyle = .long
|
||||||
|
formatter.timeStyle = .short
|
||||||
|
formatter.locale = .current
|
||||||
|
|
||||||
|
return formatter
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - String+Constants
|
||||||
|
|
||||||
|
private extension String {
|
||||||
|
enum Constants {
|
||||||
|
static let noValue = "-"
|
||||||
|
}
|
||||||
|
}
|
54
Modules/Sources/Browse/Logic/Adapters/SizeAdapter.swift
Normal file
54
Modules/Sources/Browse/Logic/Adapters/SizeAdapter.swift
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
//
|
||||||
|
// SizeAdapter.swift
|
||||||
|
// Browse
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 15/12/2022.
|
||||||
|
// Copyright © 2022 Röck+Cöde. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct SizeAdapter {
|
||||||
|
|
||||||
|
// MARK: Properties
|
||||||
|
|
||||||
|
private let measurementFormatter: MeasurementFormatter = .informationSizeFormatter
|
||||||
|
|
||||||
|
// MARK: Functions
|
||||||
|
|
||||||
|
func callAsFunction(value: Int?) -> String {
|
||||||
|
guard let value else { return .Constants.noValue }
|
||||||
|
|
||||||
|
let sizeInBytes = Measurement(
|
||||||
|
value: Double(value),
|
||||||
|
unit: UnitInformationStorage.bytes
|
||||||
|
)
|
||||||
|
|
||||||
|
return measurementFormatter.string(
|
||||||
|
from: sizeInBytes.converted(to: .megabytes)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - DateFormatter+Formats
|
||||||
|
|
||||||
|
private extension MeasurementFormatter {
|
||||||
|
static let informationSizeFormatter = {
|
||||||
|
let formatter = MeasurementFormatter()
|
||||||
|
|
||||||
|
formatter.unitStyle = .medium
|
||||||
|
formatter.numberFormatter.maximumFractionDigits = 2
|
||||||
|
formatter.locale = .current
|
||||||
|
|
||||||
|
return formatter
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - String+Constants
|
||||||
|
|
||||||
|
private extension String {
|
||||||
|
enum Constants {
|
||||||
|
static let noValue = "-"
|
||||||
|
}
|
||||||
|
}
|
36
Modules/Sources/Browse/Logic/Use Cases/GetDataUseCase.swift
Normal file
36
Modules/Sources/Browse/Logic/Use Cases/GetDataUseCase.swift
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// GetDataUseCase.swift
|
||||||
|
// Browse
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 16/12/2022.
|
||||||
|
// Copyright © 2022 Röck+Cöde. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import APIService
|
||||||
|
import DependencyInjection
|
||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct GetDataUseCase {
|
||||||
|
|
||||||
|
// MARK: Dependencies
|
||||||
|
|
||||||
|
@Dependency(\.apiService) private var apiService
|
||||||
|
|
||||||
|
// MARK: Functions
|
||||||
|
|
||||||
|
func callAsFunction(
|
||||||
|
id: String,
|
||||||
|
username: String,
|
||||||
|
password: String
|
||||||
|
) async throws -> Data {
|
||||||
|
return try await apiService.getData(
|
||||||
|
id: id,
|
||||||
|
credentials: .init(
|
||||||
|
username: username,
|
||||||
|
password: password
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -21,6 +21,9 @@
|
|||||||
"message.type_error.text.first" = "An error occurred while loading this data";
|
"message.type_error.text.first" = "An error occurred while loading this data";
|
||||||
"message.type_error.text.second" = "Please try loading this data again at a later time.";
|
"message.type_error.text.second" = "Please try loading this data again at a later time.";
|
||||||
"message.type_error.button.text" = "Try again";
|
"message.type_error.button.text" = "Try again";
|
||||||
|
"message.type_not_supported.text.first" = "This type of document cannot be opened yet";
|
||||||
|
"message.type_not_supported.text.second" = "Please be patient while the support for this type of document is being built by our development team.";
|
||||||
|
"message.type_not_supported.button.text" = "Go back to folder";
|
||||||
|
|
||||||
// BrowseView
|
// BrowseView
|
||||||
|
|
||||||
|
@ -18,6 +18,9 @@ struct DocumentItem: View {
|
|||||||
let download: ActionClosure
|
let download: ActionClosure
|
||||||
let delete: ActionClosure
|
let delete: ActionClosure
|
||||||
|
|
||||||
|
private let dateAdapter = DateAdapter()
|
||||||
|
private let sizeAdapter = SizeAdapter()
|
||||||
|
|
||||||
// MARK: Body
|
// MARK: Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -34,11 +37,11 @@ struct DocumentItem: View {
|
|||||||
.itemName()
|
.itemName()
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("lastModified")
|
Text(dateAdapter(value: document?.lastModifiedAt))
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text("fileSize")
|
Text(sizeAdapter(value: document?.size))
|
||||||
}
|
}
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
@ -48,7 +48,7 @@ struct MessageView: View {
|
|||||||
bundle: .module
|
bundle: .module
|
||||||
)
|
)
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.foregroundColor(.primary)
|
.fontWeight(.semibold)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
.tint(.red)
|
.tint(.red)
|
||||||
@ -66,6 +66,7 @@ struct MessageView: View {
|
|||||||
extension MessageView {
|
extension MessageView {
|
||||||
enum MessageType {
|
enum MessageType {
|
||||||
case noCredentials
|
case noCredentials
|
||||||
|
case notSupported
|
||||||
case empty
|
case empty
|
||||||
case error
|
case error
|
||||||
}
|
}
|
||||||
@ -76,6 +77,8 @@ private extension MessageView.MessageType {
|
|||||||
switch self {
|
switch self {
|
||||||
case .noCredentials:
|
case .noCredentials:
|
||||||
return "message.type_no_credentials.text.first"
|
return "message.type_no_credentials.text.first"
|
||||||
|
case .notSupported:
|
||||||
|
return "message.type_not_supported.text.first"
|
||||||
case .empty:
|
case .empty:
|
||||||
return "message.type_empty.text.first"
|
return "message.type_empty.text.first"
|
||||||
case .error:
|
case .error:
|
||||||
@ -87,6 +90,8 @@ private extension MessageView.MessageType {
|
|||||||
switch self {
|
switch self {
|
||||||
case .noCredentials:
|
case .noCredentials:
|
||||||
return "message.type_no_credentials.text.second"
|
return "message.type_no_credentials.text.second"
|
||||||
|
case .notSupported:
|
||||||
|
return "message.type_not_supported.text.second"
|
||||||
case .empty:
|
case .empty:
|
||||||
return "message.type_empty.text.second"
|
return "message.type_empty.text.second"
|
||||||
case .error:
|
case .error:
|
||||||
@ -98,6 +103,8 @@ private extension MessageView.MessageType {
|
|||||||
switch self {
|
switch self {
|
||||||
case .noCredentials:
|
case .noCredentials:
|
||||||
return "message.type_no_credentials.button.text"
|
return "message.type_no_credentials.button.text"
|
||||||
|
case .notSupported:
|
||||||
|
return "message.type_not_supported.button.text"
|
||||||
case .empty:
|
case .empty:
|
||||||
return "message.type_empty.button.text"
|
return "message.type_empty.button.text"
|
||||||
case .error:
|
case .error:
|
||||||
@ -121,6 +128,11 @@ struct MessageView_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
.previewDisplayName("View of type no credentials")
|
.previewDisplayName("View of type no credentials")
|
||||||
|
|
||||||
|
MessageView(type: .notSupported) {
|
||||||
|
// action closure.
|
||||||
|
}
|
||||||
|
.previewDisplayName("View of type not supported")
|
||||||
|
|
||||||
MessageView(type: .empty) {
|
MessageView(type: .empty) {
|
||||||
// action closure.
|
// action closure.
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
enum Stack {
|
enum Stack {
|
||||||
case browse(Folder)
|
case browse(Folder)
|
||||||
|
case open(Document)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Computed
|
// MARK: - Computed
|
||||||
@ -16,6 +17,8 @@ extension Stack {
|
|||||||
var tag: String {
|
var tag: String {
|
||||||
if case .browse(let folder) = self {
|
if case .browse(let folder) = self {
|
||||||
return folder.id
|
return folder.id
|
||||||
|
} else if case .open(let document) = self {
|
||||||
|
return document.id
|
||||||
} else {
|
} else {
|
||||||
return .Constants.noId
|
return .Constants.noId
|
||||||
}
|
}
|
||||||
|
16
Modules/Sources/Browse/UI/Enumerations/ViewStatus.swift
Normal file
16
Modules/Sources/Browse/UI/Enumerations/ViewStatus.swift
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// ViewStatus.swift
|
||||||
|
// Browse
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 16/12/2022.
|
||||||
|
// Copyright © 2022 Röck+Cöde. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
enum ViewStatus {
|
||||||
|
case noCredentials
|
||||||
|
case notSupported
|
||||||
|
case loading
|
||||||
|
case loaded
|
||||||
|
case empty
|
||||||
|
case error
|
||||||
|
}
|
@ -58,7 +58,9 @@ struct BrowseToolbar: ToolbarContent {
|
|||||||
)
|
)
|
||||||
} icon: {
|
} icon: {
|
||||||
Image.add
|
Image.add
|
||||||
.foregroundColor(.red)
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,7 +77,9 @@ struct BrowseToolbar: ToolbarContent {
|
|||||||
)
|
)
|
||||||
} icon: {
|
} icon: {
|
||||||
Image.profile
|
Image.profile
|
||||||
.foregroundColor(.red)
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ struct StackNavigationViewModifier<Destination: View>: ViewModifier {
|
|||||||
) {
|
) {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
|
.hidden()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +79,8 @@ private extension BrowseView {
|
|||||||
type: .noCredentials,
|
type: .noCredentials,
|
||||||
action: login
|
action: login
|
||||||
)
|
)
|
||||||
|
case .notSupported:
|
||||||
|
EmptyView()
|
||||||
case .loading:
|
case .loading:
|
||||||
LoadingView()
|
LoadingView()
|
||||||
case .loaded:
|
case .loaded:
|
||||||
@ -87,13 +89,7 @@ private extension BrowseView {
|
|||||||
case is Folder:
|
case is Folder:
|
||||||
makeFolderItem(for: item)
|
makeFolderItem(for: item)
|
||||||
case is Document:
|
case is Document:
|
||||||
DocumentItem(item: item) {
|
makeDocumentItem(for: item)
|
||||||
// TODO: show the item id in a viewer...
|
|
||||||
} download: {
|
|
||||||
// TODO: download the item id from the backend.
|
|
||||||
} delete: {
|
|
||||||
// TODO: delete the item id from the backend.
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
@ -118,27 +114,50 @@ private extension BrowseView {
|
|||||||
@ViewBuilder func makeFolderItem(
|
@ViewBuilder func makeFolderItem(
|
||||||
for item: any FileSystemItemIdentifiable
|
for item: any FileSystemItemIdentifiable
|
||||||
) -> some View {
|
) -> some View {
|
||||||
let folder = Folder(
|
if let folder = item as? Folder {
|
||||||
id: item.id,
|
FolderItem(item: item) {
|
||||||
name: item.name
|
stack = .browse(folder)
|
||||||
)
|
} delete: {
|
||||||
|
// TODO: delete the item id from the backend.
|
||||||
FolderItem(item: item) {
|
}
|
||||||
stack = .browse(folder)
|
.navigate(
|
||||||
} delete: {
|
to: BrowseView(
|
||||||
// TODO: delete the item id from the backend.
|
folder: folder,
|
||||||
|
createFolder: createFolder,
|
||||||
|
uploadFile: uploadFile,
|
||||||
|
showProfile: showProfile,
|
||||||
|
login: login
|
||||||
|
),
|
||||||
|
tagged: .browse(folder),
|
||||||
|
in: $stack
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder func makeDocumentItem(
|
||||||
|
for item: any FileSystemItemIdentifiable
|
||||||
|
) -> some View {
|
||||||
|
if let document = item as? Document {
|
||||||
|
DocumentItem(item: item) {
|
||||||
|
stack = .open(document)
|
||||||
|
} download: {
|
||||||
|
// TODO: download the item id from the backend.
|
||||||
|
} delete: {
|
||||||
|
// TODO: delete the item id from the backend.
|
||||||
|
}
|
||||||
|
.navigate(
|
||||||
|
to: DocumentView(
|
||||||
|
document: document,
|
||||||
|
login: login
|
||||||
|
),
|
||||||
|
tagged: .open(document),
|
||||||
|
in: $stack
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
}
|
}
|
||||||
.navigate(
|
|
||||||
to: BrowseView(
|
|
||||||
folder: folder,
|
|
||||||
createFolder: createFolder,
|
|
||||||
uploadFile: uploadFile,
|
|
||||||
showProfile: showProfile,
|
|
||||||
login: login
|
|
||||||
),
|
|
||||||
tagged: .browse(folder),
|
|
||||||
in: $stack
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -173,18 +192,6 @@ private extension BrowseView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Enumerations
|
|
||||||
|
|
||||||
private extension BrowseView {
|
|
||||||
enum ViewStatus {
|
|
||||||
case noCredentials
|
|
||||||
case loading
|
|
||||||
case loaded
|
|
||||||
case empty
|
|
||||||
case error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct BrowseView_Previews: PreviewProvider {
|
struct BrowseView_Previews: PreviewProvider {
|
||||||
|
152
Modules/Sources/Browse/UI/Views/DocumentView.swift
Normal file
152
Modules/Sources/Browse/UI/Views/DocumentView.swift
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
//
|
||||||
|
// DocumentView.swift
|
||||||
|
// Browse
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 16/12/2022.
|
||||||
|
// Copyright © 2022 Röck+Cöde. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import DataModels
|
||||||
|
import KeychainStorage
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct DocumentView: View {
|
||||||
|
|
||||||
|
// MARK: Environments
|
||||||
|
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
// MARK: Storages
|
||||||
|
|
||||||
|
@KeychainStorage(key: .KeychainStorage.account) private var account: Account?
|
||||||
|
|
||||||
|
// MARK: States
|
||||||
|
|
||||||
|
@State private var status: ViewStatus = .loading
|
||||||
|
@State private var loadedData: Data?
|
||||||
|
|
||||||
|
private let getData = GetDataUseCase()
|
||||||
|
|
||||||
|
// MARK: Properties
|
||||||
|
|
||||||
|
let document: Document
|
||||||
|
let login: ActionClosure
|
||||||
|
|
||||||
|
// MARK: Body
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
content
|
||||||
|
.navigationTitle(document.name)
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.task {
|
||||||
|
await loadDataIfPossible()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - UI
|
||||||
|
|
||||||
|
private extension DocumentView {
|
||||||
|
@ViewBuilder var content: some View {
|
||||||
|
switch status {
|
||||||
|
case .noCredentials:
|
||||||
|
MessageView(
|
||||||
|
type: .noCredentials,
|
||||||
|
action: login
|
||||||
|
)
|
||||||
|
case .notSupported:
|
||||||
|
MessageView(type: .notSupported) {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
case .loading:
|
||||||
|
LoadingView()
|
||||||
|
case .loaded:
|
||||||
|
Image(uiImage: imageFromData)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
case .empty:
|
||||||
|
EmptyView()
|
||||||
|
case .error:
|
||||||
|
MessageView(type: .error) {
|
||||||
|
Task {
|
||||||
|
await loadDataIfPossible()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
|
||||||
|
private extension DocumentView {
|
||||||
|
|
||||||
|
// MARK: Computed
|
||||||
|
|
||||||
|
var imageFromData: UIImage {
|
||||||
|
guard
|
||||||
|
let loadedData,
|
||||||
|
let image = UIImage(data: loadedData)
|
||||||
|
else {
|
||||||
|
return .init()
|
||||||
|
}
|
||||||
|
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Functions
|
||||||
|
|
||||||
|
func loadDataIfPossible() async {
|
||||||
|
guard document.contentType == .Constants.supportedContentType else {
|
||||||
|
status = .notSupported
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let account else {
|
||||||
|
status = .noCredentials
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
status = .loading
|
||||||
|
|
||||||
|
let data = try await getData(
|
||||||
|
id: document.id,
|
||||||
|
username: account.username,
|
||||||
|
password: account.password
|
||||||
|
)
|
||||||
|
|
||||||
|
if data.isEmpty {
|
||||||
|
status = .error
|
||||||
|
} else {
|
||||||
|
loadedData = data
|
||||||
|
status = .loaded
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
status = .error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - String+Constants
|
||||||
|
|
||||||
|
private extension String {
|
||||||
|
enum Constants {
|
||||||
|
static let supportedContentType = "image/jpeg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Previews
|
||||||
|
|
||||||
|
struct DocumentView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
DocumentView(document: .init(
|
||||||
|
id: "1234567890",
|
||||||
|
name: "Some document name goes in here...",
|
||||||
|
contentType: "some content type",
|
||||||
|
size: .random(in: 1 ... 100),
|
||||||
|
lastModifiedAt: .now
|
||||||
|
)) {
|
||||||
|
// login closure.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,10 +15,8 @@ struct LogInLabelStyle: LabelStyle {
|
|||||||
|
|
||||||
configuration.title
|
configuration.title
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.foregroundColor(.primary)
|
|
||||||
|
|
||||||
configuration.icon
|
configuration.icon
|
||||||
.tint(.primary)
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
@ -90,8 +90,8 @@ public struct ProfileView: View {
|
|||||||
"profile.button.log_out.text",
|
"profile.button.log_out.text",
|
||||||
bundle: .module
|
bundle: .module
|
||||||
)
|
)
|
||||||
|
.font(.body)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.foregroundColor(.primary)
|
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
.tint(.red)
|
.tint(.red)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user