update summon custom message screen and options

multistore
Razmig Sarkissian 2 years ago
parent 52ba7dac15
commit 116a1135d2
  1. 6
      PadelClub/Data/Tournament.swift
  2. 6
      PadelClub/Data/User.swift
  3. 18
      PadelClub/Extensions/Array+Extensions.swift
  4. 25
      PadelClub/Utils/ContactManager.swift
  5. 267
      PadelClub/Views/Calling/CallMessageCustomizationView.swift
  6. 2
      PadelClub/Views/Calling/CallView.swift

@ -943,9 +943,13 @@ class Tournament : ModelObject, Storable {
//return qualifiedTeams().count == qualifiedFromGroupStage() + groupStageAdditionalQualified //return qualifiedTeams().count == qualifiedFromGroupStage() + groupStageAdditionalQualified
} }
func paymentMethodMessage() -> String? {
DataStore.shared.user.summonsAvailablePaymentMethods ?? ContactType.defaultAvailablePaymentMethods
}
var entryFeeMessage: String { var entryFeeMessage: String {
if let entryFee { if let entryFee {
return "Inscription: " + entryFee.formatted(.currency(code: "EUR")) + "." return ["Inscription: " + entryFee.formatted(.currency(code: "EUR")) + " par joueur.", paymentMethodMessage()].compactMap { $0 }.joined(separator: "\n")
} else { } else {
return "Inscription: gratuite." return "Inscription: gratuite."
} }

@ -35,6 +35,7 @@ class User: ModelObject, UserBase, Storable {
var summonsMessageBody : String? = nil var summonsMessageBody : String? = nil
var summonsMessageSignature: String? = nil var summonsMessageSignature: String? = nil
var summonsAvailablePaymentMethods: String? = nil
var summonsDisplayFormat: Bool = false var summonsDisplayFormat: Bool = false
var summonsDisplayEntryFee: Bool = false var summonsDisplayEntryFee: Bool = false
var summonsUseFullCustomMessage: Bool = false var summonsUseFullCustomMessage: Bool = false
@ -68,6 +69,10 @@ class User: ModelObject, UserBase, Storable {
return try? federalContext.fetch(fetchRequest).first return try? federalContext.fetch(fetchRequest).first
} }
func defaultSignature() -> String {
return "Sportivement,\n\(firstName) \(lastName), votre JAP."
}
func hasClubs() -> Bool { func hasClubs() -> Bool {
self.clubs.isEmpty == false self.clubs.isEmpty == false
} }
@ -106,6 +111,7 @@ class User: ModelObject, UserBase, Storable {
case _country = "country" case _country = "country"
case _summonsMessageBody = "summonsMessageBody" case _summonsMessageBody = "summonsMessageBody"
case _summonsMessageSignature = "summonsMessageSignature" case _summonsMessageSignature = "summonsMessageSignature"
case _summonsAvailablePaymentMethods = "summonsAvailablePaymentMethods"
case _summonsDisplayFormat = "summonsDisplayFormat" case _summonsDisplayFormat = "summonsDisplayFormat"
case _summonsDisplayEntryFee = "summonsDisplayEntryFee" case _summonsDisplayEntryFee = "summonsDisplayEntryFee"
case _summonsUseFullCustomMessage = "summonsUseFullCustomMessage" case _summonsUseFullCustomMessage = "summonsUseFullCustomMessage"

@ -31,3 +31,21 @@ extension Array where Element: Equatable {
} }
} }
} }
extension Array where Element: CustomStringConvertible {
func customJoined(separator: String, lastSeparator: String) -> String {
switch count {
case 0:
return ""
case 1:
return "\(self[0])"
case 2:
return "\(self[0]) \(lastSeparator) \(self[1])"
default:
let firstPart = dropLast().map { "\($0)" }.joined(separator: ", ")
let lastPart = "\(lastSeparator) \(last!)"
return "\(firstPart) \(lastPart)"
}
}
}

