[#1368] Update Settings flows UI
- About - Private data export - Reset Zashi - What’s New - Recovery Phrase [#1373] update buttons across the app and remove the grid pattern - Send feedback finished [#1373] update buttons across the app and remove the grid pattern - All settings screens localized [#1373] update buttons across the app and remove the grid pattern - Recovery phrase screen prepared for onboarding [#1368] Update Settings flows UI - changelog updated [#1368] Update Settings flows UI - SDK bumped up to 2.2.5 [#1368] Update Settings flows UI - Zasji testnet version bumped up [#1368] Update Settings flows UI - Rebased [#1368] Update Settings flows UI - tooltip for the birthday added [#1368] Update Settings flows UI - spacing [#1368] Update Settings flows UI - regenerated cleanup
This commit is contained in:
parent
821976d068
commit
9871a1ad61
|
@ -14,6 +14,7 @@ directly impact users rather than highlighting other crucial architectural updat
|
|||
|
||||
### Changed
|
||||
- Not enough free space screen has been redesigned.
|
||||
- All settings flow screen have been redesigned
|
||||
|
||||
### Fixed
|
||||
- Splash screen animation is blocked by the main thread on iOS 16 and older.
|
||||
|
|
|
@ -60,6 +60,7 @@ let package = Package(
|
|||
.library(name: "SecItem", targets: ["SecItem"]),
|
||||
.library(name: "SecurityWarning", targets: ["SecurityWarning"]),
|
||||
.library(name: "SendConfirmation", targets: ["SendConfirmation"]),
|
||||
.library(name: "SendFeedback", targets: ["SendFeedback"]),
|
||||
.library(name: "SendFlow", targets: ["SendFlow"]),
|
||||
.library(name: "ServerSetup", targets: ["ServerSetup"]),
|
||||
.library(name: "Settings", targets: ["Settings"]),
|
||||
|
@ -101,7 +102,6 @@ let package = Package(
|
|||
"Generated",
|
||||
"Models",
|
||||
"UIComponents",
|
||||
"WhatsNew",
|
||||
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
|
||||
],
|
||||
path: "Sources/Features/About"
|
||||
|
@ -675,6 +675,17 @@ let package = Package(
|
|||
],
|
||||
path: "Sources/Features/SendConfirmation"
|
||||
),
|
||||
.target(
|
||||
name: "SendFeedback",
|
||||
dependencies: [
|
||||
"Generated",
|
||||
"SupportDataGenerator",
|
||||
"UIComponents",
|
||||
"Utils",
|
||||
.product(name: "ComposableArchitecture", package: "swift-composable-architecture")
|
||||
],
|
||||
path: "Sources/Features/SendFeedback"
|
||||
),
|
||||
.target(
|
||||
name: "SendFlow",
|
||||
dependencies: [
|
||||
|
@ -724,9 +735,10 @@ let package = Package(
|
|||
"Pasteboard",
|
||||
"PrivateDataConsent",
|
||||
"RecoveryPhraseDisplay",
|
||||
"SendFeedback",
|
||||
"ServerSetup",
|
||||
"SupportDataGenerator",
|
||||
"UIComponents",
|
||||
"WhatsNew",
|
||||
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
|
||||
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk"),
|
||||
.product(name: "Flexa", package: "flexa-ios")
|
||||
|
@ -906,6 +918,7 @@ let package = Package(
|
|||
.target(
|
||||
name: "WhatsNew",
|
||||
dependencies: [
|
||||
"AppVersion",
|
||||
"Generated",
|
||||
"UIComponents",
|
||||
"WhatsNewProvider",
|
||||
|
|
|
@ -25,7 +25,7 @@ public enum SupportDataGenerator {
|
|||
public static let subjectPPE = L10n.ProposalPartial.mailSubject
|
||||
}
|
||||
|
||||
public static func generate() -> SupportData {
|
||||
public static func generate(_ prefix: String? = nil) -> SupportData {
|
||||
let items: [SupportDataGeneratorItem] = [
|
||||
TimeItem(),
|
||||
AppVersionItem(),
|
||||
|
@ -42,7 +42,13 @@ public enum SupportDataGenerator {
|
|||
.map { "\($0.0): \($0.1)" }
|
||||
.joined(separator: "\n")
|
||||
|
||||
return SupportData(toAddress: Constants.email, subject: Constants.subject, message: message)
|
||||
if let prefix {
|
||||
let finalMessage = "\(prefix)\n\(message)"
|
||||
|
||||
return SupportData(toAddress: Constants.email, subject: Constants.subject, message: finalMessage)
|
||||
} else {
|
||||
return SupportData(toAddress: Constants.email, subject: Constants.subject, message: message)
|
||||
}
|
||||
}
|
||||
|
||||
public static func generatePartialProposalError(txIds: [String], statuses: [String]) -> SupportData {
|
||||
|
@ -62,7 +68,7 @@ public enum SupportDataGenerator {
|
|||
|
||||
\(data.message)
|
||||
|
||||
Transaction statuses:
|
||||
\(L10n.ProposalPartial.transactionStatuses)
|
||||
\(statusStrings)
|
||||
"""
|
||||
|
||||
|
@ -160,8 +166,8 @@ private struct LocaleItem: SupportDataGeneratorItem {
|
|||
|
||||
return [
|
||||
(Constants.localeKey, locale.identifier),
|
||||
(Constants.groupingSeparatorKey, locale.groupingSeparator ?? Constants.unknownSeparator),
|
||||
(Constants.decimalSeparatorKey, locale.decimalSeparator ?? Constants.unknownSeparator)
|
||||
(Constants.groupingSeparatorKey, "'\(locale.groupingSeparator ?? Constants.unknownSeparator)'"),
|
||||
(Constants.decimalSeparatorKey, "'\(locale.decimalSeparator ?? Constants.unknownSeparator)'")
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import ComposableArchitecture
|
|||
|
||||
import AppVersion
|
||||
import Generated
|
||||
import WhatsNew
|
||||
|
||||
@Reducer
|
||||
public struct About {
|
||||
|
@ -11,19 +10,13 @@ public struct About {
|
|||
public struct State: Equatable {
|
||||
public var appVersion = ""
|
||||
public var appBuild = ""
|
||||
public var whatsNewState: WhatsNew.State
|
||||
public var whatsNewViewBinding: Bool = false
|
||||
|
||||
public init(
|
||||
appVersion: String = "",
|
||||
appBuild: String = "",
|
||||
whatsNewState: WhatsNew.State,
|
||||
whatsNewViewBinding: Bool = false
|
||||
appBuild: String = ""
|
||||
) {
|
||||
self.appVersion = appVersion
|
||||
self.appBuild = appBuild
|
||||
self.whatsNewState = whatsNewState
|
||||
self.whatsNewViewBinding = whatsNewViewBinding
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,8 +24,6 @@ public struct About {
|
|||
case binding(BindingAction<About.State>)
|
||||
case onAppear
|
||||
case privacyPolicyButtonTapped
|
||||
case whatsNew(WhatsNew.Action)
|
||||
case whatsNewButtonTapped
|
||||
}
|
||||
|
||||
@Dependency(\.appVersion) var appVersion
|
||||
|
@ -41,10 +32,6 @@ public struct About {
|
|||
|
||||
public var body: some Reducer<State, Action> {
|
||||
BindingReducer()
|
||||
|
||||
Scope(state: \.whatsNewState, action: \.whatsNew) {
|
||||
WhatsNew()
|
||||
}
|
||||
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
|
@ -58,13 +45,6 @@ public struct About {
|
|||
|
||||
case .privacyPolicyButtonTapped:
|
||||
return .none
|
||||
|
||||
case .whatsNew:
|
||||
return .none
|
||||
|
||||
case .whatsNewButtonTapped:
|
||||
state.whatsNewViewBinding = true
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import SwiftUI
|
|||
import ComposableArchitecture
|
||||
import Generated
|
||||
import UIComponents
|
||||
import WhatsNew
|
||||
|
||||
public struct AboutView: View {
|
||||
@Environment(\.openURL) var openURL
|
||||
|
@ -22,55 +21,47 @@ public struct AboutView: View {
|
|||
|
||||
public var body: some View {
|
||||
WithPerceptionTracking {
|
||||
VStack(alignment: .leading) {
|
||||
VStack(alignment: .center) {
|
||||
Asset.Assets.zashiTitle.image
|
||||
.zImage(width: 63, height: 17, color: Asset.Colors.primary.color)
|
||||
.padding(.top, 15)
|
||||
.padding(.bottom, 8)
|
||||
VStack(spacing: 0) {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(L10n.About.title)
|
||||
.zFont(.semiBold, size: 24, style: Design.Text.primary)
|
||||
.padding(.top, 40)
|
||||
|
||||
Text(L10n.About.version(store.appVersion, store.appBuild))
|
||||
.font(.custom(FontFamily.Inter.bold.name, size: 12))
|
||||
.foregroundColor(Asset.Colors.primary.color)
|
||||
.padding(.bottom, 25)
|
||||
Text(L10n.About.info)
|
||||
.zFont(size: 14, style: Design.Text.primary)
|
||||
.padding(.top, 12)
|
||||
|
||||
Text(L10n.About.additionalInfo)
|
||||
.zFont(size: 14, style: Design.Text.primary)
|
||||
.padding(.top, 8)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Text(L10n.About.info)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 14))
|
||||
.foregroundColor(Asset.Colors.shade30.color)
|
||||
.padding(.bottom, 30)
|
||||
|
||||
ZashiButton(L10n.About.whatsNew) {
|
||||
store.send(.whatsNewButtonTapped)
|
||||
}
|
||||
.padding(.bottom, 15)
|
||||
|
||||
ZashiButton(L10n.About.privacyPolicy) {
|
||||
ActionRow(
|
||||
icon: Asset.Assets.infoCircle.image,
|
||||
title: L10n.About.privacyPolicy,
|
||||
divider: false,
|
||||
horizontalPadding: 4
|
||||
) {
|
||||
if let url = URL(string: "https://electriccoin.co/zashi-privacy-policy/") {
|
||||
openURL(url)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 25)
|
||||
.padding(.top, 32)
|
||||
|
||||
Spacer()
|
||||
|
||||
Asset.Assets.zashiTitle.image
|
||||
.zImage(width: 73, height: 20, color: Asset.Colors.primary.color)
|
||||
.padding(.bottom, 16)
|
||||
|
||||
Text(L10n.Settings.version(store.appVersion, store.appBuild))
|
||||
.zFont(size: 16, style: Design.Text.tertiary)
|
||||
.padding(.bottom, 24)
|
||||
}
|
||||
.padding(.top, 20)
|
||||
.onAppear { store.send(.onAppear) }
|
||||
.zashiBack()
|
||||
.screenTitle(L10n.Settings.about)
|
||||
.walletStatusPanel(background: .transparent)
|
||||
.navigationLinkEmpty(
|
||||
isActive: $store.whatsNewViewBinding,
|
||||
destination: {
|
||||
WhatsNewView(
|
||||
store: store.scope(
|
||||
state: \.whatsNewState,
|
||||
action: \.whatsNew
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.screenHorizontalPadding()
|
||||
|
@ -81,7 +72,7 @@ public struct AboutView: View {
|
|||
// MARK: Placeholders
|
||||
|
||||
extension About.State {
|
||||
public static let initial = About.State(whatsNewState: .initial)
|
||||
public static let initial = About.State()
|
||||
}
|
||||
|
||||
extension About {
|
||||
|
|
|
@ -19,46 +19,43 @@ public struct DeleteWalletView: View {
|
|||
|
||||
public var body: some View {
|
||||
WithPerceptionTracking {
|
||||
ScrollView {
|
||||
Group {
|
||||
ZashiIcon()
|
||||
|
||||
Text(L10n.DeleteWallet.title)
|
||||
.font(.custom(FontFamily.Inter.semiBold.name, size: 25))
|
||||
.padding(.bottom, 15)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(L10n.DeleteWallet.message1)
|
||||
.font(.custom(FontFamily.Inter.bold.name, size: 16))
|
||||
|
||||
Text(L10n.DeleteWallet.message2)
|
||||
.font(.custom(FontFamily.Inter.medium.name, size: 16))
|
||||
.padding(.top, 20)
|
||||
}
|
||||
|
||||
HStack {
|
||||
ZashiToggle(
|
||||
isOn: $store.isAcknowledged,
|
||||
label: L10n.DeleteWallet.iUnderstand
|
||||
)
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(L10n.DeleteWallet.title)
|
||||
.zFont(.semiBold, size: 24, style: Design.Text.primary)
|
||||
.padding(.top, 40)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 30)
|
||||
|
||||
ZashiButton(L10n.DeleteWallet.actionButtonTitle) {
|
||||
store.send(.deleteTapped)
|
||||
}
|
||||
.disabled(!store.isAcknowledged || store.isProcessing)
|
||||
.padding(.vertical, 50)
|
||||
Text(L10n.DeleteWallet.message1)
|
||||
.zFont(.semiBold, size: 16, style: Design.Text.primary)
|
||||
.padding(.top, 12)
|
||||
|
||||
Text(L10n.DeleteWallet.message2)
|
||||
.zFont(size: 14, style: Design.Text.primary)
|
||||
.padding(.top, 8)
|
||||
.lineSpacing(1.5)
|
||||
|
||||
Spacer()
|
||||
|
||||
ZashiToggle(
|
||||
isOn: $store.isAcknowledged,
|
||||
label: L10n.DeleteWallet.iUnderstand
|
||||
)
|
||||
.padding(.bottom, 24)
|
||||
|
||||
ZashiButton(
|
||||
L10n.DeleteWallet.actionButtonTitle,
|
||||
type: .destructive1
|
||||
) {
|
||||
store.send(.deleteTapped)
|
||||
}
|
||||
.disabled(!store.isAcknowledged || store.isProcessing)
|
||||
.padding(.bottom, 20)
|
||||
}
|
||||
.padding(.vertical, 1)
|
||||
.zashiBack(store.isProcessing)
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.screenHorizontalPadding()
|
||||
.applyScreenBackground()
|
||||
.screenTitle(L10n.DeleteWallet.screenTitle.uppercased())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,88 +22,79 @@ public struct PrivateDataConsentView: View {
|
|||
|
||||
public var body: some View {
|
||||
WithPerceptionTracking {
|
||||
ScrollView {
|
||||
Group {
|
||||
ZashiIcon()
|
||||
.padding(.top, walletStatus != .none ? 30 : 0)
|
||||
|
||||
Text(L10n.PrivateDataConsent.title)
|
||||
.font(.custom(FontFamily.Inter.semiBold.name, size: 25))
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.bottom, 35)
|
||||
|
||||
Text(L10n.PrivateDataConsent.message)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 14))
|
||||
.padding(.bottom, 10)
|
||||
.lineSpacing(3)
|
||||
|
||||
Text(L10n.PrivateDataConsent.note)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 12))
|
||||
.lineSpacing(2)
|
||||
|
||||
HStack {
|
||||
ZashiToggle(
|
||||
isOn: $store.isAcknowledged,
|
||||
label: L10n.PrivateDataConsent.confirmation
|
||||
)
|
||||
|
||||
Spacer()
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(L10n.PrivateDataConsent.title)
|
||||
.zFont(.semiBold, size: 24, style: Design.Text.primary)
|
||||
.padding(.top, 40)
|
||||
|
||||
Text(L10n.PrivateDataConsent.message1)
|
||||
.zFont(size: 14, style: Design.Text.primary)
|
||||
.padding(.top, 12)
|
||||
|
||||
Text(L10n.PrivateDataConsent.message2)
|
||||
.zFont(size: 14, style: Design.Text.primary)
|
||||
.padding(.top, 8)
|
||||
|
||||
Text(L10n.PrivateDataConsent.message3)
|
||||
.zFont(size: 14, style: Design.Text.primary)
|
||||
.padding(.top, 8)
|
||||
|
||||
Text(L10n.PrivateDataConsent.message4)
|
||||
.zFont(size: 14, style: Design.Text.primary)
|
||||
.padding(.top, 8)
|
||||
|
||||
Spacer()
|
||||
|
||||
ZashiToggle(
|
||||
isOn: $store.isAcknowledged,
|
||||
label: L10n.PrivateDataConsent.confirmation
|
||||
)
|
||||
.padding(.bottom, 24)
|
||||
|
||||
if store.isExportingData {
|
||||
ZashiButton(
|
||||
L10n.Settings.exportPrivateData,
|
||||
type: .secondary,
|
||||
accessoryView: ProgressView()
|
||||
) {
|
||||
store.send(.exportRequested)
|
||||
}
|
||||
.padding(.top, 20)
|
||||
.padding(.bottom, 40)
|
||||
|
||||
if store.isExportingData {
|
||||
ZashiButton(
|
||||
L10n.Settings.exportPrivateData,
|
||||
type: .secondary,
|
||||
accessoryView: ProgressView()
|
||||
) {
|
||||
store.send(.exportRequested)
|
||||
}
|
||||
.disabled(true)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.bottom, 8)
|
||||
} else {
|
||||
ZashiButton(
|
||||
L10n.Settings.exportPrivateData,
|
||||
type: .secondary
|
||||
) {
|
||||
store.send(.exportRequested)
|
||||
}
|
||||
.disabled(!store.isExportPossible)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.bottom, 8)
|
||||
.disabled(true)
|
||||
.padding(.bottom, 8)
|
||||
} else {
|
||||
ZashiButton(
|
||||
L10n.Settings.exportPrivateData,
|
||||
type: .secondary
|
||||
) {
|
||||
store.send(.exportRequested)
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
if store.isExportingLogs {
|
||||
ZashiButton(
|
||||
L10n.Settings.exportLogsOnly,
|
||||
accessoryView: ProgressView()
|
||||
) {
|
||||
store.send(.exportLogsRequested)
|
||||
}
|
||||
.disabled(true)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.bottom, 50)
|
||||
} else {
|
||||
ZashiButton(
|
||||
L10n.Settings.exportLogsOnly
|
||||
) {
|
||||
store.send(.exportLogsRequested)
|
||||
}
|
||||
.disabled(!store.isExportPossible)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.bottom, 50)
|
||||
}
|
||||
#endif
|
||||
.disabled(!store.isExportPossible)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
if store.isExportingLogs {
|
||||
ZashiButton(
|
||||
L10n.Settings.exportLogsOnly,
|
||||
accessoryView: ProgressView()
|
||||
) {
|
||||
store.send(.exportLogsRequested)
|
||||
}
|
||||
.disabled(true)
|
||||
.padding(.bottom, 20)
|
||||
} else {
|
||||
ZashiButton(
|
||||
L10n.Settings.exportLogsOnly
|
||||
) {
|
||||
store.send(.exportLogsRequested)
|
||||
}
|
||||
.disabled(!store.isExportPossible)
|
||||
.padding(.bottom, 20)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
.padding(.vertical, 1)
|
||||
.zashiBack()
|
||||
.onAppear {
|
||||
store.send(.onAppear)
|
||||
}
|
||||
.onAppear { store.send(.onAppear)}
|
||||
.walletStatusPanel()
|
||||
|
||||
shareLogsView()
|
||||
|
@ -111,6 +102,7 @@ public struct PrivateDataConsentView: View {
|
|||
.navigationBarTitleDisplayMode(.inline)
|
||||
.screenHorizontalPadding()
|
||||
.applyScreenBackground()
|
||||
.screenTitle(L10n.PrivateDataConsent.screenTitle.uppercased())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,21 +19,24 @@ public struct RecoveryPhraseDisplay {
|
|||
@ObservableState
|
||||
public struct State: Equatable {
|
||||
@Presents public var alert: AlertState<Action>?
|
||||
public var phrase: RecoveryPhrase?
|
||||
public var showBackButton = false
|
||||
public var birthday: Birthday?
|
||||
public var birthdayValue: String?
|
||||
public var isBirthdayHintVisible = false
|
||||
public var isRecoveryPhraseHidden = true
|
||||
public var phrase: RecoveryPhrase?
|
||||
public var showBackButton = false
|
||||
|
||||
|
||||
public init(
|
||||
phrase: RecoveryPhrase? = nil,
|
||||
showBackButton: Bool = false,
|
||||
birthday: Birthday? = nil,
|
||||
birthdayValue: String? = nil
|
||||
birthdayValue: String? = nil,
|
||||
phrase: RecoveryPhrase? = nil,
|
||||
showBackButton: Bool = false
|
||||
) {
|
||||
self.phrase = phrase
|
||||
self.showBackButton = showBackButton
|
||||
self.birthday = birthday
|
||||
self.birthdayValue = birthdayValue
|
||||
self.phrase = phrase
|
||||
self.showBackButton = showBackButton
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +44,8 @@ public struct RecoveryPhraseDisplay {
|
|||
case alert(PresentationAction<Action>)
|
||||
case finishedPressed
|
||||
case onAppear
|
||||
case recoveryPhraseTapped
|
||||
case tooltipTapped
|
||||
}
|
||||
|
||||
@Dependency(\.walletStorage) var walletStorage
|
||||
|
@ -52,6 +57,7 @@ public struct RecoveryPhraseDisplay {
|
|||
Reduce { state, action in
|
||||
switch action {
|
||||
case .onAppear:
|
||||
state.isRecoveryPhraseHidden = true
|
||||
do {
|
||||
let storedWallet = try walletStorage.exportWallet()
|
||||
state.birthday = storedWallet.birthday
|
||||
|
@ -78,6 +84,14 @@ public struct RecoveryPhraseDisplay {
|
|||
|
||||
case .finishedPressed:
|
||||
return .none
|
||||
|
||||
case .tooltipTapped:
|
||||
state.isBirthdayHintVisible.toggle()
|
||||
return .none
|
||||
|
||||
case .recoveryPhraseTapped:
|
||||
state.isRecoveryPhraseHidden.toggle()
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,11 @@ import MnemonicSwift
|
|||
import Utils
|
||||
|
||||
public struct RecoveryPhraseDisplayView: View {
|
||||
enum Constants {
|
||||
static let blurValue = 15.0
|
||||
static let blurBDValue = 10.0
|
||||
}
|
||||
|
||||
@Perception.Bindable var store: StoreOf<RecoveryPhraseDisplay>
|
||||
|
||||
public init(store: StoreOf<RecoveryPhraseDisplay>) {
|
||||
|
@ -20,85 +25,187 @@ public struct RecoveryPhraseDisplayView: View {
|
|||
}
|
||||
|
||||
public var body: some View {
|
||||
ScrollView {
|
||||
WithPerceptionTracking {
|
||||
VStack(alignment: .center) {
|
||||
ZashiIcon()
|
||||
WithPerceptionTracking {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
if let groups = store.phrase?.toGroups() {
|
||||
Text(L10n.RecoveryPhraseDisplay.title)
|
||||
.zFont(.semiBold, size: 24, style: Design.Text.primary)
|
||||
.padding(.top, 40)
|
||||
|
||||
if let groups = store.phrase?.toGroups() {
|
||||
VStack {
|
||||
Text(L10n.RecoveryPhraseDisplay.titlePart1)
|
||||
.font(.custom(FontFamily.Inter.semiBold.name, size: 25))
|
||||
Text(L10n.RecoveryPhraseDisplay.titlePart2)
|
||||
.font(.custom(FontFamily.Inter.semiBold.name, size: 25))
|
||||
}
|
||||
.padding(.bottom, 15)
|
||||
Text(L10n.RecoveryPhraseDisplay.description)
|
||||
.zFont(size: 14, style: Design.Text.primary)
|
||||
.lineSpacing(1.5)
|
||||
.padding(.top, 8)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
Text(L10n.RecoveryPhraseDisplay.description)
|
||||
.font(.custom(FontFamily.Inter.medium.name, size: 14))
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.bottom, 15)
|
||||
|
||||
HStack {
|
||||
ForEach(groups, id: \.startIndex) { group in
|
||||
VStack(alignment: .leading) {
|
||||
HStack(spacing: 2) {
|
||||
VStack(alignment: .trailing, spacing: 2) {
|
||||
ForEach(Array(group.words.enumerated()), id: \.offset) { seedWord in
|
||||
Text("\(seedWord.offset + group.startIndex + 1).")
|
||||
.fixedSize()
|
||||
.font(.custom(FontFamily.Inter.medium.name, size: 16))
|
||||
}
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
ForEach(Array(group.words.enumerated()), id: \.offset) { seedWord in
|
||||
Text("\(seedWord.element.data)")
|
||||
.fixedSize()
|
||||
.font(.custom(FontFamily.Inter.medium.name, size: 16))
|
||||
}
|
||||
}
|
||||
|
||||
if group.startIndex == 0 {
|
||||
ForEach(groups, id: \.startIndex) { group in
|
||||
VStack(alignment: .leading) {
|
||||
VStack(spacing: 5) {
|
||||
ForEach(Array(group.words.enumerated()), id: \.offset) { seedWord in
|
||||
HStack(spacing: 0) {
|
||||
Text("\(seedWord.offset + group.startIndex + 1)")
|
||||
.zFont(.semiBold, size: 14, style: Design.Text.tertiary)
|
||||
.padding(.trailing, 8)
|
||||
|
||||
Text("\(seedWord.element.data)")
|
||||
.zFont(size: 14, style: Design.Text.primary)
|
||||
.minimumScaleFactor(0.35)
|
||||
.lineLimit(1)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal, 15)
|
||||
.padding(.bottom, 15)
|
||||
|
||||
if let birthdayValue = store.birthdayValue {
|
||||
Text(L10n.RecoveryPhraseDisplay.birthdayHeight(birthdayValue))
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 14))
|
||||
.padding(.bottom, 15)
|
||||
}
|
||||
} else {
|
||||
Text(L10n.RecoveryPhraseDisplay.noWords)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 14))
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.bottom, 35)
|
||||
|
||||
}
|
||||
|
||||
if !store.showBackButton {
|
||||
ZashiButton(L10n.RecoveryPhraseDisplay.Button.wroteItDown) {
|
||||
store.send(.finishedPressed)
|
||||
.blur(radius: store.isRecoveryPhraseHidden ? Constants.blurValue : 0)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 24)
|
||||
.padding(.horizontal, 16)
|
||||
.background {
|
||||
RoundedRectangle(cornerRadius: 24)
|
||||
.fill(Design.Surfaces.bgSecondary.color)
|
||||
}
|
||||
.onTapGesture {
|
||||
if !store.showBackButton {
|
||||
store.send(.recoveryPhraseTapped, animation: .easeInOut)
|
||||
}
|
||||
}
|
||||
.overlay {
|
||||
if !store.showBackButton && store.isRecoveryPhraseHidden {
|
||||
VStack(spacing: 0) {
|
||||
Asset.Assets.eyeOn.image
|
||||
.zImage(size: 26, style: Design.Text.primary)
|
||||
|
||||
Text(L10n.RecoveryPhraseDisplay.reveal)
|
||||
.zFont(.semiBold, size: 20, style: Design.Text.primary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.top, 20)
|
||||
|
||||
if let birthdayValue = store.birthdayValue {
|
||||
HStack {
|
||||
Button {
|
||||
store.send(.tooltipTapped)
|
||||
} label: {
|
||||
HStack(spacing: 4) {
|
||||
Text(L10n.RecoveryPhraseDisplay.birthdayTitle)
|
||||
.zFont(.medium, size: 14, style: Design.Inputs.Filled.text)
|
||||
Asset.Assets.infoOutline.image
|
||||
.zImage(size: 16, style: Design.Inputs.Default.icon)
|
||||
}
|
||||
.padding(.top, 24)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.anchorPreference(
|
||||
key: BirthdayPreferenceKey.self,
|
||||
value: .bounds
|
||||
) { $0 }
|
||||
|
||||
HStack {
|
||||
Text("\(birthdayValue)")
|
||||
.zFont(.medium, size: 16, style: Design.Inputs.Filled.text)
|
||||
.padding(.horizontal, 14)
|
||||
.padding(.vertical, 12)
|
||||
.blur(radius: store.isRecoveryPhraseHidden ? Constants.blurBDValue : 0)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.background {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(Design.Surfaces.bgSecondary.color)
|
||||
}
|
||||
.padding(.top, 6)
|
||||
}
|
||||
} else {
|
||||
Text(L10n.RecoveryPhraseDisplay.noWords)
|
||||
.zFont(.semiBold, size: 24, style: Design.Text.primary)
|
||||
.padding(.top, 40)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Asset.Assets.infoOutline.image
|
||||
.zImage(size: 20, style: Design.Utility.WarningYellow._500)
|
||||
.padding(.trailing, 12)
|
||||
|
||||
Text(L10n.RecoveryPhraseDisplay.warning)
|
||||
.zFont(.medium, size: 12, style: Design.Utility.WarningYellow._700)
|
||||
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
.padding(.bottom, 24)
|
||||
.padding(.horizontal, 20)
|
||||
|
||||
if !store.showBackButton {
|
||||
ZashiButton(L10n.RecoveryPhraseDisplay.Button.wroteItDown) {
|
||||
store.send(.finishedPressed)
|
||||
}
|
||||
.padding(.bottom, 20)
|
||||
} else {
|
||||
if store.isRecoveryPhraseHidden {
|
||||
ZashiButton(
|
||||
L10n.RecoveryPhraseDisplay.reveal,
|
||||
prefixView:
|
||||
Asset.Assets.eyeOn.image
|
||||
.zImage(size: 20, style: Design.Btns.Primary.fg)
|
||||
) {
|
||||
store.send(.recoveryPhraseTapped, animation: .easeInOut)
|
||||
}
|
||||
.padding(.bottom, 20)
|
||||
} else {
|
||||
ZashiButton(
|
||||
L10n.RecoveryPhraseDisplay.hide,
|
||||
prefixView:
|
||||
Asset.Assets.eyeOff.image
|
||||
.zImage(size: 20, style: Design.Btns.Primary.fg)
|
||||
) {
|
||||
store.send(.recoveryPhraseTapped, animation: .easeInOut)
|
||||
}
|
||||
.padding(.bottom, 20)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear { store.send(.onAppear) }
|
||||
.alert($store.scope(state: \.alert, action: \.alert))
|
||||
.zashiBack(false, hidden: !store.showBackButton)
|
||||
.overlayPreferenceValue(BirthdayPreferenceKey.self) { preferences in
|
||||
if store.isBirthdayHintVisible {
|
||||
GeometryReader { geometry in
|
||||
preferences.map {
|
||||
Tooltip(
|
||||
title: L10n.RecoveryPhraseDisplay.birthdayTitle,
|
||||
desc: L10n.RecoveryPhraseDisplay.birthdayDesc,
|
||||
bottomMode: true
|
||||
) {
|
||||
store.send(.tooltipTapped)
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.frame(width: geometry.size.width)
|
||||
.position(x: geometry[$0].midX, y: geometry[$0].minY)
|
||||
.offset(x: 0, y: -geometry[$0].height - 10)
|
||||
}
|
||||
.padding(.bottom, 50)
|
||||
}
|
||||
}
|
||||
.onAppear { store.send(.onAppear) }
|
||||
.alert($store.scope(state: \.alert, action: \.alert))
|
||||
.zashiBack(false, hidden: !store.showBackButton)
|
||||
}
|
||||
}
|
||||
.navigationBarBackButtonHidden()
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.padding(.vertical, 1)
|
||||
.screenHorizontalPadding()
|
||||
.applyScreenBackground()
|
||||
.screenTitle(L10n.RecoveryPhraseDisplay.screenTitle.uppercased())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,9 +215,9 @@ public struct RecoveryPhraseDisplayView: View {
|
|||
store:
|
||||
StoreOf<RecoveryPhraseDisplay>(
|
||||
initialState: RecoveryPhraseDisplay.State(
|
||||
birthdayValue: nil,
|
||||
phrase: .placeholder,
|
||||
showBackButton: true,
|
||||
birthdayValue: nil
|
||||
showBackButton: true
|
||||
)
|
||||
) {
|
||||
RecoveryPhraseDisplay()
|
||||
|
@ -123,8 +230,8 @@ public struct RecoveryPhraseDisplayView: View {
|
|||
|
||||
extension RecoveryPhraseDisplay.State {
|
||||
public static let initial = RecoveryPhraseDisplay.State(
|
||||
phrase: nil,
|
||||
birthday: nil
|
||||
birthday: nil,
|
||||
phrase: nil
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -203,6 +203,10 @@ public struct Root {
|
|||
Welcome()
|
||||
}
|
||||
|
||||
Scope(state: \.phraseDisplayState, action: \.phraseDisplay) {
|
||||
RecoveryPhraseDisplay()
|
||||
}
|
||||
|
||||
initializationReduce()
|
||||
|
||||
destinationReduce()
|
||||
|
|
|
@ -22,10 +22,9 @@ public struct SecurityWarningView: View {
|
|||
WithPerceptionTracking {
|
||||
ScrollView {
|
||||
Group {
|
||||
ZashiIcon()
|
||||
|
||||
Text(L10n.SecurityWarning.title)
|
||||
.font(.custom(FontFamily.Inter.semiBold.name, size: 25))
|
||||
.padding(.top, 40)
|
||||
.padding(.bottom, 15)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
|
@ -54,6 +53,7 @@ public struct SecurityWarningView: View {
|
|||
Spacer()
|
||||
}
|
||||
.padding(.top, 30)
|
||||
.padding(.leading, 1)
|
||||
|
||||
ZashiButton(L10n.SecurityWarning.confirm) {
|
||||
store.send(.confirmTapped)
|
||||
|
@ -81,6 +81,7 @@ public struct SecurityWarningView: View {
|
|||
.navigationBarTitleDisplayMode(.inline)
|
||||
.screenHorizontalPadding()
|
||||
.applyScreenBackground()
|
||||
.screenTitle(L10n.SecurityWarning.screenTitle.uppercased())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
//
|
||||
// SendFeedbackStore.swift
|
||||
// Zashi
|
||||
//
|
||||
// Created by Lukáš Korba on 10-11-2024.
|
||||
//
|
||||
|
||||
import ComposableArchitecture
|
||||
import UIComponents
|
||||
import MessageUI
|
||||
import SupportDataGenerator
|
||||
import Generated
|
||||
|
||||
@Reducer
|
||||
public struct SendFeedback {
|
||||
@ObservableState
|
||||
public struct State: Equatable {
|
||||
public var canSendMail = false
|
||||
public var memoState: MessageEditor.State = .initial
|
||||
public var messageToBeShared: String?
|
||||
public let ratings = ["😠", "😒", "🙂", "😄", "😍"]
|
||||
public var selectedRating: Int?
|
||||
public var supportData: SupportData?
|
||||
|
||||
public var invalidForm: Bool {
|
||||
selectedRating == nil || memoState.text.isEmpty
|
||||
}
|
||||
|
||||
public init(
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
public enum Action: BindableAction, Equatable {
|
||||
case binding(BindingAction<SendFeedback.State>)
|
||||
case memo(MessageEditor.Action)
|
||||
case onAppear
|
||||
case ratingTapped(Int)
|
||||
case sendTapped
|
||||
case sendSupportMailFinished
|
||||
case shareFinished
|
||||
}
|
||||
|
||||
public init() { }
|
||||
|
||||
public var body: some Reducer<State, Action> {
|
||||
BindingReducer()
|
||||
|
||||
Scope(state: \.memoState, action: \.memo) {
|
||||
MessageEditor()
|
||||
}
|
||||
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
case .onAppear:
|
||||
state.memoState.text = ""
|
||||
state.selectedRating = nil
|
||||
state.canSendMail = MFMailComposeViewController.canSendMail()
|
||||
return .none
|
||||
|
||||
case .sendTapped:
|
||||
guard let selectedRating = state.selectedRating else {
|
||||
return .none
|
||||
}
|
||||
|
||||
var prefixMessage = "\(L10n.SendFeedback.ratingQuestion)\n\(state.ratings[selectedRating]) \(selectedRating + 1)/\(state.ratings.count)\n\n"
|
||||
prefixMessage += "\(L10n.SendFeedback.howCanWeHelp)\n\(state.memoState.text)\n\n"
|
||||
|
||||
if state.canSendMail {
|
||||
state.supportData = SupportDataGenerator.generate(prefixMessage)
|
||||
return .none
|
||||
} else {
|
||||
var sharePrefix =
|
||||
"""
|
||||
===
|
||||
\(L10n.SendFeedback.Share.notAppleMailInfo) \(SupportDataGenerator.Constants.email)
|
||||
===
|
||||
|
||||
\(prefixMessage)
|
||||
"""
|
||||
let supportData = SupportDataGenerator.generate(sharePrefix)
|
||||
state.messageToBeShared = supportData.message
|
||||
}
|
||||
return .none
|
||||
|
||||
case .sendSupportMailFinished:
|
||||
state.supportData = nil
|
||||
return .none
|
||||
|
||||
case .binding:
|
||||
return .none
|
||||
|
||||
case .memo:
|
||||
return .none
|
||||
|
||||
case .ratingTapped(let rating):
|
||||
state.selectedRating = rating
|
||||
return .none
|
||||
|
||||
case .shareFinished:
|
||||
state.messageToBeShared = nil
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
//
|
||||
// SendFeedbackView.swift
|
||||
// Zashi
|
||||
//
|
||||
// Created by Lukáš Korba on 10-11-2024.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
import Generated
|
||||
import UIComponents
|
||||
import Utils
|
||||
|
||||
public struct SendFeedbackView: View {
|
||||
@Perception.Bindable var store: StoreOf<SendFeedback>
|
||||
|
||||
public init(store: StoreOf<SendFeedback>) {
|
||||
self.store = store
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
WithPerceptionTracking {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(L10n.SendFeedback.title)
|
||||
.zFont(.semiBold, size: 24, style: Design.Text.primary)
|
||||
.padding(.top, 40)
|
||||
|
||||
Text(L10n.SendFeedback.desc)
|
||||
.zFont(size: 14, style: Design.Text.primary)
|
||||
.padding(.top, 8)
|
||||
|
||||
Text(L10n.SendFeedback.ratingQuestion)
|
||||
.zFont(.medium, size: 14, style: Design.Text.primary)
|
||||
.padding(.top, 32)
|
||||
|
||||
HStack(spacing: 12) {
|
||||
ForEach(0..<5) { rating in
|
||||
WithPerceptionTracking {
|
||||
Button {
|
||||
store.send(.ratingTapped(rating))
|
||||
} label: {
|
||||
Text(store.ratings[rating])
|
||||
.padding(.vertical, 12)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background {
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(Design.Surfaces.bgSecondary.color)
|
||||
}
|
||||
.padding(3)
|
||||
.overlay {
|
||||
if let selectedRating = store.selectedRating, selectedRating == rating {
|
||||
RoundedRectangle(cornerRadius: 14)
|
||||
.stroke(Design.Text.primary.color)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.top, 12)
|
||||
|
||||
Text(L10n.SendFeedback.howCanWeHelp)
|
||||
.zFont(.medium, size: 14, style: Design.Text.primary)
|
||||
.padding(.top, 24)
|
||||
|
||||
MessageEditorView(
|
||||
store: store.memoStore(),
|
||||
title: "",
|
||||
placeholder: L10n.SendFeedback.hcwhPlaceholder
|
||||
)
|
||||
.frame(height: 155)
|
||||
|
||||
if let supportData = store.supportData {
|
||||
UIMailDialogView(
|
||||
supportData: supportData,
|
||||
completion: {
|
||||
store.send(.sendSupportMailFinished)
|
||||
}
|
||||
)
|
||||
// UIMailDialogView only wraps MFMailComposeViewController presentation
|
||||
// so frame is set to 0 to not break SwiftUIs layout
|
||||
.frame(width: 0, height: 0)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
ZashiButton(
|
||||
L10n.General.send
|
||||
) {
|
||||
store.send(.sendTapped)
|
||||
}
|
||||
.disabled(store.invalidForm)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
shareView()
|
||||
}
|
||||
.zashiBack()
|
||||
.onAppear { store.send(.onAppear) }
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.screenHorizontalPadding()
|
||||
.applyScreenBackground()
|
||||
.screenTitle(L10n.SendFeedback.screenTitle.uppercased())
|
||||
}
|
||||
}
|
||||
|
||||
extension SendFeedbackView {
|
||||
@ViewBuilder func shareView() -> some View {
|
||||
if let message = store.messageToBeShared{
|
||||
UIShareDialogView(activityItems: [
|
||||
ShareableMessage(
|
||||
title: L10n.SendFeedback.Share.title,
|
||||
message: message,
|
||||
desc: L10n.SendFeedback.Share.desc
|
||||
),
|
||||
]) {
|
||||
store.send(.shareFinished)
|
||||
}
|
||||
// UIShareDialogView only wraps UIActivityViewController presentation
|
||||
// so frame is set to 0 to not break SwiftUIs layout
|
||||
.frame(width: 0, height: 0)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
#Preview {
|
||||
SendFeedbackView(store: SendFeedback.initial)
|
||||
}
|
||||
|
||||
// MARK: - Store
|
||||
|
||||
extension SendFeedback {
|
||||
public static var initial = StoreOf<SendFeedback>(
|
||||
initialState: .initial
|
||||
) {
|
||||
SendFeedback()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Placeholders
|
||||
|
||||
extension SendFeedback.State {
|
||||
public static let initial = SendFeedback.State()
|
||||
}
|
||||
|
||||
extension StoreOf<SendFeedback> {
|
||||
func memoStore() -> StoreOf<MessageEditor> {
|
||||
self.scope(
|
||||
state: \.memoState,
|
||||
action: \.memo
|
||||
)
|
||||
}
|
||||
}
|
|
@ -30,14 +30,14 @@ public struct AdvancedSettingsView: View {
|
|||
VStack(spacing: 0) {
|
||||
List {
|
||||
Group {
|
||||
SettingsRow(
|
||||
ActionRow(
|
||||
icon: Asset.Assets.Icons.key.image,
|
||||
title: L10n.Settings.recoveryPhrase
|
||||
) {
|
||||
store.send(.protectedAccessRequest(.backupPhrase))
|
||||
}
|
||||
|
||||
SettingsRow(
|
||||
ActionRow(
|
||||
icon: Asset.Assets.Icons.downloadCloud.image,
|
||||
title: L10n.Settings.exportPrivateData
|
||||
) {
|
||||
|
@ -45,14 +45,14 @@ public struct AdvancedSettingsView: View {
|
|||
}
|
||||
|
||||
if store.isEnoughFreeSpaceMode {
|
||||
SettingsRow(
|
||||
ActionRow(
|
||||
icon: Asset.Assets.Icons.server.image,
|
||||
title: L10n.Settings.chooseServer
|
||||
) {
|
||||
store.send(.updateDestination(.serverSetup))
|
||||
}
|
||||
|
||||
SettingsRow(
|
||||
ActionRow(
|
||||
icon: Asset.Assets.Icons.currencyDollar.image,
|
||||
title: L10n.CurrencyConversion.title
|
||||
) {
|
||||
|
@ -201,9 +201,9 @@ extension AdvancedSettings.State {
|
|||
currencyConversionSetupState: .init(isSettingsView: true),
|
||||
deleteWalletState: .initial,
|
||||
phraseDisplayState: RecoveryPhraseDisplay.State(
|
||||
birthday: nil,
|
||||
phrase: nil,
|
||||
showBackButton: false,
|
||||
birthday: nil
|
||||
showBackButton: false
|
||||
),
|
||||
privateDataConsentState: .initial,
|
||||
serverSetupState: ServerSetup.State()
|
||||
|
@ -222,8 +222,8 @@ extension StoreOf<AdvancedSettings> {
|
|||
currencyConversionSetupState: .initial,
|
||||
deleteWalletState: .initial,
|
||||
phraseDisplayState: RecoveryPhraseDisplay.State(
|
||||
phrase: nil,
|
||||
birthday: nil
|
||||
birthday: nil,
|
||||
phrase: nil
|
||||
),
|
||||
privateDataConsentState: .initial,
|
||||
serverSetupState: ServerSetup.State()
|
||||
|
|
|
@ -28,7 +28,7 @@ public struct IntegrationsView: View {
|
|||
List {
|
||||
Group {
|
||||
if store.inAppBrowserURL != nil {
|
||||
SettingsRow(
|
||||
ActionRow(
|
||||
icon: Asset.Assets.Partners.coinbase.image,
|
||||
title: L10n.Settings.buyZecCB,
|
||||
desc: L10n.Settings.coinbaseDesc,
|
||||
|
@ -40,7 +40,7 @@ public struct IntegrationsView: View {
|
|||
}
|
||||
|
||||
if store.featureFlags.flexa {
|
||||
SettingsRow(
|
||||
ActionRow(
|
||||
icon: walletStatus == .restoring
|
||||
? Asset.Assets.Partners.flexaDisabled.image
|
||||
: Asset.Assets.Partners.flexa.image,
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
import MessageUI
|
||||
|
||||
import About
|
||||
import AppVersion
|
||||
import Generated
|
||||
import Models
|
||||
import Pasteboard
|
||||
import SupportDataGenerator
|
||||
import ZcashLightClientKit
|
||||
import AddressBook
|
||||
import WhatsNew
|
||||
import SendFeedback
|
||||
|
||||
@Reducer
|
||||
public struct Settings {
|
||||
|
@ -20,12 +19,13 @@ public struct Settings {
|
|||
case addressBook
|
||||
case advanced
|
||||
case integrations
|
||||
case sendFeedback
|
||||
case whatsNew
|
||||
}
|
||||
|
||||
public var aboutState: About.State
|
||||
public var addressBookState: AddressBook.State
|
||||
public var advancedSettingsState: AdvancedSettings.State
|
||||
@Presents public var alert: AlertState<Action>?
|
||||
public var appVersion = ""
|
||||
public var appBuild = ""
|
||||
public var destination: Destination?
|
||||
|
@ -33,7 +33,9 @@ public struct Settings {
|
|||
public var integrationsState: Integrations.State
|
||||
public var isEnoughFreeSpaceMode = true
|
||||
public var supportData: SupportData?
|
||||
|
||||
public var sendFeedbackState: SendFeedback.State = .initial
|
||||
public var whatsNewState: WhatsNew.State = .initial
|
||||
|
||||
public init(
|
||||
aboutState: About.State,
|
||||
addressBookState: AddressBook.State,
|
||||
|
@ -41,8 +43,7 @@ public struct Settings {
|
|||
appVersion: String = "",
|
||||
appBuild: String = "",
|
||||
destination: Destination? = nil,
|
||||
integrationsState: Integrations.State,
|
||||
supportData: SupportData? = nil
|
||||
integrationsState: Integrations.State
|
||||
) {
|
||||
self.aboutState = aboutState
|
||||
self.addressBookState = addressBookState
|
||||
|
@ -51,7 +52,6 @@ public struct Settings {
|
|||
self.appBuild = appBuild
|
||||
self.destination = destination
|
||||
self.integrationsState = integrationsState
|
||||
self.supportData = supportData
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,19 +60,16 @@ public struct Settings {
|
|||
case addressBook(AddressBook.Action)
|
||||
case addressBookButtonTapped
|
||||
case advancedSettings(AdvancedSettings.Action)
|
||||
case alert(PresentationAction<Action>)
|
||||
case copyEmail
|
||||
case integrations(Integrations.Action)
|
||||
case onAppear
|
||||
case protectedAccessRequest(State.Destination)
|
||||
case sendSupportMail
|
||||
case sendSupportMailFinished
|
||||
case sendFeedback(SendFeedback.Action)
|
||||
case updateDestination(Settings.State.Destination?)
|
||||
case whatsNew(WhatsNew.Action)
|
||||
}
|
||||
|
||||
@Dependency(\.appVersion) var appVersion
|
||||
@Dependency(\.localAuthentication) var localAuthentication
|
||||
@Dependency(\.pasteboard) var pasteboard
|
||||
|
||||
public init() { }
|
||||
|
||||
|
@ -93,6 +90,14 @@ public struct Settings {
|
|||
Integrations()
|
||||
}
|
||||
|
||||
Scope(state: \.sendFeedbackState, action: \.sendFeedback) {
|
||||
SendFeedback()
|
||||
}
|
||||
|
||||
Scope(state: \.whatsNewState, action: \.whatsNew) {
|
||||
WhatsNew()
|
||||
}
|
||||
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
case .onAppear:
|
||||
|
@ -110,13 +115,15 @@ public struct Settings {
|
|||
case .addressBookButtonTapped:
|
||||
return .none
|
||||
|
||||
case .copyEmail:
|
||||
pasteboard.setString(SupportDataGenerator.Constants.email.redacted)
|
||||
return .none
|
||||
|
||||
case .integrations:
|
||||
return .none
|
||||
|
||||
case .sendFeedback:
|
||||
return .none
|
||||
|
||||
case .whatsNew:
|
||||
return .none
|
||||
|
||||
case .protectedAccessRequest(let destination):
|
||||
return .run { send in
|
||||
if await localAuthentication.authenticate() {
|
||||
|
@ -128,51 +135,9 @@ public struct Settings {
|
|||
state.destination = destination
|
||||
return .none
|
||||
|
||||
case .sendSupportMail:
|
||||
if MFMailComposeViewController.canSendMail() {
|
||||
state.supportData = SupportDataGenerator.generate()
|
||||
} else {
|
||||
state.alert = AlertState.sendSupportMail()
|
||||
}
|
||||
return .none
|
||||
|
||||
case .sendSupportMailFinished:
|
||||
state.supportData = nil
|
||||
return .none
|
||||
|
||||
case .alert(.presented(let action)):
|
||||
return Effect.send(action)
|
||||
|
||||
case .alert(.dismiss):
|
||||
state.alert = nil
|
||||
return .none
|
||||
|
||||
case .advancedSettings:
|
||||
return .none
|
||||
|
||||
case .alert:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
.ifLet(\.$alert, action: \.alert)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Alerts
|
||||
|
||||
extension AlertState where Action == Settings.Action {
|
||||
public static func sendSupportMail() -> AlertState {
|
||||
AlertState {
|
||||
TextState(L10n.Settings.Alert.CantSendEmail.title)
|
||||
} actions: {
|
||||
ButtonState(action: .copyEmail) {
|
||||
TextState(L10n.Settings.Alert.CantSendEmail.copyEmail(SupportDataGenerator.Constants.email))
|
||||
}
|
||||
ButtonState(action: .sendSupportMailFinished) {
|
||||
TextState(L10n.General.close)
|
||||
}
|
||||
} message: {
|
||||
TextState(L10n.Settings.Alert.CantSendEmail.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import UIComponents
|
|||
import PrivateDataConsent
|
||||
import ServerSetup
|
||||
import AddressBook
|
||||
import WhatsNew
|
||||
import SendFeedback
|
||||
|
||||
public struct SettingsView: View {
|
||||
@Perception.Bindable var store: StoreOf<Settings>
|
||||
|
@ -21,7 +23,7 @@ public struct SettingsView: View {
|
|||
VStack {
|
||||
List {
|
||||
Group {
|
||||
SettingsRow(
|
||||
ActionRow(
|
||||
icon: Asset.Assets.Icons.user.image,
|
||||
title: L10n.Settings.addressBook
|
||||
) {
|
||||
|
@ -29,15 +31,15 @@ public struct SettingsView: View {
|
|||
}
|
||||
|
||||
if store.isEnoughFreeSpaceMode {
|
||||
SettingsRow(
|
||||
ActionRow(
|
||||
icon: Asset.Assets.Icons.integrations.image,
|
||||
title: L10n.Settings.integrations,
|
||||
accessoryView:
|
||||
HStack(spacing: 0) {
|
||||
Asset.Assets.Partners.coinbaseSeeklogo.image
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
if store.featureFlags.flexa {
|
||||
Asset.Assets.Partners.flexaSeekLogo.image
|
||||
.resizable()
|
||||
|
@ -50,26 +52,34 @@ public struct SettingsView: View {
|
|||
}
|
||||
}
|
||||
|
||||
SettingsRow(
|
||||
ActionRow(
|
||||
icon: Asset.Assets.Icons.settings.image,
|
||||
title: L10n.Settings.advanced
|
||||
) {
|
||||
store.send(.updateDestination(.advanced))
|
||||
}
|
||||
|
||||
SettingsRow(
|
||||
|
||||
ActionRow(
|
||||
icon: Asset.Assets.Icons.magicWand.image,
|
||||
title: L10n.Settings.whatsNew
|
||||
) {
|
||||
store.send(.updateDestination(.whatsNew))
|
||||
}
|
||||
|
||||
ActionRow(
|
||||
icon: Asset.Assets.infoOutline.image,
|
||||
title: L10n.Settings.about
|
||||
) {
|
||||
store.send(.updateDestination(.about))
|
||||
}
|
||||
|
||||
SettingsRow(
|
||||
ActionRow(
|
||||
icon: Asset.Assets.Icons.messageSmile.image,
|
||||
title: L10n.Settings.feedback,
|
||||
divider: false
|
||||
) {
|
||||
store.send(.sendSupportMail)
|
||||
store.send(.updateDestination(.sendFeedback))
|
||||
//store.send(.sendSupportMail)
|
||||
}
|
||||
}
|
||||
.listRowInsets(EdgeInsets())
|
||||
|
@ -102,22 +112,22 @@ public struct SettingsView: View {
|
|||
AddressBookView(store: store.addressBookStore())
|
||||
}
|
||||
)
|
||||
.navigationLinkEmpty(
|
||||
isActive: store.bindingFor(.whatsNew),
|
||||
destination: {
|
||||
WhatsNewView(store: store.whatsNewStore())
|
||||
}
|
||||
)
|
||||
.navigationLinkEmpty(
|
||||
isActive: store.bindingFor(.sendFeedback),
|
||||
destination: {
|
||||
SendFeedbackView(store: store.sendFeedbackStore())
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
store.send(.onAppear)
|
||||
}
|
||||
|
||||
if let supportData = store.supportData {
|
||||
UIMailDialogView(
|
||||
supportData: supportData,
|
||||
completion: {
|
||||
store.send(.sendSupportMailFinished)
|
||||
}
|
||||
)
|
||||
// UIMailDialogView only wraps MFMailComposeViewController presentation
|
||||
// so frame is set to 0 to not break SwiftUIs layout
|
||||
.frame(width: 0, height: 0)
|
||||
}
|
||||
|
||||
|
||||
Spacer()
|
||||
|
||||
Asset.Assets.zashiTitle.image
|
||||
|
@ -132,10 +142,6 @@ public struct SettingsView: View {
|
|||
.applyScreenBackground()
|
||||
.listStyle(.plain)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.alert(store: store.scope(
|
||||
state: \.$alert,
|
||||
action: \.alert
|
||||
))
|
||||
.zashiBack()
|
||||
.screenTitle(L10n.Settings.title)
|
||||
.walletStatusPanel()
|
||||
|
@ -198,18 +204,32 @@ extension StoreOf<Settings> {
|
|||
action: \.about
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
func addressBookStore() -> StoreOf<AddressBook> {
|
||||
self.scope(
|
||||
state: \.addressBookState,
|
||||
action: \.addressBook
|
||||
)
|
||||
}
|
||||
|
||||
func integrationsStore() -> StoreOf<Integrations> {
|
||||
self.scope(
|
||||
state: \.integrationsState,
|
||||
action: \.integrations
|
||||
)
|
||||
}
|
||||
|
||||
func addressBookStore() -> StoreOf<AddressBook> {
|
||||
|
||||
func sendFeedbackStore() -> StoreOf<SendFeedback> {
|
||||
self.scope(
|
||||
state: \.addressBookState,
|
||||
action: \.addressBook
|
||||
state: \.sendFeedbackState,
|
||||
action: \.sendFeedback
|
||||
)
|
||||
}
|
||||
|
||||
func whatsNewStore() -> StoreOf<WhatsNew> {
|
||||
self.scope(
|
||||
state: \.whatsNewState,
|
||||
action: \.whatsNew
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,14 @@
|
|||
|
||||
import ComposableArchitecture
|
||||
import WhatsNewProvider
|
||||
import AppVersion
|
||||
|
||||
@Reducer
|
||||
public struct WhatsNew {
|
||||
@ObservableState
|
||||
public struct State: Equatable {
|
||||
public var appVersion = ""
|
||||
public var appBuild = ""
|
||||
public var latest: WhatNewRelease
|
||||
public var releases: WhatNewReleases
|
||||
|
||||
|
@ -28,6 +31,7 @@ public struct WhatsNew {
|
|||
case onAppear
|
||||
}
|
||||
|
||||
@Dependency(\.appVersion) var appVersion
|
||||
@Dependency(\.whatsNewProvider) var whatsNewProvider
|
||||
|
||||
public init() { }
|
||||
|
@ -36,6 +40,8 @@ public struct WhatsNew {
|
|||
Reduce { state, action in
|
||||
switch action {
|
||||
case .onAppear:
|
||||
state.appVersion = appVersion.appVersion()
|
||||
state.appBuild = appVersion.appBuild()
|
||||
state.latest = whatsNewProvider.latest()
|
||||
state.releases = whatsNewProvider.all()
|
||||
return .none
|
||||
|
|
|
@ -20,65 +20,79 @@ public struct WhatsNewView: View {
|
|||
|
||||
public var body: some View {
|
||||
WithPerceptionTracking {
|
||||
ScrollView {
|
||||
HStack {
|
||||
Text(L10n.WhatsNew.version(store.latest.version))
|
||||
.font(.custom(FontFamily.Inter.bold.name, size: 14))
|
||||
VStack(spacing: 0) {
|
||||
ScrollView {
|
||||
HStack(spacing: 0) {
|
||||
Text(L10n.WhatsNew.version(store.latest.version))
|
||||
.zFont(.semiBold, size: 20, style: Design.Text.primary)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(store.latest.date)
|
||||
.zFont(.semiBold, size: 14, style: Design.Text.primary)
|
||||
}
|
||||
.padding(.top, 40)
|
||||
.padding(.bottom, 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(store.latest.date)
|
||||
.font(.custom(FontFamily.Inter.bold.name, size: 14))
|
||||
}
|
||||
.padding(.vertical, 25)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal, 35)
|
||||
|
||||
WithPerceptionTracking {
|
||||
ForEach(0..<store.latest.sections.count, id: \.self) { sectionIndex in
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
WithPerceptionTracking {
|
||||
Text(store.latest.sections[sectionIndex].title)
|
||||
.font(.custom(FontFamily.Inter.bold.name, size: 14))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
WithPerceptionTracking {
|
||||
ForEach(0..<store.latest.sections.count, id: \.self) { sectionIndex in
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
WithPerceptionTracking {
|
||||
ForEach(0..<store.latest.sections[sectionIndex].bulletpoints.count, id: \.self) { index in
|
||||
WithPerceptionTracking {
|
||||
if let previewText = try? AttributedString(
|
||||
markdown: store.latest.sections[sectionIndex].bulletpoints[index],
|
||||
including: \.zashiApp) {
|
||||
HStack {
|
||||
VStack {
|
||||
Circle()
|
||||
.frame(width: 4, height: 4)
|
||||
.padding(.top, 7)
|
||||
Text(store.latest.sections[sectionIndex].title)
|
||||
.zFont(.semiBold, size: 16, style: Design.Text.primary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
WithPerceptionTracking {
|
||||
ForEach(0..<store.latest.sections[sectionIndex].bulletpoints.count, id: \.self) { index in
|
||||
WithPerceptionTracking {
|
||||
if let previewText = try? AttributedString(
|
||||
markdown: store.latest.sections[sectionIndex].bulletpoints[index],
|
||||
including: \.zashiApp) {
|
||||
HStack {
|
||||
VStack {
|
||||
Circle()
|
||||
.frame(width: 4, height: 4)
|
||||
.padding(.top, 7)
|
||||
.padding(.leading, 8)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Spacer()
|
||||
ZashiText(withAttributedString: previewText)
|
||||
.zFont(size: 14, style: Design.Text.primary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.accentColor(Asset.Colors.primary.color)
|
||||
.lineSpacing(1.5)
|
||||
}
|
||||
|
||||
ZashiText(withAttributedString: previewText)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 14))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.accentColor(Asset.Colors.primary.color)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 16)
|
||||
}
|
||||
.padding(.bottom, 20)
|
||||
}
|
||||
.padding(.horizontal, 35)
|
||||
}
|
||||
.padding(.vertical, 1)
|
||||
.zashiBack()
|
||||
.onAppear { store.send(.onAppear) }
|
||||
.screenTitle(L10n.Settings.whatsNew.uppercased())
|
||||
|
||||
Spacer()
|
||||
|
||||
Asset.Assets.zashiTitle.image
|
||||
.zImage(width: 73, height: 20, color: Asset.Colors.primary.color)
|
||||
.padding(.bottom, 16)
|
||||
|
||||
Text(L10n.Settings.version(store.appVersion, store.appBuild))
|
||||
.zFont(size: 16, style: Design.Text.tertiary)
|
||||
.padding(.bottom, 24)
|
||||
}
|
||||
.padding(.vertical, 1)
|
||||
.zashiBack()
|
||||
.onAppear { store.send(.onAppear) }
|
||||
.screenTitle(L10n.About.whatsNew)
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.screenHorizontalPadding()
|
||||
.applyScreenBackground()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,17 +15,15 @@ public enum L10n {
|
|||
return L10n.tr("Localizable", "qrCodeFor", String(describing: p1), fallback: "QR Code for %@")
|
||||
}
|
||||
public enum About {
|
||||
/// Built by Zcashers, for Zcashers. Developed and maintained by Electric Coin Co., the inventor of Zcash, Zashi features a built-in user-feedback mechanism to enable more features, more quickly.
|
||||
public static let additionalInfo = L10n.tr("Localizable", "about.additionalInfo", fallback: "Built by Zcashers, for Zcashers. Developed and maintained by Electric Coin Co., the inventor of Zcash, Zashi features a built-in user-feedback mechanism to enable more features, more quickly.")
|
||||
/// Send and receive ZEC on Zashi!
|
||||
/// Zashi is a minimal-design, self-custody, ZEC-only shielded wallet that keeps your transaction history and wallet balance private. Built by Zcashers, for Zcashers. Developed and maintained by Electric Coin Co., the inventor of Zcash, Zashi features a built-in user-feedback mechanism to enable more features, more quickly.
|
||||
public static let info = L10n.tr("Localizable", "about.info", fallback: "Send and receive ZEC on Zashi!\nZashi is a minimal-design, self-custody, ZEC-only shielded wallet that keeps your transaction history and wallet balance private. Built by Zcashers, for Zcashers. Developed and maintained by Electric Coin Co., the inventor of Zcash, Zashi features a built-in user-feedback mechanism to enable more features, more quickly.")
|
||||
/// Zashi is a minimal-design, self-custody, ZEC-only shielded wallet that keeps your transaction history and wallet balance private.
|
||||
public static let info = L10n.tr("Localizable", "about.info", fallback: "Send and receive ZEC on Zashi!\nZashi is a minimal-design, self-custody, ZEC-only shielded wallet that keeps your transaction history and wallet balance private.")
|
||||
/// Privacy Policy
|
||||
public static let privacyPolicy = L10n.tr("Localizable", "about.privacyPolicy", fallback: "Privacy Policy")
|
||||
/// Zashi Version %@ (%@)
|
||||
public static func version(_ p1: Any, _ p2: Any) -> String {
|
||||
return L10n.tr("Localizable", "about.version", String(describing: p1), String(describing: p2), fallback: "Zashi Version %@ (%@)")
|
||||
}
|
||||
/// What's new
|
||||
public static let whatsNew = L10n.tr("Localizable", "about.whatsNew", fallback: "What's new")
|
||||
/// Introducing Zashi
|
||||
public static let title = L10n.tr("Localizable", "about.title", fallback: "Introducing Zashi")
|
||||
}
|
||||
public enum AddressBook {
|
||||
/// Add New Contact
|
||||
|
@ -170,16 +168,18 @@ public enum L10n {
|
|||
public static let title = L10n.tr("Localizable", "deeplinkWarning.title", fallback: "Looks like you used a third-party app to scan for payment.")
|
||||
}
|
||||
public enum DeleteWallet {
|
||||
/// Delete
|
||||
public static let actionButtonTitle = L10n.tr("Localizable", "deleteWallet.actionButtonTitle", fallback: "Delete")
|
||||
/// Reset Zashi
|
||||
public static let actionButtonTitle = L10n.tr("Localizable", "deleteWallet.actionButtonTitle", fallback: "Reset Zashi")
|
||||
/// I understand
|
||||
public static let iUnderstand = L10n.tr("Localizable", "deleteWallet.iUnderstand", fallback: "I understand")
|
||||
/// Please don't delete this app unless you're sure you understand the effects.
|
||||
public static let message1 = L10n.tr("Localizable", "deleteWallet.message1", fallback: "Please don't delete this app unless you're sure you understand the effects.")
|
||||
/// Deleting the Zashi app will delete the database and cached data. Any funds you have in this wallet will be lost and can only be recovered by using your Zashi secret recovery phrase in Zashi or another Zcash wallet.
|
||||
public static let message2 = L10n.tr("Localizable", "deleteWallet.message2", fallback: "Deleting the Zashi app will delete the database and cached data. Any funds you have in this wallet will be lost and can only be recovered by using your Zashi secret recovery phrase in Zashi or another Zcash wallet.")
|
||||
/// Delete Zashi
|
||||
public static let title = L10n.tr("Localizable", "deleteWallet.title", fallback: "Delete Zashi")
|
||||
/// Please don't reset this app unless you're sure you understand the effects.
|
||||
public static let message1 = L10n.tr("Localizable", "deleteWallet.message1", fallback: "Please don't reset this app unless you're sure you understand the effects.")
|
||||
/// Resetting the Zashi app will delete the database and cached data. Any funds you have in this wallet will be lost and can only be recovered by using your Zashi secret recovery phrase in another Zcash wallet.
|
||||
public static let message2 = L10n.tr("Localizable", "deleteWallet.message2", fallback: "Resetting the Zashi app will delete the database and cached data. Any funds you have in this wallet will be lost and can only be recovered by using your Zashi secret recovery phrase in another Zcash wallet.")
|
||||
/// Reset
|
||||
public static let screenTitle = L10n.tr("Localizable", "deleteWallet.screenTitle", fallback: "Reset")
|
||||
/// Reset Zashi
|
||||
public static let title = L10n.tr("Localizable", "deleteWallet.title", fallback: "Reset Zashi")
|
||||
}
|
||||
public enum ExportLogs {
|
||||
public enum Alert {
|
||||
|
@ -354,16 +354,18 @@ public enum L10n {
|
|||
}
|
||||
}
|
||||
public enum PrivateDataConsent {
|
||||
/// I agree
|
||||
public static let confirmation = L10n.tr("Localizable", "privateDataConsent.confirmation", fallback: "I agree")
|
||||
/// By clicking "I Agree" below, you give your consent to export Zashi’s private data which includes the entire history of the wallet, all private information, memos, amounts and recipient addresses, even for your shielded activity.*
|
||||
///
|
||||
/// This private data also gives the ability to see certain future actions you take with Zashi.
|
||||
///
|
||||
/// Sharing this private data is irrevocable — once you have shared this private data with someone, there is no way to revoke their access.
|
||||
public static let message = L10n.tr("Localizable", "privateDataConsent.message", fallback: "By clicking \"I Agree\" below, you give your consent to export Zashi’s private data which includes the entire history of the wallet, all private information, memos, amounts and recipient addresses, even for your shielded activity.*\n\nThis private data also gives the ability to see certain future actions you take with Zashi.\n\nSharing this private data is irrevocable — once you have shared this private data with someone, there is no way to revoke their access.")
|
||||
/// I agree to Zashi's Export Private Data Policies and Privacy Policy
|
||||
public static let confirmation = L10n.tr("Localizable", "privateDataConsent.confirmation", fallback: "I agree to Zashi's Export Private Data Policies and Privacy Policy")
|
||||
/// By clicking “I Agree” below, you give your consent to export Zashi’s private data which includes the entire history of the wallet, sll private information, memos, amounts, and recipient addresses, even for your shielded activity.*
|
||||
public static let message1 = L10n.tr("Localizable", "privateDataConsent.message1", fallback: "By clicking “I Agree” below, you give your consent to export Zashi’s private data which includes the entire history of the wallet, sll private information, memos, amounts, and recipient addresses, even for your shielded activity.*")
|
||||
/// The private data also gives the ability to see certain future actions you take with Zashi.
|
||||
public static let message2 = L10n.tr("Localizable", "privateDataConsent.message2", fallback: "The private data also gives the ability to see certain future actions you take with Zashi.")
|
||||
/// Sharing this private data is irrevocable - once you have shared this private data with someone, there is no way to revoke their access.
|
||||
public static let message3 = L10n.tr("Localizable", "privateDataConsent.message3", fallback: "Sharing this private data is irrevocable - once you have shared this private data with someone, there is no way to revoke their access.")
|
||||
/// *Note that this private data does not give them the ability to spend your funds, only the ability to see what you do with your funds.
|
||||
public static let note = L10n.tr("Localizable", "privateDataConsent.note", fallback: "*Note that this private data does not give them the ability to spend your funds, only the ability to see what you do with your funds.")
|
||||
public static let message4 = L10n.tr("Localizable", "privateDataConsent.message4", fallback: "*Note that this private data does not give them the ability to spend your funds, only the ability to see what you do with your funds.")
|
||||
/// Data Export
|
||||
public static let screenTitle = L10n.tr("Localizable", "privateDataConsent.screenTitle", fallback: "Data Export")
|
||||
/// Consent for Exporting Private Data
|
||||
public static let title = L10n.tr("Localizable", "privateDataConsent.title", fallback: "Consent for Exporting Private Data")
|
||||
}
|
||||
|
@ -388,6 +390,8 @@ public enum L10n {
|
|||
public static let title = L10n.tr("Localizable", "proposalPartial.title", fallback: "Send Failed")
|
||||
/// Transaction Ids
|
||||
public static let transactionIds = L10n.tr("Localizable", "proposalPartial.transactionIds", fallback: "Transaction Ids")
|
||||
/// Transaction statuses:
|
||||
public static let transactionStatuses = L10n.tr("Localizable", "proposalPartial.transactionStatuses", fallback: "Transaction statuses:")
|
||||
}
|
||||
public enum Receive {
|
||||
/// Copy
|
||||
|
@ -422,18 +426,24 @@ public enum L10n {
|
|||
}
|
||||
}
|
||||
public enum RecoveryPhraseDisplay {
|
||||
/// Wallet birthday height: %@
|
||||
public static func birthdayHeight(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "recoveryPhraseDisplay.birthdayHeight", String(describing: p1), fallback: "Wallet birthday height: %@")
|
||||
}
|
||||
/// The following 24 words are the keys to your funds and are the only way to recover your funds if you get locked out or get a new device. Protect your ZEC by storing this phrase in a place you trust and never share it with anyone!
|
||||
public static let description = L10n.tr("Localizable", "recoveryPhraseDisplay.description", fallback: "The following 24 words are the keys to your funds and are the only way to recover your funds if you get locked out or get a new device. Protect your ZEC by storing this phrase in a place you trust and never share it with anyone!")
|
||||
/// Wallet Birthday Height determines the birth (chain) height of your wallet and facilitates faster wallet restore process. Save this number together with your seed phrase in a safe place.
|
||||
public static let birthdayDesc = L10n.tr("Localizable", "recoveryPhraseDisplay.birthdayDesc", fallback: "Wallet Birthday Height determines the birth (chain) height of your wallet and facilitates faster wallet restore process. Save this number together with your seed phrase in a safe place.")
|
||||
/// Wallet Birthday Height
|
||||
public static let birthdayTitle = L10n.tr("Localizable", "recoveryPhraseDisplay.birthdayTitle", fallback: "Wallet Birthday Height")
|
||||
/// The following 24 words are the keys to your funds and are the only way to recover your funds if you get locked out or get a new device.
|
||||
public static let description = L10n.tr("Localizable", "recoveryPhraseDisplay.description", fallback: "The following 24 words are the keys to your funds and are the only way to recover your funds if you get locked out or get a new device.")
|
||||
/// Hide security details
|
||||
public static let hide = L10n.tr("Localizable", "recoveryPhraseDisplay.hide", fallback: "Hide security details")
|
||||
/// The keys are missing. No backup phrase is stored in the keychain.
|
||||
public static let noWords = L10n.tr("Localizable", "recoveryPhraseDisplay.noWords", fallback: "The keys are missing. No backup phrase is stored in the keychain.")
|
||||
/// Your Secret
|
||||
public static let titlePart1 = L10n.tr("Localizable", "recoveryPhraseDisplay.titlePart1", fallback: "Your Secret")
|
||||
/// Reveal security details
|
||||
public static let reveal = L10n.tr("Localizable", "recoveryPhraseDisplay.reveal", fallback: "Reveal security details")
|
||||
/// Recovery Phrase
|
||||
public static let titlePart2 = L10n.tr("Localizable", "recoveryPhraseDisplay.titlePart2", fallback: "Recovery Phrase")
|
||||
public static let screenTitle = L10n.tr("Localizable", "recoveryPhraseDisplay.screenTitle", fallback: "Recovery Phrase")
|
||||
/// Secure Your Wallet
|
||||
public static let title = L10n.tr("Localizable", "recoveryPhraseDisplay.title", fallback: "Secure Your Wallet")
|
||||
/// Protect your ZEC by storing this phrase in a place you trust and never share it with anyone!
|
||||
public static let warning = L10n.tr("Localizable", "recoveryPhraseDisplay.warning", fallback: "Protect your ZEC by storing this phrase in a place you trust and never share it with anyone!")
|
||||
public enum Alert {
|
||||
public enum Failed {
|
||||
/// Attempt to load the stored wallet from the keychain failed. Error: %@
|
||||
|
@ -641,6 +651,8 @@ public enum L10n {
|
|||
public static let acknowledge = L10n.tr("Localizable", "securityWarning.acknowledge", fallback: "I acknowledge")
|
||||
/// Confirm
|
||||
public static let confirm = L10n.tr("Localizable", "securityWarning.confirm", fallback: "Confirm")
|
||||
/// Privacy Notice
|
||||
public static let screenTitle = L10n.tr("Localizable", "securityWarning.screenTitle", fallback: "Privacy Notice")
|
||||
/// Security warning:
|
||||
public static let title = L10n.tr("Localizable", "securityWarning.title", fallback: "Security warning:")
|
||||
/// Zashi %@ (%@) is a Zcash-only, shielded wallet — built by Zcashers for Zcashers. Zashi has been engineered for your privacy and safety. By installing and using Zashi, you consent to share crash reports with Electric Coin Co. (the wallet developer), which will help us improve the Zashi user experience.*
|
||||
|
@ -734,6 +746,28 @@ public enum L10n {
|
|||
public static let total = L10n.tr("Localizable", "send.requestPayment.total", fallback: "Total")
|
||||
}
|
||||
}
|
||||
public enum SendFeedback {
|
||||
/// Please let us know about any problems you have had, or features you want to see in the future.
|
||||
public static let desc = L10n.tr("Localizable", "sendFeedback.desc", fallback: "Please let us know about any problems you have had, or features you want to see in the future.")
|
||||
/// I would like to ask about...
|
||||
public static let hcwhPlaceholder = L10n.tr("Localizable", "sendFeedback.hcwhPlaceholder", fallback: "I would like to ask about...")
|
||||
/// How can we help you?
|
||||
public static let howCanWeHelp = L10n.tr("Localizable", "sendFeedback.howCanWeHelp", fallback: "How can we help you?")
|
||||
/// How is your Zashi experience?
|
||||
public static let ratingQuestion = L10n.tr("Localizable", "sendFeedback.ratingQuestion", fallback: "How is your Zashi experience?")
|
||||
/// Support
|
||||
public static let screenTitle = L10n.tr("Localizable", "sendFeedback.screenTitle", fallback: "Support")
|
||||
/// Send Us Feedback
|
||||
public static let title = L10n.tr("Localizable", "sendFeedback.title", fallback: "Send Us Feedback")
|
||||
public enum Share {
|
||||
/// Zashi
|
||||
public static let desc = L10n.tr("Localizable", "sendFeedback.share.desc", fallback: "Zashi")
|
||||
/// Your device doesn’t have an Apple email set up, so we prepared this message for you to send using your preferred email client. Please send this message to:
|
||||
public static let notAppleMailInfo = L10n.tr("Localizable", "sendFeedback.share.notAppleMailInfo", fallback: "Your device doesn’t have an Apple email set up, so we prepared this message for you to send using your preferred email client. Please send this message to:")
|
||||
/// Support message
|
||||
public static let title = L10n.tr("Localizable", "sendFeedback.share.title", fallback: "Support message")
|
||||
}
|
||||
}
|
||||
public enum ServerSetup {
|
||||
/// Active
|
||||
public static let active = L10n.tr("Localizable", "serverSetup.active", fallback: "Active")
|
||||
|
@ -771,8 +805,8 @@ public enum L10n {
|
|||
}
|
||||
}
|
||||
public enum Settings {
|
||||
/// About Us
|
||||
public static let about = L10n.tr("Localizable", "settings.about", fallback: "About Us")
|
||||
/// About
|
||||
public static let about = L10n.tr("Localizable", "settings.about", fallback: "About")
|
||||
/// Address Book
|
||||
public static let addressBook = L10n.tr("Localizable", "settings.addressBook", fallback: "Address Book")
|
||||
/// Advanced Settings
|
||||
|
@ -785,8 +819,8 @@ public enum L10n {
|
|||
public static let coinbaseDesc = L10n.tr("Localizable", "settings.coinbaseDesc", fallback: "A hassle-free way to buy ZEC and get it directly into your Zashi wallet.")
|
||||
/// Currency Conversion
|
||||
public static let currencyConversion = L10n.tr("Localizable", "settings.currencyConversion", fallback: "Currency Conversion")
|
||||
/// Delete Zashi
|
||||
public static let deleteZashi = L10n.tr("Localizable", "settings.deleteZashi", fallback: "Delete Zashi")
|
||||
/// Reset Zashi
|
||||
public static let deleteZashi = L10n.tr("Localizable", "settings.deleteZashi", fallback: "Reset Zashi")
|
||||
/// You will be asked to confirm on the next screen
|
||||
public static let deleteZashiWarning = L10n.tr("Localizable", "settings.deleteZashiWarning", fallback: "You will be asked to confirm on the next screen")
|
||||
/// Export logs only
|
||||
|
@ -811,6 +845,8 @@ public enum L10n {
|
|||
public static func version(_ p1: Any, _ p2: Any) -> String {
|
||||
return L10n.tr("Localizable", "settings.version", String(describing: p1), String(describing: p2), fallback: "Version %@ (%@)")
|
||||
}
|
||||
/// What's new
|
||||
public static let whatsNew = L10n.tr("Localizable", "settings.whatsNew", fallback: "What's new")
|
||||
public enum Alert {
|
||||
public enum CantSendEmail {
|
||||
/// Copy %@
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "eyeOff.png",
|
||||
"filename" : "eye-off.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
|
BIN
modules/Sources/Generated/Resources/Assets.xcassets/eyeOff.imageset/eye-off.png
vendored
Normal file
BIN
modules/Sources/Generated/Resources/Assets.xcassets/eyeOff.imageset/eye-off.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
Before Width: | Height: | Size: 10 KiB |
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "eyeOn.png",
|
||||
"filename" : "eye.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
Before Width: | Height: | Size: 8.7 KiB |
12
modules/Sources/Generated/Resources/Assets.xcassets/icons/magicWand.imageset/Contents.json
vendored
Normal file
12
modules/Sources/Generated/Resources/Assets.xcassets/icons/magicWand.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "magicWand.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
modules/Sources/Generated/Resources/Assets.xcassets/icons/magicWand.imageset/magicWand.png
vendored
Normal file
BIN
modules/Sources/Generated/Resources/Assets.xcassets/icons/magicWand.imageset/magicWand.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.2 KiB |
|
@ -5,6 +5,7 @@
|
|||
|
||||
// MARK: - Security Warning
|
||||
"securityWarning.title" = "Security warning:";
|
||||
"securityWarning.screenTitle" = "Privacy Notice";
|
||||
"securityWarning.warningA" = "Zashi %@ (%@) is a Zcash-only, shielded wallet — built by Zcashers for Zcashers. Zashi has been engineered for your privacy and safety. By installing and using Zashi, you consent to share crash reports with Electric Coin Co. (the wallet developer), which will help us improve the Zashi user experience.*";
|
||||
"securityWarning.warningB" = "Please acknowledge and confirm below to proceed.";
|
||||
"securityWarning.warningC" = "*Note";
|
||||
|
@ -13,12 +14,16 @@
|
|||
"securityWarning.confirm" = "Confirm";
|
||||
|
||||
// MARK: - Secret Recovery Phrase Display
|
||||
"recoveryPhraseDisplay.titlePart1" = "Your Secret";
|
||||
"recoveryPhraseDisplay.titlePart2" = "Recovery Phrase";
|
||||
"recoveryPhraseDisplay.description" = "The following 24 words are the keys to your funds and are the only way to recover your funds if you get locked out or get a new device. Protect your ZEC by storing this phrase in a place you trust and never share it with anyone!";
|
||||
"recoveryPhraseDisplay.screenTitle" = "Recovery Phrase";
|
||||
"recoveryPhraseDisplay.title" = "Secure Your Wallet";
|
||||
"recoveryPhraseDisplay.description" = "The following 24 words are the keys to your funds and are the only way to recover your funds if you get locked out or get a new device.";
|
||||
"recoveryPhraseDisplay.button.wroteItDown" = "I've saved it";
|
||||
"recoveryPhraseDisplay.noWords" = "The keys are missing. No backup phrase is stored in the keychain.";
|
||||
"recoveryPhraseDisplay.birthdayHeight" = "Wallet birthday height: %@";
|
||||
"recoveryPhraseDisplay.birthdayTitle" = "Wallet Birthday Height";
|
||||
"recoveryPhraseDisplay.birthdayDesc" = "Wallet Birthday Height determines the birth (chain) height of your wallet and facilitates faster wallet restore process. Save this number together with your seed phrase in a safe place.";
|
||||
"recoveryPhraseDisplay.warning" = "Protect your ZEC by storing this phrase in a place you trust and never share it with anyone!";
|
||||
"recoveryPhraseDisplay.reveal" = "Reveal security details";
|
||||
"recoveryPhraseDisplay.hide" = "Hide security details";
|
||||
"recoveryPhraseDisplay.alert.failed.title" = "Failed to load stored wallet";
|
||||
"recoveryPhraseDisplay.alert.failed.message" = "Attempt to load the stored wallet from the keychain failed. Error: %@";
|
||||
|
||||
|
@ -33,6 +38,7 @@
|
|||
"proposalPartial.mailPart1" = "Hi Zashi Team,";
|
||||
"proposalPartial.mailPart2" = "While sending a transaction to a TEX address, I encountered an error state. I'm reaching out to get guidance on how to recover my funds.";
|
||||
"proposalPartial.mailPart3" = "Thank you.";
|
||||
"proposalPartial.transactionStatuses" = "Transaction statuses:";
|
||||
|
||||
// MARK: - Import Wallet Screen
|
||||
"importWallet.description" = "Enter secret\nrecovery phrase";
|
||||
|
@ -175,11 +181,12 @@
|
|||
"zecKeyboard.invalid" = "This transaction amount is invalid.";
|
||||
|
||||
// MARK: - Delete Wallet
|
||||
"deleteWallet.title" = "Delete Zashi";
|
||||
"deleteWallet.message1" = "Please don't delete this app unless you're sure you understand the effects.";
|
||||
"deleteWallet.message2" = "Deleting the Zashi app will delete the database and cached data. Any funds you have in this wallet will be lost and can only be recovered by using your Zashi secret recovery phrase in Zashi or another Zcash wallet.";
|
||||
"deleteWallet.title" = "Reset Zashi";
|
||||
"deleteWallet.message1" = "Please don't reset this app unless you're sure you understand the effects.";
|
||||
"deleteWallet.message2" = "Resetting the Zashi app will delete the database and cached data. Any funds you have in this wallet will be lost and can only be recovered by using your Zashi secret recovery phrase in another Zcash wallet.";
|
||||
"deleteWallet.iUnderstand" = "I understand";
|
||||
"deleteWallet.actionButtonTitle" = "Delete";
|
||||
"deleteWallet.actionButtonTitle" = "Reset Zashi";
|
||||
"deleteWallet.screenTitle" = "Reset";
|
||||
|
||||
// MARK: - Not Enogh Free Space
|
||||
"notEnoughFreeSpace.title" = "Not enough free space";
|
||||
|
@ -190,9 +197,9 @@
|
|||
|
||||
// MARK: - About
|
||||
"about.info" = "Send and receive ZEC on Zashi!
|
||||
Zashi is a minimal-design, self-custody, ZEC-only shielded wallet that keeps your transaction history and wallet balance private. Built by Zcashers, for Zcashers. Developed and maintained by Electric Coin Co., the inventor of Zcash, Zashi features a built-in user-feedback mechanism to enable more features, more quickly.";
|
||||
"about.version" = "Zashi Version %@ (%@)";
|
||||
"about.whatsNew" = "What's new";
|
||||
Zashi is a minimal-design, self-custody, ZEC-only shielded wallet that keeps your transaction history and wallet balance private.";
|
||||
"about.additionalInfo" = "Built by Zcashers, for Zcashers. Developed and maintained by Electric Coin Co., the inventor of Zcash, Zashi features a built-in user-feedback mechanism to enable more features, more quickly.";
|
||||
"about.title" = "Introducing Zashi";
|
||||
"about.privacyPolicy" = "Privacy Policy";
|
||||
|
||||
// MARK: - What's new
|
||||
|
@ -202,7 +209,7 @@ Zashi is a minimal-design, self-custody, ZEC-only shielded wallet that keeps you
|
|||
"settings.title" = "Settings";
|
||||
"settings.addressBook" = "Address Book";
|
||||
"settings.advanced" = "Advanced Settings";
|
||||
"settings.about" = "About Us";
|
||||
"settings.about" = "About";
|
||||
"settings.feedback" = "Send Us Feedback";
|
||||
"settings.integrations" = "Integrations";
|
||||
"settings.recoveryPhrase" = "Recovery Phrase";
|
||||
|
@ -210,25 +217,38 @@ Zashi is a minimal-design, self-custody, ZEC-only shielded wallet that keeps you
|
|||
"settings.exportLogsOnly" = "Export logs only";
|
||||
"settings.chooseServer" = "Choose a Server";
|
||||
"settings.currencyConversion" = "Currency Conversion";
|
||||
"settings.whatsNew" = "What's new";
|
||||
"settings.buyZecCB" = "Buy ZEC with Coinbase";
|
||||
"settings.coinbaseDesc" = "A hassle-free way to buy ZEC and get it directly into your Zashi wallet.";
|
||||
"settings.flexa" = "Pay with Flexa";
|
||||
"settings.flexaDesc" = "Pay with Flexa payment clips and explore a new way of spending Zcash.";
|
||||
"settings.restoreWarning" = "During the Restore process, it is not possible to use payment integrations.";
|
||||
"settings.deleteZashi" = "Delete Zashi";
|
||||
"settings.deleteZashi" = "Reset Zashi";
|
||||
"settings.deleteZashiWarning" = "You will be asked to confirm on the next screen";
|
||||
"settings.version" = "Version %@ (%@)";
|
||||
"settings.alert.cantSendEmail.title" = "Oh, no!";
|
||||
"settings.alert.cantSendEmail.message" = "It looks like you don't have a default email app configured on your device. Copy the address below, and use your favorite email client to send us a message.";
|
||||
"settings.alert.cantSendEmail.copyEmail" = "Copy %@";
|
||||
|
||||
// MARK: - Send Feedback
|
||||
"sendFeedback.title" = "Send Us Feedback";
|
||||
"sendFeedback.screenTitle" = "Support";
|
||||
"sendFeedback.desc" = "Please let us know about any problems you have had, or features you want to see in the future.";
|
||||
"sendFeedback.ratingQuestion" = "How is your Zashi experience?";
|
||||
"sendFeedback.howCanWeHelp" = "How can we help you?";
|
||||
"sendFeedback.hcwhPlaceholder" = "I would like to ask about...";
|
||||
"sendFeedback.share.title" = "Support message";
|
||||
"sendFeedback.share.desc" = "Zashi";
|
||||
"sendFeedback.share.notAppleMailInfo" = "Your device doesn’t have an Apple email set up, so we prepared this message for you to send using your preferred email client. Please send this message to:";
|
||||
|
||||
// MARK: - Private Data Consent
|
||||
"privateDataConsent.title" = "Consent for Exporting Private Data";
|
||||
"privateDataConsent.message" = "By clicking \"I Agree\" below, you give your consent to export Zashi’s private data which includes the entire history of the wallet, all private information, memos, amounts and recipient addresses, even for your shielded activity.*\n
|
||||
This private data also gives the ability to see certain future actions you take with Zashi.\n
|
||||
Sharing this private data is irrevocable — once you have shared this private data with someone, there is no way to revoke their access.";
|
||||
"privateDataConsent.note" = "*Note that this private data does not give them the ability to spend your funds, only the ability to see what you do with your funds.";
|
||||
"privateDataConsent.confirmation" = "I agree";
|
||||
"privateDataConsent.message1" = "By clicking “I Agree” below, you give your consent to export Zashi’s private data which includes the entire history of the wallet, sll private information, memos, amounts, and recipient addresses, even for your shielded activity.*";
|
||||
"privateDataConsent.message2" = "The private data also gives the ability to see certain future actions you take with Zashi.";
|
||||
"privateDataConsent.message3" = "Sharing this private data is irrevocable - once you have shared this private data with someone, there is no way to revoke their access.";
|
||||
"privateDataConsent.message4" = "*Note that this private data does not give them the ability to spend your funds, only the ability to see what you do with your funds.";
|
||||
"privateDataConsent.confirmation" = "I agree to Zashi's Export Private Data Policies and Privacy Policy";
|
||||
"privateDataConsent.screenTitle" = "Data Export";
|
||||
|
||||
// MARK: - Sync message
|
||||
"sync.message.uptodate" = "Up-To-Date";
|
||||
|
|
|
@ -74,6 +74,7 @@ public enum Asset {
|
|||
public static let imageLibrary = ImageAsset(name: "imageLibrary")
|
||||
public static let integrations = ImageAsset(name: "integrations")
|
||||
public static let key = ImageAsset(name: "key")
|
||||
public static let magicWand = ImageAsset(name: "magicWand")
|
||||
public static let messageSmile = ImageAsset(name: "messageSmile")
|
||||
public static let partial = ImageAsset(name: "partial")
|
||||
public static let pencil = ImageAsset(name: "pencil")
|
||||
|
|
|
@ -29,11 +29,11 @@ public struct RecoveryPhrase: Equatable, Redactable {
|
|||
}
|
||||
|
||||
public func toGroups() -> [Group] {
|
||||
let chunks = words.count / 2
|
||||
let chunks = words.count / 3
|
||||
|
||||
var res: [Group] = []
|
||||
|
||||
for i in 0..<2 {
|
||||
for i in 0..<3 {
|
||||
var subwords: [RedactableString] = []
|
||||
for j in (i * chunks)..<((i + 1) * chunks) {
|
||||
subwords.append(words[j])
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// SettingsRow.swift
|
||||
// ActionRow.swift
|
||||
// Zashi
|
||||
//
|
||||
// Created by Lukáš Korba on 22.08.2024.
|
||||
|
@ -9,7 +9,7 @@ import SwiftUI
|
|||
|
||||
import Generated
|
||||
|
||||
public struct SettingsRow<AccessoryContent>: View where AccessoryContent: View{
|
||||
public struct ActionRow<AccessoryContent>: View where AccessoryContent: View{
|
||||
@Environment(\.isEnabled) private var isEnabled
|
||||
|
||||
let icon: Image
|
||||
|
@ -18,15 +18,17 @@ public struct SettingsRow<AccessoryContent>: View where AccessoryContent: View{
|
|||
let customIcon: Bool
|
||||
@ViewBuilder let accessoryView: AccessoryContent?
|
||||
let divider: Bool
|
||||
let horizontalPadding: CGFloat
|
||||
let action: () -> Void
|
||||
|
||||
init(
|
||||
public init(
|
||||
icon: Image,
|
||||
title: String,
|
||||
desc: String? = nil,
|
||||
customIcon: Bool = false,
|
||||
accessoryView: AccessoryContent? = EmptyView(),
|
||||
divider: Bool = true,
|
||||
horizontalPadding: CGFloat = 20,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.icon = icon
|
||||
|
@ -35,6 +37,7 @@ public struct SettingsRow<AccessoryContent>: View where AccessoryContent: View{
|
|||
self.customIcon = customIcon
|
||||
self.accessoryView = accessoryView
|
||||
self.divider = divider
|
||||
self.horizontalPadding = horizontalPadding
|
||||
self.action = action
|
||||
}
|
||||
|
||||
|
@ -89,7 +92,7 @@ public struct SettingsRow<AccessoryContent>: View where AccessoryContent: View{
|
|||
.zImage(size: 20, style: Design.Text.quaternary)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
|
||||
if divider {
|
||||
Design.Surfaces.divider.color
|
|
@ -15,25 +15,21 @@ public struct CheckboxToggleStyle: ToggleStyle {
|
|||
HStack {
|
||||
ZStack {
|
||||
if configuration.isOn {
|
||||
Image(systemName: "checkmark.square.fill")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .center)
|
||||
.background {
|
||||
Asset.Colors.background.color
|
||||
.scaleEffect(x: 0.94, y: 0.94)
|
||||
RoundedRectangle(cornerRadius: 4)
|
||||
.fill(Design.Checkboxes.onBg.color)
|
||||
.frame(width: 16, height: 16)
|
||||
.overlay {
|
||||
Asset.Assets.check.image
|
||||
.zImage(size: 12, style: Design.Checkboxes.onFg)
|
||||
}
|
||||
.foregroundColor(Asset.Colors.primary.color)
|
||||
} else {
|
||||
Image(systemName: "square")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .center)
|
||||
RoundedRectangle(cornerRadius: 4)
|
||||
.fill(Design.Checkboxes.offBg.color)
|
||||
.frame(width: 16, height: 16)
|
||||
.background {
|
||||
Asset.Colors.background.color
|
||||
.scaleEffect(x: 0.94, y: 0.94)
|
||||
RoundedRectangle(cornerRadius: 4)
|
||||
.stroke(Design.Checkboxes.offStroke.color)
|
||||
}
|
||||
.foregroundColor(Asset.Colors.primary.color)
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// BirthdayPreferenceKey.swift
|
||||
// Zashi
|
||||
//
|
||||
// Created by Lukáš Korba on 10-30-2024.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
public struct BirthdayPreferenceKey: PreferenceKey {
|
||||
public typealias Value = Anchor<CGRect>?
|
||||
|
||||
public static var defaultValue: Value = nil
|
||||
|
||||
public static func reduce(
|
||||
value: inout Value,
|
||||
nextValue: () -> Value
|
||||
) {
|
||||
value = nextValue() ?? value
|
||||
}
|
||||
}
|
|
@ -27,11 +27,17 @@ public struct ZashiToggle: View {
|
|||
Button {
|
||||
isOn.toggle()
|
||||
} label: {
|
||||
Toggle(isOn: $isOn, label: {
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Toggle(isOn: $isOn, label: {})
|
||||
.toggleStyle(CheckboxToggleStyle())
|
||||
.padding(.trailing, 8)
|
||||
|
||||
Text(label)
|
||||
.font(.custom(FontFamily.Inter.medium.name, size: 14))
|
||||
})
|
||||
.toggleStyle(CheckboxToggleStyle())
|
||||
.zFont(.medium, size: 14, style: Design.Text.primary)
|
||||
.multilineTextAlignment(.leading)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.foregroundColor(textColor)
|
||||
}
|
||||
|
|
|
@ -13,22 +13,27 @@ public struct Tooltip: View {
|
|||
public var onTapGesture: () -> Void
|
||||
public var title: String
|
||||
public var desc: String
|
||||
public var bottomMode: Bool
|
||||
|
||||
public init(
|
||||
title: String,
|
||||
desc: String,
|
||||
bottomMode: Bool = false,
|
||||
onTapGesture: @escaping () -> Void
|
||||
) {
|
||||
self.title = title
|
||||
self.desc = desc
|
||||
self.bottomMode = bottomMode
|
||||
self.onTapGesture = onTapGesture
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack(alignment: .center, spacing: 0) {
|
||||
Asset.Assets.tooltip.image
|
||||
.zImage(width: 16, height: 6, style: Design.HintTooltips.surfacePrimary)
|
||||
.offset(x: 0, y: 2)
|
||||
if !bottomMode {
|
||||
Asset.Assets.tooltip.image
|
||||
.zImage(width: 16, height: 6, style: Design.HintTooltips.surfacePrimary)
|
||||
.offset(x: 0, y: 2)
|
||||
}
|
||||
|
||||
HStack(alignment: .top) {
|
||||
VStack(alignment: .leading) {
|
||||
|
@ -41,6 +46,7 @@ public struct Tooltip: View {
|
|||
.font(.custom(FontFamily.Inter.medium.name, size: 14))
|
||||
.foregroundColor(Design.Text.lightSupport.color)
|
||||
.lineLimit(nil)
|
||||
.lineSpacing(1.5)
|
||||
}
|
||||
|
||||
Asset.Assets.buttonCloseX.image
|
||||
|
@ -54,6 +60,13 @@ public struct Tooltip: View {
|
|||
// TODO: Colors from Design once available
|
||||
.shadow(color: Color(red: 0.137, green: 0.122, blue: 0.125).opacity(0.03), radius: 4, x: 0, y: 4)
|
||||
.shadow(color: Color(red: 0.137, green: 0.122, blue: 0.125).opacity(0.08), radius: 8, x: 0, y: 12)
|
||||
|
||||
if bottomMode {
|
||||
Asset.Assets.tooltip.image
|
||||
.zImage(width: 16, height: 6, style: Design.HintTooltips.surfacePrimary)
|
||||
.rotationEffect(Angle(degrees: 180))
|
||||
.offset(x: 0, y: -2)
|
||||
}
|
||||
}
|
||||
.onTapGesture { onTapGesture() }
|
||||
}
|
||||
|
|
|
@ -48,6 +48,46 @@ public final class ShareableImage: NSObject, UIActivityItemSource {
|
|||
}
|
||||
}
|
||||
|
||||
public final class ShareableMessage: NSObject, UIActivityItemSource {
|
||||
let title: String
|
||||
let message: String
|
||||
let desc: String
|
||||
|
||||
public init(title: String, message: String, desc: String) {
|
||||
self.title = title
|
||||
self.message = message
|
||||
self.desc = desc
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
public func activityViewControllerPlaceholderItem(
|
||||
_ activityViewController: UIActivityViewController
|
||||
) -> Any {
|
||||
message
|
||||
}
|
||||
|
||||
public func activityViewController(
|
||||
_ activityViewController: UIActivityViewController,
|
||||
itemForActivityType activityType: UIActivity.ActivityType?
|
||||
) -> Any? {
|
||||
message
|
||||
}
|
||||
|
||||
public func activityViewControllerLinkMetadata(
|
||||
_ activityViewController: UIActivityViewController
|
||||
) -> LPLinkMetadata? {
|
||||
let metadata = LPLinkMetadata()
|
||||
if let image = UIImage(named: "ZashiLogo") {
|
||||
metadata.iconProvider = NSItemProvider(object: image)
|
||||
}
|
||||
metadata.title = title
|
||||
metadata.originalURL = URL(fileURLWithPath: desc)
|
||||
|
||||
return metadata
|
||||
}
|
||||
}
|
||||
|
||||
public class UIShareDialog: UIView {
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
|
|
|
@ -1948,7 +1948,7 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = "zashi-internal.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = RLPRR8CPQG;
|
||||
|
@ -1979,7 +1979,7 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = "zashi-internal.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = RLPRR8CPQG;
|
||||
ENABLE_BITCODE = NO;
|
||||
|
@ -2009,7 +2009,7 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = "zashi-internal.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = RLPRR8CPQG;
|
||||
ENABLE_BITCODE = NO;
|
||||
|
|
Loading…
Reference in New Issue