✨Add Views
This commit is contained in:
@@ -12,7 +12,7 @@ struct AlloVoisinsSwiftUIApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
NavigationStack {
|
||||
MarketingSupportSelectionView()
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
AlloVoisinsSwiftUI/Core/Models/SQFormFieldError.swift
Normal file
34
AlloVoisinsSwiftUI/Core/Models/SQFormFieldError.swift
Normal file
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// SQFormFieldError.swift
|
||||
// AlloVoisinsSwiftUI
|
||||
//
|
||||
// Created by Victor on 22/05/2025.
|
||||
//
|
||||
|
||||
enum SQFormFieldError: Equatable {
|
||||
case none
|
||||
case empty
|
||||
case siret
|
||||
case invalidPhoneNumber
|
||||
case confirmation
|
||||
case minCharacters(Int)
|
||||
case api(String)
|
||||
case custom(String)
|
||||
|
||||
var isInError: Bool {
|
||||
self != .none
|
||||
}
|
||||
|
||||
var message: String {
|
||||
switch self {
|
||||
case .none: return ""
|
||||
case .empty: return "Ce champ est obligatoire."
|
||||
case .siret: return "Le numéro SIRET doit être composé de 14 chiffres."
|
||||
case .invalidPhoneNumber: return "Le numéro de téléphone n'est pas valide."
|
||||
case .confirmation: return "Cette condition est obligatoire."
|
||||
case let .minCharacters(count): return "Ce champ doit comporter au minimum \(count) caractères."
|
||||
case let .api(message): return message
|
||||
case let .custom(message): return message
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,10 @@ struct OnlyForPremierView: View {
|
||||
SQText("Réservé aux abonnés Premier", size: 18, font: .bold)
|
||||
SQText("Seuls les abonnés Premier peuvent profiter de cette fonctionnalité.")
|
||||
}
|
||||
SQButton( "Découvrir l’abonnement Premier", color: Color.sqOrange(50), textColor: .white) {
|
||||
SQButton( "Découvrir l’abonnement Premier") {
|
||||
|
||||
}
|
||||
.colorScheme(.orange)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
@@ -58,9 +58,10 @@ struct OnlyForPremierModal: View {
|
||||
SQText("Essai gratuit de 14 jours", size: 18, font: .bold)
|
||||
SQText("à partir de 29,99 € / mois")
|
||||
}
|
||||
SQButton("Je m'abonne", color: .sqOrange(50), textColor: .white) {
|
||||
SQButton("Je m'abonne") {
|
||||
|
||||
}
|
||||
.colorScheme(.orange)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 24)
|
||||
|
||||
@@ -48,7 +48,7 @@ struct RegulatedProfessionEditProfilModal: View {
|
||||
Spacer()
|
||||
|
||||
VStack {
|
||||
SQButton("fermer", color: .sqNeutral(100), textColor: .white) {}
|
||||
SQButton("fermer") {}
|
||||
.disabled(true)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
@@ -24,7 +24,7 @@ struct RegulatedProfessionModal: View {
|
||||
Spacer()
|
||||
|
||||
VStack {
|
||||
SQButton("J'ai compris", color: .sqNeutral(100), textColor: .white) {}
|
||||
SQButton("J'ai compris") {}
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 24)
|
||||
|
||||
@@ -9,16 +9,17 @@ import Lottie
|
||||
import SwiftUI
|
||||
|
||||
struct SQButton: View {
|
||||
var title: String
|
||||
var color: Color = .sqNeutral(10)
|
||||
var textColor: Color = .black
|
||||
var textSize: CGFloat = 16
|
||||
var font: SQTextFont = .demiBold
|
||||
var icon: SQIcon?
|
||||
var isFull: Bool = true
|
||||
@Binding var isLoading: Bool
|
||||
let title: String
|
||||
let action: () -> Void
|
||||
|
||||
@State private var type: SQButtonType = .solid
|
||||
@State private var colorScheme: SQButtonColorScheme = .neutral
|
||||
@State private var textSize: CGFloat = 16
|
||||
@State private var font: SQTextFont = .demiBold
|
||||
@State private var icon: SQIcon? = nil
|
||||
@State private var isLoading: Bool = false
|
||||
@State private var isDisabled: Bool = false
|
||||
|
||||
private var textWidth: CGFloat {
|
||||
let font = UIFont.systemFont(ofSize: textSize)
|
||||
let attributes = [NSAttributedString.Key.font: font]
|
||||
@@ -26,21 +27,25 @@ struct SQButton: View {
|
||||
return size.width + (icon != nil ? 24 : 0)
|
||||
}
|
||||
|
||||
init(_ title: String, color: Color = .sqNeutral(10), textColor: Color = .black, textSize: CGFloat = 16, font: SQTextFont = .demiBold, icon: SQIcon? = nil, isFull: Bool = true, isLoading: Binding<Bool> = .constant(false), action: @escaping () -> Void) {
|
||||
init(_ title: String, action: @escaping () -> Void) {
|
||||
self.title = title
|
||||
self.color = color
|
||||
self.textColor = textColor
|
||||
self.textSize = textSize
|
||||
self.font = font
|
||||
self.icon = icon
|
||||
self.isFull = isFull
|
||||
self._isLoading = isLoading
|
||||
self.action = action
|
||||
}
|
||||
|
||||
private var buttonStyle: SQButtonStyle {
|
||||
SQButtonStyle(
|
||||
type: type,
|
||||
colorScheme: colorScheme,
|
||||
isLoading: isLoading,
|
||||
isDisabled: isDisabled
|
||||
)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button(action: {
|
||||
self.action()
|
||||
if !isLoading && !isDisabled {
|
||||
action()
|
||||
}
|
||||
}, label: {
|
||||
HStack {
|
||||
if isLoading {
|
||||
@@ -50,56 +55,347 @@ struct SQButton: View {
|
||||
} else {
|
||||
if icon != nil {
|
||||
icon
|
||||
.foregroundColor(buttonStyle.textColor)
|
||||
}
|
||||
|
||||
SQText(title, size: textSize, font: font, textColor: textColor)
|
||||
SQText(title, size: textSize, font: font, textColor: buttonStyle.textColor)
|
||||
}
|
||||
}
|
||||
.frame(minWidth: textWidth)
|
||||
.padding(.horizontal, 30)
|
||||
.padding(.vertical, 12)
|
||||
.frame(height: 40, alignment: .center)
|
||||
.background(isFull ? color : .clear)
|
||||
.background(buttonStyle.backgroundColor)
|
||||
.cornerRadius(100)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 100)
|
||||
.inset(by: 0.5)
|
||||
.stroke(color.opacity(isFull ? 0 : 1), lineWidth: 1)
|
||||
.stroke(buttonStyle.borderColor, lineWidth: 1)
|
||||
)
|
||||
})
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.disabled(isLoading || isDisabled)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Modifiers
|
||||
extension SQButton {
|
||||
func sqStyle(_ theme: SQButtonStyle = .neutral) -> some View {
|
||||
modifier(SQButtonModifier(theme: theme))
|
||||
func buttonType(_ type: SQButtonType) -> SQButton {
|
||||
var copy = self
|
||||
copy._type = State(initialValue: type)
|
||||
return copy
|
||||
}
|
||||
|
||||
func colorScheme(_ scheme: SQButtonColorScheme) -> SQButton {
|
||||
var copy = self
|
||||
copy._colorScheme = State(initialValue: scheme)
|
||||
return copy
|
||||
}
|
||||
|
||||
func textSize(_ size: CGFloat) -> SQButton {
|
||||
var copy = self
|
||||
copy._textSize = State(initialValue: size)
|
||||
return copy
|
||||
}
|
||||
|
||||
func font(_ font: SQTextFont) -> SQButton {
|
||||
var copy = self
|
||||
copy._font = State(initialValue: font)
|
||||
return copy
|
||||
}
|
||||
|
||||
func icon(_ icon: SQIcon?) -> SQButton {
|
||||
var copy = self
|
||||
copy._icon = State(initialValue: icon)
|
||||
return copy
|
||||
}
|
||||
|
||||
func loading(_ isLoading: Bool) -> SQButton {
|
||||
var copy = self
|
||||
copy._isLoading = State(initialValue: isLoading)
|
||||
return copy
|
||||
}
|
||||
|
||||
func disabled(_ isDisabled: Bool) -> SQButton {
|
||||
var copy = self
|
||||
copy._isDisabled = State(initialValue: isDisabled)
|
||||
return copy
|
||||
}
|
||||
}
|
||||
|
||||
struct SQButtonModifier: ViewModifier {
|
||||
let theme: SQButtonStyle
|
||||
// MARK: - Properties
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
}
|
||||
enum SQButtonType: String, CaseIterable {
|
||||
case solid
|
||||
case line
|
||||
case light
|
||||
case glass
|
||||
}
|
||||
|
||||
enum SQButtonStyle {
|
||||
case royal
|
||||
enum SQButtonColorScheme: String, CaseIterable {
|
||||
case neutral
|
||||
case purple
|
||||
case green
|
||||
case pink
|
||||
case blue
|
||||
case yellow
|
||||
case gold
|
||||
case orange
|
||||
case red
|
||||
case grape
|
||||
case forest
|
||||
case royal
|
||||
|
||||
var baseColor: Color {
|
||||
switch self {
|
||||
case .neutral:
|
||||
return .sqNeutral(100)
|
||||
case .green:
|
||||
return .sqGreen(60)
|
||||
case .pink:
|
||||
return .sqPink(60)
|
||||
case .blue:
|
||||
return .sqBlue(50)
|
||||
case .yellow:
|
||||
return .sqYellow(50)
|
||||
case .gold:
|
||||
return .sqGold(50)
|
||||
case .orange:
|
||||
return .sqOrange(50)
|
||||
case .red:
|
||||
return .sqRed(60)
|
||||
case .grape:
|
||||
return .sqGrape(80)
|
||||
case .forest:
|
||||
return .sqForest(100)
|
||||
case .royal:
|
||||
return .sqRoyal(60)
|
||||
}
|
||||
}
|
||||
|
||||
var mediumColor: Color {
|
||||
switch self {
|
||||
case .neutral:
|
||||
return .sqNeutral(30)
|
||||
case .green:
|
||||
return .sqGreen(30)
|
||||
case .pink:
|
||||
return .sqPink(30)
|
||||
case .blue:
|
||||
return .sqBlue(30)
|
||||
case .yellow:
|
||||
return .sqYellow(30)
|
||||
case .gold:
|
||||
return .sqGold(30)
|
||||
case .orange:
|
||||
return .sqOrange(30)
|
||||
case .red:
|
||||
return .sqRed(10)
|
||||
case .grape:
|
||||
return .sqGrape(30)
|
||||
case .forest:
|
||||
return .sqForest(50)
|
||||
case .royal:
|
||||
return .sqRoyal(30)
|
||||
}
|
||||
}
|
||||
|
||||
var lightColor: Color {
|
||||
switch self {
|
||||
case .neutral:
|
||||
return .sqNeutral(15)
|
||||
case .green:
|
||||
return .sqGreen(10)
|
||||
case .pink:
|
||||
return .sqPink(10)
|
||||
case .blue:
|
||||
return .sqBlue(10)
|
||||
case .yellow:
|
||||
return .sqYellow(10)
|
||||
case .gold:
|
||||
return .sqGold(10)
|
||||
case .orange:
|
||||
return .sqOrange(10)
|
||||
case .red:
|
||||
return .sqRed(10)
|
||||
case .grape:
|
||||
return .sqGrape(10)
|
||||
case .forest:
|
||||
return .sqForest(10)
|
||||
case .royal:
|
||||
return .sqRoyal(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SQButtonStyle {
|
||||
let type: SQButtonType
|
||||
let colorScheme: SQButtonColorScheme
|
||||
let isLoading: Bool
|
||||
let isDisabled: Bool
|
||||
|
||||
var backgroundColor: Color {
|
||||
if isDisabled {
|
||||
switch type {
|
||||
case .solid:
|
||||
return colorScheme.mediumColor
|
||||
case .line:
|
||||
return .clear
|
||||
case .light:
|
||||
return colorScheme.lightColor
|
||||
case .glass:
|
||||
return .clear
|
||||
}
|
||||
}
|
||||
|
||||
if isLoading {
|
||||
switch type {
|
||||
case .solid:
|
||||
return colorScheme.mediumColor
|
||||
case .line:
|
||||
return .clear
|
||||
case .light:
|
||||
return colorScheme.lightColor
|
||||
case .glass:
|
||||
return .clear
|
||||
}
|
||||
}
|
||||
|
||||
switch type {
|
||||
case .solid:
|
||||
return colorScheme.baseColor
|
||||
case .line:
|
||||
return .clear
|
||||
case .light:
|
||||
return colorScheme.lightColor
|
||||
case .glass:
|
||||
return .clear
|
||||
}
|
||||
}
|
||||
|
||||
var borderColor: Color {
|
||||
switch type {
|
||||
|
||||
case .solid:
|
||||
if isDisabled {
|
||||
return colorScheme.lightColor.opacity(0.5)
|
||||
}
|
||||
|
||||
if isLoading {
|
||||
return colorScheme.lightColor.opacity(0.8)
|
||||
}
|
||||
case .line:
|
||||
if isDisabled {
|
||||
return colorScheme.mediumColor
|
||||
}
|
||||
|
||||
if isLoading {
|
||||
return colorScheme.mediumColor
|
||||
}
|
||||
|
||||
return colorScheme.baseColor
|
||||
|
||||
case .light:
|
||||
if isDisabled {
|
||||
return colorScheme.lightColor.opacity(0.5)
|
||||
}
|
||||
|
||||
if isLoading {
|
||||
return colorScheme.lightColor.opacity(0.8)
|
||||
}
|
||||
|
||||
return colorScheme.lightColor
|
||||
|
||||
case .glass:
|
||||
return .clear
|
||||
}
|
||||
|
||||
return colorScheme.baseColor
|
||||
}
|
||||
|
||||
var textColor: Color {
|
||||
if isDisabled {
|
||||
switch type {
|
||||
case .solid:
|
||||
return .white
|
||||
case .line, .light, .glass:
|
||||
return colorScheme.mediumColor
|
||||
}
|
||||
}
|
||||
|
||||
switch type {
|
||||
case .solid:
|
||||
return .white
|
||||
case .line, .light, .glass:
|
||||
return colorScheme.baseColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
VStack(spacing: 32) {
|
||||
HStack {
|
||||
VStack(spacing: 16) {
|
||||
SQButton("C'est parti !", color: .sqNeutral(100), isFull: false) {}
|
||||
SQButton("C'est parti !") {}
|
||||
SQButton("C'est parti !", color: .sqOrange(50), textColor: .white) {}
|
||||
.colorScheme(.royal)
|
||||
SQButton("C'est parti !") {}
|
||||
.colorScheme(.royal)
|
||||
.buttonType(.line)
|
||||
SQButton("C'est parti !") {}
|
||||
.colorScheme(.royal)
|
||||
.buttonType(.light)
|
||||
SQButton("C'est parti !") {}
|
||||
.colorScheme(.royal)
|
||||
.buttonType(.glass)
|
||||
}
|
||||
VStack(spacing: 16) {
|
||||
SQButton("C'est parti !") {}
|
||||
.colorScheme(.royal)
|
||||
.loading(true)
|
||||
SQButton("C'est parti !") {}
|
||||
.colorScheme(.royal)
|
||||
.buttonType(.line)
|
||||
.loading(true)
|
||||
SQButton("C'est parti !") {}
|
||||
.colorScheme(.royal)
|
||||
.buttonType(.light)
|
||||
.loading(true)
|
||||
SQButton("C'est parti !") {}
|
||||
.colorScheme(.royal)
|
||||
.buttonType(.glass)
|
||||
.loading(true)
|
||||
}
|
||||
}
|
||||
|
||||
HStack {
|
||||
VStack(spacing: 16) {
|
||||
SQButton("C'est parti !") {}
|
||||
.colorScheme(.royal)
|
||||
SQButton("C'est parti !") {}
|
||||
.colorScheme(.royal)
|
||||
.buttonType(.line)
|
||||
SQButton("C'est parti !") {}
|
||||
.colorScheme(.royal)
|
||||
.buttonType(.light)
|
||||
SQButton("C'est parti !") {}
|
||||
.colorScheme(.royal)
|
||||
.buttonType(.glass)
|
||||
}
|
||||
VStack(spacing: 16) {
|
||||
SQButton("C'est parti !") {}
|
||||
.colorScheme(.royal)
|
||||
.disabled(true)
|
||||
SQButton("Imprimer", color: .sqNeutral(100), textColor: .white, icon: SQIcon(.print, color: .white)) {}
|
||||
SQButton("C'est parti !") {}
|
||||
.colorScheme(.royal)
|
||||
.buttonType(.line)
|
||||
.disabled(true)
|
||||
SQButton("C'est parti !", isLoading: .constant(true)) {}
|
||||
SQButton("C'est parti !") {}
|
||||
.colorScheme(.royal)
|
||||
.buttonType(.light)
|
||||
.disabled(true)
|
||||
SQButton("C'est parti !") {}
|
||||
.colorScheme(.royal)
|
||||
.buttonType(.glass)
|
||||
.disabled(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,22 +10,22 @@ import SwiftUI
|
||||
struct SQCheckbox: View {
|
||||
var text: String
|
||||
@Binding var isChecked: Bool
|
||||
var errorText: String?
|
||||
@Binding var isInError: Bool
|
||||
var error: Binding<SQFormFieldError>
|
||||
var alignment: VerticalAlignment = .top
|
||||
|
||||
init(_ text: String, isChecked: Binding<Bool>, errorText: String? = nil, isInError: Binding<Bool> = .constant(false)) {
|
||||
init(_ text: String, isChecked: Binding<Bool>, error: Binding<SQFormFieldError> = .constant(.none), alignment: VerticalAlignment = .top) {
|
||||
self.text = text
|
||||
self._isChecked = isChecked
|
||||
self.errorText = errorText
|
||||
self._isInError = isInError
|
||||
self.error = error
|
||||
self.alignment = alignment
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(alignment: .top) {
|
||||
HStack(alignment: alignment) {
|
||||
if isChecked {
|
||||
SQImage("checked_neutral", height: 20)
|
||||
} else if isInError {
|
||||
} else if error.wrappedValue.isInError {
|
||||
SQImage("checkbox_unchecked_error", height: 20)
|
||||
} else {
|
||||
SQImage("checkbox_unchecked", height: 20)
|
||||
@@ -35,20 +35,21 @@ struct SQCheckbox: View {
|
||||
.onTapGesture {
|
||||
isChecked.toggle()
|
||||
|
||||
if isInError && isChecked {
|
||||
isInError = false
|
||||
if error.wrappedValue.isInError && isChecked {
|
||||
error.wrappedValue = .none
|
||||
}
|
||||
}
|
||||
|
||||
HStack {
|
||||
SQIcon(.circle_exclamation, customSize: 13, type: .solid, color: .sqSemanticRed)
|
||||
SQText("Cette condition est obligatoire.", size: 12, font: .demiBold, textColor: .sqSemanticRed)
|
||||
SQText(error.wrappedValue.message, size: 12, font: .demiBold, textColor: .sqSemanticRed)
|
||||
}
|
||||
.isHidden(hidden: !isInError)
|
||||
.isHidden(hidden: !error.wrappedValue.isInError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SQCheckbox("Je comprends que j’engage ma responsabilité sur l’exhaustivité et l’authenticité des informations renseignées ci-dessus.", isChecked: .constant(false))
|
||||
SQCheckbox("Je comprends que j’engage ma responsabilité sur l’exhaustivité et l’authenticité des informations renseignées ci-dessus.", isChecked: .constant(false), error: .constant(.empty))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// SQCircleButton.swift
|
||||
// AlloVoisinsSwiftUI
|
||||
//
|
||||
// Created by Victor on 05/06/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
enum SQCircleButtonSize {
|
||||
case s
|
||||
case m
|
||||
case l
|
||||
|
||||
var vertical: CGFloat {
|
||||
switch self {
|
||||
case .s:
|
||||
return 12
|
||||
case .m:
|
||||
return 12
|
||||
case .l:
|
||||
return 16
|
||||
}
|
||||
}
|
||||
|
||||
var horizontal: CGFloat {
|
||||
switch self {
|
||||
case .s:
|
||||
return 30
|
||||
case .m:
|
||||
return 30
|
||||
case .l:
|
||||
return 25
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SQCircleButton: View {
|
||||
let icon: SQIcon
|
||||
let text: String
|
||||
var color: Color
|
||||
var size: SQCircleButtonSize
|
||||
var isFull: Bool
|
||||
let action: () -> Void
|
||||
|
||||
init(_ icon: SQIcon, text: String = "", color: Color = .white, size: SQCircleButtonSize = .m, isFull: Bool = true, action: @escaping () -> Void) {
|
||||
self.icon = icon
|
||||
self.text = text
|
||||
self.color = color
|
||||
self.size = size
|
||||
self.isFull = isFull
|
||||
self.action = action
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
self.action()
|
||||
} label: {
|
||||
VStack(spacing: 2) {
|
||||
icon
|
||||
.padding(.horizontal, size.horizontal)
|
||||
.padding(.vertical, size.vertical)
|
||||
.cornerRadius(100)
|
||||
.background {
|
||||
Circle()
|
||||
.fill(isFull ? color : .white)
|
||||
.overlay(
|
||||
Circle()
|
||||
.stroke(isFull ? Color.white : color, lineWidth: 1)
|
||||
)
|
||||
}
|
||||
.shadow(color: Color.black.opacity(0.15), radius: 8, x: 0, y: 4)
|
||||
|
||||
if !text.isEmpty {
|
||||
SQText(text, font: .demiBold, textColor: .sqNeutral(80))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SQCircleButton(
|
||||
SQIcon(.arrow_right, size: .l)
|
||||
) {}
|
||||
SQCircleButton(
|
||||
SQIcon(.arrow_left, size: .l)
|
||||
) {}
|
||||
SQCircleButton(
|
||||
SQIcon(.plus, size: .l, color: .sqNeutral(80)),
|
||||
text: "Sélectionner",
|
||||
color: .sqNeutral(80),
|
||||
size: .l,
|
||||
isFull: false
|
||||
) {}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ struct SQColorPicker: View {
|
||||
UIColorPickerViewController_SwiftUI(selectedColor: $selectedColor)
|
||||
.background(Color.white)
|
||||
.sqNavigationBar(title: "Choisir une couleur personnalisée")
|
||||
SQButton("Valider", color: .sqNeutral(100), textColor: .white) {
|
||||
SQButton("Valider") {
|
||||
showColorPicker.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +147,7 @@ enum SQIconName: String {
|
||||
case pen_to_square
|
||||
case phone_flip
|
||||
case phone
|
||||
case phone_hangup
|
||||
case play_store_brand
|
||||
case plus
|
||||
case print
|
||||
@@ -182,6 +183,7 @@ enum SQIconName: String {
|
||||
case user
|
||||
case users
|
||||
case video
|
||||
case video_slash
|
||||
case whatsapp_brand
|
||||
case xmark
|
||||
case x_twitter_brand
|
||||
|
||||
@@ -28,18 +28,18 @@ struct SQNavigationBar: ViewModifier {
|
||||
Button(action: {
|
||||
backAction?()
|
||||
}) {
|
||||
style.closeIcon
|
||||
style.leadingIcon
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if showBackButton {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button(action: {
|
||||
}
|
||||
|
||||
if style == .modal || style == .booster {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button {
|
||||
dismiss()
|
||||
}) {
|
||||
style.closeIcon
|
||||
}
|
||||
} label: {
|
||||
style.trailingIcon
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,30 +58,35 @@ extension View {
|
||||
|
||||
enum SQNavigationBarStyle {
|
||||
case white
|
||||
case modal
|
||||
case booster
|
||||
case boosterFreeTrialResiliation
|
||||
|
||||
var backgroundColor: Color {
|
||||
switch self {
|
||||
case .white: return .white
|
||||
case .booster: return .sqRoyal(60)
|
||||
case .boosterFreeTrialResiliation: return .sqRoyal(60)
|
||||
case .white, .modal: return .white
|
||||
case .booster, .boosterFreeTrialResiliation: return .sqRoyal(60)
|
||||
}
|
||||
}
|
||||
|
||||
var foregroundColor: Color {
|
||||
switch self {
|
||||
case .white: return .sqNeutral(100)
|
||||
case .booster: return .white
|
||||
case .boosterFreeTrialResiliation: return .white
|
||||
case .white, .modal: return .sqNeutral(100)
|
||||
case .booster, .boosterFreeTrialResiliation: return .white
|
||||
}
|
||||
}
|
||||
|
||||
var closeIcon: SQIcon {
|
||||
var leadingIcon: SQIcon {
|
||||
switch self {
|
||||
case .white: return SQIcon(.chevron_left, size: .l, color: foregroundColor)
|
||||
case .booster: return SQIcon(.xmark, size: .l, color: foregroundColor)
|
||||
case .boosterFreeTrialResiliation: return SQIcon(.chevron_left, size: .l, color: foregroundColor)
|
||||
default: return SQIcon(.chevron_left, size: .l, color: foregroundColor)
|
||||
}
|
||||
}
|
||||
|
||||
var trailingIcon: SQIcon {
|
||||
switch self {
|
||||
case .modal, .booster: return .init(.xmark, size: .l, color: foregroundColor)
|
||||
default:
|
||||
return .init(.xmark, size: .l, color: .clear)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// SQProgressIndicator.swift
|
||||
// AlloVoisinsSwiftUI
|
||||
//
|
||||
// Created by Victor on 02/06/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SQProgressIndicator: View {
|
||||
@Binding var isActivated: Bool
|
||||
|
||||
init(_ isActivated: Binding<Bool>) {
|
||||
self._isActivated = isActivated
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Rectangle()
|
||||
.foregroundColor(.clear)
|
||||
.frame(maxWidth: .infinity, minHeight: 4, maxHeight: 4)
|
||||
.background(isActivated ? Color.sqSemanticGreen : Color.sqNeutral(20))
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SQProgressIndicator(.constant(true))
|
||||
}
|
||||
@@ -12,29 +12,45 @@ enum SQRadioOrientation {
|
||||
case vertical
|
||||
}
|
||||
|
||||
struct SQRadioOption: Identifiable {
|
||||
let id: Int = UUID().hashValue
|
||||
let title: String
|
||||
var desc: String? = nil
|
||||
|
||||
init(title: String, desc: String? = nil) {
|
||||
self.title = title
|
||||
self.desc = desc
|
||||
}
|
||||
}
|
||||
|
||||
struct SQRadio: View {
|
||||
let title: String?
|
||||
let titleSize: CGFloat
|
||||
var radioTextSize: CGFloat = 16
|
||||
var orientation: SQRadioOrientation = .vertical
|
||||
let options: [String]
|
||||
let options: [SQRadioOption]
|
||||
var error: Binding<SQFormFieldError>
|
||||
@Binding var selectedIndex: Int?
|
||||
|
||||
init(title: String? = nil, orientation: SQRadioOrientation = .vertical, options: [String], selectedIndex: Binding<Int?>) {
|
||||
init(title: String? = nil, titleSize: CGFloat = 24, radioTextSize: CGFloat = 16, orientation: SQRadioOrientation = .vertical, options: [SQRadioOption], selectedIndex: Binding<Int?>, error: Binding<SQFormFieldError> = .constant(.none)) {
|
||||
self.title = title
|
||||
self.titleSize = titleSize
|
||||
self.radioTextSize = radioTextSize
|
||||
self.orientation = orientation
|
||||
self.options = options
|
||||
self.error = error
|
||||
self._selectedIndex = selectedIndex
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
VStack(alignment: orientation == .vertical ? .leading : .center, spacing: 16) {
|
||||
if let title = title {
|
||||
SQText(title, size: 24, font: .bold)
|
||||
SQText(title, size: titleSize, font: .bold)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
Group {
|
||||
if orientation == .horizontal {
|
||||
HorizontalRadioButtons(options: options, selectedIndex: $selectedIndex)
|
||||
.background(Color.blue)
|
||||
HorizontalRadioButtons(options: options, titleSize: radioTextSize, selectedIndex: $selectedIndex, isInError: error.wrappedValue.isInError)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
} else {
|
||||
@@ -43,26 +59,34 @@ struct SQRadio: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if error.wrappedValue != .none {
|
||||
SQText(error.wrappedValue.message, size: 12, font: .demiBold, textColor: Color.sqSemanticRed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var radioButtons: some View {
|
||||
ForEach(Array(options.enumerated()), id: \.offset) { index, option in
|
||||
RadioButton(
|
||||
title: option,
|
||||
title: option.title,
|
||||
desc: option.desc,
|
||||
titleSize: radioTextSize,
|
||||
isSelected: Binding(
|
||||
get: { selectedIndex == index },
|
||||
set: { _ in selectedIndex = index }
|
||||
),
|
||||
isInError: error.wrappedValue.isInError,
|
||||
orientation: orientation
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct HorizontalRadioButtons: View {
|
||||
let options: [String]
|
||||
private struct HorizontalRadioButtons: View {
|
||||
let options: [SQRadioOption]
|
||||
let titleSize: CGFloat
|
||||
@Binding var selectedIndex: Int?
|
||||
var isInError: Bool
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
@@ -70,11 +94,14 @@ private struct HorizontalRadioButtons: View {
|
||||
Spacer()
|
||||
ForEach(Array(options.enumerated()), id: \.offset) { index, option in
|
||||
RadioButton(
|
||||
title: option,
|
||||
title: option.title,
|
||||
desc: option.desc,
|
||||
titleSize: titleSize,
|
||||
isSelected: Binding(
|
||||
get: { selectedIndex == index },
|
||||
set: { _ in selectedIndex = index }
|
||||
),
|
||||
isInError: isInError,
|
||||
orientation: .horizontal
|
||||
)
|
||||
.frame(width: geometry.size.width / CGFloat(options.count))
|
||||
@@ -84,11 +111,14 @@ private struct HorizontalRadioButtons: View {
|
||||
}
|
||||
.frame(height: 80)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct RadioButton: View {
|
||||
private struct RadioButton: View {
|
||||
let title: String
|
||||
let desc: String?
|
||||
let titleSize: CGFloat
|
||||
@Binding var isSelected: Bool
|
||||
var isInError: Bool
|
||||
let orientation: SQRadioOrientation
|
||||
|
||||
var body: some View {
|
||||
@@ -100,10 +130,16 @@ private struct RadioButton: View {
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
} else {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(spacing: 8) {
|
||||
radioCircle
|
||||
SQText(title)
|
||||
}
|
||||
|
||||
if let desc = desc {
|
||||
SQText(desc, size: 12)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle()) // Make the entire area tappable
|
||||
@@ -115,22 +151,39 @@ private struct RadioButton: View {
|
||||
private var radioCircle: some View {
|
||||
ZStack {
|
||||
Circle()
|
||||
.stroke(isSelected ? Color.sqNeutral(100) : Color.sqNeutral(30), lineWidth: 1)
|
||||
.stroke(isInError ? Color.sqSemanticRed :
|
||||
(isSelected ?
|
||||
Color.sqNeutral(100) :
|
||||
Color.sqNeutral(30)), lineWidth: 1)
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
if isSelected {
|
||||
Circle()
|
||||
.inset(by: 3)
|
||||
.stroke(Color.sqNeutral(100), lineWidth: 6)
|
||||
.stroke(isInError ? Color.sqSemanticRed : Color.sqNeutral(100), lineWidth: 6)
|
||||
.frame(width: 20, height: 20)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
VStack(spacing: 32) {
|
||||
SQRadio(title: "Vertical", options: ["Option 1", "Option 2", "Option 3"], selectedIndex: .constant(0))
|
||||
SQRadio(title: "Horizontal", orientation: .horizontal, options: ["1\n Non Jamais", "2", "3", "4", "5\nOui"], selectedIndex: .constant(2))
|
||||
VStack(alignment: .leading, spacing: 32) {
|
||||
SQRadio(title: "Vertical", options: [
|
||||
SQRadioOption(title: "Option 1", desc: "L’utilisateur semble proposer un service relevant d’une activité réglementée sans disposer des autorisations requises."),
|
||||
SQRadioOption(title: "Option 2", desc: "L’utilisateur semble évoquer ou proposer des activités interdites par la loi (trafic d’animaux, substances illicites, armes, etc.)."),
|
||||
SQRadioOption(title: "Option 3", desc: "L’utilisateur semble fournir de fausses informations dans son profil."),
|
||||
SQRadioOption(title: "Option 4", desc: "L’utilisateur semble avoir usurpé mon identité ou l’identité d’un autre utilisateur (SIRET, numéro de téléphone...).")
|
||||
], selectedIndex: .constant(0), error: .constant(.custom("Merci de détailler votre signalement")))
|
||||
|
||||
SQRadio(title: "Horizontal", orientation: .horizontal, options: [
|
||||
SQRadioOption(title: "1\n Non Jamais"),
|
||||
SQRadioOption(title: "2"),
|
||||
SQRadioOption(title: "3"),
|
||||
SQRadioOption(title: "4"),
|
||||
SQRadioOption(title: "5\nOui")
|
||||
], selectedIndex: .constant(2))
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// SQRowDivider.swift
|
||||
// AlloVoisinsSwiftUI
|
||||
//
|
||||
// Created by Victor on 05/06/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SQRowDivider: View {
|
||||
var body: some View {
|
||||
Rectangle()
|
||||
.frame(height: 16)
|
||||
.foregroundColor(Color.sqNeutral(10))
|
||||
}
|
||||
}
|
||||
|
||||
struct SQDivider: View {
|
||||
var backgroundColor: Color
|
||||
|
||||
init(_ backgroundColor: Color = Color.sqNeutral(20)) {
|
||||
self.backgroundColor = backgroundColor
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Divider()
|
||||
.overlay(backgroundColor)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SQRowDivider()
|
||||
}
|
||||
@@ -11,12 +11,11 @@ struct SQTextEditor: View {
|
||||
var label: String
|
||||
var placeholder: String
|
||||
var type: SQTextFieldType = .text
|
||||
var errorText: String
|
||||
var error: Binding<SQFormFieldError>
|
||||
var icon: SQIcon?
|
||||
var isDisabled: Bool = false
|
||||
var isOptional: Bool = false
|
||||
var tooltipText: String?
|
||||
@Binding var isInError: Bool
|
||||
var minCharacters: Int?
|
||||
var maxCharacters: Int?
|
||||
@Binding var text: String
|
||||
@@ -30,13 +29,12 @@ struct SQTextEditor: View {
|
||||
init(_ label: String,
|
||||
placeholder: String,
|
||||
type: SQTextFieldType = .text,
|
||||
errorText: String = "",
|
||||
error: Binding<SQFormFieldError> = .constant(.none),
|
||||
text: Binding<String>,
|
||||
icon: SQIcon? = nil,
|
||||
isDisabled: Bool = false,
|
||||
isOptional: Bool = false,
|
||||
tooltipText: String? = nil,
|
||||
isInError: Binding<Bool> = .constant(false),
|
||||
minCharacters: Int? = nil,
|
||||
maxCharacters: Int? = nil,
|
||||
infoAction: (() -> Void)? = nil,
|
||||
@@ -45,13 +43,12 @@ struct SQTextEditor: View {
|
||||
self.label = label
|
||||
self.placeholder = placeholder
|
||||
self.type = type
|
||||
self.errorText = errorText
|
||||
self.error = error
|
||||
self._text = text
|
||||
self.icon = icon
|
||||
self.isDisabled = isDisabled
|
||||
self.isOptional = isOptional
|
||||
self.tooltipText = tooltipText
|
||||
self._isInError = isInError
|
||||
self.minCharacters = minCharacters
|
||||
self.maxCharacters = maxCharacters
|
||||
self.infoAction = infoAction
|
||||
@@ -72,7 +69,7 @@ struct SQTextEditor: View {
|
||||
set: { self.text = String($0.prefix(self.maxCharacters ?? Int.max)) }
|
||||
))
|
||||
.onChange(of: self.text, perform: { _ in
|
||||
self.isInError = false
|
||||
error.wrappedValue = .none
|
||||
})
|
||||
.font(.sq(.medium))
|
||||
.foregroundStyle(Color.sqNeutral(100))
|
||||
@@ -84,13 +81,37 @@ struct SQTextEditor: View {
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isInError ? .sqSemanticRed : isFocused ? accentColor : isDisabled ? Color.sqNeutral(20) : Color.sqNeutral(30), lineWidth: 1)
|
||||
.stroke(error.wrappedValue.isInError ? .sqSemanticRed : isFocused ? accentColor : isDisabled ? Color.sqNeutral(20) : Color.sqNeutral(30), lineWidth: 1)
|
||||
)
|
||||
HStack(spacing: 16) {
|
||||
if error.wrappedValue.isInError {
|
||||
HStack(spacing: 8) {
|
||||
SQIcon(.circle_exclamation, customSize: 12, type: .solid, color: .sqSemanticRed)
|
||||
SQText(error.wrappedValue.message, size: 12, font: .demiBold, textColor: .sqSemanticRed)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
if !characterCountText.isEmpty {
|
||||
SQText(characterCountText, size: 12, textColor: .sqNeutral(50))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var characterCountText: String {
|
||||
let count = text.count
|
||||
if let min = minCharacters, count < min {
|
||||
return "\(count)/\(min)"
|
||||
} else if let max = maxCharacters {
|
||||
return count >= (max * 4 / 5) ? "\(count)/\(max)" : "\(count)"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SQTextEditor("Zone de texte", placeholder: "Ex : Pro Solutions propose une gamme de services complète pour tout type de dépannage électroménager. Bénéficiez de nos 10 ans d’expérience ! Devis gratuit", text: .constant("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."))
|
||||
.padding()
|
||||
SQTextEditor("Zone de texte", placeholder: "Ex : Pro Solutions propose une gamme de services complète pour tout type de dépannage électroménager. Bénéficiez de nos 10 ans d’expérience ! Devis gratuit", error: .constant(.empty), text: .constant("Lorem ipsum dolor sit amet, consectetur"), minCharacters: 50)
|
||||
.padding()
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@ struct SQTextField: View {
|
||||
var label: String
|
||||
var placeholder: String
|
||||
var type: SQTextFieldType = .text
|
||||
var errorText: String
|
||||
var icon: SQIcon?
|
||||
var isDisabled: Bool = false
|
||||
var isOptional: Bool = false
|
||||
var tooltipText: String?
|
||||
@Binding var isInError: Bool
|
||||
var helperText: String?
|
||||
var error: Binding<SQFormFieldError>
|
||||
var minCharacters: Int?
|
||||
var maxCharacters: Int?
|
||||
@Binding var text: String
|
||||
@@ -35,13 +35,13 @@ struct SQTextField: View {
|
||||
init(_ label: String,
|
||||
placeholder: String,
|
||||
type: SQTextFieldType = .text,
|
||||
errorText: String = "",
|
||||
text: Binding<String>,
|
||||
icon: SQIcon? = nil,
|
||||
isDisabled: Bool = false,
|
||||
isOptional: Bool = false,
|
||||
tooltipText: String? = nil,
|
||||
isInError: Binding<Bool> = .constant(false),
|
||||
helperText: String? = nil,
|
||||
error: Binding<SQFormFieldError> = .constant(.none),
|
||||
minCharacters: Int? = nil,
|
||||
maxCharacters: Int? = nil,
|
||||
infoAction: (() -> Void)? = nil,
|
||||
@@ -50,13 +50,13 @@ struct SQTextField: View {
|
||||
self.label = label
|
||||
self.placeholder = placeholder
|
||||
self.type = type
|
||||
self.errorText = errorText
|
||||
self._text = text
|
||||
self.icon = icon
|
||||
self.isDisabled = isDisabled
|
||||
self.isOptional = isOptional
|
||||
self.tooltipText = tooltipText
|
||||
self._isInError = isInError
|
||||
self.helperText = helperText
|
||||
self.error = error
|
||||
self.minCharacters = minCharacters
|
||||
self.maxCharacters = maxCharacters
|
||||
self.infoAction = infoAction
|
||||
@@ -98,17 +98,13 @@ struct SQTextField: View {
|
||||
// TextField
|
||||
HStack(spacing: 4) {
|
||||
if type == .phoneNumber {
|
||||
TextField(placeholder, text: Binding(
|
||||
get: { formatPhoneNumber(self.text) },
|
||||
set: {
|
||||
let filtered = $0.filter { $0.isNumber }
|
||||
let truncated = String(filtered.prefix(10))
|
||||
self.text = truncated
|
||||
self.isInError = !isValidPhoneNumber(truncated)
|
||||
TextField("", text: $text)
|
||||
.placeholder(when: text.isEmpty) {
|
||||
SQText(placeholder, textColor: .sqNeutral(50))
|
||||
}
|
||||
))
|
||||
.onReceive(self.text.publisher.collect()) {
|
||||
self.text = String($0.prefix(10))
|
||||
let formatted = formatPhoneNumber(String($0.prefix(15)))
|
||||
self.text = formatted
|
||||
}
|
||||
.keyboardType(.numberPad)
|
||||
.font(.sq(.medium))
|
||||
@@ -120,7 +116,7 @@ struct SQTextField: View {
|
||||
set: { self.text = String($0.prefix(self.maxCharacters ?? Int.max)) }
|
||||
))
|
||||
.onChange(of: self.text, perform: { _ in
|
||||
self.isInError = false
|
||||
self.error.wrappedValue = .none
|
||||
})
|
||||
.font(.sq(.medium))
|
||||
.foregroundStyle(Color.sqNeutral(100))
|
||||
@@ -135,26 +131,24 @@ struct SQTextField: View {
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.background {
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.fill(Color.white)
|
||||
}
|
||||
.foregroundStyle(Color.sqNeutral())
|
||||
.background(isDisabled ? Color.sqNeutral(10) : .clear)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.inset(by: 0.5)
|
||||
.stroke(isInError ? .sqSemanticRed : isFocused ? accentColor : isDisabled ? Color.sqNeutral(20) : Color.sqNeutral(30), lineWidth: 1)
|
||||
.stroke(error.wrappedValue.isInError ? .sqSemanticRed : isFocused ? accentColor : isDisabled ? Color.sqNeutral(20) : Color.sqNeutral(30), lineWidth: 1)
|
||||
)
|
||||
.focused($isFocused)
|
||||
.disabled(isDisabled)
|
||||
HStack(spacing: 16) {
|
||||
// HStack error
|
||||
if isInError {
|
||||
if error.wrappedValue.isInError {
|
||||
HStack(spacing: 4) {
|
||||
SQIcon(.circle_exclamation, customSize: 12, type: .solid, color: .sqSemanticRed)
|
||||
SQText(errorText, size: 12, textColor: .sqSemanticRed)
|
||||
SQText(error.wrappedValue.message, size: 12, textColor: .sqSemanticRed)
|
||||
}
|
||||
} else if helperText != nil {
|
||||
SQText(helperText!, size: 12, textColor: .sqNeutral(50))
|
||||
}
|
||||
Spacer()
|
||||
if !characterCountText.isEmpty {
|
||||
@@ -214,7 +208,7 @@ struct SQTextField: View {
|
||||
SQTextField("Label name", placeholder: "Placeholder", text: .constant("Bonjour, "), minCharacters: 20)
|
||||
SQTextField("Label name", placeholder: "Placeholder", text: .constant(""), isOptional: true)
|
||||
SQTextField("Label name", placeholder: "Placeholder", text: .constant(""), isDisabled: true)
|
||||
SQTextField("Label name", placeholder: "Placeholder", errorText: "Champ invalide", text: .constant(""), isInError: .constant(true))
|
||||
SQTextField("Label name", placeholder: "Placeholder", text: .constant(""), error: .constant(.empty))
|
||||
SQTextField("Téléphone", placeholder: "01 23 45 67 89", type: .phoneNumber, text: .constant(""))
|
||||
SQTextField(
|
||||
"Numéro de RCS",
|
||||
|
||||
@@ -17,9 +17,10 @@ struct BoosterPromotionView: View {
|
||||
}
|
||||
BoosterStatsView()
|
||||
|
||||
SQButton("Activer l’option Booster", color: .sqRoyal(), textColor: .white) {
|
||||
SQButton("Activer l’option Booster") {
|
||||
|
||||
}
|
||||
.colorScheme(.royal)
|
||||
}
|
||||
.padding()
|
||||
.foregroundColor(.sqRoyal())
|
||||
|
||||
@@ -43,7 +43,7 @@ struct ConfigPrestationSearchView: View {
|
||||
.padding(.horizontal)
|
||||
Spacer()
|
||||
SQFooter {
|
||||
SQButton("Afficher la vue", color: .sqNeutral(100), textColor: .white) {
|
||||
SQButton("Afficher la vue") {
|
||||
showSearchView.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,14 +58,14 @@ struct DebugLandView: View {
|
||||
HStack(spacing: 8) {
|
||||
VStack {
|
||||
SQTextField("Visiter le profil :", placeholder: "UserID", text: $userId)
|
||||
SQButton("Visiter le profil", color: .sqNeutral(100), textColor: .white) {
|
||||
SQButton("Visiter le profil") {
|
||||
|
||||
}
|
||||
}
|
||||
VStack {
|
||||
SQTextField("Se connecter sur :", placeholder: "UserID", text: $userId)
|
||||
.keyboardType(.numberPad)
|
||||
SQButton("Se connecter", color: .sqNeutral(100), textColor: .white) {
|
||||
SQButton("Se connecter") {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ struct CardColorSelectionView: View {
|
||||
}
|
||||
.padding()
|
||||
SQFooter {
|
||||
SQButton("Continuer", color: .sqNeutral(100), textColor: .white) {
|
||||
SQButton("Continuer") {
|
||||
self.goToNext.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,9 +60,9 @@ struct CardFormView: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
SQTextField("Titre", placeholder: "Ex : Pro Solutions", errorText: "", text: $title)
|
||||
SQTextField("Sous-titre", placeholder: "Ex : Martin Dupont", errorText: "", text: $subtitle, isOptional: true)
|
||||
SQTextField("Métier", placeholder: "Ex : Dépannage électroménager", errorText: "", text: $job, isOptional: true)
|
||||
SQTextField("Titre", placeholder: "Ex : Pro Solutions", text: $title)
|
||||
SQTextField("Sous-titre", placeholder: "Ex : Martin Dupont", text: $subtitle, isOptional: true)
|
||||
SQTextField("Métier", placeholder: "Ex : Dépannage électroménager", text: $job, isOptional: true)
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Toggle(isOn: $showRating) {
|
||||
SQText("Afficher ma note AlloVoisins", font: .demiBold)
|
||||
@@ -83,17 +83,18 @@ struct CardFormView: View {
|
||||
.foregroundColor(Color.sqNeutral(10))
|
||||
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
SQTextField("Numéro de téléphone", placeholder: "Ex : 06 12 34 56 78", errorText: "", text: $phoneNumber)
|
||||
SQTextField("Adresse complète", placeholder: "Ex : 1 rue de la gare, 67000 Strasbourg", errorText: "", text: $address)
|
||||
SQTextField("Numéro de téléphone", placeholder: "Ex : 06 12 34 56 78", text: $phoneNumber)
|
||||
SQTextField("Adresse complète", placeholder: "Ex : 1 rue de la gare, 67000 Strasbourg", text: $address)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
SQFooter {
|
||||
SQButton("Aperçu", color: .sqNeutral(100), textColor: .sqNeutral(100), icon: SQIcon(.eye, color: .sqNeutral(100)), isFull: false) {}
|
||||
.sqStyle()
|
||||
SQButton("Imprimer", color: .sqNeutral(100), textColor: .white, icon: SQIcon(.print, color: .white)) {}
|
||||
.sqStyle()
|
||||
SQButton("Aperçu") {}
|
||||
.icon(SQIcon(.eye, color: .sqNeutral(100)))
|
||||
.buttonType(.line)
|
||||
SQButton("Imprimer") {}
|
||||
.icon(SQIcon(.print, color: .white))
|
||||
}
|
||||
}
|
||||
.confirmationDialog("Choisir une image", isPresented: $showActionSheet, actions: {
|
||||
|
||||
@@ -17,7 +17,7 @@ struct CardPrintView: View {
|
||||
.frame(height: 100)
|
||||
.frame(maxWidth: .infinity)
|
||||
SQText("Nous vous recommandons : \n • Une impression haute qualité, en couleur \n • L’utilisation d’un papier d’au moins
200 g/m2 ")
|
||||
SQButton("Imprimer", color: .sqNeutral(100), textColor: .white) {}
|
||||
SQButton("Imprimer") {}
|
||||
.frame(maxWidth: .infinity)
|
||||
Spacer()
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ struct CardTemplateSelectionView: View {
|
||||
}
|
||||
}
|
||||
SQFooter {
|
||||
SQButton("Continuer", color: .sqNeutral(100), textColor: .white) {
|
||||
SQButton("Continuer") {
|
||||
if selectedTemplate != nil {
|
||||
goToNext.toggle()
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ struct FlyerColorSelectionView: View {
|
||||
}
|
||||
.padding()
|
||||
SQFooter {
|
||||
SQButton("Continuer", color: .sqNeutral(100), textColor: .white) {
|
||||
SQButton("Continuer") {
|
||||
self.goToNext.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,9 +62,9 @@ struct FlyerFormView: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
SQTextField("Titre", placeholder: "Ex : Pro Solutions", errorText: "", text: $title)
|
||||
SQTextField("Sous-titre", placeholder: "Ex : Martin Dupont", errorText: "", text: $subtitle, isOptional: true)
|
||||
SQTextField("Métier", placeholder: "Ex : Dépannage électroménager", errorText: "", text: $job, isOptional: true)
|
||||
SQTextField("Titre", placeholder: "Ex : Pro Solutions", text: $title)
|
||||
SQTextField("Sous-titre", placeholder: "Ex : Martin Dupont", text: $subtitle, isOptional: true)
|
||||
SQTextField("Métier", placeholder: "Ex : Dépannage électroménager", text: $job, isOptional: true)
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Toggle(isOn: $showRating) {
|
||||
SQText("Afficher ma note AlloVoisins", font: .demiBold)
|
||||
@@ -87,11 +87,11 @@ struct FlyerFormView: View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
SQTextEditor("Zone de text", placeholder: "Ex : Pro Solutions propose une gamme de services complète pour tout type de dépannage électroménager. Bénéficiez de nos 10 ans d’expérience ! Devis gratuit", text: .constant(""))
|
||||
|
||||
SQTextField("Prestation 1", placeholder: "Ex : Réparation lave-vaisselle", errorText: "", text: $job, isOptional: true)
|
||||
SQTextField("Prestation 2", placeholder: "Ex : Réparation machine à laver", errorText: "", text: $job, isOptional: true)
|
||||
SQTextField("Prestation 3", placeholder: "Ex : Réparation four", errorText: "", text: $job, isOptional: true)
|
||||
SQTextField("Prestation 4", placeholder: "Ex : Réparation outillage", errorText: "", text: $job, isOptional: true)
|
||||
SQTextField("Prestation 5", placeholder: "Ex : Dépannage électroménager", errorText: "", text: $job, isOptional: true)
|
||||
SQTextField("Prestation 1", placeholder: "Ex : Réparation lave-vaisselle", text: $job, isOptional: true)
|
||||
SQTextField("Prestation 2", placeholder: "Ex : Réparation machine à laver", text: $job, isOptional: true)
|
||||
SQTextField("Prestation 3", placeholder: "Ex : Réparation four", text: $job, isOptional: true)
|
||||
SQTextField("Prestation 4", placeholder: "Ex : Réparation outillage", text: $job, isOptional: true)
|
||||
SQTextField("Prestation 5", placeholder: "Ex : Dépannage électroménager", text: $job, isOptional: true)
|
||||
}
|
||||
.padding()
|
||||
|
||||
@@ -100,8 +100,8 @@ struct FlyerFormView: View {
|
||||
.foregroundColor(Color.sqNeutral(10))
|
||||
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
SQTextField("Numéro de téléphone", placeholder: "Ex : 06 12 34 56 78", errorText: "", text: $phoneNumber)
|
||||
SQTextField("Adresse complète", placeholder: "Ex : 1 rue de la gare, 67000 Strasbourg", errorText: "", text: $address)
|
||||
SQTextField("Numéro de téléphone", placeholder: "Ex : 06 12 34 56 78", text: $phoneNumber)
|
||||
SQTextField("Adresse complète", placeholder: "Ex : 1 rue de la gare, 67000 Strasbourg", text: $address)
|
||||
}
|
||||
.padding()
|
||||
|
||||
@@ -112,30 +112,31 @@ struct FlyerFormView: View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
SQText("Mentions légales obligatoires", size: 18, font: .bold)
|
||||
|
||||
SQTextField("Dénomination sociale", placeholder: "Ex : Pro solutions", errorText: "", text: $job, isOptional: true)
|
||||
SQTextField("Adresse du siège social", placeholder: "Ex : 16 rue de la Redoute, 67500 Haguenau", errorText: "", text: $job, isOptional: true)
|
||||
SQTextField("Dénomination sociale", placeholder: "Ex : Pro solutions", text: $job, isOptional: true)
|
||||
SQTextField("Adresse du siège social", placeholder: "Ex : 16 rue de la Redoute, 67500 Haguenau", text: $job, isOptional: true)
|
||||
VStack(spacing: 0) {
|
||||
SQTextField("Numéro SIRET", placeholder: "Ex : 12345678901234", errorText: "", text: $job, isOptional: true)
|
||||
SQTextField("Numéro SIRET", placeholder: "Ex : 12345678901234", text: $job, isOptional: true)
|
||||
SQText("Le numéro SIRET se compose de 14 chiffres : les 9 chiffres du SIREN + 5 chiffres propres à chaque établissement (NIC).", size: 12, textColor: .sqNeutral(50))
|
||||
}
|
||||
SQTextField("Numéro de RCS", placeholder: "Ex : RCS STRASBOURG B 123456789", errorText: "", text: $job, isOptional: true)
|
||||
SQTextField("Statut juridique", placeholder: "Ex : SARL", errorText: "", text: $job, isOptional: true)
|
||||
SQTextField("Montant du capital social (€)", placeholder: "Ex : 1 000,00", errorText: "", text: $job, isOptional: true)
|
||||
SQTextField("Autre champ relatif à votre activité", placeholder: "Ex : Pour votre santé, mangez 5 fruits et légumes par jour", errorText: "", text: $job, isOptional: true)
|
||||
SQCheckbox("Je comprends que j’engage ma responsabilité sur l’exhaustivité et l’authenticité des informations renseignées ci-dessus.", isChecked: $authentConfirm, isInError: $confirmIsInError)
|
||||
SQTextField("Numéro de RCS", placeholder: "Ex : RCS STRASBOURG B 123456789", text: $job, isOptional: true)
|
||||
SQTextField("Statut juridique", placeholder: "Ex : SARL", text: $job, isOptional: true)
|
||||
SQTextField("Montant du capital social (€)", placeholder: "Ex : 1 000,00", text: $job, isOptional: true)
|
||||
SQTextField("Autre champ relatif à votre activité", placeholder: "Ex : Pour votre santé, mangez 5 fruits et légumes par jour", text: $job, isOptional: true)
|
||||
SQCheckbox("Je comprends que j’engage ma responsabilité sur l’exhaustivité et l’authenticité des informations renseignées ci-dessus.", isChecked: $authentConfirm, error: .constant(.none))
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
SQFooter {
|
||||
SQButton("Aperçu", color: .sqNeutral(100), textColor: .sqNeutral(100), icon: SQIcon(.eye, color: .sqNeutral(100)), isFull: false) {}
|
||||
.sqStyle()
|
||||
SQButton("Imprimer", color: .sqNeutral(100), textColor: .white, icon: SQIcon(.print, color: .white)) {
|
||||
SQButton("Aperçu") {}
|
||||
.icon(SQIcon(.eye, color: .sqNeutral(100)))
|
||||
.buttonType(.line)
|
||||
SQButton("Imprimer") {
|
||||
if authentConfirm == false {
|
||||
self.confirmIsInError = true
|
||||
}
|
||||
}
|
||||
.sqStyle()
|
||||
.icon(SQIcon(.print, color: .white))
|
||||
}
|
||||
}
|
||||
.confirmationDialog("Choisir une image", isPresented: $showActionSheet, actions: {
|
||||
|
||||
@@ -88,7 +88,7 @@ struct FlyerTemplateSelectionView: View {
|
||||
}
|
||||
}
|
||||
SQFooter {
|
||||
SQButton("Continuer", color: .sqNeutral(100), textColor: .white) {
|
||||
SQButton("Continuer") {
|
||||
if selectedTemplate != nil {
|
||||
goToNext.toggle()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// MoreNeighborsSelectedBadge.swift
|
||||
// AlloVoisinsSwiftUI
|
||||
//
|
||||
// Created by Victor on 02/06/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MoreNeighborsSelectedBadge: View {
|
||||
@Binding var selectedNeighbors: Int
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .center, spacing: 10) {
|
||||
SQText("+\(selectedNeighbors)", font: .bold, textColor: .sqNeutral(50))
|
||||
}
|
||||
.frame(width: 32, height: 32, alignment: .center)
|
||||
.background(Color.sqNeutral(20))
|
||||
.cornerRadius(80)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
MoreNeighborsSelectedBadge(selectedNeighbors: .constant(6))
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// NeighborsSelectionFooter.swift
|
||||
// AlloVoisinsSwiftUI
|
||||
//
|
||||
// Created by Victor on 02/06/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct NeighborsSelectionFooter: View {
|
||||
var inProfile: Bool = true
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
if inProfile {
|
||||
VStack {
|
||||
HStack(alignment: .top) {
|
||||
SQCircleButton(
|
||||
SQIcon(.arrow_left, size: .l)
|
||||
) {}
|
||||
Spacer()
|
||||
VStack {
|
||||
SQCircleButton(
|
||||
SQIcon(.plus, size: .l),
|
||||
color: .sqNeutral(),
|
||||
size: .l,
|
||||
isFull: false
|
||||
) {}
|
||||
SQText("Sélectionner", font: .demiBold)
|
||||
}
|
||||
Spacer()
|
||||
SQCircleButton(
|
||||
SQIcon(.arrow_right, size: .l)
|
||||
) {}
|
||||
}
|
||||
|
||||
SQDivider()
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 8)
|
||||
.background(
|
||||
LinearGradient(
|
||||
stops: [
|
||||
Gradient.Stop(color: Color.sqNeutral(10).opacity(0), location: 0.00),
|
||||
Gradient.Stop(color: Color.sqNeutral(10), location: 0.70),
|
||||
],
|
||||
startPoint: UnitPoint(x: 0.5, y: 0),
|
||||
endPoint: UnitPoint(x: 0.5, y: 1)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
SQText("Nous vous recommandons de sélectionner 3 offreurs :", size: 13, font: .bold)
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack(spacing: 8) {
|
||||
SQImage("neighbor_avatar", height: 32)
|
||||
SQImage("neighbor_avatar", height: 32)
|
||||
SQImage("neighbor_avatar", height: 32)
|
||||
MoreNeighborsSelectedBadge(selectedNeighbors: .constant(4))
|
||||
}
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
SQText("0 offreur sélectionné")
|
||||
Spacer()
|
||||
}
|
||||
HStack(spacing: 4) {
|
||||
SQProgressIndicator(.constant(true))
|
||||
SQProgressIndicator(.constant(true))
|
||||
SQProgressIndicator(.constant(false))
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
Spacer()
|
||||
SQButton("Continuer") {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color.sqNeutral(10))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NeighborsSelectionFooter()
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// NeihborsCard.swift
|
||||
// AlloVoisinsSwiftUI
|
||||
//
|
||||
// Created by Victor on 05/06/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct NeihborsCard: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(spacing: 8) {
|
||||
AVAvatar(model: AVAvatarModel(avatarString: "https://preprod.allovoisins.com//uploads/u/avatars/9/b/9/9b93e04d6d_4681618_l.jpg", onlineStatus: .online(), sizing: .L))
|
||||
VStack(alignment: .leading) {
|
||||
SQText("Julien L.", font: .demiBold)
|
||||
SQText("Julien, Petits Jobs À Nantes", size: 12, font: .demiBold)
|
||||
SQText("Nantes (Gloriette-Feydeaux) - 3,1 km", size: 12, textColor: .sqNeutral(80))
|
||||
SQText("En ligne", size: 12, textColor: .sqNeutral(80))
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
||||
HStack {
|
||||
SQImage("ladame", height: 100)
|
||||
SQImage("ladame", height: 100)
|
||||
}
|
||||
|
||||
HStack {
|
||||
SQIcon(.star, type: .solid, color: .sqGold(50))
|
||||
SQText("4,5/5", font: .bold)
|
||||
SQText("(35 avis)")
|
||||
}
|
||||
}
|
||||
.frame(height: 247)
|
||||
.padding()
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color.sqNeutral(20), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NeihborsCard()
|
||||
.padding()
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// AVProfileReportCell.swift
|
||||
// AlloVoisinsSwiftUI
|
||||
//
|
||||
// Created by Victor on 21/05/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AVProfileReportCell: View {
|
||||
var body: some View {
|
||||
HStack(spacing: 16) {
|
||||
SQIcon(.flag)
|
||||
SQText("Signaler un profil")
|
||||
Spacer()
|
||||
}
|
||||
.padding(16)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
AVProfileReportCell()
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// ReportConfirmationView.swift
|
||||
// AlloVoisinsSwiftUI
|
||||
//
|
||||
// Created by Victor on 21/05/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ReportConfirmationView: View {
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
VStack {
|
||||
SQIcon(.check, customSize: 64, type: .solid, color: .sqSemanticGreen)
|
||||
SQText("Votre signalement a bien été pris en compte.", size: 18, font: .bold)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
SQText("Merci de participer à améliorer la communauté. Nous nous efforçons de traiter votre signalement dans les plus brefs délais et nous vous informerons de la suite qui sera donnée.", font: .demiBold)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
SQText("Souhaitez-vous bloquer cet utilisateur ?", font: .demiBold)
|
||||
HStack {
|
||||
Toggle(isOn: .constant(true)) {
|
||||
SQText("En bloquant cet utilisateur, il ne pourra plus vous contacter.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
SQButton("Confirmer") {
|
||||
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.sqNavigationBar(title: "Signaler un profil", style: .modal)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NavigationView {
|
||||
ReportConfirmationView()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// ReportFinalizationView.swift
|
||||
// AlloVoisinsSwiftUI
|
||||
//
|
||||
// Created by Victor on 21/05/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ReportFinalizationView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
SQText("Votre signalement", font: .bold)
|
||||
HStack(alignment: .top) {
|
||||
SQText("Motif :", font: .demiBold)
|
||||
SQText("Image non conforme")
|
||||
}
|
||||
Divider()
|
||||
HStack(alignment: .top) {
|
||||
SQText("Sous-motif :", font: .demiBold)
|
||||
SQText("Photo de profil ou couverture inappropriée")
|
||||
}
|
||||
Divider()
|
||||
VStack {
|
||||
SQTextEditor("Explication", placeholder: "", error: .constant(.none), text: .constant(""), minCharacters: 50)
|
||||
}
|
||||
Spacer()
|
||||
VStack(spacing: 0) {
|
||||
SQCheckbox("Je certifie que toutes les informations renseignées sont exactes et complètes et que je les ai fournies en toute bonne foi.", isChecked: .constant(false), error: .constant(.none), alignment: .center)
|
||||
SQCheckbox("Je comprends que tout signalement abusif pourra faire l’objet de sanctions.", isChecked: .constant(true), error: .constant(.none), alignment: .center)
|
||||
SQButton("Signaler") {}
|
||||
.colorScheme(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.sqNavigationBar(title: "Signaler un profil", style: .modal)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NavigationView {
|
||||
ReportFinalizationView()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// ReportReasonsView.swift
|
||||
// AlloVoisinsSwiftUI
|
||||
//
|
||||
// Created by Victor on 21/05/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ReportReasonsView: View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
SQText("Seules les interactions ayant eu lieu sur AlloVoisins pourront être prises en compte.")
|
||||
SQRadio(title: "Sélectionner un motif :",
|
||||
titleSize: 16,
|
||||
options: [
|
||||
SQRadioOption(title: "Comportement inapproprié"),
|
||||
SQRadioOption(title: "Comportement suspect ou frauduleux"),
|
||||
SQRadioOption(title: "Litige avec ce membre"),
|
||||
SQRadioOption(title: "Spammeur / Démarcheur"),
|
||||
],
|
||||
selectedIndex: .constant(1),
|
||||
error: .constant(.custom("Merci de détailler votre signalement.")))
|
||||
}
|
||||
|
||||
Spacer()
|
||||
SQButton("Continuer") {}
|
||||
}
|
||||
.padding(16)
|
||||
.sqNavigationBar(title: "Signaler un profil", style: .modal)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NavigationView {
|
||||
ReportReasonsView()
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ struct AlloVoisinReputationScreen: View {
|
||||
.padding(16)
|
||||
.background(Color.white)
|
||||
.cornerRadius(8)
|
||||
SQButton("Conserver mon abonnement", color: .sqNeutral(100), textColor: .white) {
|
||||
SQButton("Conserver mon abonnement") {
|
||||
navigateToNext = true
|
||||
}
|
||||
}
|
||||
@@ -83,9 +83,11 @@ struct AlloVoisinReputationScreen: View {
|
||||
.background(Color.sqBlue(10))
|
||||
.cornerRadius(8)
|
||||
|
||||
SQButton("J’ai compris, mais je souhaite résilier", color: .white, textSize: 13) {
|
||||
SQButton("J’ai compris, mais je souhaite résilier") {
|
||||
navigateToNext = true
|
||||
}
|
||||
.buttonType(.glass)
|
||||
.textSize(13)
|
||||
}
|
||||
.navigationDestination(isPresented: $navigateToNext) {
|
||||
if let screen = viewModel.nextPromotionalScreen {
|
||||
|
||||
@@ -14,7 +14,13 @@ struct AskIfWillComeBackScreen: View {
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
SQRadio(title: "Pensez-vous redevenir abonné Premier ?", orientation: .horizontal, options: ["1\nNon,Jamais", "2", "3", "4", "5\nOui, sûrement"], selectedIndex: $selectedIndex)
|
||||
SQRadio(title: "Pensez-vous redevenir abonné Premier ?", orientation: .horizontal, options: [
|
||||
SQRadioOption(title: "1\nNon,Jamais"),
|
||||
SQRadioOption(title: "2"),
|
||||
SQRadioOption(title: "3"),
|
||||
SQRadioOption(title: "4"),
|
||||
SQRadioOption(title: "5\nOui, sûrement")
|
||||
], selectedIndex: $selectedIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,14 @@ struct ContinueAsParticularScreen: View {
|
||||
SQText("Continuez de proposer vos services en tant que particulier pour arrondir vos fins de mois. À partir de 9,99 € / mois (sans engagement).", font: .demiBold)
|
||||
.multilineTextAlignment(.center)
|
||||
VStack {
|
||||
SQButton("Changer de statut", color: .sqNeutral(100), textColor: .white) {
|
||||
SQButton("Changer de statut") {
|
||||
navigateToNext = true
|
||||
}
|
||||
SQButton("J’ai compris, mais je souhaite résilier", color: .white) {
|
||||
SQButton("J’ai compris, mais je souhaite résilier")
|
||||
{
|
||||
navigateToNext = true
|
||||
}
|
||||
.buttonType(.glass)
|
||||
}
|
||||
}
|
||||
.navigationDestination(isPresented: $navigateToNext) {
|
||||
|
||||
@@ -44,15 +44,15 @@ enum ResiliationScreenType {
|
||||
}
|
||||
}
|
||||
|
||||
var buttonColor: Color {
|
||||
var buttonColor: SQButtonColorScheme {
|
||||
switch self {
|
||||
case .alloVoisinReputation, .continueAsParticular, .profileCompletion, .softwarePresentation:
|
||||
return Color.sqNeutral(100)
|
||||
case .getMoreRatings: return Color.sqGold(50)
|
||||
case .moreTime: return Color.sqBlue(50)
|
||||
case .onlyProRequests, .personalizedSupport, .webinaire: return Color.sqGrape(80)
|
||||
case .resizePerimeter, .statusChange: return Color.sqOrange(50)
|
||||
default: return Color.sqNeutral(100)
|
||||
return .neutral
|
||||
case .getMoreRatings: return .gold
|
||||
case .moreTime: return .blue
|
||||
case .onlyProRequests, .personalizedSupport, .webinaire: return .grape
|
||||
case .resizePerimeter, .statusChange: return .orange
|
||||
default: return .neutral
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,13 +97,16 @@ struct GenericResiliationScreen<Content: ResiliationContentView>: View {
|
||||
.background(content.screenType.mainColor)
|
||||
.cornerRadius(8)
|
||||
|
||||
SQButton(content.screenType.buttonTitle, color: content.screenType.buttonColor, textColor: .white, action: buttonAction)
|
||||
SQButton(content.screenType.buttonTitle, action: buttonAction)
|
||||
.colorScheme(content.screenType.buttonColor)
|
||||
}
|
||||
|
||||
if content.screenType != .statusChange {
|
||||
SQButton(content.screenType.cancelButtonTitle, color: .white, textSize: 13) {
|
||||
SQButton(content.screenType.cancelButtonTitle) {
|
||||
navigateToNext = true
|
||||
}
|
||||
.buttonType(.glass)
|
||||
.textSize(13)
|
||||
}
|
||||
}
|
||||
.navigationDestination(isPresented: $navigateToNext) {
|
||||
|
||||
@@ -30,17 +30,20 @@ struct GetMoreRatingsScreen: View {
|
||||
SQText("Vous pouvez recueillir des avis auprès de vos clients hors AlloVoisins pour faire décoller votre activité.")
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
SQButton("Recueillir des avis", color: .sqGold(50), textColor: .white) {
|
||||
SQButton("Recueillir des avis") {
|
||||
navigateToNext = true
|
||||
}
|
||||
.colorScheme(.gold)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.sqGold(10))
|
||||
.cornerRadius(8)
|
||||
|
||||
SQButton("J’ai compris, mais je souhaite résilier", color: .white, textSize: 13) {
|
||||
SQButton("J’ai compris, mais je souhaite résilier") {
|
||||
navigateToNext = true
|
||||
}
|
||||
.buttonType(.glass)
|
||||
.textSize(13)
|
||||
}
|
||||
.navigationDestination(isPresented: $navigateToNext) {
|
||||
if let screen = viewModel.nextPromotionalScreen {
|
||||
|
||||
@@ -22,17 +22,20 @@ struct MoreTimeScreen: View {
|
||||
SQText("d’essai supplémentaires pour découvrir toutes les fonctionnalités de l’abonnement Premier.")
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
SQButton("Je prolonge ma période d’essai", color: .sqBlue(50), textColor: .white) {
|
||||
SQButton("Je prolonge ma période d’essai") {
|
||||
navigateToNext = true
|
||||
}
|
||||
.colorScheme(.blue)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.sqBlue(10))
|
||||
.cornerRadius(8)
|
||||
|
||||
SQButton("Non merci, je souhaite résilier", color: .white, textSize: 13) {
|
||||
SQButton("Non merci, je souhaite résilier") {
|
||||
navigateToNext = true
|
||||
}
|
||||
.buttonType(.glass)
|
||||
.textSize(13)
|
||||
}
|
||||
.navigationDestination(isPresented: $navigateToNext) {
|
||||
if let screen = viewModel.nextPromotionalScreen {
|
||||
|
||||
@@ -30,17 +30,20 @@ struct OnlyProRequestsScreen: View {
|
||||
SQText("Sur le menu abonnement, vous pouvez filtrer les demandes réservées aux pros.")
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
SQButton("Voir les demandes", color: .sqGrape(80), textColor: .white) {
|
||||
SQButton("Voir les demandes") {
|
||||
navigateToNext = true
|
||||
}
|
||||
.colorScheme(.grape)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.sqGrape(10))
|
||||
.cornerRadius(8)
|
||||
|
||||
SQButton("J’ai compris, mais je souhaite résilier", color: .white, textSize: 13) {
|
||||
SQButton("J’ai compris, mais je souhaite résilier") {
|
||||
navigateToNext = true
|
||||
}
|
||||
.buttonType(.glass)
|
||||
.textSize(13)
|
||||
}
|
||||
.navigationDestination(isPresented: $navigateToNext) {
|
||||
if let screen = viewModel.nextPromotionalScreen {
|
||||
|
||||
@@ -25,17 +25,20 @@ struct PersonalizedSupportScreen: View {
|
||||
SQText("Notre équipe dédiée aux professionnels est disponible pour répondre à toutes vos questions par téléphone.")
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
SQButton("Je souhaite être appelé", color: .sqGrape(80), textColor: .white) {
|
||||
SQButton("Je souhaite être appelé") {
|
||||
navigateToNext = true
|
||||
}
|
||||
.colorScheme(.grape)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.sqGrape(10))
|
||||
.cornerRadius(8)
|
||||
|
||||
SQButton("Non merci, je souhaite résilier", color: .white, textSize: 13) {
|
||||
SQButton("Non merci, je souhaite résilier") {
|
||||
navigateToNext = true
|
||||
}
|
||||
.buttonType(.glass)
|
||||
.textSize(13)
|
||||
}
|
||||
.navigationDestination(isPresented: $navigateToNext) {
|
||||
if let screen = viewModel.nextPromotionalScreen {
|
||||
|
||||
@@ -54,7 +54,7 @@ struct ProfileCompletionScreen: View {
|
||||
SQText("90%", size: 32, font: .bold)
|
||||
SQText("des demandeurs comparent systématiquement les profils des offreurs pour faire leur choix.")
|
||||
.multilineTextAlignment(.center)
|
||||
SQButton("Je complète mon profil", color: .sqNeutral(100), textColor: .white) {
|
||||
SQButton("Je complète mon profil") {
|
||||
navigateToNext = true
|
||||
}
|
||||
}
|
||||
@@ -62,9 +62,11 @@ struct ProfileCompletionScreen: View {
|
||||
.background(Color.sqNeutral(10))
|
||||
.cornerRadius(8)
|
||||
|
||||
SQButton("J’ai compris, mais je souhaite résilier", color: .white, textSize: 13) {
|
||||
SQButton("J’ai compris, mais je souhaite résilier") {
|
||||
navigateToNext = true
|
||||
}
|
||||
.buttonType(.glass)
|
||||
.textSize(13)
|
||||
}
|
||||
.navigationDestination(isPresented: $navigateToNext) {
|
||||
if let screen = viewModel.nextPromotionalScreen {
|
||||
|
||||
@@ -42,10 +42,9 @@ struct ResiliationCheckStepsScreen: View {
|
||||
}
|
||||
|
||||
HStack {
|
||||
SQButton("Annuler", color: .sqNeutral(100), isFull: false) {
|
||||
|
||||
}
|
||||
SQButton("Continuer", color: .sqNeutral(100), textColor: .white) {
|
||||
SQButton("Annuler") {}
|
||||
.buttonType(.glass)
|
||||
SQButton("Continuer") {
|
||||
navigateToReasonScreen = true
|
||||
}
|
||||
.disabled(!allStepsSelected)
|
||||
|
||||
@@ -17,7 +17,7 @@ struct ResiliationConfirmationScreen: View {
|
||||
.scaledToFit()
|
||||
.frame(height: 200)
|
||||
SQText("Votre résiliation a été prise en compte. Elle sera effective le JJ/MM/AAAA.", size: 18, font: .bold)
|
||||
SQButton("Terminer", color: .sqNeutral(100), textColor: .white) {
|
||||
SQButton("Terminer") {
|
||||
|
||||
}
|
||||
.padding()
|
||||
|
||||
@@ -18,17 +18,18 @@ struct ResiliationReasonScreen: View {
|
||||
ScrollView {
|
||||
VStack(spacing: 16) {
|
||||
SQRadio(title: "Aidez-nous à nous améliorer : précisez le motif de votre résiliation",
|
||||
options: viewModel.resiliationReasons.map { $0.text },
|
||||
options: viewModel.resiliationReasons.map { SQRadioOption(title: $0.text) },
|
||||
selectedIndex: $selectedIndex)
|
||||
if selectedIndex == viewModel.resiliationReasons.count - 1 {
|
||||
SQTextField("", placeholder: "Précisez-nous le motif de votre résiliation", text: $resiliationOtherMotif)
|
||||
}
|
||||
|
||||
HStack {
|
||||
SQButton("Annuler", color: .sqNeutral(100), isFull: false) {
|
||||
SQButton("Annuler") {
|
||||
dismiss()
|
||||
}
|
||||
SQButton("Continuer", color: .sqNeutral(100), textColor: .white) {
|
||||
.buttonType(.glass)
|
||||
SQButton("Continuer") {
|
||||
if let index = selectedIndex {
|
||||
let selectedReason = viewModel.resiliationReasons[index]
|
||||
viewModel.setSelectedReason(selectedReason)
|
||||
|
||||
@@ -25,17 +25,20 @@ struct ResizePerimeterScreen: View {
|
||||
SQText("Si vous le souhaitez, vous pouvez modifier à tout moment votre abonnement en élargissant votre périmètre d’intervention.")
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
SQButton("Modifier mon périmètre", color: .sqOrange(50), textColor: .white) {
|
||||
SQButton("Modifier mon périmètre") {
|
||||
navigateToNext = true
|
||||
}
|
||||
.colorScheme(.orange)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.sqOrange(10))
|
||||
.cornerRadius(8)
|
||||
|
||||
SQButton("J’ai compris, mais je souhaite résilier", color: .white, textSize: 13) {
|
||||
SQButton("J’ai compris, mais je souhaite résilier") {
|
||||
navigateToNext = true
|
||||
}
|
||||
.buttonType(.glass)
|
||||
.textSize(13)
|
||||
}
|
||||
.navigationDestination(isPresented: $navigateToNext) {
|
||||
if let screen = viewModel.nextPromotionalScreen {
|
||||
|
||||
@@ -39,12 +39,14 @@ struct SoftwarePresentationScreen: View {
|
||||
SQText("Inclus, sans surcoût", size: 13, font: .demiBold)
|
||||
}
|
||||
VStack {
|
||||
SQButton("Découvrir le logiciel", color: .sqNeutral(100), textColor: .white) {
|
||||
SQButton("Découvrir le logiciel") {
|
||||
navigateToNext = true
|
||||
}
|
||||
SQButton("J’ai compris, mais je souhaite résilier", color: .white, textSize: 13) {
|
||||
SQButton("J’ai compris, mais je souhaite résilier") {
|
||||
navigateToNext = true
|
||||
}
|
||||
.buttonType(.glass)
|
||||
.textSize(13)
|
||||
}
|
||||
}
|
||||
.navigationDestination(isPresented: $navigateToNext) {
|
||||
|
||||
@@ -21,9 +21,10 @@ struct StatusChangeScreen: View {
|
||||
SQText("Si vous le souhaitez, vous pourrez vous abonner à l’abonnement Premier en tant que particulier.")
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
SQButton("Résilier et changer de statut", color: .sqOrange(50), textColor: .white) {
|
||||
SQButton("Résilier et changer de statut") {
|
||||
navigateToNext = true
|
||||
}
|
||||
.colorScheme(.orange)
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.sqOrange(10))
|
||||
|
||||
@@ -33,8 +33,9 @@ struct WebinaireScreen: View {
|
||||
ResiliationConfirmationScreen()
|
||||
}
|
||||
} label: {
|
||||
SQButton("Je m’inscris", color: .sqGrape(80), textColor: .white) {
|
||||
SQButton("Je m’inscris") {
|
||||
}
|
||||
.colorScheme(.grape)
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
@@ -48,8 +49,10 @@ struct WebinaireScreen: View {
|
||||
ResiliationConfirmationScreen()
|
||||
}
|
||||
} label: {
|
||||
SQButton("Non merci, je souhaite résilier", color: .white, textSize: 13) {
|
||||
SQButton("Non merci, je souhaite résilier") {
|
||||
}
|
||||
.buttonType(.glass)
|
||||
.textSize(13)
|
||||
}
|
||||
}
|
||||
.navigationDestination(isPresented: $navigateToNext) {
|
||||
|
||||
@@ -23,12 +23,12 @@ enum Pricing: String, CaseIterable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
var footerButtonColor: Color {
|
||||
var footerButtonColor: SQButtonColorScheme {
|
||||
switch self {
|
||||
case .standard:
|
||||
return .sqNeutral(100)
|
||||
return .neutral
|
||||
case .premier, .premierPro:
|
||||
return .sqOrange(50)
|
||||
return .orange
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,9 @@ struct NewPerimeterCellView: View {
|
||||
SQText("Vous souhaitez ajouter un nouveau périmètre ?", size: 24, font: .bold)
|
||||
.multilineTextAlignment(.center)
|
||||
SQText("Si vous souhaitez couvrir une zone géographique différente de votre périmètre actuel, souscrivez à un nouvel abonnement pour proposer vos services dans ce nouveau secteur.")
|
||||
SQButton("Souscrire à un nouveau périmètre", color: .sqOrange(50), textColor: .sqOrange(50), isFull: false) {}
|
||||
SQButton("Souscrire à un nouveau périmètre") {}
|
||||
.buttonType(.line)
|
||||
.colorScheme(.orange)
|
||||
SQText("À partir de 9,99€ / mois, sans engagement", size: 13)
|
||||
}
|
||||
.padding(.horizontal, 32)
|
||||
|
||||
@@ -16,9 +16,11 @@ struct PricingSubscribeFooter: View {
|
||||
if !pricing.footerSecondaryText.isEmpty {
|
||||
SQText(pricing.footerSecondaryText, size: 13, textColor: pricing.footerTextColor)
|
||||
}
|
||||
SQButton("Continuer", color: pricing.footerButtonColor, textColor: .white) {
|
||||
SQButton("Continuer") {
|
||||
|
||||
}
|
||||
.colorScheme(pricing.footerButtonColor)
|
||||
|
||||
if !pricing.footerTertiaryText.isEmpty {
|
||||
SQText(pricing.footerTertiaryText, size: 12, textColor: .sqOrange(70))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// VisioPermissionsModal.swift
|
||||
// AlloVoisinsSwiftUI
|
||||
//
|
||||
// Created by Victor on 19/05/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct VisioPermissionsModal: View {
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 16) {
|
||||
SQText("Appels vocaux et vidéo", size: 18, font: .bold)
|
||||
Spacer()
|
||||
Image("new")
|
||||
SQText("NOUVEAU", size: 9, font: .bold, textColor: .white)
|
||||
.padding(.horizontal, 4)
|
||||
.padding(.vertical, 2)
|
||||
.background {
|
||||
RoundedRectangle(cornerRadius: 4)
|
||||
.fill(Color.sqSemanticRed)
|
||||
}
|
||||
Button {
|
||||
|
||||
} label: {
|
||||
SQIcon(.xmark, size: .m, color: .sqNeutral(100))
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
|
||||
Divider()
|
||||
.overlay {
|
||||
Color.sqNeutral(20)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Image("visio_permissions")
|
||||
SQText("Nouveau ! Échangez en appel vocal ou vidéo avec les autres utilisateurs, depuis la messagerie, sans avoir à communiquer votre numéro de téléphone !")
|
||||
SQButton("Autoriser l'accès au micro") {
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
EmptyView()
|
||||
.sheet(isPresented: .constant(true)) {
|
||||
VisioPermissionsModal()
|
||||
}
|
||||
.presentationDetents([.height(300)])
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Name=video-slash, Size=32.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<svg width="40" height="32" viewBox="0 0 40 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2376_165)">
|
||||
<path d="M2.42535 0.318888C1.77535 -0.193612 0.831601 -0.0748623 0.319101 0.575138C-0.193399 1.22514 -0.0746487 2.16889 0.575351 2.68139L37.5754 31.6814C38.2254 32.1939 39.1691 32.0751 39.6816 31.4251C40.1941 30.7751 40.0754 29.8314 39.4254 29.3189L34.0254 25.0876L34.8879 25.6626C35.5004 26.0689 36.2878 26.1126 36.9441 25.7626C37.6003 25.4126 38.0004 24.7376 38.0004 24.0001V8.00014C38.0004 7.26264 37.5941 6.58764 36.9441 6.23764C36.2941 5.88764 35.5066 5.92514 34.8879 6.33764L28.8879 10.3376L28.0004 10.9314V12.0001V20.0001V20.3626L26.0004 18.7939V8.00014C26.0004 5.79389 24.2066 4.00014 22.0004 4.00014H7.1191L2.42535 0.318888ZM25.4379 26.0439L2.0191 7.59389C2.0066 7.72514 2.00035 7.86264 2.00035 8.00014V24.0001C2.00035 26.2064 3.7941 28.0001 6.00035 28.0001H22.0004C23.4629 28.0001 24.7441 27.2126 25.4379 26.0439Z" fill="#172433"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2376_165">
|
||||
<rect width="40" height="32" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 726 KiB |
12
AlloVoisinsSwiftUI/Resources/Assets.xcassets/Images/ladame.imageset/Contents.json
vendored
Normal file
12
AlloVoisinsSwiftUI/Resources/Assets.xcassets/Images/ladame.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "07df87a0478c154775ee348712373fc2.jpeg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 668 KiB |
12
AlloVoisinsSwiftUI/Resources/Assets.xcassets/Images/lemonsieur.imageset/Contents.json
vendored
Normal file
12
AlloVoisinsSwiftUI/Resources/Assets.xcassets/Images/lemonsieur.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "3eef4f9f219f60bfe2aaa7c4076f35b9.jpeg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
6
AlloVoisinsSwiftUI/Resources/Assets.xcassets/Images/neighbor_avatar.imageset/Avatar.svg
vendored
Normal file
6
AlloVoisinsSwiftUI/Resources/Assets.xcassets/Images/neighbor_avatar.imageset/Avatar.svg
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity="0.5">
|
||||
<rect x="0.5" y="0.5" width="33" height="33" rx="16.5" stroke="#374A61" stroke-dasharray="4 4"/>
|
||||
<path d="M17 16.9997C19.7404 16.9997 21.9583 14.7817 21.9583 12.0413C21.9583 9.30098 19.7404 7.08301 17 7.08301C14.2596 7.08301 12.0417 9.30098 12.0417 12.0413C12.0417 14.7817 14.2596 16.9997 17 16.9997ZM20.4 18.4163H20.0326C19.1117 18.859 18.0891 19.1247 17 19.1247C15.9109 19.1247 14.8927 18.859 13.9674 18.4163H13.6C10.7844 18.4163 8.5 20.7007 8.5 23.5163V24.7913C8.5 25.9645 9.45182 26.9163 10.625 26.9163H23.375C24.5482 26.9163 25.5 25.9645 25.5 24.7913V23.5163C25.5 20.7007 23.2156 18.4163 20.4 18.4163Z" fill="#778BA3"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 751 B |
12
AlloVoisinsSwiftUI/Resources/Assets.xcassets/Images/neighbor_avatar.imageset/Contents.json
vendored
Normal file
12
AlloVoisinsSwiftUI/Resources/Assets.xcassets/Images/neighbor_avatar.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Avatar.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
12
AlloVoisinsSwiftUI/Resources/Assets.xcassets/Images/visio_permissions.imageset/Contents.json
vendored
Normal file
12
AlloVoisinsSwiftUI/Resources/Assets.xcassets/Images/visio_permissions.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "illustration.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
20
AlloVoisinsSwiftUI/Resources/Assets.xcassets/Images/visio_permissions.imageset/illustration.svg
vendored
Normal file
20
AlloVoisinsSwiftUI/Resources/Assets.xcassets/Images/visio_permissions.imageset/illustration.svg
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg width="99" height="79" viewBox="0 0 99 79" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M43.4785 3.52671C25.5808 4.62837 16.9441 15.2318 11.7413 21.2909C6.6804 27.1847 0.988698 50.2094 0.121628 56.8193C-1.79931 71.4632 19.4877 75.0884 31.8589 75.2721C35.7321 76.0524 51.6494 76.8764 82.1529 68.5244C112.329 60.262 94.8611 36.5764 84.234 25.5599C77.9906 19.0876 61.3763 2.42506 43.4785 3.52671Z" fill="#F2F8FF"/>
|
||||
<path d="M45.2568 26.792L41.5287 16.7954C41.3066 16.1997 40.6435 15.8969 40.0478 16.1191C39.4521 16.3412 39.1493 17.0043 39.3715 17.6L43.0996 27.5965C43.3217 28.1923 43.9847 28.4951 44.5805 28.2729C45.1762 28.0507 45.479 27.3877 45.2568 26.792Z" fill="#249EE6"/>
|
||||
<path d="M39.386 29.0835L34.4791 24.5891C34.0102 24.1597 33.282 24.1916 32.8526 24.6605L32.8511 24.6621C32.4216 25.1309 32.4536 25.8591 32.9224 26.2886L37.8294 30.783C38.2982 31.2124 39.0264 31.1805 39.4559 30.7116L39.4574 30.71C39.8868 30.2412 39.8549 29.513 39.386 29.0835Z" fill="#249EE6"/>
|
||||
<path d="M51.1969 26.0451L52.179 19.4638C52.2728 18.835 51.8391 18.2491 51.2103 18.1553L51.2081 18.155C50.5793 18.0611 49.9934 18.4948 49.8996 19.1236L48.9175 25.7049C48.8237 26.3337 49.2573 26.9196 49.8862 27.0134L49.8884 27.0138C50.5172 27.1076 51.103 26.6739 51.1969 26.0451Z" fill="#249EE6"/>
|
||||
<g clip-path="url(#clip0_2476_6083)">
|
||||
<path d="M34.5082 42.1113L37.7249 58.8042C38.021 60.3407 37.0154 61.8264 35.4789 62.1225L18.786 65.3392C17.2494 65.6353 15.7638 64.6298 15.4677 63.0932L12.251 46.4003C11.9549 44.8638 12.9604 43.3781 14.497 43.082L31.1899 39.8653C32.7259 39.5693 34.2121 40.5747 34.5082 42.1113ZM45.8099 40.8652L48.6793 55.7555C48.9641 57.2335 47.4397 58.4224 46.0466 57.8131L38.8503 54.6619L37.0923 45.5388L42.6009 39.935C43.6723 38.8503 45.5262 39.393 45.8099 40.8652Z" fill="#0982C9"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip1_2476_6083)">
|
||||
<path d="M83.2322 52.231L81.8061 57.8447C81.6056 58.6383 80.8923 59.1809 80.0711 59.1627C65.9489 58.8495 54.7164 47.1094 55.0285 32.9866C55.0467 32.166 55.6202 31.4764 56.4219 31.3121L62.0931 30.1353C62.919 29.963 63.7492 30.4081 64.0735 31.1939L66.5433 37.3248C66.8319 38.0469 66.6081 38.8759 65.9955 39.3541L62.9283 41.7073C64.7447 45.624 67.8255 48.844 71.6592 50.8328L74.1932 47.8758C74.6928 47.2831 75.5372 47.0929 76.2457 47.4186L82.2616 50.1562C82.9836 50.5566 83.4406 51.418 83.2322 52.231Z" fill="#0982C9"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2476_6083">
|
||||
<rect width="34" height="30.2222" fill="white" transform="translate(11 39.9082) rotate(-10.9073)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_2476_6083">
|
||||
<rect width="28.6641" height="28.6668" fill="white" transform="translate(55.0959 29.9365) rotate(1.26602)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
Reference in New Issue
Block a user