Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[INSPECT-xxx][REFACTOR] Lucy-338 station information db change #342

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
ARCHIVE_NAME: ${{ 'invasivesbc-mussels.iOS.xcarchive' }}
EXPORT_DIR: ${{ 'export' }}
IPA_NAME: ${{ 'invasivesbc-mussels.iOS.ipa' }}
APP_BUILD_VERSION: "2.7.3"
APP_BUILD_VERSION: "2.7.4"

steps:
- uses: maxim-lobanov/setup-xcode@v1
Expand Down
8 changes: 6 additions & 2 deletions ipad.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@
A7AE5275237CC3660044DBB7 /* unapproved-cross.json in Resources */ = {isa = PBXBuildFile; fileRef = A7AE5272237CC3660044DBB7 /* unapproved-cross.json */; };
A7AE5276237CC3660044DBB7 /* check-mark-success.json in Resources */ = {isa = PBXBuildFile; fileRef = A7AE5273237CC3660044DBB7 /* check-mark-success.json */; };
A7AE5277237CC3660044DBB7 /* sync-circle.json in Resources */ = {isa = PBXBuildFile; fileRef = A7AE5274237CC3660044DBB7 /* sync-circle.json */; };
E42434792D1499A800C7EF20 /* ScrollAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42434782D14999600C7EF20 /* ScrollAlert.swift */; };
F5EB30A127B5C77400F22712 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 29CEC91F23676424003B21B9 /* Main.storyboard */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -467,6 +468,7 @@
A7AE5272237CC3660044DBB7 /* unapproved-cross.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "unapproved-cross.json"; sourceTree = "<group>"; };
A7AE5273237CC3660044DBB7 /* check-mark-success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "check-mark-success.json"; sourceTree = "<group>"; };
A7AE5274237CC3660044DBB7 /* sync-circle.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "sync-circle.json"; sourceTree = "<group>"; };
E42434782D14999600C7EF20 /* ScrollAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollAlert.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -724,6 +726,7 @@
299BF01A23FC9CE9001732CD /* PDFViewer */,
294EF3102370884300EEB3B6 /* Banner */,
29BAC50523676EA400A620F4 /* Alert.swift */,
E42434782D14999600C7EF20 /* ScrollAlert.swift */,
29BAC50F2367984000A620F4 /* GradiantView.swift */,
);
path = UI;
Expand Down Expand Up @@ -1774,6 +1777,7 @@
2907E4B72368D24900946B3F /* InputModal.swift in Sources */,
29547B4A23F0BF4F000173F1 /* SelectedWaterBodyCollectionViewCell.swift in Sources */,
2991969023846AE000634F81 /* ShiftViewController.swift in Sources */,
E42434792D1499A800C7EF20 /* ScrollAlert.swift in Sources */,
29DB01092370EFD60046E605 /* TextAreaInputCollectionViewCell.swift in Sources */,
5C82D3EA2374BCFA00B065BA /* FoundationExtension.swift in Sources */,
29CEC91E23676424003B21B9 /* ViewController.swift in Sources */,
Expand Down Expand Up @@ -2059,7 +2063,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.8.4;
MARKETING_VERSION = 2.8.6;
PRODUCT_BUNDLE_IDENTIFIER = ca.bc.gov.InvasivesBC;
PRODUCT_NAME = Inspect;
PROVISIONING_PROFILE_SPECIFIER = "InvasivesBC Muscles - 2023/24";
Expand Down Expand Up @@ -2089,7 +2093,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.8.4;
MARKETING_VERSION = 2.8.6;
PRODUCT_BUNDLE_IDENTIFIER = ca.bc.gov.InvasivesBC;
PRODUCT_NAME = Inspect;
PROVISIONING_PROFILE_SPECIFIER = "InvasivesBC Muscles - 2023/24";
Expand Down
2 changes: 1 addition & 1 deletion ipad/Models/Shift/ShiftModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ class ShiftModel: Object, BaseRealmObject {
"location": "NA",
"motorizedBlowBys": motorizedBlowBys,
"nonMotorizedBlowBys": nonMotorizedBlowBys,
"stationComments": stationComments.count > 1 ? stationComments : "",
"stationInformation": stationComments.count > 1 ? stationComments : "",
"shiftStartComment": shiftStartComments.count > 1 ? shiftStartComments : "",
"shiftEndComment": shiftEndComments.count > 1 ? shiftEndComments : "",
"boatsInspected": boatsInspected,
Expand Down
18 changes: 18 additions & 0 deletions ipad/Utilities/Core/Extensions/UIViewControllerExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,21 @@ extension UIViewController {
return alert
}
}

