mirror of
https://github.com/bitwarden/ios.git
synced 2025-12-10 17:46:07 -06:00
[PM-26064] Consolidate various shared components (#2036)
This commit is contained in:
parent
9393aa3859
commit
9a81338d2e
@ -1,107 +0,0 @@
|
||||
import BitwardenKit
|
||||
import SwiftUI
|
||||
|
||||
/// A standardized view used to display some text into a row of a list. This is commonly used in
|
||||
/// forms.
|
||||
struct BitwardenTextValueField<AccessoryContent>: View where AccessoryContent: View {
|
||||
// MARK: Properties
|
||||
|
||||
/// The (optional) title of the field.
|
||||
var title: String?
|
||||
|
||||
/// The (optional) accessibility identifier to apply to the title of the field (if it exists)
|
||||
var titleAccessibilityIdentifier: String?
|
||||
|
||||
/// The text value to display in this field.
|
||||
var value: String
|
||||
|
||||
/// The (optional) accessibility identifier to apply to the displayed value of the field
|
||||
var valueAccessibilityIdentifier: String?
|
||||
|
||||
/// Any accessory content that should be displayed on the trailing edge of the field. This
|
||||
/// content automatically has the `AccessoryButtonStyle` applied to it.
|
||||
var accessoryContent: AccessoryContent?
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
BitwardenField(title: title, titleAccessibilityIdentifier: titleAccessibilityIdentifier) {
|
||||
Text(value)
|
||||
.styleGuide(.body)
|
||||
.multilineTextAlignment(.leading)
|
||||
.foregroundColor(Asset.Colors.textPrimary.swiftUIColor)
|
||||
.accessibilityIdentifier(valueAccessibilityIdentifier ?? value)
|
||||
} accessoryContent: {
|
||||
accessoryContent
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
/// Creates a new `BitwardenTextValueField`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - title: The (optional) title of the field.
|
||||
/// - titleAccessibilityIdentifier: The (optional) accessibility identifier to apply
|
||||
/// to the title of the field (if it exists)
|
||||
/// - value: The text value to display in this field.
|
||||
/// - valueAccessibilityIdentifier: The (optional) accessibility identifier to apply
|
||||
/// to the displayed value of the field
|
||||
/// - accessoryContent: Any accessory content that should be displayed on the trailing edge of
|
||||
/// the field. This content automatically has the `AccessoryButtonStyle` applied to it.
|
||||
///
|
||||
init(
|
||||
title: String? = nil,
|
||||
titleAccessibilityIdentifier: String? = "ItemName",
|
||||
value: String,
|
||||
valueAccessibilityIdentifier: String? = "ItemValue",
|
||||
@ViewBuilder accessoryContent: () -> AccessoryContent,
|
||||
) {
|
||||
self.title = title
|
||||
self.titleAccessibilityIdentifier = titleAccessibilityIdentifier
|
||||
self.value = value
|
||||
self.valueAccessibilityIdentifier = valueAccessibilityIdentifier
|
||||
self.accessoryContent = accessoryContent()
|
||||
}
|
||||
}
|
||||
|
||||
extension BitwardenTextValueField where AccessoryContent == EmptyView {
|
||||
/// Creates a new `BitwardenTextValueField` without accessory content.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - title: The (optional) title of the field.
|
||||
/// - titleAccessibilityIdentifier: The (optional) accessibility identifier to apply
|
||||
/// to the title of the field (if it exists)
|
||||
/// - value: The text value to display in this field.
|
||||
///
|
||||
init(
|
||||
title: String? = nil,
|
||||
titleAccessibilityIdentifier: String? = "ItemName",
|
||||
value: String,
|
||||
valueAccessibilityIdentifier: String? = "ItemValue",
|
||||
) {
|
||||
self.init(
|
||||
title: title,
|
||||
titleAccessibilityIdentifier: titleAccessibilityIdentifier,
|
||||
value: value,
|
||||
valueAccessibilityIdentifier: valueAccessibilityIdentifier,
|
||||
) {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Previews
|
||||
|
||||
#if DEBUG
|
||||
#Preview("No buttons") {
|
||||
VStack {
|
||||
BitwardenTextValueField(
|
||||
title: "Title",
|
||||
value: "Text field text",
|
||||
)
|
||||
.padding()
|
||||
}
|
||||
.background(Color(.systemGroupedBackground))
|
||||
}
|
||||
#endif
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - SearchNoResultsView
|
||||
|
||||
/// A view that displays the no search results image and text.
|
||||
///
|
||||
struct SearchNoResultsView<Content: View>: View {
|
||||
// MARK: Properties
|
||||
|
||||
/// An optional view to display at the top of the scroll view above the no results image and text.
|
||||
var headerView: Content?
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { reader in
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
if let headerView {
|
||||
headerView
|
||||
}
|
||||
|
||||
VStack(spacing: 35) {
|
||||
Image(decorative: SharedAsset.Icons.search24)
|
||||
.resizable()
|
||||
.frame(width: 74, height: 74)
|
||||
.foregroundColor(Asset.Colors.textSecondary.swiftUIColor)
|
||||
|
||||
Text(Localizations.thereAreNoItemsThatMatchTheSearch)
|
||||
.multilineTextAlignment(.center)
|
||||
.styleGuide(.callout)
|
||||
.foregroundColor(Asset.Colors.textPrimary.swiftUIColor)
|
||||
}
|
||||
.accessibilityIdentifier("NoSearchResultsLabel")
|
||||
.frame(maxWidth: .infinity, minHeight: reader.size.height, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(Color(asset: Asset.Colors.backgroundSecondary))
|
||||
}
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
/// Initialize a `SearchNoResultsView`.
|
||||
///
|
||||
init() where Content == EmptyView {
|
||||
headerView = nil
|
||||
}
|
||||
|
||||
/// Initialize a `SearchNoResultsView` with a header view.
|
||||
///
|
||||
/// - Parameter headerView: An optional view to display at the top of the scroll view above the
|
||||
/// no results image and text.
|
||||
///
|
||||
init(headerView: () -> Content) {
|
||||
self.headerView = headerView()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
#Preview("No Results") {
|
||||
SearchNoResultsView()
|
||||
}
|
||||
|
||||
#Preview("No Results With Header") {
|
||||
SearchNoResultsView {
|
||||
Text("Optional header text!")
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - SectionHeaderView
|
||||
|
||||
/// A section header.
|
||||
///
|
||||
struct SectionHeaderView: View {
|
||||
// MARK: Properties
|
||||
|
||||
/// The section title.
|
||||
let title: String
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
Text(title)
|
||||
.styleGuide(.footnote)
|
||||
.accessibilityAddTraits(.isHeader)
|
||||
.foregroundColor(Color(asset: Asset.Colors.textSecondary))
|
||||
.textCase(.uppercase)
|
||||
}
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
/// Initializes a new section header.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - title: The section title.
|
||||
///
|
||||
init(_ title: String) {
|
||||
self.title = title
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Previews
|
||||
|
||||
#Preview {
|
||||
SectionHeaderView("Section header")
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
// swiftlint:disable:this file_name
|
||||
import SnapshotTesting
|
||||
import XCTest
|
||||
|
||||
@testable import AuthenticatorShared
|
||||
|
||||
class SectionViewTests: BitwardenTestCase {
|
||||
// MARK: Tests
|
||||
|
||||
/// Test a snapshot of the sectionView.
|
||||
func disabletest_snapshot_sectionView() {
|
||||
for preview in SectionView_Previews._allPreviews {
|
||||
assertSnapshots(
|
||||
of: preview.content,
|
||||
as: [.defaultPortrait],
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,74 +0,0 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - SectionView
|
||||
|
||||
/// A section view.
|
||||
///
|
||||
struct SectionView<Content: View>: View {
|
||||
// MARK: Properties
|
||||
|
||||
/// Content displayed below section header view.
|
||||
let content: Content
|
||||
|
||||
/// The spacing of the content.
|
||||
let contentSpacing: CGFloat
|
||||
|
||||
/// The section header title.
|
||||
let title: String
|
||||
|
||||
/// The spacing between title and content.
|
||||
let titleSpacing: CGFloat
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: titleSpacing) {
|
||||
SectionHeaderView(title)
|
||||
|
||||
VStack(alignment: .leading, spacing: contentSpacing) {
|
||||
content
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
/// Initializes a new section view.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - title: The section header title.
|
||||
/// - titleSpacing: The spacing between title and content.
|
||||
/// - content: The content displayed below the section title.
|
||||
/// - contentSpacing: The spacing of content items.
|
||||
///
|
||||
init(
|
||||
_ title: String,
|
||||
titleSpacing: CGFloat = 8,
|
||||
contentSpacing: CGFloat = 16,
|
||||
@ViewBuilder content: () -> Content,
|
||||
) {
|
||||
self.title = title
|
||||
self.titleSpacing = titleSpacing
|
||||
self.contentSpacing = contentSpacing
|
||||
self.content = content()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Previews
|
||||
|
||||
#if DEBUG
|
||||
struct SectionView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
SectionView("Section View") {
|
||||
BitwardenTextField(title: Localizations.name, text: .constant("name"))
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.background(Asset.Colors.backgroundSecondary.swiftUIColor)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -1,10 +1,9 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
/// A standardized view used to display some text into a row of a list. This is commonly used in
|
||||
/// forms.
|
||||
struct BitwardenTextValueField<AccessoryContent>: View where AccessoryContent: View {
|
||||
public struct BitwardenTextValueField<AccessoryContent>: View where AccessoryContent: View {
|
||||
// MARK: Properties
|
||||
|
||||
/// Whether the text selection is enabled.
|
||||
@ -42,7 +41,7 @@ struct BitwardenTextValueField<AccessoryContent>: View where AccessoryContent: V
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
BitwardenField(
|
||||
title: title,
|
||||
titleAccessibilityIdentifier: titleAccessibilityIdentifier,
|
||||
@ -90,7 +89,7 @@ struct BitwardenTextValueField<AccessoryContent>: View where AccessoryContent: V
|
||||
/// - useUIKitTextView: Whether we should use a UITextView or a SwiftUI version.
|
||||
/// - accessoryContent: Any accessory content that should be displayed on the trailing edge of
|
||||
/// the field. This content automatically has the `AccessoryButtonStyle` applied to it.
|
||||
init(
|
||||
public init(
|
||||
title: String? = nil,
|
||||
titleAccessibilityIdentifier: String? = "ItemName",
|
||||
value: String,
|
||||
@ -109,7 +108,7 @@ struct BitwardenTextValueField<AccessoryContent>: View where AccessoryContent: V
|
||||
}
|
||||
}
|
||||
|
||||
extension BitwardenTextValueField where AccessoryContent == EmptyView {
|
||||
public extension BitwardenTextValueField where AccessoryContent == EmptyView {
|
||||
/// Creates a new `BitwardenTextValueField` without accessory content.
|
||||
///
|
||||
/// - Parameters:
|
||||
@ -144,7 +143,7 @@ extension BitwardenTextValueField where AccessoryContent == EmptyView {
|
||||
}
|
||||
}
|
||||
|
||||
extension BitwardenTextValueField where AccessoryContent == AccessoryButton {
|
||||
public extension BitwardenTextValueField where AccessoryContent == AccessoryButton {
|
||||
/// Creates a new `BitwardenTextValueField` with a button as accessory content.
|
||||
///
|
||||
/// - Parameters:
|
||||
@ -1,4 +1,3 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
@ -7,12 +6,12 @@ import UIKit
|
||||
|
||||
/// A custom `UITextView` wrapped in a `UIViewRepresentable` for use in SwiftUI.
|
||||
///
|
||||
struct BitwardenUITextView: UIViewRepresentable {
|
||||
public struct BitwardenUITextView: UIViewRepresentable {
|
||||
// MARK: - Coordinator
|
||||
|
||||
/// A coordinator to act as the delegate for `UITextView`, handling text changes and other events.
|
||||
///
|
||||
class Coordinator: NSObject, UITextViewDelegate {
|
||||
public class Coordinator: NSObject, UITextViewDelegate {
|
||||
/// The parent view.
|
||||
var parent: BitwardenUITextView
|
||||
|
||||
@ -29,7 +28,7 @@ struct BitwardenUITextView: UIViewRepresentable {
|
||||
/// - calculatedHeight: The height of the text view.
|
||||
/// - isFocused: A binding for whether the text view has focus.
|
||||
///
|
||||
init(
|
||||
public init(
|
||||
_ parent: BitwardenUITextView,
|
||||
calculatedHeight: Binding<CGFloat>,
|
||||
isFocused: Binding<Bool>,
|
||||
@ -39,11 +38,11 @@ struct BitwardenUITextView: UIViewRepresentable {
|
||||
_isFocused = isFocused
|
||||
}
|
||||
|
||||
func textViewDidBeginEditing(_ textView: UITextView) {
|
||||
public func textViewDidBeginEditing(_ textView: UITextView) {
|
||||
isFocused = true
|
||||
}
|
||||
|
||||
func textViewDidChange(_ uiView: UITextView) {
|
||||
public func textViewDidChange(_ uiView: UITextView) {
|
||||
parent.text = uiView.text
|
||||
parent.recalculateHeight(
|
||||
view: uiView,
|
||||
@ -51,7 +50,7 @@ struct BitwardenUITextView: UIViewRepresentable {
|
||||
)
|
||||
}
|
||||
|
||||
func textViewDidEndEditing(_ textView: UITextView) {
|
||||
public func textViewDidEndEditing(_ textView: UITextView) {
|
||||
isFocused = false
|
||||
}
|
||||
}
|
||||
@ -75,11 +74,34 @@ struct BitwardenUITextView: UIViewRepresentable {
|
||||
/// A binding for whether the text view has focus.
|
||||
@Binding var isFocused: Bool
|
||||
|
||||
// MARK: - Initializers
|
||||
|
||||
/// Public version of synthesized initializer.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - text: The text entered into the text field.
|
||||
/// - calculatedHeight: The calculated height of the `UITextView`. This value is dynamically updated
|
||||
/// based on the content size, and it helps to adjust the height of the view in SwiftUI.
|
||||
/// - isEditable: Indicates whether the `UITextView` is editable. When set to `true`, the user can edit the
|
||||
/// text. If `false`, the text view is read-only.
|
||||
/// - isFocused: A binding for whether the text view has focus.
|
||||
public init(
|
||||
text: Binding<String>,
|
||||
calculatedHeight: Binding<CGFloat>,
|
||||
isEditable: Bool,
|
||||
isFocused: Binding<Bool>,
|
||||
) {
|
||||
_text = text
|
||||
_calculatedHeight = calculatedHeight
|
||||
self.isEditable = isEditable
|
||||
_isFocused = isFocused
|
||||
}
|
||||
|
||||
/// Creates and returns the coordinator for the `UITextView`.
|
||||
///
|
||||
/// - Returns: A `Coordinator` instance to manage the `UITextView`'s events.
|
||||
///
|
||||
func makeCoordinator() -> Coordinator {
|
||||
public func makeCoordinator() -> Coordinator {
|
||||
Coordinator(self, calculatedHeight: $calculatedHeight, isFocused: $isFocused)
|
||||
}
|
||||
|
||||
@ -90,7 +112,7 @@ struct BitwardenUITextView: UIViewRepresentable {
|
||||
/// - Parameter context: The context containing the coordinator for this view.
|
||||
/// - Returns: A configured `UITextView` instance.
|
||||
///
|
||||
func makeUIView(context: Context) -> UITextView {
|
||||
public func makeUIView(context: Context) -> UITextView {
|
||||
let textView = UITextView()
|
||||
textView.adjustsFontForContentSizeCategory = true
|
||||
textView.autocapitalizationType = .sentences
|
||||
@ -118,7 +140,7 @@ struct BitwardenUITextView: UIViewRepresentable {
|
||||
/// - uiView: The `UITextView` instance being updated.
|
||||
/// - context: The context containing the coordinator for this view.
|
||||
///
|
||||
func updateUIView(
|
||||
public func updateUIView(
|
||||
_ uiView: UITextView,
|
||||
context: Context,
|
||||
) {
|
||||
@ -172,7 +194,7 @@ struct BitwardenUITextView: UIViewRepresentable {
|
||||
}
|
||||
|
||||
@available(iOS 16, *)
|
||||
func sizeThatFits(_ proposal: ProposedViewSize, uiView: UITextView, context: Context) -> CGSize? {
|
||||
public func sizeThatFits(_ proposal: ProposedViewSize, uiView: UITextView, context: Context) -> CGSize? {
|
||||
guard let width = proposal.width else { return nil }
|
||||
let size = uiView.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
|
||||
return CGSize(width: width, height: size.height)
|
||||
@ -5,7 +5,7 @@ import SwiftUI
|
||||
|
||||
/// A view that displays the no search results image and text.
|
||||
///
|
||||
struct SearchNoResultsView<Content: View>: View {
|
||||
public struct SearchNoResultsView<Content: View>: View {
|
||||
// MARK: Properties
|
||||
|
||||
/// An optional view to display at the top of the scroll view above the no results image and text.
|
||||
@ -13,7 +13,7 @@ struct SearchNoResultsView<Content: View>: View {
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
GeometryReader { reader in
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
@ -45,7 +45,7 @@ struct SearchNoResultsView<Content: View>: View {
|
||||
|
||||
/// Initialize a `SearchNoResultsView`.
|
||||
///
|
||||
init() where Content == EmptyView {
|
||||
public init() where Content == EmptyView {
|
||||
headerView = nil
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ struct SearchNoResultsView<Content: View>: View {
|
||||
/// - Parameter headerView: An optional view to display at the top of the scroll view above the
|
||||
/// no results image and text.
|
||||
///
|
||||
init(headerView: () -> Content) {
|
||||
public init(headerView: () -> Content) {
|
||||
self.headerView = headerView()
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,7 @@ import SwiftUI
|
||||
|
||||
/// A section header.
|
||||
///
|
||||
struct SectionHeaderView: View {
|
||||
public struct SectionHeaderView: View {
|
||||
// MARK: Properties
|
||||
|
||||
/// The section title.
|
||||
@ -13,7 +13,7 @@ struct SectionHeaderView: View {
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
Text(title)
|
||||
.styleGuide(.caption1, weight: .bold)
|
||||
.accessibilityAddTraits(.isHeader)
|
||||
@ -29,7 +29,7 @@ struct SectionHeaderView: View {
|
||||
/// - Parameters:
|
||||
/// - title: The section title.
|
||||
///
|
||||
init(_ title: String) {
|
||||
public init(_ title: String) {
|
||||
self.title = title
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
@ -6,7 +5,7 @@ import SwiftUI
|
||||
|
||||
/// A section view.
|
||||
///
|
||||
struct SectionView<Content: View>: View {
|
||||
public struct SectionView<Content: View>: View {
|
||||
// MARK: Properties
|
||||
|
||||
/// Content displayed below section header view.
|
||||
@ -23,7 +22,7 @@ struct SectionView<Content: View>: View {
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
VStack(alignment: .leading, spacing: titleSpacing) {
|
||||
SectionHeaderView(title)
|
||||
|
||||
@ -44,7 +43,7 @@ struct SectionView<Content: View>: View {
|
||||
/// - content: The content displayed below the section title.
|
||||
/// - contentSpacing: The spacing of content items.
|
||||
///
|
||||
init(
|
||||
public init(
|
||||
_ title: String,
|
||||
titleSpacing: CGFloat = 8,
|
||||
contentSpacing: CGFloat = 16,
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
|
||||
@ -79,7 +79,7 @@ struct BitwardenUITextViewType: BaseViewType {
|
||||
static var typePrefix: String = "BitwardenUITextView"
|
||||
|
||||
static var namespacedPrefixes: [String] = [
|
||||
"BitwardenShared.BitwardenUITextView",
|
||||
"BitwardenKit.BitwardenUITextView",
|
||||
]
|
||||
}
|
||||
|
||||
@ -112,7 +112,7 @@ struct SettingsMenuFieldType: BaseViewType {
|
||||
static var typePrefix: String = "SettingsMenuField"
|
||||
|
||||
static var namespacedPrefixes: [String] = [
|
||||
"BitwardenShared.SettingsMenuField",
|
||||
"BitwardenKit.SettingsMenuField",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user