@ -30,11 +30,11 @@ enum ContactType: Identifiable {
} }
extension ContactType { extension ContactType {
static let defaultCustomMessage = "Il est conseillé de vous présenter 10 minutes avant de jouer.\nMerci de confirmer en répondant à ce message et de prévenir votre partenaire." static let defaultCustomMessage = "Il est conseillé de vous présenter 10 minutes avant de jouer.\nMerci de me confirmer votre présence avec votre nom et de prévenir votre partenaire."
static let defaultSignature = "" static let defaultAvailablePaymentMethods = "Règlement possible par chèque ou espèce."
static func callingGroupStageCustomMessage(tournament: Tournament?, startDate: Date?, roundLabel: String) -> String { static func callingCustomMessage(source: String? = nil, tournament: Tournament?, startDate: Date?, roundLabel: String) -> String {
let tournamentCustomMessage = DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage let tournamentCustomMessage = source ?? DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage
let clubName = tournament?.clubName ?? "" let clubName = tournament?.clubName ?? ""
var text = tournamentCustomMessage var text = tournamentCustomMessage
@ -42,45 +42,42 @@ extension ContactType {
if let tournament { if let tournament {
text = text.replacingOccurrences(of: "#titre", with: tournament.tournamentTitle()) text = text.replacingOccurrences(of: "#titre", with: tournament.tournamentTitle())
text = text.replacingOccurrences(of: "#prix", with: tournament.entryFeeMessage)
} }
text = text.replacingOccurrences(of: "#club", with: clubName) text = text.replacingOccurrences(of: "#club", with: clubName)
text = text.replacingOccurrences(of: "#round", with: roundLabel) text = text.replacingOccurrences(of: "#manche", with: roundLabel.lowercased())
text = text.replacingOccurrences(of: "#jour", with: "\(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide)))") text = text.replacingOccurrences(of: "#jour", with: "\(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide)))")
text = text.replacingOccurrences(of: "#horaire", with: "\(date.formatted(Date.FormatStyle().hour().minute()))") text = text.replacingOccurrences(of: "#horaire", with: "\(date.formatted(Date.FormatStyle().hour().minute()))")
let signature = DataStore.shared.user.summonsMessageSignature ?? defaultSignature let signature = DataStore.shared.user.summonsMessageSignature ?? DataStore.shared.user.defaultSignature()
text = text.replacingOccurrences(of: "#signature", with: signature) text = text.replacingOccurrences(of: "#signature", with: signature)
return text return text
} }
static func callingGroupStageMessage(tournament: Tournament?, startDate: Date?, roundLabel: String, matchFormat: MatchFormat?) -> String { static func callingMessage(tournament: Tournament?, startDate: Date?, roundLabel: String, matchFormat: MatchFormat?) -> String {
let useFullCustomMessage = DataStore.shared.user.summonsUseFullCustomMessage let useFullCustomMessage = DataStore.shared.user.summonsUseFullCustomMessage
if useFullCustomMessage { if useFullCustomMessage {
return callingGroupStageCustomMessage(tournament: tournament, startDate: startDate, roundLabel: roundLabel) return callingCustomMessage(tournament: tournament, startDate: startDate, roundLabel: roundLabel)
} }
let date = startDate ?? tournament?.startDate ?? Date() let date = startDate ?? tournament?.startDate ?? Date()
let clubName = tournament?.clubName ?? "" let clubName = tournament?.clubName ?? ""
let message = DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage let message = DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage
let signature = DataStore.shared.user.summonsMessageSignature ?? defaultSignature let signature = DataStore.shared.user.summonsMessageSignature ?? DataStore.shared.user.defaultSignature()
let localizedCalled = "convoqué" + (tournament?.tournamentCategory == .women ? "e" : "") + "s" let localizedCalled = "convoqué" + (tournament?.tournamentCategory == .women ? "e" : "") + "s"
var formatMessage: String? {
(DataStore.shared.user.summonsDisplayFormat) ? matchFormat?.computedLongLabel.appending(".") : nil
}
var entryFeeMessage: String? { var entryFeeMessage: String? {
(DataStore.shared.user.summonsDisplayEntryFee) ? tournament?.entryFeeMessage : nil (DataStore.shared.user.summonsDisplayEntryFee) ? tournament?.entryFeeMessage : nil
} }
var computedMessage: String { var computedMessage: String {
[formatMessage, entryFeeMessage, message].compacted().map { $0.trimmed }.joined(separator: "\n") [entryFeeMessage, message].compacted().map { $0.trimmed }.joined(separator: "\n")
} }
if let tournament { if let tournament {

@ -11,33 +11,37 @@ import LeStorage
struct CallMessageCustomizationView: View { struct CallMessageCustomizationView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
var tournament: Tournament var tournament: Tournament
enum Field {
case signature
case body
case paymentMethods
case clubName
}
@FocusState private var textEditor: Bool @FocusState var focusedField: CallMessageCustomizationView.Field?
@State private var customClubName: String = "" @State private var customClubName: String = ""
@State private var customCallMessageBody: String = "" @State private var customCallMessageBody: String = ""
@State private var customCallMessageSignature: String = "" @State private var customCallMessageSignature: String = ""
@State private var summonsAvailablePaymentMethods: String = ""
init(tournament: Tournament) { init(tournament: Tournament) {
self.tournament = tournament self.tournament = tournament
_customCallMessageBody = State(wrappedValue: DataStore.shared.user.summonsMessageBody ?? "") _customCallMessageBody = State(wrappedValue: DataStore.shared.user.summonsMessageBody ?? (DataStore.shared.user.summonsUseFullCustomMessage ? "" : ContactType.defaultCustomMessage))
_customCallMessageSignature = State(wrappedValue: DataStore.shared.user.summonsMessageSignature ?? "") _customCallMessageSignature = State(wrappedValue: DataStore.shared.user.summonsMessageSignature ?? DataStore.shared.user.defaultSignature())
_customClubName = State(wrappedValue: tournament.clubName ?? "") _customClubName = State(wrappedValue: tournament.clubName ?? "Lieu du tournoi")
_summonsAvailablePaymentMethods = State(wrappedValue: DataStore.shared.user.summonsAvailablePaymentMethods ?? ContactType.defaultAvailablePaymentMethods)
} }
var clubName: String { var clubName: String {
customClubName customClubName
} }
var formatMessage: String? {
dataStore.user.summonsDisplayFormat ? tournament.matchFormat.computedLongLabel + "." : nil
}
var entryFeeMessage: String? { var entryFeeMessage: String? {
dataStore.user.summonsDisplayEntryFee ? tournament.entryFeeMessage : nil dataStore.user.summonsDisplayEntryFee ? tournament.entryFeeMessage : nil
} }
var computedMessage: String { var computedMessage: String {
[formatMessage, entryFeeMessage, customCallMessageBody].compacted().map { $0.trimmed }.joined(separator: "\n") [entryFeeMessage, customCallMessageBody].compacted().map { $0.trimmed }.joined(separator: "\n")
} }
var finalMessage: String? { var finalMessage: String? {
@ -48,16 +52,12 @@ struct CallMessageCustomizationView: View {
var body: some View { var body: some View {
@Bindable var user = dataStore.user @Bindable var user = dataStore.user
List { List {
Section { _renderingView()
ZStack { _optionsView()
Text(customCallMessageBody).hidden() _editorView()
.padding(.vertical, 20)
TextEditor(text: $customCallMessageBody) if user.summonsUseFullCustomMessage {
.autocorrectionDisabled() _paymentMethodsView()
.focused($textEditor)
}
} header: {
Text("Personnalisation du message de convocation")
} }
Section { Section {
@ -65,100 +65,54 @@ struct CallMessageCustomizationView: View {
Text(customCallMessageSignature).hidden() Text(customCallMessageSignature).hidden()
TextEditor(text: $customCallMessageSignature) TextEditor(text: $customCallMessageSignature)
.autocorrectionDisabled() .autocorrectionDisabled()
.focused($textEditor) .focused($focusedField, equals: .signature)
} }
} header: { } header: {
Text("Signature du message") Text("Signature du message")
} } footer: {
HStack {
_clubNameView() Spacer()
FooterButtonView("éditer") {
Section { focusedField = .signature
if user.summonsUseFullCustomMessage {
Text(self.computedFullCustomMessage())
.contextMenu {
Button("Coller dans le presse-papier") {
UIPasteboard.general.string = self.computedFullCustomMessage()
}
}
}
else if let finalMessage {
Text(finalMessage)
.contextMenu {
Button("Coller dans le presse-papier") {
UIPasteboard.general.string = finalMessage
}
}
}
} header: {
Text("Exemple")
}
Section {
LabeledContent {
Toggle(isOn: $user.summonsUseFullCustomMessage) {
} }
} label: {
Text("contrôle complet du message")
} }
} header: {
Text("Personnalisation complète")
} footer: {
Text("Utilisez ces balises dans votre texte : #titre, #jour, #horaire, #club, #signature")
} }
_clubNameView()
} }
.headerProminence(.increased)
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Message de convocation") .navigationTitle("Message de convocation")
.toolbar { .toolbar {
ToolbarItem(placement: .topBarTrailing) {
Menu {
Picker(selection: $user.summonsDisplayFormat) {
Text("Afficher le format").tag(true)
Text("Masquer le format").tag(false)
} label: {
}
Picker(selection: $user.summonsDisplayEntryFee) {
Text("Afficher le prix d'inscription").tag(true)
Text("Masquer le prix d'inscription").tag(false)
} label: {
}
} label: {
LabelOptions()
}
}
ToolbarItemGroup(placement: .keyboard) { ToolbarItemGroup(placement: .keyboard) {
if textEditor { if focusedField != .clubName {
Spacer() Spacer()
Button { Button {
textEditor = false focusedField = nil
user.summonsMessageBody = customCallMessageBody
user.summonsMessageSignature = customCallMessageSignature
user.summonsAvailablePaymentMethods = summonsAvailablePaymentMethods
_save()
} label: { } label: {
Label("Fermer", systemImage: "xmark") Text("Valider")
} }
.buttonStyle(.bordered)
} }
} }
} }
.onChange(of: user.summonsUseFullCustomMessage) { .onChange(of: user.summonsUseFullCustomMessage) {
user.summonsMessageBody = nil
if user.summonsUseFullCustomMessage == false { if user.summonsUseFullCustomMessage == false {
user.summonsMessageBody = ContactType.defaultCustomMessage customCallMessageBody = ContactType.defaultCustomMessage
} else {
customCallMessageBody = ""
} }
_save() _save()
} }
.onChange(of: customCallMessageBody) {
user.summonsMessageBody = customCallMessageBody
_save()
}
.onChange(of: customCallMessageSignature) {
user.summonsMessageSignature = customCallMessageSignature
_save()
}
.onChange(of: user.summonsDisplayEntryFee) { .onChange(of: user.summonsDisplayEntryFee) {
_save() _save()
} }
.onChange(of: user.summonsDisplayFormat) {
_save()
}
} }
private func _save() { private func _save() {
@ -169,6 +123,68 @@ struct CallMessageCustomizationView: View {
} }
} }
@ViewBuilder
private func _editorView() -> some View {
@Bindable var user = dataStore.user
Section {
ZStack {
Text(customCallMessageBody).hidden()
.padding(.vertical, 20)
TextEditor(text: $customCallMessageBody)
.autocorrectionDisabled()
.focused($focusedField, equals: .body)
}
} header: {
if user.summonsUseFullCustomMessage {
Text("Message de convocation")
} else {
Text("Information supplémentaire")
}
} footer: {
if user.summonsUseFullCustomMessage {
LazyVStack(alignment: .leading) {
Text("Utilisez ces balises")
FooterButtonView("#titre") {
customCallMessageBody.append("#titre")
focusedField = .body
}
FooterButtonView("#manche") {
customCallMessageBody.append("#manche")
focusedField = .body
}
FooterButtonView("#prix") {
customCallMessageBody.append("#prix")
focusedField = .body
}
FooterButtonView("#jour") {
customCallMessageBody.append("#jour")
focusedField = .body
}
FooterButtonView("#horaire") {
customCallMessageBody.append("#horaire")
focusedField = .body
}
FooterButtonView("#club") {
customCallMessageBody.append("#club")
focusedField = .body
}
FooterButtonView("#signature") {
customCallMessageBody.append("#signature")
focusedField = .body
}
}
} else {
HStack {
Spacer()
FooterButtonView("éditer") {
focusedField = .body
}
}
}
}
}
@ViewBuilder @ViewBuilder
private func _clubNameView() -> some View { private func _clubNameView() -> some View {
if let eventClub = tournament.eventObject()?.clubObject() { if let eventClub = tournament.eventObject()?.clubObject() {
@ -176,6 +192,7 @@ struct CallMessageCustomizationView: View {
Section { Section {
TextField("Nom du club", text: $customClubName) TextField("Nom du club", text: $customClubName)
.autocorrectionDisabled() .autocorrectionDisabled()
.focused($focusedField, equals: .clubName)
.onSubmit { .onSubmit {
eventClub.name = customClubName eventClub.name = customClubName
do { do {
@ -190,17 +207,79 @@ struct CallMessageCustomizationView: View {
} footer: { } footer: {
if hasBeenCreated == false { if hasBeenCreated == false {
Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed) Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed)
} else {
HStack {
Spacer()
FooterButtonView("éditer") {
focusedField = .clubName
}
}
}
}
}
}
@ViewBuilder
private func _renderingView() -> some View {
@Bindable var user = dataStore.user
Section {
if user.summonsUseFullCustomMessage {
Text(self.computedFullCustomMessage())
}
else if let finalMessage {
Text(finalMessage)
}
} header: {
Text("Rendu généré automatiquement")
}
}
@ViewBuilder
private func _optionsView() -> some View {
@Bindable var user = dataStore.user
Section {
Toggle(isOn: $user.summonsUseFullCustomMessage) {
Text("Tout personnaliser")
Text("Écrivez votre propre message.")
}
if user.summonsUseFullCustomMessage == false {
Toggle(isOn: $user.summonsDisplayEntryFee) {
Text("Afficher le prix d'inscription")
Text("et les informations sur le paiement")
}
}
} header: {
Text("Options")
}
if user.summonsDisplayEntryFee && user.summonsUseFullCustomMessage == false {
_paymentMethodsView()
}
}
@ViewBuilder
private func _paymentMethodsView() -> some View {
Section {
ZStack {
Text(summonsAvailablePaymentMethods).hidden()
.padding(.vertical, 20)
TextEditor(text: $summonsAvailablePaymentMethods)
.autocorrectionDisabled()
.focused($focusedField, equals: .paymentMethods)
}
} header: {
Text("Information sur le paiement")
} footer: {
HStack {
Spacer()
FooterButtonView("éditer") {
focusedField = .paymentMethods
} }
} }
} }
} }
func computedFullCustomMessage() -> String { func computedFullCustomMessage() -> String {
var text = customCallMessageBody.replacingOccurrences(of: "#titre", with: tournament.tournamentTitle()) return ContactType.callingCustomMessage(source: customCallMessageBody, tournament: tournament, startDate: Date(), roundLabel: RoundRule.roundName(fromRoundIndex: 2))
text = text.replacingOccurrences(of: "#club", with: clubName)
text = text.replacingOccurrences(of: "#jour", with: "\(Date().formatted(Date.FormatStyle().weekday(.wide).day().month(.wide)))")
text = text.replacingOccurrences(of: "#horaire", with: "\(Date().formatted(Date.FormatStyle().hour().minute()))")
text = text.replacingOccurrences(of: "#signature", with: customCallMessageSignature)
return text
} }
} }

@ -79,7 +79,7 @@ struct CallView: View {
} }
var finalMessage: String { var finalMessage: String {
ContactType.callingGroupStageMessage(tournament: tournament, startDate: callDate, roundLabel: roundLabel, matchFormat: matchFormat) ContactType.callingMessage(tournament: tournament, startDate: callDate, roundLabel: roundLabel, matchFormat: matchFormat)
} }
// TODO: Guard // TODO: Guard

Loading…
Cancel
Save