extension UIViewController {
func topMostViewController() -> UIViewController {
if let presented = self.presentedViewController {
return presented.topMostViewController()
}

if let navigation = self as? UINavigationController {
return navigation.visibleViewController?.topMostViewController() ?? navigation
}

if let tab = self as? UITabBarController {
return tab.selectedViewController?.topMostViewController() ?? tab
}

return self
}
}
53 changes: 15 additions & 38 deletions ipad/Utilities/Core/UI/Alert.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,57 +17,34 @@ class Alert {
and return call back when user makes selection
*/
static func show(title: String, message: String, yes: @escaping()-> Void, no: @escaping()-> Void) {
//self.showDefaultAlert(title: title, message: message, yes: yes, no: no)
self.showCustomAlert(title: title, message: message, yes: yes, no: no);
self.showCustomAlert(title: title, message: message, yes: yes, no: no)
}

/**
Show an alert message with an okay button
*/
static func show(title: String, message: String) {
//self.showDefaultAlert(title: title, message: message)
self.showCustomAlert(title: title, message: message);
ModalAlert.show(title: title, message: message)
}

// MARK: Default iOS alerts
private static func showDefaultAlert(title: String, message: String, yes: @escaping()-> Void, no: @escaping()-> Void) {
DispatchQueue.main.async(execute: {
let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindow.Level.alert + 1

let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Yes", style: UIAlertAction.Style.default, handler: { action in
return yes();
}))
alert.addAction(UIAlertAction(title: "No", style: UIAlertAction.Style.cancel, handler: { action in
return no();
}))

alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alert, animated: true, completion: nil)
})
/**
Show a validation alert message for inspections
*/
static func showValidation(title: String, message: String) {
if let topVC = UIApplication.shared.windows.first?.rootViewController?.topMostViewController() {
let alertVC = ScrollableAlertViewController()
alertVC.modalPresentationStyle = .overFullScreen
alertVC.modalTransitionStyle = .crossDissolve
alertVC.configure(title: title, message: message)
topVC.present(alertVC, animated: true)
}
}

private static func showDefaultAlert(title: String, message: String) {
DispatchQueue.main.async(execute: {
let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindow.Level.alert + 1

let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let defaultAction2 = UIAlertAction(title: "OK", style: .default, handler: { action in
})
alert.addAction(defaultAction2)

alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alert, animated: true, completion: nil)
})
}


// MARK: Custom alerts using Modal pod
private static func showCustomAlert(title: String, message: String, yes: @escaping()-> Void, no: @escaping()-> Void) {
ModalAlert.show(title: title, message: message, yes: yes, no: no);
ModalAlert.show(title: title, message: message, yes: yes, no: no)
}

private static func showCustomAlert(title: String, message: String) {
Expand Down
95 changes: 95 additions & 0 deletions ipad/Utilities/Core/UI/ScrollAlert.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// ScrollAlert.swift
// ipad
//
// Created by Paul Garewal on 2024-12-19.
// Copyright © 2024 Amir Shayegh. All rights reserved.
//

import UIKit

class ScrollableAlertViewController: UIViewController {

private let titleLabel = UILabel()
private let textView = UITextView()
private let okButton = UIButton(type: .system)

override func viewDidLoad() {
super.viewDidLoad()
setupView()
}

private func setupView() {
// Configure background
view.backgroundColor = UIColor.black.withAlphaComponent(0.5)

// Container for the alert
let alertContainer = UIView()
alertContainer.backgroundColor = .white
alertContainer.layer.cornerRadius = 12
alertContainer.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(alertContainer)

// Title label
titleLabel.font = .boldSystemFont(ofSize: 17)
titleLabel.textColor = .black
titleLabel.numberOfLines = 1
titleLabel.textAlignment = .center
titleLabel.translatesAutoresizingMaskIntoConstraints = false
alertContainer.addSubview(titleLabel)

// Scrollable text view
textView.isEditable = false
textView.isScrollEnabled = true
textView.font = .systemFont(ofSize: 14)
textView.textColor = .black
textView.backgroundColor = .clear
textView.showsVerticalScrollIndicator = true
textView.translatesAutoresizingMaskIntoConstraints = false
alertContainer.addSubview(textView)

// OK button
okButton.setTitle("OK", for: .normal)
okButton.titleLabel?.font = .systemFont(ofSize: 17, weight: .semibold)
okButton.setTitleColor(.systemBlue, for: .normal)
okButton.translatesAutoresizingMaskIntoConstraints = false
okButton.contentEdgeInsets = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16)
okButton.backgroundColor = .clear
alertContainer.addSubview(okButton)
okButton.addTarget(self, action: #selector(dismissAlert), for: .touchUpInside)

NSLayoutConstraint.activate([
// Alert container
alertContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor),
alertContainer.centerYAnchor.constraint(equalTo: view.centerYAnchor),
alertContainer.widthAnchor.constraint(equalToConstant: 270),

// Title label
titleLabel.topAnchor.constraint(equalTo: alertContainer.topAnchor, constant: 16),
titleLabel.leadingAnchor.constraint(equalTo: alertContainer.leadingAnchor, constant: 16),
titleLabel.trailingAnchor.constraint(equalTo: alertContainer.trailingAnchor, constant: -16),

// Text view
textView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8),
textView.leadingAnchor.constraint(equalTo: alertContainer.leadingAnchor, constant: 16),
textView.trailingAnchor.constraint(equalTo: alertContainer.trailingAnchor, constant: -16),
textView.heightAnchor.constraint(equalToConstant: 200),

// OK button
okButton.topAnchor.constraint(equalTo: textView.bottomAnchor, constant: 8),
okButton.bottomAnchor.constraint(equalTo: alertContainer.bottomAnchor, constant: -8),
okButton.leadingAnchor.constraint(equalTo: alertContainer.leadingAnchor),
okButton.trailingAnchor.constraint(equalTo: alertContainer.trailingAnchor),
okButton.heightAnchor.constraint(equalToConstant: 44)
])
}

@objc private func dismissAlert() {
dismiss(animated: true)
}

func configure(title: String, message: String) {
titleLabel.text = title
textView.text = message
}
}
91 changes: 41 additions & 50 deletions ipad/ViewControllers/Shift/ShiftViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -290,90 +290,81 @@ class ShiftViewController: BaseViewController {
}

func validationMessage() -> String {
var message: String = ""
guard let model = self.model else { return message }
var counter = 1
var messages: [String] = []
guard let model = self.model else { return "" }

// Group validation messages by category

// Shift Time Validations
if model.startTime.isEmpty {
message = "\(message)\n\(counter)- Missing Shift Start time."
counter += 1
messages.append("⏰ Shift Start time is required")
}

if model.endTime.isEmpty {
message = "\(message)\n\(counter)- Missing Shift End time."
counter += 1
messages.append("⏰ Shift End time is required")
}

// Inspection Count Validations
if model.inspections.count > 0 && model.boatsInspected == false {
message = "\(message)\n\(counter)- You indicated that no boats were inspected, but inspections exist."
counter += 1
messages.append("⚠️ Inspection count mismatch: You indicated no boats were inspected, but inspections exist")
}

if model.inspections.count < 1 && model.boatsInspected == true {
message = "\(message)\n\(counter)- You indicated that boats were inspected but inspections are missing."
counter += 1
messages.append("⚠️ Inspection count mismatch: You indicated boats were inspected but no inspections are recorded")
}

// Station Validations
if model.station.isEmpty {
message = "\(message)\n\(counter)- Please choose a station."
counter += 1
messages.append("📍 Station selection is required")
}

if model.stationComments.isEmpty && ShiftModel.stationRequired(model.station) {
message = "\(message)\n\(counter)- Please add station information."
counter += 1
messages.append("📍 Station information is required")
}

for inspection in model.inspections {
// Inspection Detail Validations
for (index, inspection) in model.inspections.enumerated() {
if inspection.inspectionTime.isEmpty {
message = "\(message)\n\(counter)- Missing Time of Inspection."
counter += 1
messages.append("🕒 Inspection #\(index + 1): Time of inspection is required")
}

if inspection.unknownPreviousWaterBody == true ||
inspection.commercialManufacturerAsPreviousWaterBody == true ||
inspection.previousDryStorage == true {
if inspection.previousMajorCities.isEmpty {
message = "\(message)\n\(counter)- Please add Closest Major City for Previous Waterbody."
counter += 1
}
// Previous Waterbody Validations
if (inspection.unknownPreviousWaterBody ||
inspection.commercialManufacturerAsPreviousWaterBody ||
inspection.previousDryStorage) {
messages.append("🌊 Inspection #\(index + 1): Previous waterbody requires closest major city")
}

if inspection.unknownDestinationWaterBody == true ||
inspection.commercialManufacturerAsDestinationWaterBody == true ||
inspection.destinationDryStorage == true {
if inspection.destinationMajorCities.isEmpty {
message = "\(message)\n\(counter)- Please add Closest Major City for Destination Waterbody."
counter += 1
}
// Destination Waterbody Validations
if (inspection.unknownDestinationWaterBody ||
inspection.commercialManufacturerAsDestinationWaterBody ||
inspection.destinationDryStorage) && inspection.destinationMajorCities.isEmpty {
messages.append("🎯 Inspection #\(index + 1): Destination waterbody requires closest major city")
}

if !inspection.highRiskAssessments.isEmpty {
for highRisk in inspection.highRiskAssessments {
if highRisk.sealIssued == true && highRisk.sealNumber <= 0 {
message = "\(message)\n\(counter)- Please input the Seal #."
counter += 1
}

if highRisk.decontaminationOrderIssued == true && highRisk.decontaminationOrderNumber <= 0 {
message = "\(message)\n\(counter)- Please input the Decontamination order number."
counter += 1
}
// High Risk Assessment Validations
for (riskIndex, highRisk) in inspection.highRiskAssessments.enumerated() {
if highRisk.sealIssued && highRisk.sealNumber <= 0 {
messages.append("🏷️ Inspection #\(index + 1) Risk #\(riskIndex + 1): Seal number is required")
}

if highRisk.decontaminationOrderIssued && highRisk.decontaminationOrderNumber <= 0 {
messages.append("📄 Inspection #\(index + 1) Risk #\(riskIndex + 1): Decontamination order number is required")
}
}
}

// Check for invalid inspections
// Form Validation Status
let invalidInspections = model.inspections.filter { !$0.formDidValidate }
if !invalidInspections.isEmpty {
message = "\(message)\n\(counter)- One or more inspections contain validation errors. Please review each inspection."
counter += 1
messages.append("❌ One or more inspections contain validation errors")
}

if !message.isEmpty {
model.set(status: .Errors)
}
if !messages.isEmpty {
model.set(status: .Errors)
}

return message
return messages.joined(separator: "\n\n")
}

func createTestModel() {
Expand Down
Loading
Loading