IOS app test
CaseAide Source code AbbrWordDict.json { "Dictionary":[ {"Abbreviation":"CCL", "Word":"community care licensing"}, {"Abbreviation":"CASA", "Word":"Court Appointed Special Advocate"}, {"Abbreviation":"CPS", "Word":"Child Protective Services"}, {"Abbreviation":"CFS", "Word":"Children and Family Services"}, {"Abbreviation":"DPSS", "Word":"Department of Public Social Services"}, {"Abbreviation":"DBH", "Word":"Department of Mental Health"}, {"Abbreviation":"SW", "Word":"Social Worker"}, {"Abbreviation":"ILP", "Word":"Independent Living Program"}, {"Abbreviation":"ILPSW", "Word":"Independent Living Program Social Worker"}, {"Abbreviation":"MSW", "Word":"Masters in Social Work"}, {"Abbreviation":"AWOL", "Word":"Absent Without Leave"}, {"Abbreviation":"FFA", "Word":"Foster Family Agency"}, {"Abbreviation":"FF", "Word":"Face to face"}, {"Abbreviation":"F2F", "Word":"Family to Family"}, {"Abbreviation":"FP", "Word":"foster parent"}, {"Abbreviation":"FH", "Word":"foster home"}, {"Abbreviation":"FR", "Word":"Family Reunification"}, {"Abbreviation":"FM", "Word":"Family Maintenance"}, {"Abbreviation":"GH", "Word":"group home"}, {"Abbreviation":"TC", "Word":"telephone call"}, {"Abbreviation":"TDM", "Word":"Team Decision Meeting"}, {"Abbreviation":"NDM", "Word":"non-minor dependent"}, {"Abbreviation":"MD", "Word":"mandated reporter"}, {"Abbreviation":"NREFM", "Word":"non-relative extended family member"}, {"Abbreviation":"LG", "Word":"legal guardian"}, {"Abbreviation":"SDM", "Word":"structured decision making"}, {"Abbreviation":"JH", "Word":"Juvenile Hall"}, {"Abbreviation":"PO", "Word":"Probation Officer"}, {"Abbreviation":"AB 12", "Word":"Assembly Bill 12"}, {"Abbreviation":"WRAP", "Word":"wraparound services"}, {"Abbreviation":"ER", "Word":"emergency response"}, {"Abbreviation":"CDU", "Word":"Court Dependency Unit"}, {"Abbreviation":"WIC", "Word":"Welfare and Institutions Code"}, {"Abbreviation":"Postox", "Word":"Positive Toxicity"}, {"Abbreviation":"Tm", "Word":"telephone message"}, {"Abbreviation":"ICWA", "Word":"Indian Child Welfare Act"}, {"Abbreviation":"NMD", "Word":"non-minor dependant"}, {"Abbreviation":"OHI", "Word":"Out of Home Investigation"}, {"Abbreviation":"CP", "Word":"court report"}, {"Abbreviation":"CR", "Word":"Court Report"}, {"Abbreviation":"IR", "Word":"immediate response"}, {"Abbreviation":"IYRT", "Word":"Interagency youth resiliency team"}, {"Abbreviation":"ES", "Word":"emergency shelter"}, {"Abbreviation":"PFA", "Word":"Peer and Family Assistant"}, {"Abbreviation":"TCO", "Word":"translational conference"}, {"Abbreviation":"AA", "Word":"alcoholics Anonymous"}, {"Abbreviation":"ADD", "Word":"Attention Deficit Disorder"}, {"Abbreviation":"ADHD", "Word":"Attention Deficit Hyperactive Disorder"}, {"Abbreviation":"ODD", "Word":"Oppositional Defiance Disorder"}, {"Abbreviation":"BPB", "Word":"Boarderline personality Disorder"}, {"Abbreviation":"RA", "Word":"Reactice Attachment"}, {"Abbreviation":"APB", "Word":"All Points Bulletin"}, {"Abbreviation":"BIA", "Word":"Bureau of Indian Affairs"}, {"Abbreviation":"CBO", "Word":"community based organization"}, {"Abbreviation":"CMT", "Word":"Case Management Team"}, {"Abbreviation":"CM", "Word":"case management"}, {"Abbreviation":"clt", "Word":"client"}, {"Abbreviation":"DA", "Word":"district attorney"}, {"Abbreviation":"DCFS", "Word":"Department of Children and Family Services"}, {"Abbreviation":"HIV", "Word":"Human Immunodeficiency"}, {"Abbreviation":"ICU", "Word":"intensive care unit"}, {"Abbreviation":"IL", "Word":"Independent Living"}, {"Abbreviation":"NA", "Word":"Narcotics Anonymous"}, {"Abbreviation":"PHN", "Word":"Public Health Nurse"}, {"Abbreviation":"CD", "Word":"Conduct Disorder"}, {"Abbreviation":"TBS", "Word":"Therapeutic Behavioral Services"}, {"Abbreviation":"MHS", "Word":"Mental Health Services"}, {"Abbreviation":"SOP", "Word":"Safety Organized Practices"}, {"Abbreviation":"Plt", "Word":"Placement"}, {"Abbreviation":"CW", "Word":"Child welfare"}, {"Abbreviation":"CSWE", "Word":"Council of Social Work Education"}, {"Abbreviation":"RJC", "Word":"Riverside Juvenile Court"}, {"Abbreviation":"SBJC", "Word":"San Bernardino Juvenile Court"}, {"Abbreviation":"IYRT", "Word":"Interagency Youth Resiliencey Team"}, {"Abbreviation":"EFC", "Word":"Extended Foster Care"}, {"Abbreviation":"DMH", "Word":"Department of Mental Health"}, {"Abbreviation":"IHSS", "Word":"In Home Support Services"}, {"Abbreviation":"APS", "Word":"Adult Protective Services"}, {"Abbreviation":"DOB", "Word":"date of birth"}, {"Abbreviation":"DUI", "Word":"driving under the influence"}, {"Abbreviation":"EAP", "Word":"Employment Assistance Program"}, {"Abbreviation":"EW", "Word":"Eligibility Worker"}, {"Abbreviation":"SSA", "Word":"Social Service Assistant"}, {"Abbreviation":"SSP", "Word":"Social Service Practitioner"}, {"Abbreviation":"SSS", "Word":"Social Service Supervisor"}, {"Abbreviation":"TILP", "Word":"Transitional Independent Living Plan"}, {"Abbreviation":"SILP", "Word":"Supervised Independent Living Placement"}, {"Abbreviation":"CFVAN", "Word":"Client was free from any visible signs of abuse or neglect"}, {"Abbreviation":"CVAN", "Word":"Client had visible sings of abuse and neglect"}, {"Abbreviation":"IRC", "Word":"Inland Regional Center"}, {"Abbreviation":"Sup", "Word":"supervisor"}, {"Abbreviation":"DPO", "Word":"Deputy Probation Officer"}, {"Abbreviation":"SO", "Word":"Sherriff Officer"}, {"Abbreviation":"CAPTS", "Word":"Child Abuse Prevention and Treatment Team"}, {"Abbreviation":"ACT", "Word":"Assessment and Consultation Team"}, {"Abbreviation":"FVSC", "Word":"Family Visitation and Support Center"}, {"Abbreviation":"CFT’s", "Word":"Child and Family Team Meeting"}, {"Abbreviation":"CLETS", "Word":"California Law Enforcement Telecommunication System"}, {"Abbreviation":"CPR", "Word":"Concurrent Planning Review"}, {"Abbreviation":"CNS", "Word":"Children’s Strengths and Needs"}, {"Abbreviation":"MDT", "Word":"Multi-Disciplinary Team"}, {"Abbreviation":"PAS", "Word":"Purchase Authorization for Service"}, {"Abbreviation":"PMU", "Word":"Placement Managenent Unit"}, {"Abbreviation":"PPLA", "Word":"Planned Permanent Living Arrangement"}, {"Abbreviation":"RAU", "Word":"Relative assessment Unit"}, {"Abbreviation":"RCAT", "Word":"Riverside Child Assessment Team"}, {"Abbreviation":"MHST", "Word":"Mental health Screening Tool"}, {"Abbreviation":"PGM", "Word":"parental grandmother"}, {"Abbreviation":"PGF", "Word":"paternal grandfather"}, {"Abbreviation":"MGM", "Word":"maternal grandmother"}, {"Abbreviation":"MGF", "Word":"maternal grandfather"}, {"Abbreviation":"GM", "Word":"Grandmother"}, {"Abbreviation":"GF", "Word":"Grandfather"}, {"Abbreviation":"5150", "Word":"72 hour hold for assessment (5150)"}, {"Abbreviation":"5250", "Word":"14 days maximum hold for treatment (5250)"}, {"Abbreviation":"COE", "Word":"County Office of Education"}, {"Abbreviation":"CYC", "Word":"California Youth Connection"}, {"Abbreviation":"DSS", "Word":"California Department of Social Services"}, {"Abbreviation":"FYS", "Word":"Foster Youth Services"}, {"Abbreviation":"CAC", "Word":"Children’s Assessment Center"}, {"Abbreviation":"DOJ", "Word":"Department of Justice"}, {"Abbreviation":"OHC", "Word":"Out of Home Care"}, {"Abbreviation":"JD", "Word":"Jurisdictional/Dispositional"}, {"Abbreviation":"SD", "Word":"Staff Development"}, {"Abbreviation":"PC", "Word":"protective capacity"}, {"Abbreviation":"MSLC", "Word":"minimum sufficient level of care"}, {"Abbreviation":"RAM", "Word":"risk assessment meeting"}, {"Abbreviation":"BS", "Word":"basic needs"}, {"Abbreviation":"FTT", "Word":"failure to thrive"}, {"Abbreviation":"SA", "Word":"Safety Assessment"}, {"Abbreviation":"RA", "Word":"Risk Assessment"}, {"Abbreviation":"PIP", "Word":"Program Improvement Plan"}, {"Abbreviation":"SIP", "Word":"System Improvement Plan"}, {"Abbreviation":"SIDS", "Word":"sudden infant death syndrome"}, {"Abbreviation":"SIS", "Word":"shaken infant syndrome"}, {"Abbreviation":"IRB", "Word":"Institutional review board"}, {"Abbreviation":"NASW", "Word":"National Association of Social Workers"}, {"Abbreviation":"NAMI", "Word":"National Alliance of Mentally Ill"}, {"Abbreviation":"MSW", "Word":"Master of Social Work"}, {"Abbreviation":"BASW", "Word":"Bachelors of Social Work"}, {"Abbreviation":"LCSW", "Word":"Licensed Clinical Social Worker"}, {"Abbreviation":"CalSWEC", "Word":"California Social Work Education Center"}, {"Abbreviation":"CSWE", "Word":"Counsel on Social Work Education"}, {"Abbreviation":"FI", "Word":"field instructor"}, {"Abbreviation":"FL", "Word":"field liaison"}, {"Abbreviation":"Pro", "Word":"professor"}, {"Abbreviation":"MFT", "Word":"Marriage and Family Therapist"}, {"Abbreviation":"SOSW", "Word":"School of Social Work"}, {"Abbreviation":"SWSA", "Word":"Social Work Student Association"}, {"Abbreviation":"PDEP", "Word":"Pathways Distance Education Program"}, {"Abbreviation":"GIM", "Word":"Generalist Intervention Model"}, {"Abbreviation":"EPAS", "Word":"Education Policy and Accreditation Standards"}, {"Abbreviation":"LPA", "Word":"Learning Plan Agreement"}, {"Abbreviation":"PR", "Word":"process recordings"}, {"Abbreviation":"OH", "Word":"Office Hours"}, {"Abbreviation":"BPSS", "Word":"BPSS = bio-psycho-social-spiritual"}, {"Abbreviation":"COETH", "Word":"code of ethics"}, {"Abbreviation":"CV", "Word":"core values"}, {"Abbreviation":"NREFM", "Word":"non-related extended family member"} ] } AndroidTextField.swift // // AndroidTextField.swift // CaseAide // // Created by Alan Perez on 7/21/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class AndroidTextField: UITextField { var borderHeight: CGFloat = 4.0 var borderColor: CGColor = UIColor(white: 1, alpha: 0.30).CGColor required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.borderStyle = UITextBorderStyle.None } override init(frame: CGRect) { super.init(frame: frame) self.borderStyle = UITextBorderStyle.None } // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. override func drawRect(rect: CGRect) { let context = UIGraphicsGetCurrentContext() CGContextSetStrokeColorWithColor(context, borderColor) CGContextSetLineWidth(context, borderHeight) CGContextBeginPath(context) let leftBottom = CGPointMake(rect.origin.x, rect.origin.y + rect.height) let rightBottom = CGPointMake(leftBottom.x + rect.width, leftBottom.y) CGContextMoveToPoint(context, leftBottom.x, leftBottom.y) CGContextAddLineToPoint(context, rightBottom.x, rightBottom.y) CGContextClosePath(context) CGContextStrokePath(context) } } AssetErrorView.swift // // AssetErrorView.swift // CaseAide // // Created by Alan Perez on 8/12/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit protocol AssetErrorViewDelegate { func assetErrorViewTapped(view: AssetErrorView) } class AssetErrorView: UIView { private var tapGesture: UITapGestureRecognizer! @IBOutlet weak private var errorLabel: UILabel! var delegate: AssetErrorViewDelegate? var message: String = "Some error message" override func awakeFromNib() { self.accessibilityViewIsModal = true self.tapGesture = UITapGestureRecognizer(target: self, action: Selector("handleTap:")) self.addGestureRecognizer(self.tapGesture) } func show() { self.errorLabel.text = message self.hidden = false } func hide() { self.hidden = true } func handleTap(sender: UITapGestureRecognizer) { if sender.state == .Ended { println("tap ended") self.delegate?.assetErrorViewTapped(self) } } } AppDelegate.swift // // AppDelegate.swift // CaseAide // // Created by Alan Perez on 7/14/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Override point for customization after application launch. UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.LightContent // R:74, G:144, B:226 UINavigationBar.appearance().barTintColor = UIColor.caseAideBlue() UINavigationBar.appearance().tintColor = UIColor.whiteColor() UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()] UIPageControl.appearance().pageIndicatorTintColor = UIColor.lightGrayColor() UIPageControl.appearance().currentPageIndicatorTintColor = UIColor.blackColor() UIPageControl.appearance().backgroundColor = UIColor.whiteColor() UITabBar.appearance().tintColor = UIColor.caseAideBlue() return true } func applicationWillResignActive(application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } func applicationDidEnterBackground(application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } func applicationWillEnterForeground(application: UIApplication) { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } func applicationDidBecomeActive(application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } func applicationWillTerminate(application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } } AssetHeaderView.swift // // AssetHeaderView.swift // CaseAide // // Created by Alan Perez on 8/12/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class AssetHeaderView: UICollectionReusableView { @IBOutlet weak var titleLabel: UILabel! } AssetLoadingView.swift // // AssetLoadingView.swift // CaseAide // // Created by Alan Perez on 8/12/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class AssetLoadingView: UIView { @IBOutlet weak var activityIndicator: UIActivityIndicatorView! override func awakeFromNib() { super.awakeFromNib() self.accessibilityViewIsModal = true } func show() { self.activityIndicator.startAnimating() self.hidden = false } func hide() { self.activityIndicator.stopAnimating() self.hidden = true } } AssetView.swift // // ReportView.swift // CaseAide // // Created by Alan Perez on 8/19/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class AssetView: UIView { @IBOutlet weak var dayLabel: UILabel! @IBOutlet weak var dateLabel: UILabel! /* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. override func drawRect(rect: CGRect) { // Drawing code } */ } BasicCell.swift // // BasicReportCell.swift // CaseAide // // Created by Alan Perez on 8/19/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class BasicCell: UITableViewCell { @IBOutlet weak var fieldLabel: UILabel! @IBOutlet weak var valueLabel: UILabel! override func layoutSubviews() { super.layoutSubviews() self.contentView.layoutIfNeeded() self.valueLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.valueLabel.frame) } } BlobCell.swift // // TemplateReportCell.swift // CaseAide // // Created by Alan Perez on 8/19/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class BlobCell: UITableViewCell { @IBOutlet weak var fieldLabel: UILabel! @IBOutlet weak var valueLabel: UILabel! override func layoutSubviews() { super.layoutSubviews() self.contentView.layoutIfNeeded() self.valueLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.valueLabel.frame) } } CAAddClientInfo.swift // // CAAddClientInfo.swift // CaseAide // // Created by Andrew on 9/27/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import UIKit class CAAddClientInfo: UIViewController , UITextFieldDelegate { @IBOutlet weak var scrollView: UIScrollView! @IBOutlet weak var containerView: UIView! @IBOutlet weak var phoneNumberTextField: AndroidTextField! @IBOutlet weak var EmailTextField: AndroidTextField! @IBOutlet weak var addressTextField: AndroidTextField! @IBOutlet weak var submitButton: UIButton! //SEEDED CLIENT var client: CAClient? private var emailToAdd: CAEmail? private var phoneNumberToAdd: CAPhoneNumber? private var addressToAdd: CAAddress? private var keyboardShown: Bool = false private var keyboardRectWRTView = CGRectZero private var preKeyboardContentOffset = CGPointZero private let api: CAApi = CAApi.defaultApi override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) self.customizeView() title = "Add Client Contact Information" NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil) } override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil) NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil) } override func viewDidLoad() { super.viewDidLoad() self.phoneNumberTextField.delegate = self self.EmailTextField.delegate = self self.addressTextField.delegate = self // used to hide keyboard when clicking outside of it let tapGesture = UITapGestureRecognizer(target: self, action: Selector("dismissKeyboard")) self.containerView.addGestureRecognizer(tapGesture) } @IBAction func submit(sender: UIButton) { dismissKeyboard() addClientInformation() } func addClientInformation() { var phoneNumber = phoneNumberTextField.text let email = EmailTextField.text let address = addressTextField.text //no email or phone number input if count(phoneNumber) == 0 && count(email) == 0 && count(address) == 0 { // nothing to input UIAlertView(title: "Input error", message: "No Phone Number, Email, or Address input to be added.", delegate: nil, cancelButtonTitle: "Ok").show() } //email, phone Number, and address else if (count(phoneNumber) == 10 || count(phoneNumber) == 11) && count(email) > 0 && count(address) > 0{ createPhoneNumber() createEmail() createAddress() } //phone number, email else if (count(phoneNumber) == 10 || count(phoneNumber) == 11) && count(email) > 0 && count(address) == 0{ createPhoneNumber() createEmail() } //phone Number, address else if (count(phoneNumber) == 10 || count(phoneNumber) == 11) && count(email) == 0 && count(address) > 0{ createPhoneNumber() createAddress() } //email, address else if (count(phoneNumber) == 0) && count(email) > 0 && count(address) > 0{ createEmail() createAddress() } //only phone number input else if (count(phoneNumber) == 10 || count(phoneNumber) == 11) && count(email) == 0 && count(address) == 0 { //for only phone additions createPhoneNumber() } //only email input else if(count(phoneNumber) == 0 && count(address) == 0){ //For only email additions createEmail() } //only Address else if(count(phoneNumber) == 0 && count(email) == 0){ //For only email additions createAddress() } else if ( count(phoneNumber) > 10 || count(phoneNumber) < 10) { phoneNumberInputError() println("Is not a valid phonenumber") } phoneNumberTextField.text = "" EmailTextField.text = "" addressTextField.text = "" } func createEmail(){ var emailID = self.client?.emails.last?.id ?? 1 let email = EmailTextField.text self.emailToAdd = CAEmail(id: emailID + 1 , address: email) println(email) saveEmail() } func createAddress(){ var addressId = self.client?.addresses.last?.id ?? 1 let address = addressTextField.text self.addressToAdd = CAAddress(id: addressId + 1 , address: address) println(address) saveAddress() } func createPhoneNumber(){ var phoneNumber = phoneNumberTextField.text var phoneID = self.client?.phoneNumbers.last?.id ?? 1 self.phoneNumberToAdd = CAPhoneNumber(id: phoneID, phonenumber: phoneNumber) if count(phoneNumber) == 10{ if checkNumber(phoneNumber) { phoneNumber = convertToPhoneNumberStandard(phoneNumber) self.phoneNumberToAdd?.number = phoneNumber println(phoneNumber) savePhoneNumber() println("Is a valid integer") } else { phoneNumberInputError() println("Is not a valid integer") } } } func convertToPhoneNumberStandard(phoneNumber: String!) -> String{ var number = "(" number += indexing(phoneNumber, beginingIndex: 0, endingIndex: -7) number += ") " number += indexing(phoneNumber, beginingIndex: 3, endingIndex: -4) number += "-" number += indexing(phoneNumber, beginingIndex: 6, endingIndex: 0) return number } //A way to index strings without using integer notation func indexing(string: String, beginingIndex: Int, endingIndex: Int) ->String{ var stringToReturn: String var index = advance(string.startIndex, beginingIndex) string[index] var endIndex = advance(string.endIndex, endingIndex) stringToReturn = string[Range(start: index, end: endIndex)] return stringToReturn } func checkNumber(string: String) -> Bool { //return true if both the incoming string is a number var confirmedStringIsANumber = "" for char in string { let i = "\(char)" if let checkInteger = i.toInt() { confirmedStringIsANumber += i } } if confirmedStringIsANumber == string { return true } return false } // MARK: Keyboard logic func dismissKeyboard() { self.view.endEditing(true) } func textFieldShouldReturn(textField: UITextField) -> Bool { if textField === phoneNumberTextField { EmailTextField.becomeFirstResponder() } else if textField === EmailTextField { addressTextField.becomeFirstResponder() }else if textField === addressTextField{ addressTextField.resignFirstResponder() addClientInformation() } return true } func textFieldDidBeginEditing(textField: UITextField) { // update for subsequent textfield focus if keyboardShown { shiftUpIfNeeded(textField) } } func keyboardWillShow(notification: NSNotification) { let window = UIApplication.sharedApplication().keyWindow! let keyboardInfo = notification.userInfo! // keyboard frame with respect to screen let keyboardRectWRTScreen = keyboardInfo[UIKeyboardFrameEndUserInfoKey]!.CGRectValue() // set parameters and shift for first textfield focus self.keyboardRectWRTView = self.view.convertRect(keyboardRectWRTScreen, fromView: nil) self.keyboardShown = true self.preKeyboardContentOffset = self.scrollView.contentOffset if self.phoneNumberTextField.isFirstResponder() { self.shiftUpIfNeeded(self.phoneNumberTextField) } else if self.EmailTextField.isFirstResponder() { self.shiftUpIfNeeded(self.EmailTextField) } else if self.addressTextField.isFirstResponder() { self.shiftUpIfNeeded(self.addressTextField) } } func keyboardWillHide(notification: NSNotification) { keyboardShown = false shiftBack() } func shiftUpIfNeeded(textField: UITextField) { let textFieldRectWRTView = self.view.convertRect(textField.bounds, fromView: textField) // if user text field is hidden by keyboard if CGRectIntersectsRect(self.keyboardRectWRTView, textFieldRectWRTView) { let overlapAmount = CGRectGetMaxY(textFieldRectWRTView) - CGRectGetMinY(self.keyboardRectWRTView) + 10 let contentOffset = scrollView.contentOffset scrollView.setContentOffset(CGPointMake(contentOffset.x, contentOffset.y + overlapAmount), animated: true) } } func shiftBack() { scrollView.setContentOffset(self.preKeyboardContentOffset, animated: true) } func savePhoneNumber(){ if let validClient = self.client { self.api.createPhone(validClient.id, phone: self.phoneNumberToAdd!, completitionHandler: { (error) -> Void in if let updateError = error { Functions.alertError(updateError) }else{ self.view.makeToast(message: "Client Information Saved") } }) } } func saveEmail(){ if let validClient = self.client{ self.api.createEmail(validClient.id, email: emailToAdd!, completitionHandler: { (error) -> Void in if let updateError = error { Functions.alertError(updateError) }else{ self.view.makeToast(message: "Client Information Saved") } }) } } func saveAddress(){ if let validClient = self.client{ self.api.createAddress(validClient.id, address: addressToAdd!, completitionHandler: { (error) -> Void in if let updateError = error { Functions.alertError(updateError) }else{ self.view.makeToast(message: "Client Information Saved") } }) } } func customizeView(){ submitButton.backgroundColor = UIColor.caseAideBlue() var greyColor = UIColor(red: 195/255, green: 195/255, blue: 195/255, alpha: 1) phoneNumberTextField.addBottomBorderWithColor(greyColor, borderWidth: 1) phoneNumberTextField.borderColor = greyColor.CGColor EmailTextField.addBottomBorderWithColor(greyColor, borderWidth: 1) EmailTextField.borderColor = greyColor.CGColor addressTextField.addBottomBorderWithColor(greyColor, borderWidth: 1) addressTextField.borderColor = greyColor.CGColor } func phoneNumberInputError(){ Functions.alert("Invalid Input", message: "Please enter a valid 10 digit phonenumber.") } } CAApi.swift // // TestApi.swift // CaseAide // // Created by Alan Perez on 9/8/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import Foundation class CAApi { // shared instance of API static let defaultApi = CAApi() private var afHttpManager: Manager private var authKey: String? // can only be set by authenticating private var baseUrl: NSURL // error handling private let caseaideDomain = "org.caseaide.error" private let userNotLoggedInErrorCode = 1 private let operationUnsuccessfulErrorCode = 2 private let authenticationErrorCode = 3 init() { self.baseUrl = NSURL(string: "https://139.182.74.19/v2-2")! self.afHttpManager = Manager.sharedInstance // alamofire manager singleton // TODO: remove this when it is no longer needed for security reasons // ignore security warnings for server let serverTrustPolicies: [String: ServerTrustPolicy] = [ "139.182.74.19": .DisableEvaluation ] self.afHttpManager.session.serverTrustPolicyManager = ServerTrustPolicyManager(policies: serverTrustPolicies) } // MARK: Generic API methods func authenticate(user: String, pass: String, completionHandler: (NSError?) -> Void) { let loginUrl = self.baseUrl.URLByAppendingPathComponent("/login") self.afHttpManager.request(.POST, loginUrl, parameters: ["username": user, "password": pass.md5()], encoding: .JSON, headers: nil).responseJSON { _, _, JSON, error in let status = self.validateAuthenticationSuccess(JSON, error: error) if let key = status.0 { self.authKey = key // set authentication key } completionHandler(status.1) } } func create<T: Mappable>(path: String, asset: T, completionHandler: (NSError?) -> Void) { self.validateUserLogin({ key in let createPath = "/\(key)/\(path)" let createUrl = self.baseUrl.URLByAppendingPathComponent(createPath) let data = Mapper().toJSON(asset) self.afHttpManager.request(.POST, createUrl, parameters: data, encoding: .JSON, headers: nil).responseJSON { _, _, JSON, error in println(JSON) completionHandler(self.validateOperationSuccess(JSON, error: error)) } }, errorBlock: { error in completionHandler(error) }) } func read<T: Mappable>(path: String, completionHandler: (T?, NSError?) -> Void) { self.validateUserLogin({ key in let readPath = "/\(key)/\(path)" let readUrl = self.baseUrl.URLByAppendingPathComponent(readPath) self.afHttpManager.request(.GET, readUrl, parameters: nil, encoding: .JSON, headers: nil).responseObject { (obj: T?, error: NSError?) -> Void in completionHandler(obj, error) } }, errorBlock: { error in completionHandler(nil, error) }) } func readAll<T: Mappable>(path: String, completionHandler: ([T]?, NSError?) -> Void) { self.validateUserLogin({ key in let readAllPath = "/\(key)/\(path)" let readAllUrl = self.baseUrl.URLByAppendingPathComponent(readAllPath) self.afHttpManager.request(.GET, readAllUrl, parameters: nil, encoding: .JSON, headers: nil).responseArray { (objects: [T]?, error: NSError?) -> Void in println(objects) completionHandler(objects, error) } }, errorBlock: { error in completionHandler(nil, error) }) } func update<T: Mappable>(path: String, asset: T, completionHandler: (NSError?) -> Void) { self.validateUserLogin({ key in let updatePath = "/\(key)/\(path)" let updateUrl = self.baseUrl.URLByAppendingPathComponent(updatePath) let data = Mapper().toJSON(asset) self.afHttpManager.request(.PUT, updateUrl, parameters: data, encoding: .JSON, headers: nil).responseJSON { _, _, JSON, error in println(JSON) completionHandler(self.validateOperationSuccess(JSON, error: error)) } }, errorBlock: { error in completionHandler(error) }) } func delete(path: String, completionHandler: (NSError?) -> Void) { self.validateUserLogin({ key in let deletePath = "/\(key)/\(path)" let deleteUrl = self.baseUrl.URLByAppendingPathComponent(deletePath) self.afHttpManager.request(.DELETE, deleteUrl).responseJSON { _, _, JSON, error in completionHandler(self.validateOperationSuccess(JSON, error: error)) } }, errorBlock: { error in completionHandler(error) }) } // MARK: Contact API methods func createContact(clientId: Int, contact: CAContact, completionHandler: (NSError?) -> Void) { let path = "clients/\(clientId)/contacts" self.create(path, asset: contact, completionHandler: completionHandler) } func readContact(contactId: Int, clientId: Int, completionHandler: (CAContact?, NSError?) -> Void) { let path = "clients/\(clientId)/contacts/\(contactId)" self.read(path, completionHandler: completionHandler) } func readAllContacts(clientId: Int, completionHandler: ([CAContact]?, NSError?) -> Void) { let path = "clients/\(clientId)/contacts" self.readAll(path, completionHandler: completionHandler) } func updateContact(clientId: Int, contact: CAContact, completionHandler: (NSError?) -> Void) { let path = "clients/\(clientId)/contacts/\(contact.id)" self.update(path, asset: contact, completionHandler: completionHandler) } // MARK: Report API methods func createReport(clientId: Int, report: CAReport, completionHandler: (NSError?) -> Void) { let path = "clients/\(clientId)/reports" self.create(path, asset: report, completionHandler: completionHandler) } func readReport(reportId: Int, clientId: Int, completionHandler: (CAReport?, NSError?) -> Void) { let path = "clients/\(clientId)/reports/\(reportId)" self.read(path, completionHandler: completionHandler) } func readAllReports(clientId: Int, completionHandler: ([CAReport]?, NSError?) -> Void) { let path = "clients/\(clientId)/reports" self.readAll(path, completionHandler: completionHandler) } func updateReport<ReportType: CAReport where ReportType: Mappable>(clientId: Int, report: ReportType, completionHandler: (NSError?) -> Void) { let path = "clients/\(clientId)/reports/\(report.id)" self.update(path, asset: report, completionHandler: completionHandler) } // MARK: Hearing API methods func createHearing(clientId: Int, hearing: CAHearing, completitionHandler: (NSError?) -> Void) { let path = "clients/\(clientId)/hearings" self.create(path, asset: hearing, completionHandler: completitionHandler) } func readHearing(hearingId: Int, clientId: Int, completionHandler: (CAHearing?, NSError?) -> Void) { let path = "clients/\(clientId)/hearings/\(hearingId)" self.read(path, completionHandler: completionHandler) } func updateHearing(clientId: Int, hearing: CAHearing, completionHandler: (NSError?) -> Void) { let path = "clients/\(clientId)/hearings/\(hearing.id)" self.update(path, asset: hearing, completionHandler: completionHandler) } // MARK: Clients API methods func readClient(clientId: Int, completionHandler: (CAClient?, NSError?) -> Void) { let path = "clients/\(clientId)" self.read(path, completionHandler: completionHandler) } func readAllClients(completionHandler: ([CAClient]?, NSError?) -> Void) { let path = "clients/" self.readAll(path, completionHandler: completionHandler) } // MARK: Email API methods func createEmail(clientId: Int, email: CAEmail, completitionHandler: (NSError?) -> Void) { let path = "clients/\(clientId)/emails" self.create(path, asset: email, completionHandler: completitionHandler) } func updateEmail(clientId: Int, email: CAEmail, completionHandler: (NSError?) -> Void) { let path = "clients/\(clientId)/emails/\(email.id)" self.update(path, asset: email, completionHandler: completionHandler) } func deleteEmail(clientId: Int, email: CAEmail, completionHandler: (NSError?) -> Void) { let path = "clients/\(clientId)/emails/\(email.id)" self.delete(path, completionHandler: completionHandler) } // MARK: Phone number API methods func createPhone(clientId: Int, phone: CAPhoneNumber, completitionHandler: (NSError?) -> Void) { let path = "clients/\(clientId)/phones" self.create(path, asset: phone, completionHandler: completitionHandler) } func updatePhone(clientId: Int, phone: CAPhoneNumber, completionHandler: (NSError?) -> Void) { let path = "clients/\(clientId)/phones/\(phone.id)" self.update(path, asset: phone, completionHandler: completionHandler) } func deletePhone(clientId: Int, phone: CAPhoneNumber, completionHandler: (NSError?) -> Void) { let path = "clients/\(clientId)/phones/\(phone.id)" self.delete(path, completionHandler: completionHandler) } //// MARK: Address API methods func createAddress(clientId: Int, address: CAAddress, completitionHandler: (NSError?) -> Void) { let path = "clients/\(clientId)/addresses" self.create(path, asset: address, completionHandler: completitionHandler) } func updateAddress(clientId: Int, address: CAAddress, completionHandler: (NSError?) -> Void) { let path = "clients/\(clientId)/addresses/\(address.id)" self.update(path, asset: address, completionHandler: completionHandler) } func deleteAddress(clientId: Int, address: CAAddress, completionHandler: (NSError?) -> Void) { let path = "clients/\(clientId)/addresses/\(address.id)" self.delete(path, completionHandler: completionHandler) } // check if user is logged in, if not show error, if is run code block private func validateUserLogin(successBlock: (key: String) -> (), errorBlock: (error: NSError) -> ()) { if let authKey = self.authKey { successBlock(key: authKey) } else { // create error let userInfo = [NSLocalizedDescriptionKey: "Authentication error.", NSLocalizedFailureReasonErrorKey: "User has not been authenticated", NSLocalizedRecoverySuggestionErrorKey: "Authenticate user"] let logginError = NSError(domain: self.caseaideDomain, code: self.userNotLoggedInErrorCode, userInfo: userInfo) errorBlock(error: logginError) } } private func getJsonResponseValue(response: AnyObject?, key: String) -> String? { if let responseVal = (response as? [String: String])?[key] { return responseVal } return nil } private func validateAuthenticationSuccess(response: AnyObject?, error: NSError?) -> (String?, NSError?) { if let err = error { return (nil, err) } else { if let key = self.getJsonResponseValue(response, key: "key") { return (key, nil) } else { // if error occurred override error to be more specific let userInfo = [NSLocalizedDescriptionKey: "Authentication error.", NSLocalizedFailureReasonErrorKey: "Username or password is incorrect", NSLocalizedRecoverySuggestionErrorKey: "Type in correct username and password"] let authenticationError = NSError(domain: self.caseaideDomain, code: self.authenticationErrorCode, userInfo: userInfo) return (nil, authenticationError) } } } private func validateOperationSuccess(response: AnyObject?, error: NSError?) -> NSError? { if let err = error { return err } else { if self.getJsonResponseValue(response, key: "response") == "success" { return nil } else { // create error let userInfo = [NSLocalizedDescriptionKey: "Operation was unsuccessful.", NSLocalizedFailureReasonErrorKey: "Unable to perform operation", NSLocalizedRecoverySuggestionErrorKey: "Try again later"] let operationError = NSError(domain: self.caseaideDomain, code: self.operationUnsuccessfulErrorCode, userInfo: userInfo) return operationError } } } //---------------------------------------------------------- func readFromJson() -> [String : String] { var dictionary = [String : String]() let url = "AbbrWordDict" if let path = NSBundle.mainBundle().pathForResource(url, ofType: "json") { if let jsonData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil) { if let jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers, error: nil) as? NSDictionary { if let jsonArray = jsonResult["Dictionary"] as? NSArray { for item in jsonArray { if let abbreviation: AnyObject = item["Abbreviation"]! { if let word: AnyObject = item["Word"]!{ dictionary.updateValue(word as! String, forKey: abbreviation as! String) } } } } } } } return dictionary } } CABlobPageController.swift // // CABlobPageController.swift // CaseAide // // Created by Alan Perez on 8/22/15. // Copyright (c) 2015 CSUSB Mobile apps. All rights reserved. // import UIKit class CABlobPageController: CAPageController, UITextViewDelegate { // configuration by caller var text: String? @IBOutlet weak var fieldLabel: UILabel! @IBOutlet weak var clientNameLabel: UILabel! @IBOutlet weak var textView: UITextView! @IBOutlet weak var textViewTopConstraint: NSLayoutConstraint! @IBOutlet weak var textViewBottomConstraint: NSLayoutConstraint! private lazy var endEditingButton: UIBarButtonItem = self.createFinishEditingButton() // need to save original distance from top of textview to top relational view private var textViewTopOffset: CGFloat! // store buttons that were located in navbar before showing end editing button private var rightNavbarButtonsState: [AnyObject]? private var rightNavbarButtonState: UIBarButtonItem? override func viewDidLoad() { super.viewDidLoad() self.clientNameLabel.text = self.clientName self.fieldLabel.text = self.fieldType // fix short type cursor jumping bug self.textView.layoutManager.allowsNonContiguousLayout = false self.textView.text = self.text ?? "" self.textView.delegate = self self.textView.textContainerInset = UIEdgeInsetsMake(15, 10, 15, 10) // store textview original offset, in order to shift back to original position, when keyboard is hidden self.textViewTopOffset = self.textViewTopConstraint.constant } override func viewWillDisappear(animated: Bool) { self.dismissKeyboard() NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil) NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil) } override func viewWillAppear(animated: Bool) { NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil) if(textView.text == "") { self.showPlaceholder() } } func dismissKeyboard() { self.view.endEditing(true) } func keyboardWillShow(notification: NSNotification) { let window = UIApplication.sharedApplication().keyWindow! let keyboardInfo = notification.userInfo! // keyboard frame with respect to screen let keyboardRectWRTScreen = keyboardInfo[UIKeyboardFrameEndUserInfoKey]!.CGRectValue() let keyboardRectWRTView = self.view.convertRect(keyboardRectWRTScreen, fromView: nil) self.moveTextViewAwayFromKeyboard(keyboardRectWRTView.origin.y) } func keyboardWillHide(notification: NSNotification) { self.moveTextViewBack() } func textViewDidChange(textView: UITextView) { self.delegate?.didEditPage(self) } func getText() -> String { return self.textView.text } @IBAction func informationButton(sender: UIButton) { //description of the view let discriptionText = Functions.message("Note") //Custom box for the view Functions.discriptionBox(fieldLabel.text!, message: discriptionText) } // MARK: TextView delegate methods func textViewDidBeginEditing(textView: UITextView) { if textView.text == "| Start typing" { self.removePlaceholder() } } func textViewDidEndEditing(textView: UITextView) { if textView.text == "" { self.showPlaceholder() } } // MARK: textview placeholder methods func showPlaceholder() { self.textView.text = "| Start typing" self.textView.textColor = UIColor(red: 195/255, green: 195/255, blue: 195/255, alpha: 1) } func removePlaceholder() { self.textView.text = "" self.textView.textColor = UIColor.blackColor() } // MARK: textview shifting logic func createFinishEditingButton() -> UIBarButtonItem { return UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.Done, target: self, action: "dismissKeyboard") } func moveTextViewAwayFromKeyboard(keyboardMinY: CGFloat) { self.storeNavbarButtonState() self.navDelegate?.setRightNavbarItem(self.endEditingButton) // The textview is not at bottom of parent view, so we only want to move the textview by the offset necessary to clear the keyboard let bottomOffset = CGRectGetMaxY(self.textView.frame) - keyboardMinY self.textViewTopConstraint.constant = 0 self.textViewBottomConstraint.constant = bottomOffset } func moveTextViewBack() { self.restoreNavbarButtonState() self.textViewTopConstraint.constant = self.textViewTopOffset self.textViewBottomConstraint.constant = 0 } func storeNavbarButtonState() { self.rightNavbarButtonState = self.navDelegate?.getRightNavbarItem() self.rightNavbarButtonsState = self.navDelegate?.getRightNavbarItems() } func restoreNavbarButtonState() { self.navDelegate?.setRightNavbarItem(self.rightNavbarButtonState) self.navDelegate?.setRightNavbarItems(self.rightNavbarButtonsState) } // MARK: Short type func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool { let textViewText = textView.text let inputedText = text let inputedTextLength = count(text) let inputStartIndex = advance(textViewText.startIndex, range.location) let inputEndIndex = advance(inputStartIndex, range.length) let inputTextRange = Range<String.Index>(start: inputStartIndex, end: inputEndIndex) let isTypingCharacter = inputedTextLength == 1 // user is typing character (could be replacing exiting text or not) let isTypingMultipleWords = inputedText.numberOfWords() > 1 // user is typing many words at once (caused by paste or speech to text; could be replacing exiting text or not) // check if user typed in word seperating character if isTypingCharacter && inputedText.hasWordSeperator() { // meant to capture words as they are entered character by character // check last word before seperator for abbreviations let wordCheckRange = Range<String.Index>(start: textViewText.startIndex, end: inputStartIndex) let lastWordSubstring = textViewText.lastWordSubstringInRange(wordCheckRange) let lastWord = lastWordSubstring.word let lastWordRange = lastWordSubstring.range var mappedLastWord = CAShortType.mapToWords(lastWord) if mappedLastWord != lastWord { mappedLastWord = mappedLastWord + inputedText //add word seperator to mapped word // replace abbreviated words to their respective words textView.text.replaceRange(lastWordRange, with: mappedLastWord) let mappingWordOffset = count(mappedLastWord) - count(lastWord) let pointerLocation = range.location + mappingWordOffset // textLength is for new seperator entered textView.selectedRange = NSMakeRange(pointerLocation, 0) return false } } else if isTypingMultipleWords // meant to capture words as they are inputed in bunches such as copy and paste and speech to text { // user inserting new string or replacing range with string let mappedText = CAShortType.mapToWords(inputedText) if mappedText != inputedText { textView.text.replaceRange(inputTextRange, with: mappedText) let pointerLocation = range.location + count(mappedText) textView.selectedRange = NSMakeRange(pointerLocation, 0) return false } } return true } } CACheckboxPageController.swift // // CACheckboxPageController.swift // CaseAide // // Created by Alan Perez on 8/22/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class CACheckboxPageController: CAPageController, UITableViewDataSource, UITableViewDelegate { var options: [String]? var selectedIndicies: [Int]? // Internal parameters private var selectedOptions: [Int]! let optionCellReuseIdentifier = "optionCellReuseIdentifier" let optionCellHeight: CGFloat = 60.0 @IBOutlet weak var fieldLabel: UILabel! @IBOutlet weak var clientNameLabel: UILabel! @IBOutlet weak var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() // ensure controller has selections have valid state if let selOpt = self.selectedIndicies { self.selectedOptions = selOpt } else { self.selectedOptions = [Int]() } self.clientNameLabel.text = self.clientName self.fieldLabel.text = self.fieldType self.tableView.dataSource = self self.tableView.delegate = self } // MARK: Table view data source methods func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return options?.count ?? 0 } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier(self.optionCellReuseIdentifier, forIndexPath: indexPath) as! CheckboxCell cell.optionLabel.text = self.options?[indexPath.row] // if cell index is in selectedindexes if self.isSelectedIndex(indexPath.row).0 { cell.accessoryType = UITableViewCellAccessoryType.Checkmark } else { cell.accessoryType = UITableViewCellAccessoryType.None } return cell } func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { let cellIndex = indexPath.row let (isSelected, selIndex) = self.isSelectedIndex(cellIndex) if isSelected { self.selectedOptions.removeAtIndex(selIndex!) } else { self.selectedOptions.append(cellIndex) } tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: cellIndex, inSection: 0)], withRowAnimation: UITableViewRowAnimation.Automatic) self.delegate?.didEditPage(self) } func isSelectedIndex(index: Int) -> (Bool, Int?) { if let i = find(self.selectedOptions, index) { return (true, i) } return (false, nil) } func getSelectedIndicies() -> [Int] { return self.selectedOptions } @IBAction func informationButton(sender: UIButton) { //description of the view let discriptionText = Functions.message(fieldLabel.text!) //Custom box for the view Functions.discriptionBox(fieldLabel.text!, message: discriptionText) } } CAClient.swift // // CAClient.swift // CaseAide // // Created by Alan Perez on 8/5/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import Foundation class CAClient: Mappable { var id: Int var name: String var addresses: [CAAddress] var emails: [CAEmail] var phoneNumbers: [CAPhoneNumber] var hearing: CAHearing? var seen: Bool var daysUntilHearing: Int var reports: [CAReport] var contacts: [CAContact] init(id: Int, name: String) { self.id = id self.name = name self.seen = false self.addresses = [CAAddress]() self.phoneNumbers = [CAPhoneNumber]() self.emails = [CAEmail]() self.reports = [CAReport]() self.contacts = [CAContact]() self.daysUntilHearing = -1 } convenience init(id: Int) { self.init(id: id, name: "John Doe") } static func newInstance(map: Map) -> Mappable? { return CAClient(id: -1) } func mapping(map: Map) { self.id <- (map["id"], CAIntTransform()) self.name <- map["name"] self.addresses <- map["addresses"] self.emails <- map["emails"] self.phoneNumbers <- map["phones"] self.seen <- map["seen"] self.hearing <- map["hearing"] self.contacts <- map["contacts"] self.reports <- map["reports"] self.daysUntilHearing <- map["daysUntilHearing"] } } CAContact.swift // // CAContact.swift // CaseAide // // Created by Alan Perez on 8/5/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import Foundation class CAContact: Mappable, Equatable { var id: Int var startDate: NSDate var endDate: NSDate var staff: String var personsIncluded: [String] var inSupportOf: [String] var note: String private var _objective: Int private var _approach: Int private var _place: Int private var _outcome: Int static let objectiveOptions = ["Conduct Client Evaluation", "Consult with Attorney", "Consult with Collateral", "Consult Service Provider"] static let approachOptions = ["Email", "Fax", "In Person", "Telephone", "Written"] static let placeOptions = ["Court", "CWS Office", "Home", "In Placement", "School", "Other"] static let outcomeOptions = ["Attempted", "Completed", "Scheduled"] var objective: String { get { return CAContact.objectiveOptions[self._objective] } set { self._objective = find(CAContact.objectiveOptions, newValue) ?? 0 } } var approach: String { get { return CAContact.approachOptions[self._approach] } set { self._approach = find(CAContact.approachOptions, newValue) ?? 0 } } var place: String { get { return CAContact.placeOptions[self._place] } set { self._place = find(CAContact.placeOptions, newValue) ?? 0 } } var outcome: String { get { return CAContact.outcomeOptions[self._outcome] } set { self._outcome = find(CAContact.outcomeOptions, newValue) ?? 0 } } init (contact: CAContact) { self.id = contact.id self.startDate = contact.startDate self.endDate = contact.endDate self.staff = contact.staff self._objective = contact._objective self._approach = contact._approach self._place = contact._place self._outcome = contact._outcome self.personsIncluded = contact.personsIncluded self.inSupportOf = contact.inSupportOf self.note = contact.note } init (id: Int, startDate: NSDate, endDate: NSDate) { self.id = id self.startDate = startDate self.endDate = endDate self.staff = "" self._objective = 0 self._approach = 0 self._place = 0 self._outcome = 0 self.personsIncluded = [String]() self.inSupportOf = [String]() self.note = "" } convenience init(id: Int) { self.init (id: id, startDate: NSDate(), endDate: NSDate()) } convenience init() { self.init(id: -1, startDate: NSDate(), endDate: NSDate()) } static func newInstance(map: Map) -> Mappable? { return CAContact(id: -1) } func mapping(map: Map) { //Changed all ints to not have int transforms self.id <- (map["id"], CAIntTransform()) self.startDate <- (map["start"], CADateTransform()) self.endDate <- (map["end"], CADateTransform()) self.staff <- map["staff"] self._objective <- (map["objective"], CAIntTransform()) self._approach <- (map["approach"],CAIntTransform()) self._place <- (map["place"],CAIntTransform()) self._outcome <- (map["outcome"], CAIntTransform()) self.personsIncluded <- (map["persons"], CAArrayTransform()) self.inSupportOf <- (map["inSupportOf"], CAArrayTransform()) self.note <- map["note"] } } func ==(lhs: CAContact, rhs: CAContact) -> Bool { return lhs.id == rhs.id && lhs.startDate == rhs.startDate && lhs.endDate == rhs.endDate && lhs.staff == rhs.staff && lhs.personsIncluded == rhs.personsIncluded && lhs.inSupportOf == rhs.inSupportOf && lhs.note == rhs.note } CACourtInfoPageController.swift // // CACourtInfoPageController.swift // CaseAide // // Created by Alan Perez on 8/25/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class CACourtInfoPageController: CAPageController { var court: String? var room: String? private let defaultCourtIndex = 0 private let defaultRoomIndex = 0 static let courtOptions = ["court 1", "court 2"] static let roomOptions = ["room 1", "room 2"] @IBOutlet weak private var clientNameLabel: UILabel! @IBOutlet weak private var courtTextField: UITextField! @IBOutlet weak private var roomTextField: UITextField! var courtDownPicker: DownPicker! var roomDownPicker: DownPicker! override func viewDidLoad() { super.viewDidLoad() self.clientNameLabel.text = self.clientName // configure textField borders let borderThickness: CGFloat = 1.5 let borderColor = UIColor(white: 0, alpha: 0.15) self.courtTextField.layer.addBorder(UIRectEdge.Bottom, color: borderColor, thickness: borderThickness) self.roomTextField.layer.addBorder(UIRectEdge.Bottom, color: borderColor, thickness: borderThickness) // configure date and down pickers var initialCourt = self.court ?? CACourtInfoPageController.courtOptions[self.defaultCourtIndex] var initialRoom = self.room ?? CACourtInfoPageController.roomOptions[self.defaultRoomIndex] let initialCourtIndex = find(CACourtInfoPageController.courtOptions, initialCourt) ?? defaultCourtIndex let initialRoomIndex = find(CACourtInfoPageController.roomOptions, initialRoom) ?? defaultRoomIndex courtDownPicker = DownPicker(textField: self.courtTextField, withData: NSMutableArray(array: CACourtInfoPageController.courtOptions), withIntialOption: initialCourtIndex) roomDownPicker = DownPicker(textField: self.roomTextField, withData: NSMutableArray(array: CACourtInfoPageController.roomOptions), withIntialOption: initialRoomIndex) courtDownPicker.addTarget(self, action: Selector("pageChanged"), forControlEvents: UIControlEvents.ValueChanged) roomDownPicker.addTarget(self, action: Selector("pageChanged"), forControlEvents: UIControlEvents.ValueChanged) } func pageChanged() { self.delegate?.didEditPage(self) } func getCourt() -> String { return self.courtDownPicker.text } func getRoom() -> String { return self.roomDownPicker.text } @IBAction func informationButton(sender: UIButton) { //description of the view let discriptionText = Functions.message("Court") //Custom box for the view Functions.discriptionBox("Court Info", message: discriptionText) } } CAEmail.swift // // CAEmail.swift // CaseAide // // Created by Alan Perez on 9/10/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import Foundation class CAEmail: Mappable { var id: Int var address: String init(id: Int, address: String) { self.id = id self.address = address } convenience init(id: Int) { self.init(id: id , address: "N/A") } static func newInstance(map: Map) -> Mappable? { return CAEmail(id: -1) } func mapping(map: Map) { self.id <- (map["id"],CAIntTransform()) self.address <- map["email"] } } CAHearing.swift // // CAHearing.swift // CaseAide // // Created by Alan Perez on 8/5/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import Foundation enum CAHearingType: String { case SixMonthReview = "366.21 E, Six Month Review" case TwelveMonthReview = "366.21 F, Twelve Month Review" case EighteenMonthReview = "366.22, Eighteen Month Review" case SelectionAndImplementation = "366.26, Selection and Implementation" case PostPermPlanReview = "366.3, Post Perm Plan Review" case FamilyMaintenanceReview = "364, Family Maintenance Review" case DualStatus = "241.1, Dual Status" } class CAHearing: Mappable, Equatable { var id: Int var date: NSDate var type: CAHearingType // readonly deadlines computed by server private(set) var cprDueDate: NSDate? private(set) var supervisorDueDate: NSDate? private(set) var courtDueDate: NSDate? init(id: Int, date: NSDate, type: CAHearingType) { self.id = id self.date = date self.type = type } convenience init(id: Int) { self.init(id: id, date: NSDate(), type: CAHearingType.SixMonthReview) } init (hearing: CAHearing) { self.id = hearing.id self.date = hearing.date self.cprDueDate = hearing.cprDueDate self.supervisorDueDate = hearing.supervisorDueDate self.courtDueDate = hearing.courtDueDate self.type = hearing.type } static func newInstance(map: Map) -> Mappable? { return CAHearing(id: -1) } func mapping(map: Map) { self.id <- (map["id"],CAIntTransform()) self.date <- (map["date"], CADateTransform()) self.cprDueDate <- (map["noticingCprDue"], CADateTransform()) self.supervisorDueDate <- (map["supervisorDue"], CADateTransform()) self.courtDueDate <- (map["courtDue"], CADateTransform()) self.type <- (map["type"], EnumTransform<CAHearingType>()) } } func ==(lhs: CAHearing, rhs: CAHearing) -> Bool { return lhs.id == rhs.id && lhs.date == rhs.date && lhs.type == rhs.type && lhs.cprDueDate == rhs.cprDueDate && lhs.supervisorDueDate == rhs.supervisorDueDate && lhs.courtDueDate == rhs.courtDueDate } CAFormController.swift // // CAFormController.swift // CaseAide // // Created by Alan Perez on 8/21/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class CAFormController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, CAPageControllerDelegate, CAPageControllerNavbarDelegate, UIAlertViewDelegate { // page parameters private(set) var currentPage = 0 private(set) var pageViewController: UIPageViewController! private lazy var cancelButton: UIBarButtonItem = { return UIBarButtonItem(title: "Cancel", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("cancelAction")) }() override func viewDidLoad() { super.viewDidLoad() navigationItem.leftBarButtonItem = cancelButton navigationItem.backBarButtonItem = UIBarButtonItem.defaultCABackButton() // configure page view controller self.pageViewController = UIPageViewController(transitionStyle: UIPageViewControllerTransitionStyle.Scroll, navigationOrientation: UIPageViewControllerNavigationOrientation.Horizontal, options: nil) self.pageViewController.dataSource = self self.pageViewController.delegate = self self.prepareForm() // allow children to set start page self.currentPage = self.startPageIndex() // allow caller to configure start page if self.numberOfPages() > 0 { let startControllers = [self.viewControllerAtIndex(self.currentPage)!] self.pageViewController.setViewControllers(startControllers, direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil) self.pageViewController.view.frame = self.view.bounds self.addChildViewController(self.pageViewController) self.view.addSubview(self.pageViewController.view) self.pageViewController.didMoveToParentViewController(self) } } // MARK: Override methods for children func prepareForm() { println("prepareForm(): override this method to prepare data used in form") } func didEditPage(pageController: CAPageController) { println("didEditPage(): override this method to register for page changes") } func setRightNavbarItems(items: [AnyObject]?) { self.navigationItem.rightBarButtonItems = items } func setRightNavbarItem(item: UIBarButtonItem?) { self.navigationItem.rightBarButtonItem = item } func getRightNavbarItems() -> [AnyObject]? { return self.navigationItem.rightBarButtonItems } func getRightNavbarItem() -> UIBarButtonItem? { return self.navigationItem.rightBarButtonItem } func startPageIndex() -> Int { println("startPageIndex(): override this method for setting the initial page loaded") return 0 } func numberOfPages() -> Int { println("numberOfPages(): override this method for setting the number of pages") return 0 } func pageControllerForIndex(index: Int) -> CAPageController? { println("pageControllerForIndex(): override this method to pass in page at index") return nil } func save() { println("save(): override this method for consistent saving across all forms") println("Save form") } // MARK: Page view controller data source methods final func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? { if let index = (viewController as! CAPageController).pageNumber { return self.viewControllerAtIndex(index - 1) } return nil } final func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? { if let index = (viewController as? CAPageController)?.pageNumber { return self.viewControllerAtIndex(index + 1) } return nil } final func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [AnyObject], transitionCompleted completed: Bool) { // whenever new page is loaded, set current page and register for notifications to changes in page let currentPageController = pageViewController.viewControllers.last as! CAPageController self.currentPage = currentPageController.pageNumber! } // page control indicator methods final func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int { return self.numberOfPages() } final func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int { return self.currentPage } // MARK: Helper methods final func viewControllerAtIndex(index: Int) -> UIViewController? { if index >= self.numberOfPages() || index < 0 { return nil } var controller = self.pageControllerForIndex(index) controller?.pageNumber = index controller?.delegate = self controller?.navDelegate = self return controller } func currentPageController() -> CAPageController? { return self.pageViewController.viewControllers[0] as? CAPageController } func cancelAction() { println("cancelAction: Override this method to provide a custom implementation when user presses cancel") } } CAMapperTransforms.swift // // CAApiManager.swift // CaseAide // // Created by Alan Perez on 8/5/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import Foundation public class CADateTransform: TransformType { public typealias Object = NSDate public typealias JSON = String let dateFormat = "yyyy-MM-dd HH:mm:ss" let dateFormatter = NSDateFormatter() public init() { self.dateFormatter.dateFormat = self.dateFormat } public func transformFromJSON(value: AnyObject?) -> NSDate? { if let timeString = value as? String { return self.dateFormatter.dateFromString(timeString) } return nil } public func transformToJSON(value: NSDate?) -> JSON? { if let date = value { return self.dateFormatter.stringFromDate(date) } return nil } } public class CAArrayTransform: TransformType { public typealias Object = [String] public typealias JSON = String let seperator = "$" public init() {} public func transformFromJSON(value: AnyObject?) -> [String]? { if let string = value as? String { return string.componentsSeparatedByString(seperator) } return nil } public func transformToJSON(value: [String]?) -> JSON? { if let v = value { return seperator.join(v) } return nil } } public class CAIntTransform: TransformType { public typealias Object = Int public typealias JSON = String public init() {} public func transformFromJSON(value: AnyObject?) -> Object? { if let value = (value as? String)?.toInt() { return value } return nil } public func transformToJSON(value: Int?) -> JSON? { if let v = value { return "\(v)" } return nil } } CAPhoneNumber.swift // // CAPhoneNumber.swift // CaseAide // // Created by Alan Perez on 9/10/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import Foundation class CAPhoneNumber: Mappable { var id: Int var number: String init(id: Int, phonenumber: String) { self.id = id self.number = phonenumber } convenience init(id: Int) { self.init(id: id, phonenumber: "(000)000-0000") } static func newInstance(map: Map) -> Mappable? { return CAPhoneNumber(id: -1) } func mapping(map: Map) { self.id <- (map["id"], CAIntTransform()) self.number <- map["phone"] } } class CAAddress: Mappable { var id: Int var address: String init(id: Int, address: String) { self.id = id self.address = address } convenience init(id: Int) { self.init(id: id, address: "") } static func newInstance(map: Map) -> Mappable? { return CAAddress(id: -1) } func mapping(map: Map) { self.id <- (map["id"], CAIntTransform()) self.address <- map["address"] } } CAPageController.swift // // CAPageController.swift // CaseAide // // Created by Alan Perez on 8/22/15. // Copyright (c) 2015 CSUSB Mobile apps. All rights reserved. // import UIKit protocol CAPageControllerDelegate { func didEditPage(pageController: CAPageController) } protocol CAPageControllerNavbarDelegate { func setRightNavbarItems(items: [AnyObject]?) func setRightNavbarItem(item: UIBarButtonItem?) func getRightNavbarItems() -> [AnyObject]? func getRightNavbarItem() -> UIBarButtonItem? } class CAPageController: UIViewController { var pageNumber: Int? var clientName: String? var fieldType: String? var delegate: CAPageControllerDelegate? var navDelegate: CAPageControllerNavbarDelegate? func endEditing() { println("endEditing(): override me for form to be able to end editing for a specific page") } } CARadioPageController.swift // // CARadioPageController.swift // CaseAide // // Created by Alan Perez on 8/22/15. // Copyright (c) 2015 CSUSB Mobile apps. All rights reserved. // import UIKit class CARadioPageController: CAPageController, UITableViewDataSource, UITableViewDelegate { let radioCellReuseIdentifier = "radioCellReuseIdentifier" let optionsPrefixHeaderCellReuseIdentifier = "optionsPrefixHeaderCellReuseIdentifier" let selectedRadioImg = UIImage(named: "radio_selected") let unselectedRadioImg = UIImage(named: "radio_unselected") var optionsPrefix: String? @IBOutlet weak var fieldLabel: UILabel! @IBOutlet weak var clientNameLabel: UILabel! @IBOutlet weak var tableView: UITableView! let radioCellHeight: CGFloat = 50.0 let optionsPrefixHeaderHeight: CGFloat = 40.0 var options: [String]? var selectedIndex: Int? private var selectedOption: Int = 0 override func viewDidLoad() { super.viewDidLoad() // ensure that selected index is set if let selIndex = self.selectedIndex { self.selectedOption = selIndex } self.clientNameLabel.text = self.clientName self.fieldLabel.text = self.fieldType self.tableView.dataSource = self self.tableView.delegate = self } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // MARK: TableView data source methods func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.options?.count ?? 0 } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier(self.radioCellReuseIdentifier, forIndexPath: indexPath) as! RadioCell cell.optionLabel.text = self.options?[indexPath.row] if indexPath.row == selectedOption { cell.radioImageView.image = self.selectedRadioImg // set selected accessibility trait cell.accessibilityTraits = cell.accessibilityTraits | UIAccessibilityTraitSelected } else { cell.radioImageView.image = self.unselectedRadioImg // unset selected accessibility trait cell.accessibilityTraits = cell.accessibilityTraits & ~UIAccessibilityTraitSelected } return cell } func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return self.radioCellHeight } func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let cell = tableView.dequeueReusableCellWithIdentifier(self.optionsPrefixHeaderCellReuseIdentifier) as! UITableViewCell cell.textLabel?.text = self.optionsPrefix ?? "" return cell } func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return self.optionsPrefixHeaderHeight } // MARK: Table view delegate methods func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { var prevSelected = self.selectedOption self.selectedOption = indexPath.row let prevIndexPath = NSIndexPath(forRow: prevSelected, inSection: 0) let currIndexPath = NSIndexPath(forRow: self.selectedOption, inSection: 0) self.tableView.reloadRowsAtIndexPaths([prevIndexPath, currIndexPath], withRowAnimation: UITableViewRowAnimation.Automatic) self.delegate?.didEditPage(self) } func getSelectedOptionIndex() -> Int { return self.selectedOption } @IBAction func informationButton(sender: UIButton) { //description of the view let discriptionText = Functions.message(fieldLabel.text!) //Custom box for the view Functions.discriptionBox(fieldLabel.text!, message: discriptionText) } } CAReport.swift // // Models.swift // CaseAide // // Created by Alan Perez on 8/4/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import Foundation struct CAField: Equatable { var name: String var value: String } func ==(lhs: CAField, rhs: CAField) -> Bool { return lhs.name == rhs.name && lhs.value == rhs.value } enum CAReportType: String { case Generic = "Generic" case StatusReview = "366.3, Status Review" case ExParte = "Ex Parte" } enum CAReportTemplate { case StatusReview case ExParte case Generic var type: CAReportType { switch self { case .StatusReview: return CAReportType.StatusReview case .ExParte: return CAReportType.ExParte case .Generic: return CAReportType.Generic } } var fields: [CAField] { switch self { case .StatusReview: return [CAField(name: "Summary Recommendation", value: ""), CAField(name: "Child Whereabouts", value: ""), CAField(name: "Indian Child Welfare Status", value: ""), CAField(name: "Search Results", value: ""), CAField(name: "Hearing Reason", value: ""), CAField(name: "Legal Relationships", value: ""), CAField(name: "Family Law Status", value: ""), CAField(name: "Family Assesment Update", value: ""), CAField(name: "Child Evaluation", value: ""), CAField(name: "Out of Home placement", value: ""), CAField(name: "Placement History", value: ""), CAField(name: "Services Provided", value: ""), CAField(name: "Visitation", value: ""), CAField(name: "Contacts", value: ""), CAField(name: "Family perception of needs", value: "")] case .ExParte: return [CAField(name: "Reason for application", value: ""), CAField(name: "Notices", value: ""), CAField(name: "Reason for recommendation", value: ""), CAField(name: "Recommendation", value: "")] default: return [CAField]() } } // Status Review fields static let SUMMARY_REC_INDEX = 0 static let CHILD_WHEREABOUTS_INDEX = 1 static let ICWA_STATUS_INDEX = 2 static let SEARCH_RESULTS_INDEX = 3 static let HEARING_REASON_INDEX = 4 static let LEGAL_RELATIONSHIPS_INDEX = 5 static let FAMILY_LAW_STATUS_INDEX = 6 static let FAMILY_ASSESMENT_UPDATE_INDEX = 7 static let CHILD_EVAL_INDEX = 8 static let OUT_OF_HOME_PLACEMENT_INDEX = 9 static let PLACEMENT_HISTORY_INDEX = 10 static let SERVICES_PROVIDED_INDEX = 11 static let VISITATION_INDEX = 12 static let CONTACTS_INDEX = 13 static let FAMILY_PERCEPTION_OF_NEEDS_INDEX = 14 // Exparte fields static private let REASON_FOR_APPL_INDEX = 0 static private let NOTICES_INDEX = 1 static private let REASON_FOR_REC_INDEX = 2 static private let RECOMMENDATION_INDEX = 3 } class CAReport: Mappable, Equatable { var id: Int var date: NSDate var court: String var room: String var children: [String] private(set) var type: CAReportType private(set) var fields: [CAField] init (report: CAReport) { self.id = report.id self.date = report.date self.court = report.court self.room = report.room self.children = report.children self.type = report.type self.fields = report.fields } init (id: Int, date: NSDate, template: CAReportTemplate) { self.id = id self.date = date self.court = "" self.room = "" self.children = [String]() self.type = template.type self.fields = template.fields } convenience init (id: Int) { self.init (id: id, date: NSDate(), template: CAReportTemplate.StatusReview) } convenience init() { self.init(id: -1, date: NSDate(), template: CAReportTemplate.StatusReview) } class func newInstance(map: Map) -> Mappable? { return CAReport(id: -1, date: NSDate(), template: CAReportTemplate.Generic) } func mapping(map: Map) { let preMapReportType = self.type self.id <- (map["id"],CAIntTransform()) self.date <- (map["date"], CADateTransform()) self.court <- map["court"] self.room <- map["room"] self.children <- (map["children"], CAArrayTransform()) self.type <- (map["type"], EnumTransform<CAReportType>()) // if report type has changed update fields to new report template let modifiedType = self.type != preMapReportType if modifiedType { // want to set fields switch self.type { case .StatusReview: self.fields = CAReportTemplate.StatusReview.fields case .ExParte: self.fields = CAReportTemplate.ExParte.fields default: self.fields = CAReportTemplate.Generic.fields } } // map fields to their JSON objects switch self.type { case .StatusReview: self.fields[CAReportTemplate.SUMMARY_REC_INDEX].value <- map["summaryRecommendation"] self.fields[CAReportTemplate.CHILD_WHEREABOUTS_INDEX].value <- map["childWhereabouts"] self.fields[CAReportTemplate.ICWA_STATUS_INDEX].value <- map["indianChildWelfareActStatus"] self.fields[CAReportTemplate.SEARCH_RESULTS_INDEX].value <- map["searchResults"] self.fields[CAReportTemplate.HEARING_REASON_INDEX].value <- map["hearingReason"] self.fields[CAReportTemplate.LEGAL_RELATIONSHIPS_INDEX].value <- map["legalRelationships"] self.fields[CAReportTemplate.FAMILY_LAW_STATUS_INDEX].value <- map["familyLawStatus"] self.fields[CAReportTemplate.FAMILY_ASSESMENT_UPDATE_INDEX].value <- map["familyAssesmentUpdate"] self.fields[CAReportTemplate.CHILD_EVAL_INDEX].value <- map["childEvaluation"] self.fields[CAReportTemplate.OUT_OF_HOME_PLACEMENT_INDEX].value <- map["outOfHomePlacement"] self.fields[CAReportTemplate.PLACEMENT_HISTORY_INDEX].value <- map["placementHistory"] self.fields[CAReportTemplate.SERVICES_PROVIDED_INDEX].value <- map["serviceProvidedHistory"] self.fields[CAReportTemplate.VISITATION_INDEX].value <- map["visitation"] self.fields[CAReportTemplate.CONTACTS_INDEX].value <- map["contacts"] self.fields[CAReportTemplate.FAMILY_PERCEPTION_OF_NEEDS_INDEX].value <- map["familyPerceptionOfNeeds"] case .ExParte: // exparte mappings self.fields[CAReportTemplate.REASON_FOR_APPL_INDEX].value <- map["reasonForApplication"] self.fields[CAReportTemplate.NOTICES_INDEX].value <- map["notices"] self.fields[CAReportTemplate.REASON_FOR_REC_INDEX].value <- map["reasonForRecommendation"] self.fields[CAReportTemplate.RECOMMENDATION_INDEX].value <- map["recommendation"] default: break } } func updateTemplate(template: CAReportTemplate) { self.type = template.type self.fields = template.fields } func setFieldValue(index: Int, value: String) { self.fields[index].value = value } } func ==(lhs: CAReport, rhs: CAReport) -> Bool { return lhs.id == rhs.id && lhs.type == rhs.type && lhs.date == rhs.date && lhs.court == rhs.court && lhs.room == rhs.room && lhs.children == rhs.children && lhs.fields == rhs.fields } CAReportTypePageController.swift // // CAReportTypePageController.swift // CaseAide // // Created by Alan Perez on 8/25/15. // Copyright (c) 2015 CSUSB Mobile apps. All rights reserved. // import UIKit class CAReportTypePageController: CAPageController { var type: CAReportType? var date: NSDate? var displayType: Bool = true let reportTypeOptions = [CAReportType.StatusReview.rawValue] private var defaultDate = NSDate() private var defaultTypeIndex = 0 @IBOutlet weak private var clientNameLabel: UILabel! @IBOutlet weak private var dateTextField: UITextField! @IBOutlet weak private var timeTextField: UITextField! @IBOutlet weak private var typeTextField: UITextField! @IBOutlet weak var typeLabel: UILabel! private var dateDownPicker: DateDownPicker! private var timeDownPicker: DateDownPicker! private var typeDownPicker: DownPicker! override func viewDidLoad() { super.viewDidLoad() self.clientNameLabel.text = self.clientName // configure textField borders let borderThickness: CGFloat = 1.5 let borderColor = UIColor(white: 0, alpha: 0.15) self.dateTextField.layer.addBorder(UIRectEdge.Bottom, color: borderColor, thickness: borderThickness) self.timeTextField.layer.addBorder(UIRectEdge.Bottom, color: borderColor, thickness: borderThickness) self.typeTextField.layer.addBorder(UIRectEdge.Bottom, color: borderColor, thickness: borderThickness) // configure data and down pickers let initialDate = self.date ?? defaultDate let intialType = self.type?.rawValue ?? self.reportTypeOptions[self.defaultTypeIndex] let initialTypeIndex = find(self.reportTypeOptions, intialType) ?? defaultTypeIndex self.dateDownPicker = DateDownPicker(textField: self.dateTextField, withDate: initialDate, withMode: UIDatePickerMode.Date) self.timeDownPicker = DateDownPicker(textField: self.timeTextField, withDate: initialDate, withMode: UIDatePickerMode.Time) self.typeDownPicker = DownPicker(textField: self.typeTextField, withData: NSMutableArray(array: self.reportTypeOptions), withIntialOption: initialTypeIndex) self.dateDownPicker.addTarget(self, action: Selector("pageChanged"), forControlEvents: UIControlEvents.ValueChanged) self.timeDownPicker.addTarget(self, action: Selector("pageChanged"), forControlEvents: UIControlEvents.ValueChanged) self.typeDownPicker.addTarget(self, action: Selector("pageChanged"), forControlEvents: UIControlEvents.ValueChanged) if !displayType { self.typeTextField.hidden = true self.typeLabel.hidden = true } } func pageChanged() { self.delegate?.didEditPage(self) } func getDate() -> NSDate { let date = self.dateDownPicker.getDate() let time = self.timeDownPicker.getDate() return NSDate.combineDateAndTime(date, time: time) } func getType() -> CAReportType { return CAReportType(rawValue: self.typeDownPicker.text)! } @IBAction func informationButton(sender: UIButton) { //description of the view let discriptionText = Functions.message("Type") //Custom box for the view Functions.discriptionBox("Report Type", message: discriptionText) } } CASetContactFormController.swift // // CASetContactFormController.swift // CaseAide // // Created by Alan Perez on 8/22/15. // Copyright (c) 2015 CSUSB Mobile apps. All rights reserved. // import UIKit class CASetContactFormController: CAFormController { let dateControllerStoryboardIdentifier = "setDateControllerStoryboardIdentifier" let radioControllerStoryboardIdentifier = "setRadioControllerStoryboardIdentifier" let checkboxControllerStoryboardIdentifier = "setCheckboxControllerStoryboardIdentifier" let blobControllerStoryboardIdentifier = "setBlobControllerStoryboardIdentifier" var startPage: Int? // operation mode of controller var mode: Int! let EDIT_MODE = 1 let CREATE_MODE = 2 // page parameters let numberContactPages = 9 let datePage = 0 let staffPage = 1 let objectivePage = 2 let approachPage = 3 let placePage = 4 let outcomePage = 5 let personsIncludedPage = 6 let inSupportOfPage = 7 let notePage = 8 // contact properties var constructedContact: CAContact! var referenceContact: CAContact! // dummy data for unavailable data var personsIncludedOptions = ["Anthony Pearson", "Bernie Smith", "Clara Smith", "Don Johnson", "Fiona Johnson"] var inSupportOfOptions = ["Option 1", "Option 2", "Option 3", "Option 4", "Option 5", "Option 6", "Option 7", "Option 8", "Option 9", "Option 10", "Option 11", "Option 12"] var staffOptions = ["Staff 1", "Staff 2", "Staff 3"] // models var client: CAClient? var contact: CAContact? let api: CAApi = CAApi.defaultApi private var hasUnsavedChanges = false override func prepareForm() { if let c = self.contact { self.mode = self.EDIT_MODE self.title = "Edit Contact" // populate internal state with contact passed in self.referenceContact = c self.constructedContact = CAContact(contact: self.referenceContact) } else { self.mode = self.CREATE_MODE self.title = "Add Contact" // populate internal state with default contact parameters self.referenceContact = CAContact() // assign default values for radio fields self.referenceContact.staff = self.staffOptions[0] self.referenceContact.objective = CAContact.objectiveOptions[0] self.referenceContact.approach = CAContact.approachOptions[0] self.referenceContact.place = CAContact.placeOptions[0] self.referenceContact.outcome = CAContact.outcomeOptions[0] self.constructedContact = CAContact(contact: self.referenceContact) } // client is required for controller to save, and give client name to pages if var validClient = self.client { // TODO: remove this from persons included in future self.personsIncludedOptions.append(validClient.name) } } override func didEditPage(pageController: CAPageController) { // extract properties from edited page self.extractPageController(pageController) } func extractPageController(pageController: CAPageController) { if let pageNumber = pageController.pageNumber { switch pageNumber { case datePage: let dates = self.extractStartEndDatePageController(pageController as! CAStartEndDatePageController) self.constructedContact.startDate = dates.startDate self.constructedContact.endDate = dates.endDate case staffPage: self.constructedContact.staff = self.extractRadioPageController(pageController as! CARadioPageController, options: self.staffOptions) case objectivePage: self.constructedContact.objective = self.extractRadioPageController(pageController as! CARadioPageController, options: CAContact.objectiveOptions) case approachPage: self.constructedContact.approach = self.extractRadioPageController(pageController as! CARadioPageController, options: CAContact.approachOptions) case placePage: self.constructedContact.place = self.extractRadioPageController(pageController as! CARadioPageController, options: CAContact.placeOptions) case outcomePage: self.constructedContact.outcome = self.extractRadioPageController(pageController as! CARadioPageController, options: CAContact.outcomeOptions) case personsIncludedPage: self.constructedContact.personsIncluded = self.extractCheckboxPageController(pageController as! CACheckboxPageController, options: self.personsIncludedOptions) case inSupportOfPage: self.constructedContact.inSupportOf = self.extractCheckboxPageController(pageController as! CACheckboxPageController, options: self.inSupportOfOptions) case notePage: self.constructedContact.note = self.extractBlobController(pageController as! CABlobPageController) default: break } } self.hasUnsavedChanges = self.hasContactChanged(self.constructedContact) } func hasContactChanged(contact: CAContact)-> Bool { return self.referenceContact != contact } override func startPageIndex() -> Int { return self.startPage ?? 0 } override func numberOfPages() -> Int { return self.numberContactPages } override func pageControllerForIndex(index: Int) -> CAPageController? { let pageController = self.constructPageController(index) return pageController } @IBAction func saveButtonPressed(sender: AnyObject) { if let currentPage = self.currentPageController() { currentPage.endEditing() } self.save() } override func save() { if let c = self.client { if self.mode == self.EDIT_MODE { // edit mode api.updateContact(c.id, contact: self.constructedContact, completionHandler: { error in if let updateError = error { self.alertError(updateError) } else { CAToast.makeToast("Contact Saved") self.hasUnsavedChanges = false // update the new reference to compare against for changes self.referenceContact = CAContact(contact: self.constructedContact) } }) } else if self.mode == self.CREATE_MODE { // in create mode api.createContact(c.id, contact: self.constructedContact, completionHandler: { error in if let addError = error { self.alertError(addError) } else { CAToast.makeToast("Contact Created") self.hasUnsavedChanges = false // Have to exit form, no way for us to simply change to edit mode // because we don't know the id of the newly created report nor is there an efficient way to get it // TODO: update API to return id of newly created object self.cancelAction(); } }) } } else { // error client not provided println("Client not provided") } } func alertError(error: NSError) { UIAlertView(title: error.localizedDescription, message: error.localizedRecoverySuggestion, delegate: nil, cancelButtonTitle: "Ok").show() } func constructPageController(index: Int) -> CAPageController? { let pageController: CAPageController? switch index { case datePage: let dateController = self.createStartEndDateController() self.configureStartEndDatePageController(dateController, startDate: self.constructedContact.startDate, endDate: self.constructedContact.endDate, fieldType: "Date of Contact") pageController = dateController case staffPage: let staffController = self.createRadioController() self.configureRadioPageController(staffController, value: self.constructedContact.staff, options: self.staffOptions, optionsPrefix: nil, fieldType: "Staff") pageController = staffController case objectivePage: let objectiveRadioController = self.createRadioController() self.configureRadioPageController(objectiveRadioController, value: self.constructedContact.objective, options: CAContact.objectiveOptions, optionsPrefix: nil, fieldType: "Objective") pageController = objectiveRadioController case approachPage: let approachRadioController = self.createRadioController() self.configureRadioPageController(approachRadioController, value: self.constructedContact.approach, options: CAContact.approachOptions, optionsPrefix: "Contact with client was", fieldType: "Approach") pageController = approachRadioController case placePage: let placeRadioController = self.createRadioController() self.configureRadioPageController(placeRadioController, value: self.constructedContact.place, options: CAContact.placeOptions, optionsPrefix: "Location of client contact", fieldType: "Place") pageController = placeRadioController case outcomePage: let outcomeRadioController = self.createRadioController() self.configureRadioPageController(outcomeRadioController, value: self.constructedContact.outcome, options: CAContact.outcomeOptions, optionsPrefix: "Contact with client was", fieldType: "Outcome") pageController = outcomeRadioController case personsIncludedPage: let personsIncludedCheckboxController = self.createCheckboxController() self.configureCheckboxPageController(personsIncludedCheckboxController, values: self.constructedContact.personsIncluded, options: self.personsIncludedOptions, fieldType: "Persons Included") pageController = personsIncludedCheckboxController case inSupportOfPage: let inSupportOfCheckboxController = self.createCheckboxController() self.configureCheckboxPageController(inSupportOfCheckboxController, values: self.constructedContact.inSupportOf, options: self.inSupportOfOptions, fieldType: "In Support of") pageController = inSupportOfCheckboxController case notePage: let noteBlobController = self.createBlobController() self.configureBlobController(noteBlobController, fieldType: "Note", text: self.constructedContact.note) pageController = noteBlobController default: pageController = nil } // all page controllers have clientName pageController?.clientName = self.client?.name return pageController } func createStartEndDateController() -> CAStartEndDatePageController { return self.storyboard?.instantiateViewControllerWithIdentifier(self.dateControllerStoryboardIdentifier) as! CAStartEndDatePageController } func createRadioController() -> CARadioPageController { return self.storyboard?.instantiateViewControllerWithIdentifier(self.radioControllerStoryboardIdentifier) as! CARadioPageController } func createCheckboxController() -> CACheckboxPageController { return self.storyboard?.instantiateViewControllerWithIdentifier(self.checkboxControllerStoryboardIdentifier) as! CACheckboxPageController } func createBlobController() -> CABlobPageController { return self.storyboard?.instantiateViewControllerWithIdentifier(self.blobControllerStoryboardIdentifier) as! CABlobPageController } func configureStartEndDatePageController(startEndDateController: CAStartEndDatePageController, startDate: NSDate, endDate: NSDate, fieldType: String) { startEndDateController.fieldType = fieldType startEndDateController.startDate = startDate startEndDateController.endDate = endDate } func configureRadioPageController(radioPageController: CARadioPageController, value: String, options: [String], optionsPrefix: String?, fieldType: String) { radioPageController.options = options radioPageController.optionsPrefix = optionsPrefix radioPageController.fieldType = fieldType radioPageController.selectedIndex = find(options, value) } func configureCheckboxPageController(checkboxPageController: CACheckboxPageController, values: [String], options: [String], fieldType: String) { checkboxPageController.options = options checkboxPageController.fieldType = fieldType var selectedIndicies = [Int]() for value in values { if let selectedIndex = find(options, value) { selectedIndicies.append(selectedIndex) } } checkboxPageController.selectedIndicies = selectedIndicies } func configureBlobController(blobPageController: CABlobPageController, fieldType: String, text: String) { blobPageController.fieldType = fieldType blobPageController.text = text } func extractStartEndDatePageController(startEndDateController: CAStartEndDatePageController) -> (startDate: NSDate, endDate: NSDate) { return (startEndDateController.getStartDate(), startEndDateController.getEndDate()) } func extractRadioPageController(radioPageController: CARadioPageController, options: [String]) -> String { return options[radioPageController.getSelectedOptionIndex()] } func extractCheckboxPageController(checkboxPageController: CACheckboxPageController, options: [String]) -> [String] { var selectedIndicies = checkboxPageController.getSelectedIndicies() var values = [String]() for selectedIndex in selectedIndicies { values.append(options[selectedIndex]) } return values } func extractBlobController(blobPageController: CABlobPageController) -> String { return blobPageController.getText() } override func cancelAction() { if self.hasUnsavedChanges { saveAlert("Save Contact?") } else { self.navigationController?.popViewControllerAnimated(true) } } func saveAlert(title: String) { var alert = UIAlertView() alert.delegate = self alert.title = title; alert.message = "Exit without saving?"; alert.addButtonWithTitle("Save") alert.addButtonWithTitle("Discard") alert.show() } func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) { switch buttonIndex { case 0: save() println("save") break; case 1: self.navigationController?.popViewControllerAnimated(true) println("Yes") break; default: println("default called") break; } } } CASetReportFormController.swift // // CASetReportFormController.swift // CaseAide // // Created by Alan Perez on 8/26/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class CASetReportFormController: CAFormController { let typeControllerStoryboardIdentifier = "reportTypeControllerStoryboardIdentifier" let courtInfoControllerStoryboardIdentifier = "reportCourtInfoControllerStoryboardIdentifier" let templateControllerStoryboardIdentifier = "reportTemplatesControllerStoryboardIdentifier" let checkboxControllerStoryboardIdentifier = "setCheckboxControllerStoryboardIdentifier" var startPage: Int? // operation mode of controller var mode: Int! let EDIT_MODE = 1 let CREATE_MODE = 2 // page parameters let numberReportPages = 4 let typePage = 0 let courtInfoPage = 1 let childrenPage = 2 let templatesPage = 3 // dependent upon report type, by setting report type these fields are configured private var constructedReport: CAReport! private var referenceReport: CAReport! // models var client: CAClient? var report: CAReport? var api: CAApi = CAApi.defaultApi private var hasUnsavedChanges = false // dummy data for unavailable data var childrenOptions = ["Mike Korcha", "Andrew Lenzini", "Tevin Castle", "Samatha Harry", "DJ", "Clara Smith", "Don Johnson", "Fiona Johnson"] override func prepareForm() { // retrieve initial report values if let r = self.report { self.mode = self.EDIT_MODE self.title = "Edit Report" self.referenceReport = r self.constructedReport = CAReport(report: self.referenceReport) } else { self.mode = self.CREATE_MODE self.title = "Add Report" self.referenceReport = CAReport() self.constructedReport = CAReport(report: referenceReport) } } override func startPageIndex() -> Int { return self.startPage ?? 0 } override func numberOfPages() -> Int { return self.numberReportPages } override func pageControllerForIndex(index: Int) -> CAPageController? { let pageController = self.constructPageController(index) return pageController } @IBAction func saveButtonPressed(sender: AnyObject) { self.save() } override func save() { if let client = self.client { if self.mode == self.EDIT_MODE { println("Edit Mode") api.updateReport(client.id, report: self.constructedReport, completionHandler: { error in if let updateError = error { self.alertError(updateError) } else { CAToast.makeToast("Report Saved") self.hasUnsavedChanges = false // now we are looking for changes made relative to this report self.referenceReport = CAReport(report: self.constructedReport) } }) } else if self.mode == self.CREATE_MODE { // in create mode api.createReport(client.id, report: self.constructedReport, completionHandler: { error in if let addError = error { self.alertError(addError) } else { CAToast.makeToast("Report Created") self.hasUnsavedChanges = false // Have to exit form, no way for us to simply change to edit mode // because we don't know the id of the newly created report nor is there an efficient way to get it // TODO: update API to return id of newly created object self.cancelAction(); } }) } } } override func didEditPage(pageController: CAPageController) { self.extractPageController(pageController) } func hasReportChanged(report: CAReport) -> Bool { return report != self.referenceReport } func updateReportTemplate(type: CAReportType) { let reportTemplate: CAReportTemplate switch type { case .StatusReview: reportTemplate = CAReportTemplate.StatusReview case .ExParte: reportTemplate = CAReportTemplate.ExParte default: reportTemplate = CAReportTemplate.Generic } self.constructedReport.updateTemplate(reportTemplate) } func updateReportFields(fields: [CAField]) { for i in 0..<self.constructedReport.fields.count { self.constructedReport.setFieldValue(i, value: fields[i].value) } } func extractPageController(pageController: CAPageController) { if let pageNumber = pageController.pageNumber { switch pageNumber { case typePage: let data = self.extractTypePageController(pageController as! CAReportTypePageController) if data.type != self.constructedReport.type { self.updateReportTemplate(data.type) } self.constructedReport.date = data.date case courtInfoPage: println("courtInfo page called!") let courtInfo = self.extractCourtInfoPageController(pageController as! CACourtInfoPageController) self.constructedReport.court = courtInfo.court self.constructedReport.room = courtInfo.room case childrenPage: println("child page called!") let children = self.extractCheckboxPageController(pageController as! CACheckboxPageController, options: self.childrenOptions) self.constructedReport.children = children case templatesPage: //does not seem to get called?? let fields = self.extractTemplatesPageController(pageController as! CATemplatePageController) self.updateReportFields(fields) default: break } } self.hasUnsavedChanges = hasReportChanged(self.constructedReport) } func extractTypePageController(reportTypeController: CAReportTypePageController) -> (type: CAReportType, date: NSDate) { return (reportTypeController.getType(), reportTypeController.getDate()) } func extractCourtInfoPageController(courtInfoController: CACourtInfoPageController) -> (court: String, room: String) { return (courtInfoController.getCourt(), courtInfoController.getRoom()) } func extractTemplatesPageController(templatesController: CATemplatePageController) -> [CAField] { return templatesController.getTemplates() } func extractCheckboxPageController(checkboxPageController: CACheckboxPageController, options: [String]) -> [String] { var selectedIndicies = checkboxPageController.getSelectedIndicies() var values = [String]() for selectedIndex in selectedIndicies { values.append(options[selectedIndex]) } return values } func alertError(error: NSError) { UIAlertView(title: error.localizedDescription, message: error.localizedRecoverySuggestion, delegate: nil, cancelButtonTitle: "Ok").show() } func constructPageController(index: Int) -> CAPageController? { let pageController: CAPageController? switch index { case typePage: let typeController = self.createTypeController() let displayType = self.mode == self.CREATE_MODE self.configureTypeController(typeController, type: self.constructedReport.type, date: self.constructedReport.date, displayTypeField: displayType) pageController = typeController case courtInfoPage: let courtInfoController = self.createCourtInfoController() self.configureCourtInfoController(courtInfoController, court: self.constructedReport.court, room: self.constructedReport.room) pageController = courtInfoController case childrenPage: let childrenController = self.createCheckboxController() self.configureCheckboxPageController(childrenController, values: self.constructedReport.children, options: self.childrenOptions, fieldType: "Children") pageController = childrenController case templatesPage: let templatesController = self.createTemplatesController() self.configureTemplateController(templatesController, templates: self.constructedReport.fields) pageController = templatesController default: pageController = nil } // all page controllers have clientName pageController?.clientName = self.client?.name return pageController } // changing report type causes templates to change, templates determined by instance of report could be status review, could be exparte // have global report object that changes type when report type is changed, values of the report however are not set until the user saves func createTypeController() -> CAReportTypePageController { return self.storyboard?.instantiateViewControllerWithIdentifier(self.typeControllerStoryboardIdentifier) as! CAReportTypePageController } func createCourtInfoController() -> CACourtInfoPageController { return self.storyboard?.instantiateViewControllerWithIdentifier(self.courtInfoControllerStoryboardIdentifier) as! CACourtInfoPageController } func createTemplatesController() -> CATemplatePageController { return self.storyboard?.instantiateViewControllerWithIdentifier(self.templateControllerStoryboardIdentifier) as! CATemplatePageController } func createCheckboxController() -> CACheckboxPageController { return self.storyboard?.instantiateViewControllerWithIdentifier(self.checkboxControllerStoryboardIdentifier) as! CACheckboxPageController } func configureTypeController(typeController: CAReportTypePageController, type: CAReportType, date: NSDate, displayTypeField: Bool) { typeController.date = date typeController.type = type typeController.displayType = displayTypeField } func configureCourtInfoController(courtInfoController: CACourtInfoPageController, court: String, room: String) { courtInfoController.court = court courtInfoController.room = room } func configureTemplateController(templatesController: CATemplatePageController, templates: [CAField]) { templatesController.templates = templates } func configureCheckboxPageController(checkboxPageController: CACheckboxPageController, values: [String], options: [String], fieldType: String) { checkboxPageController.options = options checkboxPageController.fieldType = fieldType var selectedIndicies = [Int]() for value in values { if let selectedIndex = find(options, value) { selectedIndicies.append(selectedIndex) } } checkboxPageController.selectedIndicies = selectedIndicies } override func cancelAction() { if self.hasUnsavedChanges == true { saveAlert("Save Report?") } else { self.navigationController?.popViewControllerAnimated(true) } } func saveAlert(title: String) { var alert = UIAlertView() alert.delegate = self alert.title = title; alert.message = "Exit without saving?"; alert.addButtonWithTitle("Save") alert.addButtonWithTitle("Discard") alert.show() } func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) { switch buttonIndex { case 0: save() println("save") break; case 1: self.navigationController?.popViewControllerAnimated(true) println("Yes") break; default: println("default called") break; } } } CAShortType.swift // // CAShortType.swift // CaseAide // // Created by Alan on 11/30/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import Foundation class CAShortType { class func mapToWords(text: String) -> String { var mappedText = text let completeRange = Range<String.Index>(start: mappedText.startIndex, end: mappedText.endIndex) mappedText.enumerateSubstringsInRange(completeRange, options: NSStringEnumerationOptions.ByWords | NSStringEnumerationOptions.Reverse) { (substring, subrange, enclosingRange, stop) -> Void in let possibleAbbrWord = substring let normalizedPossibleAbbrWord = possibleAbbrWord.uppercaseString if var mappedWord = self.abbrWordMap[normalizedPossibleAbbrWord] { // normalize mapped word mappedWord = mappedWord.lowercaseString // if abbreviation was capitalized, capitalize first letter of first word let isAbbrCapitalized = possibleAbbrWord.isFirstLetterCapitalized() if isAbbrCapitalized { mappedWord = mappedWord.capitalizeFirstLetter() } mappedText.replaceRange(subrange, with: mappedWord) } } return mappedText } static let abbrWordMap: [String: String] = [ "CCL": "community care licensing", "CASA": "Court Appointed Special Advocate", "CPS": "Child Protective Services", "CFS": "Children and Family Services", "DPSS": "Department of Public Social Services", "DBH": "Department of Behavioral Health ", "SW": "Social Worker", "ILP": "Independent Living Program ", "ILPSW": "Independent Living Program Social Worker", "ICWA": "Indian child welfare act", "MSW": "Masters in Social Work", "AWOL": "Absent Without Leave", "FFA": "Foster Family Agency", "FF": "Face to face", "F2F": "Family to Family", "FP": "foster parent", "FH": "foster home", "FR": "Family Reunification", "FM": "Family Maintenance ", "GH": "group home", "TC": "telephone call ", "TDM": "Team Decision Meeting", "NMD": "non-minor dependent ", "NREFM": "non-relative extended family member", "LG": "legal guardian", "SDM": "structured decision making", "JH": "Juvenile Hall", "PO": "Probation Officer", "AB12": "Assembly Bill 12", "WRAP": "wraparound services", "ER": "emergency response", "CDU": "Court Dependency Unit", "WIC": "Welfare and Institutions Code", "POSTOX": "Positive Toxicity", "OHI": "Out of Home Investigation", "CR": "court report", "IR": "immediate response", "ES": "emergency shelter", "PFA": "Peer and Family Assistant", "TCO": "transitional conference", "AA": "alcoholics Anonymous", "ADD": "Attention Deficit Disorder", "ADHD": "Attention Deficit Hyperactivity Disorder", "ODD": "Oppositional Defiance Disorder", "BPD": "Borderline Personality Disorder", "APB": "All Points Bulletin", "BIA": "Bureau of Indian Affairs", "CBO": "community based organization", "CMT": "Case Management Team", "CM": "case management ", "CLT": "client", "DA": "district attorney", "DCFS": "Department of Children and Family Services", "HIV": "Human Immunodeficiency Virus", "ICU": "intensive care unit", "IL": "Independent Living ", "NA": "Narcotics Anonymous", "PHN": "Public Health Nurse ", "CD": "Conduct Disorder", "TBS": "Therapeutic Behavioral Services", "MHS": "Mental Health Services", "SOP": "Safety Organized Practices", "PLT": "Placement", "CW": "Child Welfare", "RJC": "Riverside Juvenile Court", "SBJC": "San Bernardino Juvenile Court", "IYRT": "Interagency Youth Resiliency Team", "EFC": "Extended Foster Care", "DMH": "Department of Mental Health", "IHSS": "In Home Support Services", "APS": "Adult Protective Services", "DOB": "date of birth", "DUI": "driving under the influence ", "EAP": "Employment Assistance Program", "EW": "Eligibility Worker", "SSA": "Social Service Assistant", "SSP": "Social Service Practitioner", "SSS": "Social Service Supervisor", "CP": "Case Plan", "TILP": "Transitional Independent Living Plan", "SILP": "Supervised Independent Living Placement", "CFVAN": "Client was free from any visible signs of abuse or neglect", "CVAN": "Client had visible sings of abuse and neglect", "IRC": "Inland Regional Center", "SUP": "supervisor", "DPO": "Deputy Probation Officer", "MD": "Medical Doctor", "MR": "Mandated reporter", "CAPTS": "Child Abuse Prevention and Treatment Team", "ACT": "Assessment and Consultation Team", "FVSC": "Family Visitation and Support Center", "CFT": "Child and Family Team Meeting", "CLETS": "California Law Enforcement Telecommunication System", "CPR": "Concurrent Planning Review", "CNS": "Children’s Strengths and Needs", "MDT": "Multi-Disciplinary Team", "PAS": "Purchase Authorization for Service", "PMU": "Placement Management Unit", "PPLA": "Planned Permanent Living Arrangement ", "RAU": "Relative assessment Unit", "RCCAT": "Riverside Child Assessment Team", "MHST": "Mental health Screening Tool ", "PGM": "paternal grandmother", "PGF": "paternal grandfather", "MGM": "maternal grandmother", "GM": "Grandmother", "GF": "Grandfather", "IRB": "Institutional review board", "NASW": "National Association of Social Workers", "NAMI": "National Alliance of Mentally Ill", "BASW": "Bachelors of Social Work", "FI": "field instructor", "FL": "field liaison", "PRO": "professor", "LCSW": "Licensed Clinical Social Worker ", "MFT": "Marriage and Family Therapist", "SOSW": "School of Social Work", "SWSA": "Social Work Student Association ", "PDEP": "Pathways Distance Education Program", "5150": "72 hour hold for assessment", "5250": "14 days maximum hold for treatment", "COE": "County Office of Education", "CYC": "California Youth Connection", "DSS": "California Department of Social Services", "FYS": "Foster Youth Services", "CAC": "Children’s Assessment Center", "DOJ": "Department of Justice", "OHC": "Out of Home Care", "JD": "Jurisdictional/Dispositional", "SD": "Staff Development", "PC": "protective capacity", "MSLC": "minimum sufficient level of care", "RAM": "risk assessment meeting", "BS": "basic needs", "FTT": "failure to thrive", "SA": "Safety Assessment", "RA": "Risk Assessment", "PIP": "Program Improvement Plan", "SIP": "System Improvement Plan", "SIDS": "sudden infant death syndrome", "SIS": "shaken infant syndrome", "CALSWEC": "California Social Work Education Center", "CSWE": "Counsel on Social Work Education", "GIM": "Generalist Intervention Model", "EPAS": "Education Policy and Accreditation Standards", "LPA": "Learning Plan Agreemen", "PR": "process recordings", "OH": "Office Hours", "BPSS": "bio-psycho-social-spiritual", "COETH": "code of ethics", "CV": "core values" ] } CAStartEndDatePageController.swift // // CAStartEndDatePageController.swift // CaseAide // // Created by Alan Perez on 8/22/15. // Copyright (c) 2015 CSUSB Mobile apps. All rights reserved. // import UIKit class CAStartEndDatePageController: CAPageController { var startDate: NSDate? var endDate: NSDate? let defaultStartDate = NSDate() let defaultEndDate = NSDate() @IBOutlet weak var clientNameLabel: UILabel! @IBOutlet weak var fieldLabel: UILabel! @IBOutlet weak var startDateTextField: UITextField! @IBOutlet weak var startTimeTextField: UITextField! @IBOutlet weak var endDateTextField: UITextField! @IBOutlet weak var endTimeTextField: UITextField! var startDateDownPicker: DateDownPicker! var startTimeDownPicker: DateDownPicker! var endDateDownPicker: DateDownPicker! var endTimeDownPicker: DateDownPicker! override func viewDidLoad() { super.viewDidLoad() self.clientNameLabel.text = self.clientName self.fieldLabel.text = self.fieldType // configure textField borders let borderThickness: CGFloat = 1.5 let borderColor = UIColor(white: 0, alpha: 0.15) self.startDateTextField.layer.addBorder(UIRectEdge.Bottom, color: borderColor, thickness: borderThickness) self.startTimeTextField.layer.addBorder(UIRectEdge.Bottom, color: borderColor, thickness: borderThickness) self.endDateTextField.layer.addBorder(UIRectEdge.Bottom, color: borderColor, thickness: borderThickness) self.endTimeTextField.layer.addBorder(UIRectEdge.Bottom, color: borderColor, thickness: borderThickness) let contactStartDate: NSDate let contactEndDate: NSDate if let sDate = self.startDate, let eDate = self.endDate { contactStartDate = sDate contactEndDate = eDate } else { contactStartDate = defaultStartDate contactEndDate = defaultEndDate } // configure drop down wrappers self.startDateDownPicker = DateDownPicker(textField: self.startDateTextField, withDate: contactStartDate, withMode: UIDatePickerMode.Date) self.startTimeDownPicker = DateDownPicker(textField: self.startTimeTextField, withDate: contactStartDate, withMode: UIDatePickerMode.Time) self.endDateDownPicker = DateDownPicker(textField: self.endDateTextField, withDate: contactEndDate, withMode: UIDatePickerMode.Date) self.endTimeDownPicker = DateDownPicker(textField: self.endTimeTextField, withDate: contactEndDate, withMode: UIDatePickerMode.Time) self.startDateDownPicker.addTarget(self, action: Selector("dateChanged"), forControlEvents: UIControlEvents.ValueChanged) self.startTimeDownPicker.addTarget(self, action: Selector("dateChanged"), forControlEvents: UIControlEvents.ValueChanged) self.endDateDownPicker.addTarget(self, action: Selector("dateChanged"), forControlEvents: UIControlEvents.ValueChanged) self.endTimeDownPicker.addTarget(self, action: Selector("dateChanged"), forControlEvents: UIControlEvents.ValueChanged) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func dateChanged() { self.delegate?.didEditPage(self) } func getStartDate() -> NSDate { let sDate = NSDate.combineDateAndTime(self.startDateDownPicker.getDate(), time: self.startTimeDownPicker.getDate()) return sDate } func getEndDate() -> NSDate { let eDate = NSDate.combineDateAndTime(self.endDateDownPicker.getDate(), time: self.endTimeDownPicker.getDate()) return eDate } @IBAction func informationButton(sender: UIButton) { //description of the view let discriptionText = Functions.message("Date") //Custom box for the view Functions.discriptionBox(fieldLabel.text!, message: discriptionText) } /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { // Get the new view controller using segue.destinationViewController. // Pass the selected object to the new view controller. } */ } CATemplatePageController.swift // // CATemplatePageController.swift // CaseAide // // Created by Alan Perez on 8/25/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class CATemplatePageController: CAPageController, CAPageControllerDelegate, UITableViewDataSource, UITableViewDelegate { var templates: [CAField]? private var templateDataSource: [CAField]! let editTemplateSegueIdentifier = "editTemplateSegueIdentifier" @IBOutlet weak var clientNameLabel: UILabel! @IBOutlet weak var tableView: UITableView! var selectedIndex: Int! let templateCellReuseIdentifier = "templateCellReuseIdentifier" override func viewDidLoad() { super.viewDidLoad() self.templateDataSource = self.templates self.clientNameLabel?.text = self.clientName self.tableView.dataSource = self self.tableView.delegate = self } // MARK: TableView data source func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.templateDataSource.count } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier(self.templateCellReuseIdentifier, forIndexPath: indexPath) as! UITableViewCell cell.textLabel?.text = self.templateDataSource[indexPath.row].name return cell } // MARK: TableView delegate func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { tableView.deselectRowAtIndexPath(indexPath, animated: true) self.selectedIndex = indexPath.row self.performSegueWithIdentifier(self.editTemplateSegueIdentifier, sender: self) } // MARK: CAPageController delegate method func didEditPage(pageController: CAPageController) { println("did edit page") // get notified when user changes text for template in blob controller self.templateDataSource[self.selectedIndex].value = (pageController as! CABlobPageController).getText() // notify our delegate that we changed self.delegate?.didEditPage(self) } func getTemplates() -> [CAField] { return self.templateDataSource } // MARK: - Navigation override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == self.editTemplateSegueIdentifier { let blobController = segue.destinationViewController as! CABlobPageController let selectedTemplate = self.templateDataSource[self.selectedIndex] blobController.clientName = self.clientName blobController.fieldType = selectedTemplate.name blobController.text = selectedTemplate.value blobController.delegate = self } } @IBAction func informationButton(sender: UIButton) { let discriptionText = "Choose a subject below to make notes on. " Functions.discriptionBox("Templates", message: discriptionText) } } CAToast.swift // // CAToast.swift // CaseAide // // Created by Alan on 11/12/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import UIKit // dependent upon Toast swift extension library class CAToast { class func makeToast(message: String) { UIApplication.sharedApplication().delegate?.window??.makeToast(message: message) } } CheckboxCell.swift // // CheckboxCell.swift // CaseAide // // Created by Alan on 8/21/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class CheckboxCell: UITableViewCell { @IBOutlet weak var optionLabel: UILabel! override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ClientDistributorTabController.swift // // ClientHubController.swift // CaseAide // // Created by Alan Perez on 8/18/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import UIKit class ClientResourceController: UIViewController { var client: CAClient? func clientUpdated(client: CAClient) { println("Override this method to listen for client updates") } } class ClientDistributorTabController: UITabBarController { var client: CAClient? private let api = CAApi.defaultApi override func viewDidLoad() { super.viewDidLoad() self.title = client?.name self.navigationItem.backBarButtonItem = UIBarButtonItem.defaultCABackButton() // give all our resources a copy of our client self.enumerateClientResourceControllers({ (var resourceController) -> () in resourceController.client = self.client }) } override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) if let c = self.client { // update client whenever client distributor is visited self.api.readClient(c.id, completionHandler: { responseClient, responseError in if let validResponseClient = responseClient { self.client = validResponseClient // safely update clients for client resources self.enumerateClientResourceControllers({ resourceController in resourceController.client = validResponseClient if self.activeResourceController() == resourceController { resourceController.clientUpdated(validResponseClient) } }) } }) } } // MARK: helper functions // enumerate through client resources func enumerateClientResourceControllers(action: (ClientResourceController) -> ()) { if let viewControllers = self.viewControllers { for controller in viewControllers { if var resource = controller as? ClientResourceController { action(resource) } } } } func activeResourceController() -> ClientResourceController? { let activeIndex = self.selectedIndex return self.viewControllers?[activeIndex] as? ClientResourceController } } ClientFiltering.swift // // ClientFiltering.swift // CaseAide // // Created by Alan Perez on 9/2/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import Foundation // MARK: Defining client manipulation behaviors protocol ClientFilterBehavior { func filter(clients: [CAClient]) -> [CAClient] } // MARK: Filtering algorithms class ClientFilterByAll: ClientFilterBehavior { func filter(clients: [CAClient]) -> [CAClient] { return clients } } class ClientFilterBySeen: ClientFilterBehavior { func filter(clients: [CAClient]) -> [CAClient] { let filteredClients = clients.filter { $0.seen == true } return filteredClients } } class ClientFilterByUnseen: ClientFilterBehavior { func filter(clients: [CAClient]) -> [CAClient] { let filteredClients = clients.filter { $0.seen == false } return filteredClients } } ClientInfoCell.swift // // ClientInfoCell.swift // CaseAide // // Created by Andrew on 9/11/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class ClientInfoCell: MGSwipeTableCell { override func layoutSubviews() { super.layoutSubviews() self.contentView.layoutIfNeeded() let frame = self.textLabel?.frame ?? CGRectMake(0, 0, 320, 50) self.textLabel?.preferredMaxLayoutWidth = CGRectGetWidth(frame) } } ClientInfoController.swift // // ClientInfoController.swift // CaseAide // // Created by Alan Perez on 8/18/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import UIKit class ClientInfoController: ClientResourceController, UITableViewDataSource, UITableViewDelegate, UIActionSheetDelegate, MGSwipeTableCellDelegate, UIAlertViewDelegate { let addInfoSegueIdentifier = "addInfoSegueIdentifier" let editInfoSequeIdentifier = "editInfoSequeIdentifier" let basicContactInfoCellReuseIdentifier = "clientInfoCellReuseIdentifier" let contactCategoryCellReuseIdentifier = "sectionCellReuseIdentifier" let contactCategoryCellHeight: CGFloat = 60.0 let minCellHeight: CGFloat = 60.0 private let api = CAApi.defaultApi let phoneSection = 0 let emailSection = 1 let addressSection = 2 //objects to edit private var phoneNumberToEdit: CAPhoneNumber? private var emailToEdit: CAEmail? private var addressToEdit: CAAddress? private var indexPath: NSIndexPath? @IBOutlet weak var clientView: ClientView! @IBOutlet weak var tableView: UITableView! private lazy var prototypeClientInfoCell: ClientInfoCell = self.createClientInfoCell() private var selectedPhoneNumber: CAPhoneNumber? private lazy var addInfoBarButton: UIBarButtonItem = self.createAddBarButton() private lazy var phoneNumberActionSheet: UIActionSheet = self.createPhoneNumberActionSheet() override func viewDidLoad() { super.viewDidLoad() self.navigationItem.backBarButtonItem = UIBarButtonItem.defaultCABackButton() self.updateClientView() // register section header nib tableView.registerNib(UINib(nibName: "ClientInfoSectionView", bundle: NSBundle.mainBundle()), forHeaderFooterViewReuseIdentifier: contactCategoryCellReuseIdentifier) // show + without delay when first loaded self.tabBarController?.navigationItem.rightBarButtonItem = self.addInfoBarButton } override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) // show + button whenever client info page appears self.tabBarController?.navigationItem.rightBarButtonItem = self.addInfoBarButton } override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) // hide + button self.tabBarController?.navigationItem.rightBarButtonItem = nil } override func clientUpdated(client: CAClient) { self.client = client self.updateClientView() // ensure tableView has been created self.tableView?.reloadData() } // MARK: UITableView data source methods func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 3 } func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let headerView = self.tableView.dequeueReusableHeaderFooterViewWithIdentifier(contactCategoryCellReuseIdentifier) as! ClientInfoSectionView self.configHeader(headerView, section: section) return headerView } func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return self.contactCategoryCellHeight } func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return UITableViewAutomaticDimension } func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { self.configCell(self.prototypeClientInfoCell, indexPath: indexPath) return self.calculateHeight(self.prototypeClientInfoCell) } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let nRows: Int switch section { case phoneSection: nRows = self.client?.phoneNumbers.count ?? 0 case emailSection: nRows = self.client?.emails.count ?? 0 case addressSection: nRows = self.client?.addresses.count ?? 0 default: nRows = 0 } return nRows } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier(self.basicContactInfoCellReuseIdentifier, forIndexPath: indexPath) as! ClientInfoCell self.configCell(cell, indexPath: indexPath) return cell } func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { tableView.deselectRowAtIndexPath(indexPath, animated: true) self.handleCellSelection(indexPath) } // MARK: UIActionSheet delegate func actionSheet(actionSheet: UIActionSheet, clickedButtonAtIndex buttonIndex: Int) { if actionSheet == self.phoneNumberActionSheet { self.handlePhoneAction(buttonIndex) } } // MARK: MGSwipeTableCell delegate methods func swipeTableCell(cell: MGSwipeTableCell!, canSwipe direction: MGSwipeDirection) -> Bool { if direction == MGSwipeDirection.RightToLeft { return true } return false } func swipeTableCell(cell: MGSwipeTableCell!, swipeButtonsForDirection direction: MGSwipeDirection, swipeSettings: MGSwipeSettings!, expansionSettings: MGSwipeExpansionSettings!) -> [AnyObject]! { // when cell is expanded, delete action is triggered expansionSettings.buttonIndex = 0 expansionSettings.fillOnTrigger = true expansionSettings.threshold = 1.5 swipeSettings.transition = MGSwipeTransition.Border let editButton = MGSwipeButton(title: "Edit", backgroundColor: UIColor.caseAideBlue(), callback: { (sender: MGSwipeTableCell!) -> Bool in if let indexPath = self.tableView.indexPathForCell(sender) { self.editClientInfo(indexPath) } return true }) let deleteButton = MGSwipeButton(title: "Delete", backgroundColor: UIColor.redColor(), callback: { (sender: MGSwipeTableCell!) -> Bool in if let indexPath = self.tableView.indexPathForCell(sender) { self.indexPath = indexPath self.deleteAlert() //self.deleteClientInfo(indexPath) } return true }) return [deleteButton, editButton] } // MARK: Helper functions func deleteClientInfo(indexPath: NSIndexPath) { switch indexPath.section { case self.phoneSection: self.deletePhoneNumber(indexPath.row) case self.emailSection: self.deleteEmail(indexPath.row) case self.addressSection: self.deleteAddress(indexPath.row) default: break } } func deletePhoneNumber(index: Int) { if let phoneNumber = self.client?.phoneNumbers[index], let client = self.client { // delete phone number on server api.deletePhone(client.id, phone: phoneNumber, completionHandler: { (error) -> Void in if let updateError = error { // self.alertError(updateError) self.toastError(updateError) } else { self.client?.phoneNumbers.removeAtIndex(index) self.deleteCell(NSIndexPath(forRow: index, inSection: self.phoneSection)) } }) } println("now handling deleting phone number") } func deleteEmail(index: Int) { if let email = self.client?.emails[index], let client = self.client { api.deleteEmail(client.id, email: email, completionHandler: { (error) -> Void in if let updateError = error { self.toastError(updateError) } else { self.client?.emails.removeAtIndex(index) self.deleteCell(NSIndexPath(forRow: index, inSection: self.emailSection)) } }) } println("now handling deleting email address") } func deleteAddress(index: Int) { if let address = self.client?.addresses[index], let client = self.client { api.deleteAddress(client.id, address: address, completionHandler: { (error) -> Void in if let updateError = error { self.toastError(updateError) } else { self.client?.addresses.removeAtIndex(index) // remove from data source self.deleteCell(NSIndexPath(forRow: index, inSection: self.addressSection)) // delete cell on tableview } }) } println("now handling deleting address") } func editClientInfo(indexPath: NSIndexPath) { switch indexPath.section { case self.phoneSection: self.editPhoneNumber(indexPath.row) case self.emailSection: self.editEmail(indexPath.row) case self.addressSection: self.editAddress(indexPath.row) default: break } } func editPhoneNumber(index: Int) { self.phoneNumberToEdit = self.client?.phoneNumbers[index] println("Phone Number to edit: \(self.phoneNumberToEdit?.number)") self.performSegueWithIdentifier(editInfoSequeIdentifier, sender: self) println("now handling edit phone number") } func editEmail(index: Int) { self.emailToEdit = self.client?.emails[index] self.performSegueWithIdentifier(editInfoSequeIdentifier, sender: self) println("now handling editing email address") } func editAddress(index: Int) { self.addressToEdit = self.client?.addresses[index] self.performSegueWithIdentifier(editInfoSequeIdentifier, sender: self) println("now handling editing address") } func handlePhoneAction(index: Int) { if let phone = self.selectedPhoneNumber { switch index { case 0: // calling self.call(phone.number) case 1: // texting self.text(phone.number) default: break } } } func configHeader(view: ClientInfoSectionView, section: Int) { view.backgroundView?.backgroundColor = UIColor(white: 1, alpha: 1) switch section { case phoneSection: view.titleLabel.text = "PHONE NUMBERS" view.typeImageView.image = UIImage(named: "phone") case emailSection: view.titleLabel.text = "EMAILS" view.typeImageView.image = UIImage(named: "email") case addressSection: view.titleLabel.text = "ADDRESSES" view.typeImageView.image = UIImage(named: "adress") default: break } } func configCell(cell: ClientInfoCell, indexPath: NSIndexPath) { switch indexPath.section { case phoneSection: let phone = self.client?.phoneNumbers[indexPath.row] cell.textLabel?.text = phone?.number case emailSection: let email = self.client?.emails[indexPath.row] cell.textLabel?.text = email?.address case addressSection: let address = self.client?.addresses[indexPath.row] cell.textLabel?.text = address?.address default: break } cell.delegate = self } func handleCellSelection(indexPath: NSIndexPath) { switch indexPath.section { case phoneSection: self.selectedPhoneNumber = self.client?.phoneNumbers[indexPath.row] self.phoneNumberActionSheet.showInView(self.view) case emailSection: if let email = self.client?.emails[indexPath.row] { self.email(email.address) } case addressSection: if let address = self.client?.addresses[indexPath.row] { self.maps(address.address) } default: break } } func updateClientView() { let clientName = self.client?.name var clientStatus: String? var clientStatusImg: UIImage? if let clientSeen = self.client?.seen { if clientSeen { clientStatus = "SEEN" clientStatusImg = UIImage(named: "seen_client.png") } else { clientStatus = "UNSEEN" clientStatusImg = UIImage(named: "client_icon_page_red.png") } } let clientView = self.tableView.tableHeaderView as? ClientView clientView?.nameLabel.text = clientName clientView?.statusLabel.text = clientStatus clientView?.statusImg.image = clientStatusImg } func call(number: String) { if let encodedNumber = self.encodeString(number) { UIApplication.sharedApplication().openURL(NSURL(string: "tel://\(encodedNumber)")!) } } func text(number: String) { if let encodedNumber = self.encodeString(number) { UIApplication.sharedApplication().openURL(NSURL(string: "sms:\(encodedNumber)")!) } } func email(address: String) { if let encodedEmail = self.encodeString(address) { UIApplication.sharedApplication().openURL(NSURL(string: "mailto:\(encodedEmail)")!) } } func maps(address: String) { if let encodedAddress = self.encodeString(address) { UIApplication.sharedApplication().openURL(NSURL(string: "http://maps.apple.com/?q=\(encodedAddress)")!) } } func encodeString(s: String) -> String? { return s.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) } func createAddBarButton() -> UIBarButtonItem { var addButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Add, target: self, action: "addInfo") addButton.tintColor = UIColor.whiteColor() return addButton } func createPhoneNumberActionSheet() -> UIActionSheet { var actionSheet = UIActionSheet(title: "Phone options", delegate: self, cancelButtonTitle: nil, destructiveButtonTitle: nil) actionSheet.addButtonWithTitle("Call") actionSheet.addButtonWithTitle("Text") actionSheet.addButtonWithTitle("Cancel") actionSheet.cancelButtonIndex = 2 return actionSheet } func createClientInfoCell() -> ClientInfoCell { return self.tableView.dequeueReusableCellWithIdentifier(self.basicContactInfoCellReuseIdentifier) as! ClientInfoCell } func calculateHeight(cell: UITableViewCell) -> CGFloat { cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(self.tableView.bounds), CGRectGetHeight(cell.bounds)) cell.layoutIfNeeded() let size = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) // take into account cell seperator height let calculatedHeight = size.height + 1 return max(calculatedHeight, self.minCellHeight) } func addInfo() { println("Add Client Info called") self.performSegueWithIdentifier(self.addInfoSegueIdentifier, sender: self) } override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == addInfoSegueIdentifier { var addInfo = segue.destinationViewController as! CAAddClientInfo addInfo.client = self.client } else if segue.identifier == editInfoSequeIdentifier { var addInfo = segue.destinationViewController as! ClientInfoEditPopOverViewController addInfo.client = self.client addInfo.phoneNumber = self.phoneNumberToEdit addInfo.email = self.emailToEdit addInfo.address = self.addressToEdit } } //temporary experimental functions //take indexed value and check for type //before beging the popOver func editSeque(reciever: AnyObject,editor: ClientInfoEditPopOverViewController){ if let phoneNumber = reciever as? CAPhoneNumber { //send phoneNumber to popOver editor.phoneNumber = self.phoneNumberToEdit println("Edit Phone Number") } else if let email = reciever as? CAEmail { //send email to popOver editor.email = self.emailToEdit println("Edit email") }else if let address = reciever as? CAAddress { //send Address to popOver editor.address = self.addressToEdit println("Edit Address") } } func toastError(error: NSError) { self.view.makeToast(message: error.localizedDescription) } func deleteCell(indexPath: NSIndexPath) { self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Left) } func deleteAlert(){ var alert = UIAlertView() alert.delegate = self alert.title = "Delete?"; alert.message = "Would you like to delete this content?"; alert.addButtonWithTitle("Delete") alert.addButtonWithTitle("No") alert.show() } func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) { switch buttonIndex { case 0: if let index = self.indexPath { self.deleteClientInfo(index) } self.indexPath = nil println("Delete") break; case 1: println("No") break; default: println("default called") break; } } } ClientInfoEditPopOverViewController.swift // // ClientInfoEditPopOverViewController.swift // CaseAide // // Created by Andrew on 10/17/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import UIKit class ClientInfoEditPopOverViewController: UIViewController, UITextFieldDelegate { private let api: CAApi = CAApi.defaultApi //feed in from contact page via perform seque var phoneNumber: CAPhoneNumber? var email: CAEmail? var address: CAAddress? var client: CAClient? @IBOutlet weak var editContactTextFiled: AndroidTextField! @IBOutlet weak var headerTextLabel: UILabel! @IBOutlet weak var saveButton: UIButton! private var keyboardShown: Bool = false private var keyboardRectWRTView = CGRectZero private var preKeyboardContentOffset = CGPointZero override func viewWillAppear(animated: Bool) { customizeView() customBackButton() setTitle() //keyboard logic NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil) } override func viewDidLoad() { self.editContactTextFiled.delegate = self let tapGesture = UITapGestureRecognizer(target: self, action: Selector("dismissKeyboard")) self.view.addGestureRecognizer(tapGesture) //testing seque } override func viewWillDisappear(animated: Bool) { NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil) NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil) self.phoneNumber?.number = "" self.email?.address = "" self.address?.address = "" } func setTitle(){ println(phoneNumber?.number) if (phoneNumber?.number != nil && phoneNumber?.number != "" ) { headerTextLabel.text = "Edit Phone Number" editContactTextFiled.text = self.phoneNumber?.number } else if (email?.address != nil && email?.address != "") { headerTextLabel.text = "Edit Email Address" editContactTextFiled.text = self.email?.address } else if (address?.address != nil && address?.address != "" ) { headerTextLabel.text = "Edit Address" editContactTextFiled.text = self.address?.address } } @IBAction func doneButton(sender: UIButton) { self.dismissViewControllerAnimated(true, completion: nil) } func saveEditedContact(){ if (phoneNumber?.number != nil && phoneNumber?.number != "" ){ var number: String = editContactTextFiled.text //change into phoneNumber format //println(number) println(number) if count(number) == 10 { if checkNumber(number) { number = convertToPhoneNumberStandard(number) self.phoneNumber?.number = number println(number) saveNumber() println("Is a valid integer") } else { Functions.alert("Invalid Input", message: "Please enter only numeric characters, and include area code.") println("Is not a valid integer") } } else { Functions.alert("Invalid Input", message: "Please enter a ten digit phone number") println("Is not a valid integer") } } else if (self.email?.address != nil && self.email?.address != "" ) { email?.address = editContactTextFiled.text if let validClient = client , let email = email { api.updateEmail(validClient.id, email: email, completionHandler: { (error) -> Void in if let updateError = error { self.alertError(updateError) }else{ self.view.makeToast(message: "Email Address Saved") } }) } } else if (self.address?.address != nil && self.address?.address != "" ) { address?.address = editContactTextFiled.text if let validClient = self.client , let address = self.address { api.updateAddress(validClient.id, address: address, completionHandler: { (error) -> Void in if let updateError = error { self.alertError(updateError) }else{ self.view.makeToast(message: "Address Saved") } }) } } self.editContactTextFiled.text = "" } @IBAction func saveButton(sender: UIButton) { dismissKeyboard() saveEditedContact() } func customizeView() { saveButton.backgroundColor = UIColor.caseAideBlue() var greyColor = UIColor(red: 195/255, green: 195/255, blue: 195/255, alpha: 1) editContactTextFiled.addBottomBorderWithColor(greyColor, borderWidth: 0.5) } func dismissKeyboard() { self.view.endEditing(true) } func keyboardWillShow(notification: NSNotification) { let window = UIApplication.sharedApplication().keyWindow! let keyboardInfo = notification.userInfo! // keyboard frame with respect to screen let keyboardRectWRTScreen = keyboardInfo[UIKeyboardFrameEndUserInfoKey]!.CGRectValue() // set parameters and shift for first textfield focus self.keyboardRectWRTView = self.view.convertRect(keyboardRectWRTScreen, fromView: nil) self.keyboardShown = true } func keyboardWillHide(notification: NSNotification) { keyboardShown = false } func textFieldShouldReturn(textField: UITextField) -> Bool { if textField === editContactTextFiled{ editContactTextFiled.resignFirstResponder() //perform edit here<== saveEditedContact() } return true } func alertError(error: NSError) { UIAlertView(title: error.localizedDescription, message: error.localizedRecoverySuggestion, delegate: nil, cancelButtonTitle: "Ok").show() } func customBackButton(){ let newBackButton = UIBarButtonItem(image: UIImage(named: "back"), style: UIBarButtonItemStyle.Plain, target: self, action: "backAction") self.navigationItem.leftBarButtonItem = newBackButton } func convertToPhoneNumberStandard(phoneNumber: String!) -> String{ var number = "(" number += indexing(phoneNumber, beginingIndex: 0, endingIndex: -7) number += ") " number += indexing(phoneNumber, beginingIndex: 3, endingIndex: -4) number += "-" number += indexing(phoneNumber, beginingIndex: 6, endingIndex: 0) return number } //A way to index strings without using integer notation func indexing(string: String, beginingIndex: Int, endingIndex: Int) ->String{ var stringToReturn: String var index = advance(string.startIndex, beginingIndex) string[index] var endIndex = advance(string.endIndex, endingIndex) stringToReturn = string[Range(start: index, end: endIndex)] return stringToReturn } func saveNumber(){ if let validClient = client , let phone = phoneNumber { api.updatePhone(validClient.id, phone: phone, completionHandler: { (error) -> Void in if let updateError = error { self.alertError(updateError) }else{ self.view.makeToast(message: "Phone Number Saved") } }) } } func checkNumber(string: String) -> Bool { var confirmedStringIsANumber = "" for char in string { let i = "\(char)" if let checkInteger = i.toInt() { confirmedStringIsANumber += i } if confirmedStringIsANumber == string { return true } } return false } } ClientInfoSectionView.swift // // ClientInfoSectionView.swift // CaseAide // // Created by Alan Perez on 9/14/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class ClientInfoSectionView: UITableViewHeaderFooterView { @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var typeImageView: UIImageView! } ClientInfoTabController.swift // // ClientInfoTabController.swift // CaseAide // // Created by Alan Perez on 7/28/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class ClientInfoTabController: UIViewController, UITableViewDataSource, UITableViewDelegate { let clientInfoCellReuseIdentifier = "clientInfoCellReuseIdentifier" let clientInfoHeight: CGFloat = 275.0 var clientInfoView: ClientInfoView! @IBOutlet var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() self.tableView.dataSource = self self.tableView.delegate = self self.navigationController?.navigationBar.layer.shadowColor = UIColor.blackColor().CGColor self.navigationController?.navigationBar.layer.shadowOpacity = 0.25 self.navigationController?.navigationBar.layer.shadowOffset = CGSizeMake(1.0, 1.0) self.navigationController?.navigationBar.layer.shadowRadius = 2.0 clientInfoView = self.tableView.tableHeaderView as! ClientInfoView // Do any additional setup after loading the view. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // MARK: TableView datasource methods func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 3; } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 2; } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier(clientInfoCellReuseIdentifier) as! UITableViewCell // cell.textLabel?.text = "Cell contact" return cell } // MARK: TableView delegate methods func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { tableView.deselectRowAtIndexPath(indexPath, animated: true) } /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { // Get the new view controller using segue.destinationViewController. // Pass the selected object to the new view controller. } */ } ClientInfoView.swift // // ClientInfoView.swift // CaseAide // // Created by Alan Perez on 7/28/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class ClientInfoView: UIView { @IBOutlet weak var statusImageView: UIImageView! @IBOutlet weak var statusLabel: UILabel! @IBOutlet weak var nameLabel: UILabel! /* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. override func drawRect(rect: CGRect) { // Drawing code } */ } ClientSorting.swift // // ClientSorting.swift // CaseAide // // Created by Alan Perez on 9/1/15. // Copyright (c) 2015 CSUSB Mobile apps. All rights reserved. // import Foundation // MARK: Grouping Algorithms protocol ClientSortBehavior { func sort(clients: [CAClient]) -> [CAClient] } class ClientSortByFirstName: ClientSortBehavior { func sort(clients: [CAClient]) -> [CAClient] { var clientsSortedByFirstName = clients.sorted {(clientA: CAClient, clientB: CAClient) -> Bool in let aFirstName = split(clientA.name) {$0 == " "}[0] let bFirstName = split(clientB.name) {$0 == " "}[0] return aFirstName < bFirstName } return clientsSortedByFirstName; } } class ClientSortByLastName: ClientSortBehavior { func sort(clients: [CAClient]) -> [CAClient] { let clientsSortedByLastName = clients.sorted {(clientA: CAClient, clientB: CAClient) -> Bool in let aLastName = split(clientA.name) {$0 == " "}[1] let bLastName = split(clientB.name) {$0 == " "}[1] return aLastName < bLastName } return clientsSortedByLastName } } class ClientSortByHearing: ClientSortBehavior { func sort(clients: [CAClient]) -> [CAClient] { let clientsSortedByHearing = clients.sorted { (clientA: CAClient, clientB: CAClient) -> Bool in let aDays = clientA.daysUntilHearing let bDays = clientB.daysUntilHearing // Goal: arrange clients by hearing with the closest dates coming first and all clients with no upcoming hearing to end // 4 possible cases of hearings: // 1. a has upcoming hearings b has no upcoming hearings: return true since we want a valid date to come before invalid date // 2. both have no upcoming hearings: return whatever if (!self.upcomingHearing(aDays) && !self.upcomingHearing(bDays)) || (self.upcomingHearing(aDays) && !self.upcomingHearing(bDays)) { return true } // 3. a has no upcoming hearings b has upcoming hearings: return false since we want to push invalid days to end else if (!self.upcomingHearing(aDays) && self.upcomingHearing(bDays)) { return false } // 4. both have upcoming hearings: perform test to see if a comes before b else { return aDays < bDays } } return clientsSortedByHearing } func upcomingHearing(days: Int) -> Bool { if days < 0 { return false } return true } } ClientTableViewCell.swift // // ClientTableViewCell.swift // CaseAide // // Created by Alan Perez on 7/21/15. // Copyright (c) 2015 CSUSB Mobile apps. All rights reserved. // import UIKit class ClientTableViewCell: UITableViewCell { @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var detailsLabel: UILabel! @IBOutlet weak var clientImageView: UIImageView! } ClientView.swift // // ClientView.swift // CaseAide // // Created by Alan Perez on 8/18/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class ClientView: UIView { @IBOutlet weak var statusLabel: UILabel! @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var statusImg: UIImageView! /* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. override func drawRect(rect: CGRect) { // Drawing code } */ } ClientsController.swift // // ClientsController.swift // CaseAide // // Created by Alan Perez on 8/17/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import UIKit class ClientsController: UIViewController, UITableViewDataSource, UITableViewDelegate, UIActionSheetDelegate, UISearchDisplayDelegate, UISearchBarDelegate { // declare and initialize models private let api = CAApi.defaultApi var clients = [CAClient]() @IBOutlet weak var tableView: UITableView! @IBOutlet weak var noClientsView: UIView! @IBOutlet weak var activityIndicator: UIActivityIndicatorView! private lazy var clientTableDataSource: (sections: [String], sectionedRows: [String: [CAClient]]) = self.constructTableDataSource() private lazy var searchTableDataSource: [CAClient] = self.constructSearchTableDataSource("") var selectedClient: CAClient? var isLoadingClients = false { didSet { if isLoadingClients { activityIndicator.startAnimating() } else { activityIndicator.stopAnimating() } } } // default sorting and filtering behaviors private var sortBehavior: ClientSortBehavior = ClientSortByFirstName() private var filterBehavior: ClientFilterBehavior = ClientFilterByAll() // UIActionSheets private lazy var sortingActionSheet: UIActionSheet = self.createSortingActionSheet() private lazy var filteringActionSheet: UIActionSheet = self.createFilteringActionSheet() // Navigation bar buttons private lazy var filterButton: UIButton = self.createFilterButton() private lazy var sortBarButton: UIBarButtonItem = self.createSortBarButtonItem() private lazy var resourcesBarButton: UIBarButtonItem = { return UIBarButtonItem(image: UIImage(named: "document-icon"), style: UIBarButtonItemStyle.Plain, target: self, action: Selector("navigateToResources")) }() // replace back button with a logout button @IBAction func logout(sender: UIBarButtonItem) { self.navigationController?.popToRootViewControllerAnimated(true) } struct Constants { static let LastTimeClientNotifiedKey = "ClientsController.lastTimeUserNotified" static let ClientCellReuseIdentifier = "clientReuseIdentifier" static let ClientCellHeight: CGFloat = 100.0 static let ClientHubSegueIdentifier = "clientDetailSegueIdentifier" static let ResourcesSegueIdentifier = "resourcesSegueIdentifier" } // MARK: User client notification var numberOfClientsNeededToBeSeen: Int { return clients.filter { !$0.seen }.count } var lastTimeUserWasNotifiedAboutClients: NSDate? { get { return NSUserDefaults.standardUserDefaults().objectForKey(Constants.LastTimeClientNotifiedKey) as? NSDate } set { NSUserDefaults.standardUserDefaults().setObject(newValue, forKey: Constants.LastTimeClientNotifiedKey) } } func shouldNotifyUserAboutClients() -> Bool { // only notify user if it is a wednesday and we have not notified them today let todaysDate = NSDate() if todaysDate.isWednesday() && (lastTimeUserWasNotifiedAboutClients == nil || todaysDate.isNotSameDay(lastTimeUserWasNotifiedAboutClients!)) && numberOfClientsNeededToBeSeen > 0 { return true } return false } func notifyUserAboutClientsNeededToBeSeen() { UIAlertView(title: "Client status", message: "\(numberOfClientsNeededToBeSeen) clients need to be seen this month", delegate: nil, cancelButtonTitle: "OK").show() let rightNow = NSDate() lastTimeUserWasNotifiedAboutClients = rightNow } // MARK: Controller lifecycle override func viewDidLoad() { super.viewDidLoad() // Configuring navigation bar buttons self.navigationItem.rightBarButtonItems = [self.sortBarButton, self.resourcesBarButton] self.navigationItem.titleView = self.filterButton self.navigationItem.backBarButtonItem = UIBarButtonItem.defaultCABackButton() self.tableView.registerNib(UINib(nibName: "ClientCell", bundle: NSBundle.mainBundle()), forCellReuseIdentifier: Constants.ClientCellReuseIdentifier) self.searchDisplayController?.searchResultsTableView.registerNib(UINib(nibName: "ClientCell", bundle: NSBundle.mainBundle()), forCellReuseIdentifier: Constants.ClientCellReuseIdentifier) } override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) // place filter button drop down arrow on rhs self.filterButton.transform = CGAffineTransformMakeScale(-1.0, 1.0) self.filterButton.titleLabel?.transform = CGAffineTransformMakeScale(-1.0, 1.0) self.filterButton.imageView?.transform = CGAffineTransformMakeScale(-1.0, 1.0) self.fetchClients() } // MARK: TableView data source func numberOfSectionsInTableView(tableView: UITableView) -> Int { if tableView == self.tableView { return self.clientTableDataSource.sections.count } else { return 1 } } func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { if tableView == self.tableView { return self.clientTableDataSource.sections[section] } else { return nil } } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if tableView == self.tableView { let secTitle = self.clientTableDataSource.sections[section] return self.clientTableDataSource.sectionedRows[secTitle]?.count ?? 0 } else { return self.searchTableDataSource.count } } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier(Constants.ClientCellReuseIdentifier, forIndexPath: indexPath) as! ClientTableViewCell if tableView == self.tableView { let secTitle = self.clientTableDataSource.sections[indexPath.section] if let clientsAtSection = self.clientTableDataSource.sectionedRows[secTitle] { let client = clientsAtSection[indexPath.row] self.configCell(cell, client: client) } } else { self.configCell(cell, client: self.searchTableDataSource[indexPath.row]) } return cell } func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return Constants.ClientCellHeight } // MARK: TableView delegate func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { tableView.deselectRowAtIndexPath(indexPath, animated: true) if tableView == self.tableView { let secTitle = self.clientTableDataSource.sections[indexPath.section] if let clientsAtSection = self.clientTableDataSource.sectionedRows[secTitle] { self.selectedClient = clientsAtSection[indexPath.row] } } else { self.selectedClient = self.searchTableDataSource[indexPath.row] } self.performSegueWithIdentifier(Constants.ClientHubSegueIdentifier, sender: self) } // MARK: Search controller methods func searchDisplayController(controller: UISearchDisplayController, shouldReloadTableForSearchString searchString: String!) -> Bool { // reload search table data source with search query self.searchTableDataSource = self.constructSearchTableDataSource(searchString) return true } // MARK: UIActionSheet delegate func actionSheet(actionSheet: UIActionSheet, clickedButtonAtIndex buttonIndex: Int) { if actionSheet == self.sortingActionSheet { self.handleSortActionSheetButtonClick(buttonIndex) } else if actionSheet == self.filteringActionSheet { self.handleFilterActionSheetButtonClick(buttonIndex) } } // MARK: Helper functions func findClients(clients:[CAClient], thatContain string: String) -> [CAClient] { return clients.filter { let clientName = $0.name let found = clientName.rangeOfString(string, options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil, locale: nil) != nil return found } } func getClientFirstName(client: CAClient) -> String { return split(client.name) {$0 == " "}[0] } func getClientLastName(client: CAClient) -> String { return split(client.name) {$0 == " "}[1] } func getDaysUntilClientHearing(client: CAClient) -> Int { return client.daysUntilHearing } // get client title based on type of sorting being used func getClientTitle(client: CAClient) -> String { let clientTitle: String let firstName = self.getClientFirstName(client) let lastName = self.getClientLastName(client) if self.sortBehavior is ClientSortByLastName { clientTitle = "\(lastName), \(firstName)" } else { clientTitle = "\(firstName) \(lastName)" } return clientTitle } func getSectionHeaderTitle(client: CAClient) -> String { let sectionHeaderTitle: String if self.sortBehavior is ClientSortByFirstName { let firstName = self.getClientFirstName(client) sectionHeaderTitle = firstName.firstCharString() } else if self.sortBehavior is ClientSortByLastName { let lastName = self.getClientLastName(client) sectionHeaderTitle = lastName.firstCharString() } else { let daysStr: String let days = self.getDaysUntilClientHearing(client) if days < 0 { daysStr = "N/A" } else if days == 1 { daysStr = "\(days) day" } else { daysStr = "\(days) days" } sectionHeaderTitle = daysStr } return sectionHeaderTitle } func partitionClients(clients: [CAClient]) -> (sections: [String], clients: [String: [CAClient]]) { var orderedSections = Array<String>() var groupedClients = [String: [CAClient]]() for client in clients { let secTitle = self.getSectionHeaderTitle(client) if groupedClients[secTitle] == nil { // create key entry groupedClients[secTitle] = [CAClient]() // maintain order that the keys as encountered orderedSections.append(secTitle) } groupedClients[secTitle]!.append(client) } return (orderedSections, groupedClients) } func configCell(cell: ClientTableViewCell, client: CAClient) { cell.nameLabel.text = self.getClientTitle(client) cell.detailsLabel.text = self.hearingMessage(client.daysUntilHearing) let seen = client.seen if seen { cell.clientImageView.image = UIImage(named: "green_seen") } else { cell.clientImageView.image = UIImage(named: "unseen_client") } } func fetchClients() { if !isLoadingClients { isLoadingClients = true self.api.readAllClients({ [unowned self] clientArr, err in if let clients = clientArr { self.clients = clients if self.shouldNotifyUserAboutClients() { self.notifyUserAboutClientsNeededToBeSeen() } self.reloadSearchTableView() self.reloadTableView() } self.isLoadingClients = false }) } } func reloadTableView() { self.clientTableDataSource = self.constructTableDataSource() self.tableView.reloadData() } func reloadSearchTableView() { let query = self.searchDisplayController?.searchBar.text ?? "" self.searchTableDataSource = self.constructSearchTableDataSource(query) self.searchDisplayController?.searchResultsTableView.reloadData() } func hearingMessage(daysUntilHearing: Int) -> String { let message: String switch daysUntilHearing { case let x where x < 0: message = "No upcoming hearings" case 0: message = "Hearing today" case 1: message = "\(daysUntilHearing) day until next hearing" default: message = "\(daysUntilHearing) days until next hearing" } return message } // client sorting methods func handleSortActionSheetButtonClick(buttonIndex: Int) { let updatedSort: Bool switch buttonIndex { case 0: self.sortBehavior = ClientSortByFirstName() updatedSort = true case 1: self.sortBehavior = ClientSortByLastName() updatedSort = true case 2: self.sortBehavior = ClientSortByHearing() updatedSort = true default: updatedSort = false } if updatedSort { self.reloadTableView() } } func showSortingOptions() { self.sortingActionSheet.showInView(self.view) } // client filtering methods func getCurrentFilterTitle() -> String { let filterTitle: String switch self.filterBehavior { case is ClientFilterBySeen: filterTitle = "Seen Clients" case is ClientFilterByUnseen: filterTitle = "Unseen Clients" default: filterTitle = "All Clients" } return filterTitle } func handleFilterActionSheetButtonClick(buttonIndex: Int) { let updatedFilter: Bool switch buttonIndex { case 0: self.filterBehavior = ClientFilterByAll() updatedFilter = true case 1: self.filterBehavior = ClientFilterBySeen() updatedFilter = true case 2: self.filterBehavior = ClientFilterByUnseen() updatedFilter = true default: updatedFilter = false } if updatedFilter { let filterTitle = self.getCurrentFilterTitle() self.filterButton.setTitle(filterTitle, forState: UIControlState.Normal) self.reloadTableView() } } func showFilteringOptions() { self.filteringActionSheet.showInView(self.view) } // MARK: - Navigation override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == Constants.ClientHubSegueIdentifier { var clientHubController = segue.destinationViewController as! ClientDistributorTabController clientHubController.client = self.selectedClient } } // MARK: Instantiate variables func createSortingActionSheet() -> UIActionSheet { var actionSheet = UIActionSheet(title: "Sorting methods", delegate: self, cancelButtonTitle: nil, destructiveButtonTitle: nil) actionSheet.addButtonWithTitle("First name") actionSheet.addButtonWithTitle("Last name") actionSheet.addButtonWithTitle("Hearing date") actionSheet.addButtonWithTitle("Cancel") actionSheet.cancelButtonIndex = 3 return actionSheet } func createFilteringActionSheet() -> UIActionSheet { var actionSheet = UIActionSheet(title: "Filtering methods", delegate: self, cancelButtonTitle: nil, destructiveButtonTitle: nil) actionSheet.addButtonWithTitle("All") actionSheet.addButtonWithTitle("Seen") actionSheet.addButtonWithTitle("Unseen") actionSheet.addButtonWithTitle("Cancel") actionSheet.cancelButtonIndex = 3 return actionSheet } func createFilterButton() -> UIButton { let hightlightedStateColor = UIColor(white: 1, alpha: 0.5) let filterButton = UIButton(frame: CGRectMake(0, 0, 150, 44)) let title = self.getCurrentFilterTitle() filterButton.setTitle(title, forState: UIControlState.Normal) filterButton.setTitleColor(hightlightedStateColor, forState: UIControlState.Highlighted) let downArrowImg = UIImage(named: "downarrow") filterButton.setImage(downArrowImg, forState: UIControlState.Normal) filterButton.setImage(downArrowImg?.imageWithColor(hightlightedStateColor), forState: UIControlState.Highlighted) filterButton.addTarget(self, action: Selector("showFilteringOptions"), forControlEvents: UIControlEvents.TouchUpInside) filterButton.titleEdgeInsets.left = 10.0 // add padding between image and text return filterButton } func createSortBarButtonItem() -> UIBarButtonItem { let sortBarButton = UIBarButtonItem(image: UIImage(named: "sort_button"), style: UIBarButtonItemStyle.Plain, target: self, action: Selector("showSortingOptions")) return sortBarButton } func constructTableDataSource() -> ([String], [String : [CAClient]]) { let filteredClients = self.filterBehavior.filter(self.clients) let sortedFilteredClients = self.sortBehavior.sort(filteredClients) let dataSource = self.partitionClients(sortedFilteredClients) var nClients = 0 for section in dataSource.sections { nClients += dataSource.1[section]!.count } if nClients == 0 { noClientsView.hidden = false } else { noClientsView.hidden = true } return dataSource } func constructSearchTableDataSource(searchQuery: String) -> [CAClient] { return self.findClients(self.clients, thatContain: searchQuery) } func navigateToResources() { self.performSegueWithIdentifier(Constants.ResourcesSegueIdentifier, sender: resourcesBarButton) } } ContactCell.swift // // ResourceCell.swift // CollectionView // // Created by Alan Perez on 7/30/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class ContactCell: UICollectionViewCell { let cornerRadius: CGFloat = 4.0 @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var iconImageView: UIImageView! override func awakeFromNib() { super.awakeFromNib() self.layer.cornerRadius = cornerRadius } } ContactController.swift // // ContactController.swift // CaseAide // // Created by Alan Perez on 8/17/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import UIKit class ContactController: ClientResourceController, UICollectionViewDataSource, UICollectionViewDelegate, UIActionSheetDelegate { let contactDetailSegueIdentifier = "viewContactDetailsSegueIndentifier" let addContactSegueIdentifier = "addContactSegueIdentifier" let sectionHeaderReuseIdentifier = "sectionHeaderReuseIdentifier" let contactCellReuseIdentifier = "contactCellReuseIdentifier" var selectedContact: CAContact? let dateFormat = "MMM dd, yyyy" let timeFormat = "h:mm a" private lazy var addContactBarButton: UIBarButtonItem = self.createAddBarButton() private lazy var groupContactsBarButton: UIBarButtonItem = self.createGroupBarButton() private lazy var previewNote: PreviewView = self.createPreviewView() private var previewNoteShown = false private lazy var groupingActionSheet: UIActionSheet = self.createGroupingActionSheet() @IBOutlet weak var collectionView: UICollectionView! @IBOutlet weak var noContactView: UIView! private lazy var contactDataSource: (categories: [String], groupedContacts: [String: [CAContact]]) = self.constructContactCollectionDataSource() private var sortBehavior: ContactSortBehavior = ContactSortByStartDate() private var groupingBehavior: ContactGroupingBehavior = ContactGroupingByStartDate() override func viewWillAppear(animated: Bool) { self.contactDataSource = self.constructContactCollectionDataSource() } override func viewDidLoad() { super.viewDidLoad() self.navigationItem.backBarButtonItem = UIBarButtonItem.defaultCABackButton() // setup gesture recognizers for preview notes let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: Selector("previewContactNoteGesture:")) self.collectionView.addGestureRecognizer(longPressGestureRecognizer) } override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) self.tabBarController?.navigationItem.rightBarButtonItems = [self.addContactBarButton, self.groupContactsBarButton] } override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) self.tabBarController?.navigationItem.rightBarButtonItems = nil } override func clientUpdated(client: CAClient) { self.updateContactsCollectionView() } // MARK: CollectionView reports data source func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { return self.contactDataSource.categories.count } func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { let category = self.contactDataSource.categories[section] return self.contactDataSource.groupedContacts[category]?.count ?? 0 } func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { let contactCell = collectionView.dequeueReusableCellWithReuseIdentifier(self.contactCellReuseIdentifier, forIndexPath: indexPath) as! ContactCell if let contact = self.getContact(indexPath) { self.configCell(contactCell, contact: contact) } return contactCell } func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView { let assetHeader = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: sectionHeaderReuseIdentifier, forIndexPath: indexPath) as! AssetHeaderView assetHeader.titleLabel.text = self.contactDataSource.categories[indexPath.section].uppercaseString return assetHeader } // MARK: CollectionView delegate func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { if let contact = self.getContact(indexPath) { selectedContact = contact self.performSegueWithIdentifier(self.contactDetailSegueIdentifier, sender: self) } } // MARK: UIActionSheet delegate func actionSheet(actionSheet: UIActionSheet, clickedButtonAtIndex buttonIndex: Int) { if actionSheet == self.groupingActionSheet { self.handleGroupingActionSheetButtonClick(buttonIndex) } } // MARK: Helper functions func handleGroupingActionSheetButtonClick(index: Int) { let updatedGrouping: Bool switch index { case 0: self.groupingBehavior = ContactGroupingByNone() updatedGrouping = true case 1: self.groupingBehavior = ContactGroupingByStartDate() updatedGrouping = true case 2: self.groupingBehavior = ContactGroupingByApproach() updatedGrouping = true default: updatedGrouping = false } if updatedGrouping { self.updateContactsCollectionView() } } //Individual code func configCell(cell: ContactCell, contact: CAContact) { let dateFormatter = NSDateFormatter() dateFormatter.dateFormat = self.dateFormat let contactStartDate = contact.startDate cell.titleLabel.text = dateFormatter.stringFromDate(contactStartDate) configCellImage(cell, contact: contact) } func configCellImage(contactCell: ContactCell, contact: CAContact ) { //change image based on approach switch contact.approach { case "In Person": //Show person Image contactCell.iconImageView.image = UIImage(named: "inPerson") break case "Fax": //Show Fax Image contactCell.iconImageView.image = UIImage(named: "fax") break case "Email": //Show Email Image contactCell.iconImageView.image = UIImage(named: "byemail") break case "Telephone": //Show Telephone Image contactCell.iconImageView.image = UIImage(named: "bytelephone") break case "Written": //Show Written Image contactCell.iconImageView.image = UIImage(named: "edit_pencil") break default: break } } func createAddBarButton() -> UIBarButtonItem { var addButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Add, target: self, action: "addContact") addButton.tintColor = UIColor.whiteColor() return addButton } func createGroupBarButton() -> UIBarButtonItem { var groupButton = UIBarButtonItem(image: UIImage(named: "sort_button"), style: UIBarButtonItemStyle.Plain, target: self, action: Selector("groupContacts")) groupButton.tintColor = UIColor.whiteColor() return groupButton } // selector method func addContact() { println("Add contact called") self.performSegueWithIdentifier(self.addContactSegueIdentifier, sender: self) } func groupContacts() { self.groupingActionSheet.showFromTabBar(self.tabBarController?.tabBar) } // MARK: Preview note methods func previewContactNoteGesture(gestureRecognizer: UILongPressGestureRecognizer) { if gestureRecognizer.state == UIGestureRecognizerState.Began { let contactPoint = gestureRecognizer.locationInView(self.collectionView) if let indexPath = self.collectionView.indexPathForItemAtPoint(contactPoint) { self.showContactNote(indexPath, point: contactPoint) } } else if gestureRecognizer.state == UIGestureRecognizerState.Changed { let updatedPoint = gestureRecognizer.locationInView(self.collectionView) if let indexPath = self.collectionView.indexPathForItemAtPoint(updatedPoint) { self.updateContactNoteAndPosition(indexPath, point: updatedPoint) } else { self.updateContactNotePosition(updatedPoint) } } else if gestureRecognizer.state == UIGestureRecognizerState.Ended { self.hideContactNote() } } func createPreviewView() -> PreviewView { return NSBundle.mainBundle().loadNibNamed("PreviewView", owner: self, options: nil).first as! PreviewView } func showContactNote(indexPath: NSIndexPath, point: CGPoint) { if let note = self.getContactNote(indexPath) { self.previewNote.showInView(self.collectionView, note: note, point: point) self.previewNoteShown = true } } func updateContactNoteAndPosition(indexPath: NSIndexPath, point: CGPoint) { if self.previewNoteShown { if let note = self.getContactNote(indexPath) { self.previewNote.message.text = note self.updateContactNotePosition(point) } } } func updateContactNotePosition(point: CGPoint) { if self.previewNoteShown { self.previewNote.positionPreviewNoteAtOptimalPosition(point) } } func hideContactNote() { if self.previewNoteShown { self.previewNote.hide() self.previewNoteShown = false } } func getContactNote(indexPath: NSIndexPath) -> String? { if var contactNote = self.getContact(indexPath)?.note { if contactNote == "" { contactNote = "No note available for contact" } return contactNote } return nil } func getContact(indexPath: NSIndexPath) -> CAContact? { let category = self.contactDataSource.categories[indexPath.section] return self.contactDataSource.groupedContacts[category]?[indexPath.row] } func constructContactCollectionDataSource() -> ([String], [String: [CAContact]]) { if let contacts = self.client?.contacts { if contacts.count == 0 { noContactView.hidden = false } else { noContactView.hidden = true } let sortedContacts = self.sortBehavior.sort(contacts) return self.groupingBehavior.group(sortedContacts) } let emptyCategories = [String]() let emptyGroupedContacts = [String: [CAContact]]() return (emptyCategories, emptyGroupedContacts) } func updateContactsCollectionView() { self.contactDataSource = self.constructContactCollectionDataSource() self.collectionView.reloadData() } func createGroupingActionSheet() -> UIActionSheet { var actionSheet = UIActionSheet(title: "Grouping methods", delegate: self, cancelButtonTitle: nil, destructiveButtonTitle: nil) actionSheet.addButtonWithTitle("None") actionSheet.addButtonWithTitle("Start Date") actionSheet.addButtonWithTitle("Approach") actionSheet.addButtonWithTitle("Cancel") actionSheet.cancelButtonIndex = 3 return actionSheet } // MARK: - Navigation override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == self.contactDetailSegueIdentifier { var contactDetailsController = segue.destinationViewController as! ViewContactController contactDetailsController.contact = self.selectedContact contactDetailsController.client = self.client println("line 369:" + "\(contactDetailsController.client?.name)" + "\(contactDetailsController.contact?.id)") } else if segue.identifier == self.addContactSegueIdentifier { var addContactController = segue.destinationViewController as! CASetContactFormController addContactController.client = self.client } } } ContactGrouping.swift // // ContactCategorization.swift // CaseAide // // Created by Alan Perez on 9/17/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import Foundation protocol ContactGroupingBehavior { func group(contacts: [CAContact]) -> ([String], [String: [CAContact]]) } class ContactGroupingByNone: ContactGroupingBehavior { func group(contacts: [CAContact]) -> ([String], [String : [CAContact]]) { let allContactsStr = "All Contacts" let categories = [allContactsStr] let groupedContacts = [allContactsStr: contacts] return (categories, groupedContacts) } } class ContactGroupingByApproach: ContactGroupingBehavior { func group(contacts: [CAContact]) -> ([String], [String : [CAContact]]) { var categories = [String]() var groupedContacts = [String: [CAContact]]() for contact in contacts { let approach = contact.approach if groupedContacts[approach] == nil { // create new category groupedContacts[approach] = [CAContact]() categories.append(approach) } // add contact to existing category groupedContacts[approach]!.append(contact) } return (categories, groupedContacts) } } class ContactGroupingByStartDate: ContactGroupingBehavior { let dateFormat = "MMM yyyy" let dateFormatter = NSDateFormatter() func group(contacts: [CAContact]) -> ([String], [String : [CAContact]]) { var categories = [String]() var groupedContacts = [String: [CAContact]]() self.dateFormatter.dateFormat = self.dateFormat for contact in contacts { let startDate = contact.startDate let startDateString = self.dateFormatter.stringFromDate(startDate) if groupedContacts[startDateString] == nil { // create new category groupedContacts[startDateString] = [CAContact]() categories.append(startDateString) } // add contact to existing category groupedContacts[startDateString]!.append(contact) } return (categories, groupedContacts) } } ContactSorting.swift // // ContactSorting.swift // CaseAide // // Created by Alan Perez on 9/17/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import Foundation protocol ContactSortBehavior { func sort(contacts: [CAContact]) -> [CAContact] } class ContactSortByStartDate: ContactSortBehavior { func sort(contacts: [CAContact]) -> [CAContact] { return contacts.sorted { contactA, contactB in if contactA.startDate.compare(contactB.startDate) == NSComparisonResult.OrderedDescending { return true } return false } } } ContactsViewController.swift // // ContactsViewController.swift // CaseAide // // Created by Alan Perez on 7/30/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit enum ContactMethod { case InPerson case Email case Phone } struct Contact { var name: String var contactMethod: ContactMethod } class ContactsViewController: UIViewController, UICollectionViewDataSource, VerticalLayoutDelegate { let contactCellReuseIdentifier = "contactCellReuseIdentifier" let sectionDetailViewReuseIdentifier = "sectionDetailViewReuseIdentifier" var contacts: [Contact]! @IBOutlet var collectionView: UICollectionView! override func viewDidLoad() { super.viewDidLoad() self.collectionView.registerNib(UINib(nibName: "ContactCell", bundle: NSBundle.mainBundle()), forCellWithReuseIdentifier: contactCellReuseIdentifier) self.collectionView.registerNib(UINib(nibName: "TextReusableView", bundle: NSBundle.mainBundle()), forSupplementaryViewOfKind: VerticalLayout.sectionTitleSupplementaryViewKind, withReuseIdentifier: sectionDetailViewReuseIdentifier) let contact1 = Contact(name: "Mike Korcham", contactMethod: ContactMethod.InPerson) let contact2 = Contact(name: "Drew", contactMethod: ContactMethod.InPerson) let contact3 = Contact(name: "Teven", contactMethod: ContactMethod.InPerson) let contact4 = Contact(name: "Alan", contactMethod: ContactMethod.InPerson) self.contacts = [contact1, contact2, contact3, contact4] } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // Collection View data source methods func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { return 3 } func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return self.contacts.count } func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCellWithReuseIdentifier(contactCellReuseIdentifier, forIndexPath: indexPath) as! ContactCell cell.titleLabel.text = self.contacts[indexPath.row].name return cell } func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView { let titleView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: sectionDetailViewReuseIdentifier, forIndexPath: indexPath) as! TextReusableView titleView.titleLabel.text = "May 2015" return titleView } // Vertical layout delegate methods func layoutItemHeight(layout: VerticalLayout) -> CGFloat { return 40.0 } func layoutSectionEdgeInsets(layout: VerticalLayout) -> UIEdgeInsets { return UIEdgeInsetsMake(10, 10, 25, 10) } func layoutInterItemDistance(layout: VerticalLayout) -> CGFloat { return 5.0 } func layoutSupplementalSize(layout: VerticalLayout) -> CGSize { return CGSizeMake(75.0, 150.0) } // Collection /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { // Get the new view controller using segue.destinationViewController. // Pass the selected object to the new view controller. } */ } Functions.swift // // Functions.swift // CaseAide // // Created by Andrew on 9/13/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import Foundation class Functions { class func discriptionBox(title: String , message: String) { var informationBox: UIAlertView informationBox = UIAlertView(title: title, message: message, delegate: self, cancelButtonTitle: "Close") informationBox.show() } class func message(label: String) ->String{ var message = "" switch (label){ case "Date": message = "The date of an actual meeting or communication transaction(s)" case "Staff": message = "The individual(s) who is inputting the client contact/report" case "Objective": message = "The goal of a meeting, report, or communication" case "Approach": message = "The method used to communicate" case "Place": message = "The location of the meeting/contact" case "Outcome": message = "The result of the meeting/contact/report" case "Persons Included": message = "The individual(s) a user can select who are involved in a case" case "In Support of": message = "The individual(s) a user can select who are involved in the case" case "Note": message = "Documentation of communication between parties in a case" case "Staff": message = "same definition as above" case "Court": message = "The location of the court" case "Room": message = "The room in which the hearing will be called" case "Type": message = "The category of reports to be chosen from" case "Children": message = "The users option to choose the children who are involved in a case" default:break } return message } class func alertError(error: NSError) { Functions.alert(error.localizedDescription, message: error.localizedRecoverySuggestion) } class func alert(title: String, message: String?) { UIAlertView(title: title, message: message, delegate: nil, cancelButtonTitle: "Ok").show() } } HearingView.swift // // HearingView.swift // CaseAide // // Created by Alan Perez on 8/18/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class HearingView: UIView { @IBOutlet weak var dayLabel: UILabel! @IBOutlet weak var dateLabel: UILabel! /* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. override func drawRect(rect: CGRect) { // Drawing code } */ } IntTransform.swift // // IntTransform.swift // CaseAide // // Created by Andrew on 10/1/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import Foundation public class IntTransform: TransformType { public typealias Object = Int public typealias JSON = String let Formatter: Int public init(Formatter: Int) { self.Formatter = Formatter } public func transformFromJSON(value: AnyObject?) -> Int? { if let String = value as? String { return String.toInt() } return nil } public func transformToJSON(value: Int?) -> String? { if let date = value { return Formatter. } return nil } } HearingController.swift // // HearingController.swift // CaseAide // // Created by Alan Perez on 8/18/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import UIKit class HearingController: ClientResourceController, UITableViewDataSource, UITableViewDelegate { let hearingBasicCellReuseIdentifier = "hearingBasicCellReuseIdentifier" let setHearingSegueIdentifier = "setHearingSegueIdentifier" let reportInfoSection = 0 let reportTypeIndex = 0 let reportDateIndex = 1 let reportDeadlinesSection = 1 let cprDueDateIndex = 0 let supervisorDueDateIndex = 1 let courtDueDateIndex = 2 let dateFormat = "MMM dd, yyyy" let dateTimeFormat = "MMM dd, yyyy hh:mm a" var dateFormatter = NSDateFormatter() let cellHeight: CGFloat = 60.0 @IBOutlet weak var hearingView: HearingView! @IBOutlet weak var tableView: UITableView! @IBOutlet weak var noHearingView: UIView! private var hearing: CAHearing? { didSet { if hearing == nil { noHearingView.hidden = false } else { noHearingView.hidden = true } } } lazy var addBarButton: UIBarButtonItem = self.createAddButton() lazy var editBarButton: UIBarButtonItem = self.createEditButton() override func viewDidLoad() { super.viewDidLoad() self.navigationItem.backBarButtonItem = UIBarButtonItem.defaultCABackButton() self.dateFormatter.dateFormat = self.dateFormat hearing = client?.hearing self.updateHearingView() self.tableView.reloadData() } override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) if hearing != nil { self.updateActionBarButton(self.editBarButton) } else { self.updateActionBarButton(self.addBarButton) } } override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) self.hideActionBarButton() } override func clientUpdated(client: CAClient) { hearing = client.hearing self.updateHearingView() self.tableView.reloadData() } // MARK: TableView delegate methods func numberOfSectionsInTableView(tableView: UITableView) -> Int { if hearing == nil { return 0 } return 2 } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if section == self.reportInfoSection { return 2 } else if section == self.reportDeadlinesSection { return 3 } return 0 } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { var cell = tableView.dequeueReusableCellWithIdentifier(self.hearingBasicCellReuseIdentifier, forIndexPath: indexPath) as! UITableViewCell self.configCell(cell, indexPath: indexPath) return cell } func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return self.cellHeight } func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { var title: String? switch section { case self.reportInfoSection: title = "Hearing information" case self.reportDeadlinesSection: title = "Deadlines" default: break } return title } func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { if indexPath.section == self.reportInfoSection { self.performSegueWithIdentifier(self.setHearingSegueIdentifier, sender: self) } } // MARK: Helper functions func configCell(cell: UITableViewCell, indexPath: NSIndexPath) { if indexPath.section == self.reportInfoSection { cell.selectionStyle = UITableViewCellSelectionStyle.Default switch indexPath.row { case self.reportTypeIndex: cell.textLabel?.text = "Report Type" cell.detailTextLabel?.text = hearing?.type.rawValue ?? "N/A" case self.reportDateIndex: self.dateFormatter.dateFormat = self.dateTimeFormat cell.textLabel?.text = "Date" var dateString: String? if let date = hearing?.date { dateString = self.dateFormatter.stringFromDate(date) } cell.detailTextLabel?.text = dateString default: break } } else if indexPath.section == self.reportDeadlinesSection { cell.selectionStyle = UITableViewCellSelectionStyle.None self.dateFormatter.dateFormat = self.dateFormat switch indexPath.row { case cprDueDateIndex: cell.textLabel?.text = "Noticing CPR Due" let cprDeadline: String if let cprDate = hearing?.cprDueDate { cprDeadline = self.dateFormatter.stringFromDate(cprDate) } else { cprDeadline = "N/A" } cell.detailTextLabel?.text = cprDeadline case supervisorDueDateIndex: cell.textLabel?.text = "Supervisor Due" let supervisorDeadline: String if let supervisorDate = hearing?.supervisorDueDate { supervisorDeadline = self.dateFormatter.stringFromDate(supervisorDate) } else { supervisorDeadline = "N/A" } cell.detailTextLabel?.text = supervisorDeadline case courtDueDateIndex: cell.textLabel?.text = "Court Due" let courtDeadline: String if let courtDate = hearing?.courtDueDate { courtDeadline = self.dateFormatter.stringFromDate(courtDate) } else { courtDeadline = "N/A" } cell.detailTextLabel?.text = courtDeadline default: break } } } func updateHearingView() { var titleLabel = "No upcoming hearings" var subTitleLabel = "" if let h = hearing { self.dateFormatter.dateFormat = self.dateFormat titleLabel = "\(self.dateFormatter.stringFromDate(h.date).uppercaseString)," subTitleLabel = self.dayOfWeekString(h.date.dayOfWeek()).uppercaseString } self.hearingView.dayLabel.text = titleLabel self.hearingView.dateLabel.text = subTitleLabel } func dayOfWeekString(dow: Int) -> String { let dowString: String switch dow { case 1: dowString = "Sunday" case 2: dowString = "Monday" case 3: dowString = "Tuesday" case 4: dowString = "Wednesday" case 5: dowString = "Thursday" case 6: dowString = "Friday" case 7: dowString = "Saturday" default: dowString = "Unknown" } return dowString } func updateActionBarButton(button: UIBarButtonItem) { self.tabBarController?.navigationItem.rightBarButtonItem = button } func hideActionBarButton() { self.tabBarController?.navigationItem.rightBarButtonItem = nil } func createAddButton() -> UIBarButtonItem { var addButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Add, target: self, action: "addHearing") return addButton } func createEditButton() -> UIBarButtonItem { return UIBarButtonItem(image: UIImage(named: "edit_pencil"), style: UIBarButtonItemStyle.Plain, target: self, action: Selector("editHearing")) } func addHearing() { self.performSegueWithIdentifier(self.setHearingSegueIdentifier, sender: self) } func editHearing() { self.performSegueWithIdentifier(self.setHearingSegueIdentifier, sender: self) } // MARK: Navigation override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == self.setHearingSegueIdentifier { var setHearingController = segue.destinationViewController as! SetHearingController setHearingController.client = self.client } } } NSDateExtensions.swift // // NSDateExtensions.swift // CaseAide // // Created by Alan on 8/20/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import Foundation extension NSDate { class func combineDateAndTime(date: NSDate, time: NSDate) -> NSDate { let calendar = NSCalendar.currentCalendar() let dateComponents = calendar.components(.CalendarUnitYear | .CalendarUnitMonth | .CalendarUnitDay, fromDate: date) let timeComponents = calendar.components(.CalendarUnitHour | .CalendarUnitMinute | .CalendarUnitSecond, fromDate: time) let combinedComponents = NSDateComponents() combinedComponents.year = dateComponents.year combinedComponents.month = dateComponents.month combinedComponents.day = dateComponents.day combinedComponents.hour = timeComponents.hour combinedComponents.minute = timeComponents.minute combinedComponents.second = timeComponents.second return calendar.dateFromComponents(combinedComponents)! } func dayOfWeek() -> Int { let calendar = NSCalendar.currentCalendar() let dateComponents = calendar.components(.WeekdayCalendarUnit, fromDate: self) return dateComponents.weekday } func isSunday() -> Bool { let dayOfWeek = self.dayOfWeek() return dayOfWeek == 1 } func isSaturday() -> Bool { let dayOfWeek = self.dayOfWeek() return dayOfWeek == 7 } func dateByAddingDays(days: Int) -> NSDate { var dayComponent = NSDateComponents() dayComponent.day = days let calendar = NSCalendar.currentCalendar() return calendar.dateByAddingComponents(dayComponent, toDate: self, options: NSCalendarOptions.allZeros)! } func dateByEarliestWeekday() -> NSDate { if self.isSaturday() { return self.dateByAddingDays(-1) } else if self.isSunday() { return self.dateByAddingDays(-2) } return self } func isWednesday() -> Bool { if self.dayOfWeek() == 4 { return true } return false } func isSameDay(compareDate: NSDate) -> Bool { let calendar = NSCalendar.currentCalendar() let firstDateComponents = calendar.components(NSCalendarUnit.CalendarUnitDay | NSCalendarUnit.CalendarUnitMonth | NSCalendarUnit.CalendarUnitYear, fromDate: self) let secondDateComponents = calendar.components(NSCalendarUnit.CalendarUnitDay | NSCalendarUnit.CalendarUnitMonth | NSCalendarUnit.CalendarUnitYear, fromDate: compareDate) if (firstDateComponents.day == secondDateComponents.day) && (firstDateComponents.month == secondDateComponents.month) && (firstDateComponents.year == secondDateComponents.year) { return true } return false } func isNotSameDay(compareDate: NSDate) -> Bool { return !isSameDay(compareDate) } } LoginController.swift // // LoginController.swift // CaseAide // // Created by Alan Perez on 7/15/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class LoginController: UIViewController, UITextFieldDelegate { let clientPageSegueIdentifier = "clientPageSegueIdentifier" @IBOutlet weak var scrollView: UIScrollView! @IBOutlet weak var containerView: UIView! { didSet { containerView.backgroundColor = UIColor(patternImage: UIImage(named: "Loginbackground.jpeg")!) } } @IBOutlet weak var usernameTextField: AndroidTextField! @IBOutlet weak var passwordTextField: AndroidTextField! @IBOutlet weak var loginButton: UIButton! @IBOutlet weak var activityIndicator: UIActivityIndicatorView! private var keyboardShown: Bool = false private var keyboardRectWRTView = CGRectZero private var preKeyboardContentOffset = CGPointZero private let api: CAApi = CAApi.defaultApi private var userIsInTheMiddleOfLogginIn = false { didSet { if userIsInTheMiddleOfLogginIn { loginButton.enabled = false activityIndicator.startAnimating() } else { loginButton.enabled = true activityIndicator.stopAnimating() } } } override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) // ensure password field is empty whenever this controller is reshown self.passwordTextField.text = "" NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil) self.navigationController?.navigationBar.translucent = false self.navigationController?.navigationBar.hidden = true } override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil) NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil) self.navigationController?.navigationBar.hidden = false } override func viewDidLoad() { super.viewDidLoad() // used to hide keyboard when clicking outside of it let tapGesture = UITapGestureRecognizer(target: self, action: Selector("dismissKeyboard")) self.containerView.addGestureRecognizer(tapGesture) } @IBAction func loginClicked(sender: AnyObject) { dismissKeyboard() attemptLoginUser() } func attemptLoginUser() { if !userIsInTheMiddleOfLogginIn { userIsInTheMiddleOfLogginIn = true let username = usernameTextField.text let password = passwordTextField.text if count(username) == 0 || count(password) == 0 { // dont bother, either username or password is not filed in userIsInTheMiddleOfLogginIn = false UIAlertView(title: "Input error", message: "Username or password field is empty", delegate: nil, cancelButtonTitle: "Ok").show() return } self.api.authenticate(username, pass: password, completionHandler: { [unowned self] error in self.userIsInTheMiddleOfLogginIn = false if let err = error { UIAlertView(title: err.localizedDescription, message: err.localizedRecoverySuggestion, delegate: nil, cancelButtonTitle: "Ok").show() } else { self.performSegueWithIdentifier(self.clientPageSegueIdentifier, sender: self) } }) } } // MARK: Keyboard logic func dismissKeyboard() { self.view.endEditing(true) } func textFieldShouldReturn(textField: UITextField) -> Bool { if textField === usernameTextField { passwordTextField.becomeFirstResponder() } else if textField === passwordTextField { passwordTextField.resignFirstResponder() attemptLoginUser() } return true } func textFieldDidBeginEditing(textField: UITextField) { // update for subsequent textfield focus if keyboardShown { shiftUpIfNeeded(textField) } } func keyboardWillShow(notification: NSNotification) { let window = UIApplication.sharedApplication().keyWindow! let keyboardInfo = notification.userInfo! // keyboard frame with respect to screen let keyboardRectWRTScreen = keyboardInfo[UIKeyboardFrameEndUserInfoKey]!.CGRectValue() // set parameters and shift for first textfield focus self.keyboardRectWRTView = self.view.convertRect(keyboardRectWRTScreen, fromView: nil) self.keyboardShown = true self.preKeyboardContentOffset = self.scrollView.contentOffset if self.usernameTextField.isFirstResponder() { self.shiftUpIfNeeded(self.usernameTextField) } else if self.passwordTextField.isFirstResponder() { self.shiftUpIfNeeded(self.passwordTextField) } } func keyboardWillHide(notification: NSNotification) { keyboardShown = false shiftBack() } func shiftUpIfNeeded(textField: UITextField) { let textFieldRectWRTView = self.view.convertRect(textField.bounds, fromView: textField) // if user text field is hidden by keyboard if CGRectIntersectsRect(self.keyboardRectWRTView, textFieldRectWRTView) { let overlapAmount = CGRectGetMaxY(textFieldRectWRTView) - CGRectGetMinY(self.keyboardRectWRTView) + 10 let contentOffset = scrollView.contentOffset scrollView.setContentOffset(CGPointMake(contentOffset.x, contentOffset.y + overlapAmount), animated: true) } } func shiftBack() { scrollView.setContentOffset(self.preKeyboardContentOffset, animated: true) } } PreviewView.swift // // PreviewView.swift // CaseAide // // Created by Alan Perez on 9/15/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import UIKit enum PreviewNotePosition { case TopLeft case TopRight case BottomLeft case BottomRight } class PreviewView: UIView { @IBOutlet weak var message: UILabel! var containerView: UIView? // customize parameters let maxWidth: CGFloat = 200.0 let maxHeight: CGFloat = 200.0 let noteVerticalPadding: CGFloat = 0.0 let noteHorizontalPadding: CGFloat = 8.0 func showInView(view: UIView, note: String, point: CGPoint) { self.message.text = note self.containerView = view self.containerView?.addSubview(self) self.positionPreviewNoteAtOptimalPosition(point) } func hide() { self.removeFromSuperview() } func determineOptimalPreviewPosition(point: CGPoint) -> PreviewNotePosition { if let view = self.containerView { let center = view.center if point.x < center.x { if point.y < center.y { return PreviewNotePosition.BottomRight } else { return PreviewNotePosition.TopRight } } else { if point.y < center.y { return PreviewNotePosition.BottomLeft } else { return PreviewNotePosition.TopLeft } } } return PreviewNotePosition.TopRight // default positoning } func positionPreviewNoteAtOptimalPosition(point: CGPoint) { let optimalPosition = self.determineOptimalPreviewPosition(point) self.positionPreviewNote(optimalPosition, point: point) } func positionPreviewNote(position: PreviewNotePosition, point: CGPoint) { if let view = self.containerView { let thumbOffsetPoint = self.previewNoteThumbOffset(position, point: point) let previewWidth = min(self.previewNoteAvailableWidth(position, point: thumbOffsetPoint) - 10, self.maxWidth) let text = self.message.text ?? "" let heightToFitText = self.heightForText(previewWidth - self.noteHorizontalPadding * 2, text: text, font: self.message.font) + self.noteVerticalPadding * 2 let previewHeight = min(self.previewNoteAvailableHeight(position, point: thumbOffsetPoint) - 10, self.maxHeight, heightToFitText + 20) let previewSize = CGSizeMake(previewWidth, previewHeight) let previewOrigin = self.previewNoteOrigin(position, point: thumbOffsetPoint, size: previewSize) self.frame = CGRectMake(previewOrigin.x, previewOrigin.y, previewSize.width, previewSize.height) } } func heightForText(width: CGFloat, text: String, font: UIFont) -> CGFloat { let str = text as NSString let attributes = [NSFontAttributeName : font] return str.boundingRectWithSize(CGSizeMake(width, self.maxHeight), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: attributes, context: nil).height } func previewNoteThumbOffset(position: PreviewNotePosition, point: CGPoint) -> CGPoint { let thumbXOffset: CGFloat = 20.0 let thumbYOffset: CGFloat = 0.0 return CGPointMake(self.previewNoteThumbXOffset(position, point: point, xOffset: thumbXOffset), self.previewNoteThumbYOffset(position, point: point, yOffset: thumbYOffset)) } func previewNoteThumbXOffset(position: PreviewNotePosition, point: CGPoint, xOffset: CGFloat) -> CGFloat { if position == PreviewNotePosition.TopLeft || position == PreviewNotePosition.BottomLeft { return point.x - xOffset } else { return point.x + xOffset } } func previewNoteThumbYOffset(position: PreviewNotePosition, point: CGPoint, yOffset: CGFloat) -> CGFloat { if position == PreviewNotePosition.TopLeft || position == PreviewNotePosition.TopRight { return point.y - yOffset } else { return point.y + yOffset } } func previewNoteOrigin(position: PreviewNotePosition, point: CGPoint, size: CGSize) -> CGPoint { return CGPointMake(self.previewNoteOriginXPos(position, point: point, size: size), self.previewNoteOriginYPos(position, point: point, size: size)) } func previewNoteOriginXPos(position: PreviewNotePosition, point: CGPoint, size: CGSize) -> CGFloat { // left x coordinate if position == PreviewNotePosition.TopRight || position == PreviewNotePosition.BottomRight { return point.x } else { return point.x - size.width } } func previewNoteOriginYPos(position: PreviewNotePosition, point: CGPoint, size: CGSize) -> CGFloat { // left x coordinate if position == PreviewNotePosition.TopRight || position == PreviewNotePosition.TopLeft { return point.y - size.height } else { return point.y } } func previewNoteAvailableHeight(position: PreviewNotePosition, point: CGPoint) -> CGFloat { if let view = self.containerView { if position == PreviewNotePosition.TopRight || position == PreviewNotePosition.TopLeft { return point.y - CGRectGetMinY(view.bounds) } else { return CGRectGetMaxY(view.bounds) - point.y } } return 0 // default value when no containing view } func previewNoteAvailableWidth(position: PreviewNotePosition, point: CGPoint) -> CGFloat { if let view = self.containerView { if position == PreviewNotePosition.TopRight || position == PreviewNotePosition.BottomRight { return CGRectGetMaxX(view.bounds) - point.x } else { return point.x - CGRectGetMinX(view.bounds) } } return 0 // default value when no containing view } } RadioCell.swift // // RadioCell.swift // CaseAide // // Created by Alan on 8/20/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class RadioCell: UITableViewCell { @IBOutlet weak var radioImageView: UIImageView! @IBOutlet weak var optionLabel: UILabel! override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state, set radio image } } ReportCell.swift // // ReportCell.swift // CaseAide // // Created by Alan Perez on 8/12/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class ReportCell: UICollectionViewCell { let cornerRadius: CGFloat = 4.0 @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var descriptionLabel: UILabel! override func awakeFromNib() { super.awakeFromNib() self.layer.cornerRadius = cornerRadius } } ReportController.swift // // ReportController.swift // CaseAide // // Created by Alan Perez on 8/13/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class ReportController: ClientResourceController, UICollectionViewDataSource, UICollectionViewDelegate, UIActionSheetDelegate { let api: CAApi = CAApi.defaultApi let viewReportDetailsSegueIdentifier = "viewReportDetailsSegueIndentifier" let addReportSegueIdentifier = "addReportSegueIdentifier" let sectionHeaderReuseIdentifier = "sectionHeaderReuseIdentifier" let reportCellReuseIdentifier = "reportCellReuseIdentifier" var selectedReport: CAReport? let dateFormat = "MMM dd, yyyy" let timeFormat = "h:mm a" private lazy var addReportBarButton: UIBarButtonItem = self.createAddBarButton() private lazy var groupReportsBarButton: UIBarButtonItem = self.createGroupBarButton() private lazy var groupingActionSheet: UIActionSheet = self.createGroupingActionSheet() @IBOutlet weak var collectionView: UICollectionView! @IBOutlet weak var noReportsView: UIView! private lazy var reportDataSource: (categories: [String], groupedReports: [String: [CAReport]]) = self.constructReportCollectionDataSource() private var sortBehavior: ReportSortBehavior = ReportSortByDate() private var groupingBehavior: ReportGroupingBehavior = ReportGroupingByType() override func viewDidLoad() { super.viewDidLoad() self.navigationItem.backBarButtonItem = UIBarButtonItem.defaultCABackButton() } override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) self.tabBarController?.navigationItem.rightBarButtonItems = [self.addReportBarButton, self.groupReportsBarButton] } override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) self.tabBarController?.navigationItem.rightBarButtonItems = nil } override func clientUpdated(client: CAClient) { self.updateReportsCollectionView() } // MARK: CollectionView reports data source func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { return self.reportDataSource.categories.count } func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { let category = self.reportDataSource.categories[section] return self.reportDataSource.groupedReports[category]?.count ?? 0 } func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { let reportCell = collectionView.dequeueReusableCellWithReuseIdentifier(self.reportCellReuseIdentifier, forIndexPath: indexPath) as! ReportCell let category = self.reportDataSource.categories[indexPath.section] if let report = self.reportDataSource.groupedReports[category]?[indexPath.row] { self.configCell(reportCell, report: report) } return reportCell } func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView { let assetHeader = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: sectionHeaderReuseIdentifier, forIndexPath: indexPath) as! AssetHeaderView assetHeader.titleLabel.text = self.reportDataSource.categories[indexPath.section].uppercaseString return assetHeader } func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { let category = self.reportDataSource.categories[indexPath.section] if let report = self.reportDataSource.groupedReports[category]?[indexPath.row] { self.selectedReport = report self.performSegueWithIdentifier(self.viewReportDetailsSegueIdentifier, sender: self) } } // MARK: ActionSheet delegate func actionSheet(actionSheet: UIActionSheet, clickedButtonAtIndex buttonIndex: Int) { if actionSheet == self.groupingActionSheet { self.handleGroupingActionSheetButtonClick(buttonIndex) } } // MARK: Helper functions func configCell(cell: ReportCell, report: CAReport) { let dateFormatter = NSDateFormatter() let reportDate = report.date dateFormatter.dateFormat = self.dateFormat cell.titleLabel.text = dateFormatter.stringFromDate(reportDate) dateFormatter.dateFormat = self.timeFormat cell.descriptionLabel.text = dateFormatter.stringFromDate(reportDate) } func handleGroupingActionSheetButtonClick(index: Int) { let updatedGrouping: Bool switch index { case 0: self.groupingBehavior = ReportGroupingByNone() updatedGrouping = true case 1: self.groupingBehavior = ReportGroupingByDate() updatedGrouping = true case 2: self.groupingBehavior = ReportGroupingByType() updatedGrouping = true default: updatedGrouping = false } if updatedGrouping { self.updateReportsCollectionView() } } // MARK: Add Report bar button methods func createAddBarButton() -> UIBarButtonItem { var addButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Add, target: self, action: "addReport") addButton.tintColor = UIColor.whiteColor() return addButton } func createGroupBarButton() -> UIBarButtonItem { var groupButton = UIBarButtonItem(image: UIImage(named: "sort_button"), style: UIBarButtonItemStyle.Plain, target: self, action: Selector("groupReports")) groupButton.tintColor = UIColor.whiteColor() return groupButton } // selector method func addReport() { // add report called println("Add report called") self.performSegueWithIdentifier(self.addReportSegueIdentifier, sender: self) } func groupReports() { self.groupingActionSheet.showFromTabBar(self.tabBarController?.tabBar) } func constructReportCollectionDataSource() -> ([String], [String: [CAReport]]) { if let reports = self.client?.reports { if reports.count == 0 { noReportsView.hidden = false } else { noReportsView.hidden = true } let sortedReports = self.sortBehavior.sort(reports) return self.groupingBehavior.group(sortedReports) } let emptyCategories = [String]() let emptyGroupedReports = [String: [CAReport]]() return (emptyCategories, emptyGroupedReports) } func updateReportsCollectionView() { self.reportDataSource = self.constructReportCollectionDataSource() self.collectionView.reloadData() } func createGroupingActionSheet() -> UIActionSheet { var actionSheet = UIActionSheet(title: "Grouping methods", delegate: self, cancelButtonTitle: nil, destructiveButtonTitle: nil) actionSheet.addButtonWithTitle("None") actionSheet.addButtonWithTitle("Date") actionSheet.addButtonWithTitle("Type") actionSheet.addButtonWithTitle("Cancel") actionSheet.cancelButtonIndex = 3 return actionSheet } // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == self.viewReportDetailsSegueIdentifier { var reportDetailController = segue.destinationViewController as! ViewReportController reportDetailController.report = self.selectedReport reportDetailController.client = self.client } else if segue.identifier == self.addReportSegueIdentifier { var addReportController = segue.destinationViewController as! CASetReportFormController addReportController.client = self.client } } } ReportGrouping.swift // // ReportGrouping.swift // CaseAide // // Created by Alan Perez on 9/17/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import Foundation protocol ReportGroupingBehavior { func group(reports: [CAReport]) -> ([String], [String: [CAReport]]) } class ReportGroupingByNone: ReportGroupingBehavior { func group(reports: [CAReport]) -> ([String], [String: [CAReport]]) { let allReportsStr = "All Reports" let categories = [allReportsStr] let groupedReports = [allReportsStr: reports] return (categories, groupedReports) } } class ReportGroupingByType: ReportGroupingBehavior { func group(reports: [CAReport]) -> ([String], [String: [CAReport]]) { var categories = [String]() var groupedReports = [String: [CAReport]]() for report in reports { let type = report.type.rawValue if groupedReports[type] == nil { // create new category groupedReports[type] = [CAReport]() categories.append(type) } // add report to existing category groupedReports[type]!.append(report) } return (categories, groupedReports) } } class ReportGroupingByDate: ReportGroupingBehavior { let dateFormat = "MMM yyyy" let dateFormatter = NSDateFormatter() func group(reports: [CAReport]) -> ([String], [String: [CAReport]]) { var categories = [String]() var groupedReports = [String: [CAReport]]() self.dateFormatter.dateFormat = self.dateFormat for report in reports { let date = report.date let dateString = self.dateFormatter.stringFromDate(date) if groupedReports[dateString] == nil { // create new category groupedReports[dateString] = [CAReport]() categories.append(dateString) } // add report to existing category groupedReports[dateString]!.append(report) } return (categories, groupedReports) } } ReportSorting.swift // // ReportSorting.swift // CaseAide // // Created by Alan Perez on 9/17/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import Foundation protocol ReportSortBehavior { func sort(reports: [CAReport]) -> [CAReport] } class ReportSortByDate: ReportSortBehavior { func sort(reports: [CAReport]) -> [CAReport] { return reports.sorted { reportA, reportB in if reportA.date.compare(reportB.date) == NSComparisonResult.OrderedDescending { return true } return false } } } SWResource.swift // // SWResource.swift // CaseAide // // Created by Alan Perez on 2/5/16. // Copyright (c) 2016 CaseAide. All rights reserved. // import Foundation class SWResource { struct JsonKey { static let name = "name" static let description = "description" static let location = "location" static let local = "local" } let name: String let description: String? let location: NSURL let local: Bool var fileName: String? { get { return location.lastPathComponent } } var fileExtension: String? { get { return location.pathExtension } } init (name: String, location: NSURL, local: Bool, description: String?) { self.name = name self.description = description self.location = location self.local = local } class func fromJson (json: Dictionary<String, AnyObject>) -> SWResource? { if let name = json[JsonKey.name] as? String, let location = json[JsonKey.location] as? NSURL, let local = json[JsonKey.local] as? Bool { let description = json[JsonKey.description] as? String return SWResource(name: name, location: location, local: local, description: description) } return nil } func hasExtension(ext: String) -> Bool { if let fileExt = fileExtension { if fileExt.lowercaseString == ext.lowercaseString { return true } } return false } func isWordDocument() -> Bool { if hasExtension("doc") || hasExtension("docx") { return true } return false } func isExcelDocument() -> Bool { if hasExtension("xls") { return true } return false } func isPowerpointDocument() -> Bool { if hasExtension("ppt") { return true } return false } func isPdfDocument() -> Bool { if hasExtension("pdf") { return true } return false } func isDocument() -> Bool { if isWordDocument() || isPowerpointDocument() || isExcelDocument() || isPdfDocument() { return true } return false } func isWebsite() -> Bool { if local == false && !isDocument() { return true } return false } } SWResourceController.swift // // SWResourceController.swift // CaseAide // // Created by Alan Perez on 2/5/16. // Copyright (c) 2016 CaseAide. All rights reserved. // import UIKit class SWResourceController: UIViewController, UITableViewDataSource, UITableViewDelegate { @IBOutlet weak var tableView: UITableView! { didSet { tableView.dataSource = self tableView.delegate = self } } let resourceRequester = SWResourceRequester() var resources: [SWResource]? struct Constant { static let ResourceCellIdentifier = "resource cell identifier" static let ResourceSegueIdentifier = "show sw resource segue" static let ResourceCellHeight: CGFloat = 66.0 } override func viewDidLoad() { super.viewDidLoad() resources = resourceRequester.fetch() } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return resources?.count ?? 0 } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier(Constant.ResourceCellIdentifier, forIndexPath: indexPath) as! SWResourceTableViewCell cell.resource = resources![indexPath.row] return cell } func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return Constant.ResourceCellHeight } func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { tableView.deselectRowAtIndexPath(indexPath, animated: true) } override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == Constant.ResourceSegueIdentifier { let webViewController = segue.destinationViewController as! WebViewController let resource = (sender as! SWResourceTableViewCell).resource! webViewController.url = resource.location webViewController.title = resource.name if resource.isWebsite() { webViewController.allowNavigation = true } } } } SWResourceRequester.swift // // SWResourceRequester.swift // CaseAide // // Created by Alan Perez on 2/5/16. // Copyright © 2016 CaseAide. All rights reserved. // import Foundation class SWResourceRequester { typealias JSON = [String: AnyObject] private struct ResourceJson { static let Filename = "SWResources" static let Ext = "plist" static let nameKey = "name" static let descriptionKey = "description" static let locationKey = "location" static let localKey = "local" } private var resourceLocation: String { return pathForLocalResource(ResourceJson.Filename, ext: ResourceJson.Ext)! } func fetch() -> [SWResource]? { if let formattedResourceJson = getSWResourceFormattedJson() { var resources = [SWResource]() for resourceJson in formattedResourceJson { if let resource = SWResource.fromJson(resourceJson) { resources.append(resource) } } return resources } return nil } private func getResourcesJson() -> [JSON]? { if let json = NSDictionary(contentsOfFile: self.resourceLocation) as? JSON, let resources = json["resources"] as? [JSON] { return resources } return nil } private func getSWResourceFormattedJson() -> [JSON]? { if let resourcesJson = getResourcesJson() { var formattedResources = [JSON]() for resource in resourcesJson { if let formattedJson = formatJsonToSWResourceFormat(resource) { formattedResources.append(formattedJson) } } return formattedResources } return nil } private func formatJsonToSWResourceFormat(json: JSON) -> JSON? { if let name = json[ResourceJson.nameKey] as? String, let local = json[ResourceJson.localKey] as? Bool, let location = json[ResourceJson.locationKey] as? String { var formattedJson = JSON() formattedJson[SWResource.JsonKey.name] = name formattedJson[SWResource.JsonKey.description] = json[ResourceJson.descriptionKey] as? String formattedJson[SWResource.JsonKey.location] = determineResourceUrl(local, location: location) formattedJson[SWResource.JsonKey.local] = local return formattedJson } return nil } private func determineResourceUrl(local: Bool, location: String) -> NSURL? { if local { let filename = location.lastPathComponent let ext = filename.pathExtension let name = filename.stringByDeletingPathExtension if let path = pathForLocalResource(name, ext: ext) { return NSURL(string: path) } else { return nil } } else { return NSURL(string: location) } } private func pathForLocalResource(filename: String, ext: String) -> String? { return NSBundle.mainBundle().pathForResource(filename, ofType: ext) } } SWResourceTableViewCell.swift // // SWResourceTableViewCell.swift // CaseAide // // Created by Alan Perez on 2/5/16. // Copyright (c) 2016 CaseAide. All rights reserved. // import Foundation class SWResourceTableViewCell: UITableViewCell { @IBOutlet weak var resourceIcon: UIImageView! { didSet { resourceIcon.image = iconForResource() } } @IBOutlet weak var nameLabel: UILabel! { didSet { nameLabel.text = resource?.name } } var resource: SWResource? { didSet { nameLabel?.text = resource?.name resourceIcon?.image = iconForResource() } } private func iconForResource() -> UIImage? { if let res = resource { if res.isWebsite() { return UIImage(named: "web-icon") } if res.isWordDocument() { return UIImage(named: "word-icon") } if res.isExcelDocument() { return UIImage(named: "excel-icon") } if res.isPdfDocument() { return UIImage(named: "pdf-icon") } } return nil } } SetHearingController.swift // // SetHearingController.swift // CaseAide // // Created by Alan Perez on 8/19/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import UIKit class SetHearingController: UIViewController, UIAlertViewDelegate { var client: CAClient? private var internalHearing: CAHearing! private var referenceHearing: CAHearing! //check if unsaved changes have been made private var hasUnsavedChanges = false // operation mode of controller lazy var mode: Int = self.defaultMode() let EDIT_MODE = 1 let CREATE_MODE = 2 lazy var hearingTypeOptions: [String] = self.constructHearingTypeOptions() private let api: CAApi = CAApi.defaultApi @IBOutlet weak var clientNameLabel: UILabel! @IBOutlet weak var dateTextField: UITextField! @IBOutlet weak var timeTextField: UITextField! @IBOutlet weak var typeTextField: UITextField! // text field wrappers private var dateDownPicker: DateDownPicker! private var timeDownPicker: DateDownPicker! private var typeDownPicker: DownPicker! // navigation bar buttons private lazy var cancelButton: UIBarButtonItem = { return UIBarButtonItem(title: "Cancel", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("backAction")) }() private lazy var saveButton: UIBarButtonItem = { return UIBarButtonItem(title: "Save", style: UIBarButtonItemStyle.Plain, target: self, action: "saveButtonClicked") }() // hearing not required, but client is needed in order to make requests and to display name, this controllers entire operation depends on the client being present override func viewDidLoad() { super.viewDidLoad() // client is required for controller to do anything if let validClient = self.client { self.clientNameLabel.text = validClient.name // configure textField borders let borderThickness: CGFloat = 1.5 let borderColor = UIColor(white: 0, alpha: 0.15) self.dateTextField.layer.addBorder(UIRectEdge.Bottom, color: borderColor, thickness: borderThickness) self.timeTextField.layer.addBorder(UIRectEdge.Bottom, color: borderColor, thickness: borderThickness) self.typeTextField.layer.addBorder(UIRectEdge.Bottom, color: borderColor, thickness: borderThickness) if let hearing = self.client?.hearing { self.mode = self.EDIT_MODE self.title = "Edit Hearing" self.referenceHearing = hearing self.internalHearing = CAHearing(hearing: self.referenceHearing) } else { self.mode = self.CREATE_MODE self.title = "Add Hearing" self.referenceHearing = CAHearing(id: -1, date: NSDate().dateByAddingDays(1), type: CAHearingType.SixMonthReview) self.internalHearing = CAHearing(hearing: referenceHearing) } let initialOptionIndex = find(self.hearingTypeOptions, self.internalHearing.type.rawValue) ?? 0 // wrap textfields to act like drop downs self.dateDownPicker = DateDownPicker(textField: self.dateTextField, withDate: self.internalHearing.date, withMode: UIDatePickerMode.Date) self.timeDownPicker = DateDownPicker(textField: self.timeTextField, withDate: self.internalHearing.date, withMode: UIDatePickerMode.Time) self.typeDownPicker = DownPicker(textField: self.typeTextField, withData: NSMutableArray(array: self.hearingTypeOptions), withIntialOption: initialOptionIndex) //registers the changes to hearing self.dateDownPicker.addTarget(self, action: "editedHearing", forControlEvents: UIControlEvents.ValueChanged) self.timeDownPicker.addTarget(self, action: "editedHearing", forControlEvents: UIControlEvents.ValueChanged) self.typeDownPicker.addTarget(self, action: "editedHearing", forControlEvents: UIControlEvents.ValueChanged) // navigation buttons self.navigationItem.leftBarButtonItem = cancelButton self.navigationItem.rightBarButtonItem = saveButton } } func editedHearing() { self.populateHearingWithValues(self.internalHearing) self.hasUnsavedChanges = self.hasHearingChanged(self.internalHearing) } func hasHearingChanged(hearing: CAHearing) -> Bool { return hearing != self.referenceHearing } func populateHearingWithValues(hearing: CAHearing) { let selectedDate = self.dateDownPicker.getDate() let selectedTime = self.timeDownPicker.getDate() hearing.date = NSDate.combineDateAndTime(selectedDate, time: selectedTime) hearing.type = CAHearingType(rawValue: self.typeDownPicker.text)! } func saveHearing() { if let c = self.client { if self.mode == self.EDIT_MODE { // edit mode self.api.updateHearing(c.id, hearing: self.internalHearing, completionHandler: {error in if let updateError = error { Functions.alertError(updateError) } else { CAToast.makeToast("Hearing Saved") self.hasUnsavedChanges = false // update reference self.referenceHearing = CAHearing(hearing: self.internalHearing) } }) } else if self.mode == self.CREATE_MODE { // in create mode self.api.createHearing(c.id, hearing: self.internalHearing, completitionHandler: { error in if let addError = error { Functions.alertError(addError) } else { CAToast.makeToast("Hearing Saved") self.hasUnsavedChanges = false // Have to exit form, no way for us to simply change to edit mode // because we don't know the id of the newly created report nor is there an efficient way to get it // TODO: update API to return id of newly created object self.backAction(); } }) } } else { debugPrintln("Client not provided, unable to save hearing") } } @IBAction func informationButton(sender: UIButton) { Functions.alert("Hearing Information", message: "Set the court date, time, and type here.") } // MARK: Helper functions func backAction() { //pop the first view from the stack if self.hasUnsavedChanges { saveAlert("Save Hearing?") } else { self.navigationController?.popViewControllerAnimated(true) } } func saveButtonClicked() { self.saveHearing() } // construct lazy objects func defaultMode() -> Int { return self.CREATE_MODE } func constructHearingTypeOptions() -> [String] { return [ CAHearingType.SixMonthReview.rawValue, CAHearingType.TwelveMonthReview.rawValue, CAHearingType.EighteenMonthReview.rawValue, CAHearingType.SelectionAndImplementation.rawValue, CAHearingType.PostPermPlanReview.rawValue, CAHearingType.FamilyMaintenanceReview.rawValue, CAHearingType.DualStatus.rawValue ] } func saveAlert(title: String){ var alert = UIAlertView() alert.delegate = self alert.title = title; alert.message = "Exit without saving?"; alert.addButtonWithTitle("Save") alert.addButtonWithTitle("Discard") alert.show() } func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) { switch buttonIndex { case 0: saveHearing() println("save") break; case 1: self.navigationController?.popViewControllerAnimated(true) println("Yes") break; default: println("default called") break; } } } ShortTypeTextViewDelegate.swift // // ShortTypeTextViewDelegate.swift // CaseAide // // Created by Alan on 10/22/15. // Copyright (c) 2015 CaseAide. All rights reserved. // import UIKit class ShortTypeTextViewDelegate: NSObject, UITextViewDelegate { func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool { /* Possibillities 1. User inserting new character (regular typing on keyboard) 2. User inserting new string (copy and paste, speech to text) 3. User replacing range with character (user highlight range and type on keyboard) 4. User replacing range with string (autocorrect, user copy and paste highlighted range) 5. User deleting character (user delete from keyboard) 6. User deleting string (user selects range and press delete on keyboard) */ let textLength = count(text) let commitedText = textView.text // User deleting, do nothing if textLength == 0 && range.length > 0 { println("user deleting characters") return true } // User inserting new character or user replacing range with character if textLength == 1 { let wordSeperatingChar = NSCharacterSet.alphanumericCharacterSet().invertedSet let isSeperatingChar = text.rangeOfCharacterFromSet(wordSeperatingChar, options: NSStringCompareOptions.allZeros, range: nil) != nil if isSeperatingChar { let textViewText = textView.text // startingIndex..<endingIndex is range between the last two seperators (the one just entered and one previously entered) let endingIndex = advance(textViewText.startIndex, range.location) // get ending index of textview // index to last seperator in textview var startingIndex: String.Index if let seperatorRange = textViewText.rangeOfCharacterFromSet(wordSeperatingChar, options: NSStringCompareOptions.BackwardsSearch, range: nil) { startingIndex = advance(seperatorRange.startIndex, 1) } else { startingIndex = textViewText.startIndex } let wordRange = Range<String.Index>(start: startingIndex, end: endingIndex) let word = textViewText.substringWithRange(wordRange) let updatedWord = self.convertAllAbbrToWords(word) textView.text.replaceRange(wordRange, with: updatedWord + text) return false } } else // user inserting new string or replacing range with string { let updatedText = self.convertAllAbbrToWords(text) let startIndex = advance(textView.text.startIndex, range.location) let endIndex = advance(startIndex, range.length) let updateRange = Range<String.Index>(start: startIndex , end: endIndex) textView.text.replaceRange(updateRange, with: updatedText) return false } return true } func convertAllAbbrToWords(text: String) -> String { let uppercaseCharactersSet = NSCharacterSet.uppercaseLetterCharacterSet() var updatedText = text text.enumerateSubstringsInRange(Range<String.Index>(start: text.startIndex, end: text.endIndex), options: NSStringEnumerationOptions.ByWords | NSStringEnumerationOptions.Reverse) { (substring, subrange, enclosingRange, stop) -> Void in if var word = self.abbrWordMap[substring.uppercaseString]?.lowercaseString { // if abbreviation was capitalized, capitalize first letter of first word let firstLetterAbbr = substring.substringToIndex(advance(substring.startIndex, 1)) let isAbbrUppercase = firstLetterAbbr.rangeOfCharacterFromSet(uppercaseCharactersSet, options: NSStringCompareOptions.allZeros, range: nil) != nil if isAbbrUppercase { word = word.capitalizedString } updatedText.replaceRange(subrange, with: word) } } return updatedText } func getLastWordRange(str: String, inRange range: Range<String.Index>) -> Range<String.Index>? { let alphaNumericCharacterSet = NSCharacterSet.alphanumericCharacterSet() let seperatorCharacterSet = alphaNumericCharacterSet.invertedSet if let lastLetterInWordRange = str.rangeOfCharacterFromSet(alphaNumericCharacterSet, options: NSStringCompareOptions.BackwardsSearch, range: range) { if let preWordSeperator = str.rangeOfCharacterFromSet(seperatorCharacterSet, options: NSStringCompareOptions.BackwardsSearch, range: Range<String.Index>(start: range.startIndex, end: lastLetterInWordRange.startIndex)) { // word is between the preword seperator and after lastLetterRange return Range<String.Index>(start: preWordSeperator.endIndex, end: lastLetterInWordRange.endIndex) } else { // word is from beginning of range to one after lastLetterRange return Range<String.Index>(start: range.startIndex, end: lastLetterInWordRange.endIndex) } } // no word present return nil } let abbrWordMap: [String: String] = [ "CCL": "community care licensing", "CASA": "Court Appointed Special Advocate", "CPS": "Child Protective Services", "CFS": "Children and Family Services", "DPSS": "Department of Public Social Services", "DBH": "Department of Behavioral Health ", "SW": "Social Worker", "ILP": "Independent Living Program ", "ILPSW": "Independent Living Program Social Worker", "MSW": "Masters in Social Work", "AWOL": "Absent Without Leave", "FFA": "Foster Family Agency", "FF": "Face to face", "F2F": "Family to Family", "FP": "foster parent", "FH": "foster home", "FR": "Family Reunification", "FM": "Family Maintenance ", "GH": "group home", "TC": "telephone call ", "TDM": "Team Decision Meeting", "NMD": "non-minor dependent ", "NREFM": "non-relative extended family member", "LG": "legal guardian", "SDM": "structured decision making", "JH": "Juvenile Hall", "PO": "Probation Officer", "AB 12": "Assembly Bill 12", "WRAP": "wraparound services", "ER": "emergency response", "CDU": "Court Dependency Unit", "WIC": "Welfare and Institutions Code", "Postox": "Positive Toxicity", "OHI": "Out of Home Investigation", "CR": "court report", "IR": "immediate response", "ES": "emergency shelter", "PFA": "Peer and Family Assistant", "TCO": "transitional conference", "AA": "alcoholics Anonymous", "ADD": "Attention Deficit Disorder", "ADHD": "Attention Deficit Hyperactivity Disorder", "ODD": "Oppositional Defiance Disorder", "BPB": "Borderline Personality Disorder", "RA": "Reactive Attachment", "APB": "All Points Bulletin", "BIA": "Bureau of Indian Affairs", "CBO": "community based organization", "CMT": "Case Management Team", "CM": "case management ", "Clt": "client", "DA": "district attorney", "DCFS": "Department of Children and Family Services", "HIV": "Human Immunodeficiency Virus", "ICU": "intensive care unit", "IL": "Independent Living ", "NA": "Narcotics Anonymous", "PHN": "Public Health Nurse ", "CD": "Conduct Disorder", "TBS": "Therapeutic Behavioral Services", "MHS": "Mental Health Services", "SOP": "Safety Organized Practices", "Plt": "Placement", "CW": "Child Welfare", "CSWE": "Council of Social Work Education ", "RJC": "Riverside Juvenile Court", "SBJC": "San Bernardino Juvenile Court", "IYRT": "Interagency Youth Resiliency Team", "EFC": "Extended Foster Care", "DMH": "Department of Mental Health", "IHSS": "In Home Support Services", "APS": "Adult Protective Services", "DOB": "date of birth", "DUI": "driving under the influence ", "EAP": "Employment Assistance Program", "EW": "Eligibility Worker", "SSA": "Social Service Assistant", "SSP": "Social Service Practitioner", "SSS": "Social Service Supervisor", "CP": "Case Plan", "TILP": "Transitional Independent Living Plan", "SILP": "Supervised Independent Living Placement", "CFVAN": "Client was free from any visible signs of abuse or neglect", "CVAN": "Client had visible sings of abuse and neglect", "IRC": "Inland Regional Center", "Sup": "supervisor", "DPO": "Deputy Probation Officer", "SO": "Sherriff Officer ", "MD": "Medical Doctor", "CAPTS": "Child Abuse Prevention and Treatment Team", "ACT": "Assessment and Consultation Team", "FVSC": "Family Visitation and Support Center", "CFT’s": "Child and Family Team Meeting", "CLETS": "California Law Enforcement Telecommunication System", "CPR": "Concurrent Planning Review", "CNS": "Children’s Strengths and Needs", "MDT": "Multi-Disciplinary Team", "PAS": "Purchase Authorization for Service", "PMU": "Placement Management Unit", "PPLA": "Planned Permanent Living Arrangement ", "RAU": "Relative assessment Unit", "RCAT": "Riverside Child Assessment Team", "MHST": "Mental health Screening Tool ", "PGM": "paternal grandmother", "PGF": "paternal grandfather", "MGM": "maternal grandmother", "GM": "Grandmother", "GF": "Grandfather", "IRB": "Institutional review board", "NASW": "National Association of Social Workers", "NAMI": "National Alliance of Mentally Ill", "MS": "Master of Science ", "BASW": "Bachelors of Social Work", "CalSWEC": "California Social Work Education Center", "FI": "field instructor", "FL": "field liaison", "Pro": "professor", "LCSW": "Licensed Clinical Social Worker ", "MFT": "Marriage and Family Therapist", "SOSW": "School of Social Work", "SWSA": "Social Work Student Association ", "PDEP": "Pathways Distance Education Program" ] } StartEndDateContactCell.swift // // StartEndDateContactCell.swift // CaseAide // // Created by Alan Perez on 8/19/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class StartEndDateContactCell: UITableViewCell { @IBOutlet weak var startDateFieldLabel: UILabel! @IBOutlet weak var startDateValueLabel: UILabel! @IBOutlet weak var endDateFieldLabel: UILabel! @IBOutlet weak var endDateValueLabel: UILabel! override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } StringExtensions.swift // // StringExtensions.swift // CaseAide // // Created by Alan Perez on 9/1/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import Foundation extension String { func firstCharString() -> String { let firstIndex = advance(self.startIndex, 1) return self.substringToIndex(firstIndex) } func isFirstLetterCapitalized() -> Bool { let uppercaseCharactersSet = NSCharacterSet.uppercaseLetterCharacterSet() return self.firstCharString().rangeOfCharacterFromSet(uppercaseCharactersSet, options: NSStringCompareOptions.allZeros, range: nil) != nil } func capitalizeFirstLetter() -> String { let capFirstLetter = self.firstCharString().uppercaseString let replaceRange = Range<String.Index>(start: self.startIndex, end: advance(self.startIndex, 1)) return self.stringByReplacingCharactersInRange(replaceRange, withString: capFirstLetter) } func lastWordSubstringInRange(range: Range<String.Index>) -> (word: String, range: Range<String.Index>) { let completeRange = range var lastWord = self var range = completeRange self.enumerateSubstringsInRange(completeRange, options: NSStringEnumerationOptions.ByWords | NSStringEnumerationOptions.Reverse) { (substring, subrange, enclosingRange, stop) -> Void in lastWord = substring range = subrange stop = true } return (lastWord, range) } func hasWordSeperator() -> Bool { var wordSeperatingCharSet = NSMutableCharacterSet.punctuationCharacterSet() wordSeperatingCharSet.formUnionWithCharacterSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) return self.rangeOfCharacterFromSet(wordSeperatingCharSet, options: NSStringCompareOptions.allZeros, range: nil) != nil } func numberOfWords() -> Int { let completeRange = Range<String.Index>(start: self.startIndex, end: self.endIndex) var count = 0 self.enumerateSubstringsInRange(completeRange, options: NSStringEnumerationOptions.ByWords) { (substring, subrange, enclosingRange, stop) -> Void in count++ } return count } } extension String { func md5() -> String { let cStr = self.cStringUsingEncoding(NSUTF8StringEncoding) let strLen = CC_LONG(self.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)) let digestLen = Int(CC_MD5_DIGEST_LENGTH) let result = UnsafeMutablePointer<CUnsignedChar>.alloc(digestLen) CC_MD5(cStr!, strLen, result) var hash = NSMutableString() for i in 0..<digestLen { hash.appendFormat("%02x", result[i]) } return String(hash) } //resource: By Ole Begemann July 3, 2014 //http://oleb.net/blog/2014/07/swift-strings/ subscript(integerIndex: Int) -> Character { let index = advance(startIndex, integerIndex) return self[index] } subscript(integerRange: Range<Int>) -> String { let start = advance(startIndex, integerRange.startIndex) let end = advance(startIndex, integerRange.endIndex) let range = start..<end return self[range] } } TextReusableView.swift // // TextReusableView.swift // CaseAide // // Created by Alan Perez on 7/30/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class TextReusableView: UICollectionReusableView { @IBOutlet weak var titleLabel: UILabel! } UIBarButtonItemExtensions.swift // // UIBarButtonItemExtensions.swift // CaseAide // // Created by Alan on 1/8/16. // Copyright (c) 2016 CaseAide. All rights reserved. // import UIKit extension UIBarButtonItem { class func defaultCABackButton() -> UIBarButtonItem { return UIBarButtonItem(title: "", style: UIBarButtonItemStyle.Plain, target: nil, action: nil) } } UIColorExtensions.swift // // UIColorExtensions.swift // CaseAide // // Created by Alan on 8/20/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import Foundation extension UIColor { class func caseAideBlue() -> UIColor { return UIColor(red: 74/255.0, green: 144/255.0, blue: 226/255.0, alpha: 1) } } UIImageExtensions.swift // // UIImageExtensions.swift // CaseAide // // Created by Alan on 8/20/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import Foundation extension UIImage { class func imageWithColor(color: UIColor) -> UIImage { let rect = CGRectMake(0, 0, 1, 1) UIGraphicsBeginImageContext(rect.size) let context = UIGraphicsGetCurrentContext() CGContextSetFillColorWithColor(context, color.CGColor) CGContextFillRect(context, rect) let image: UIImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image } func imageWithColor(color: UIColor) -> UIImage { UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale); let context = UIGraphicsGetCurrentContext(); CGContextTranslateCTM(context, 0, self.size.height); CGContextScaleCTM(context, 1.0, -1.0); CGContextSetBlendMode(context, kCGBlendModeNormal); let rect = CGRectMake(0, 0, self.size.width, self.size.height); CGContextClipToMask(context, rect, self.CGImage); color.setFill() CGContextFillRect(context, rect); let newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return newImage; } } UIViewExtensions.swift // // UIViewExtensions.swift // CaseAide // // Created by Alan Perez on 8/20/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import Foundation extension UIView { func addTopBorderWithColor(color: UIColor, borderWidth: CGFloat) { let border = UIView(frame: CGRectMake(0, 0, self.frame.size.width, borderWidth)) border.backgroundColor = color self.addSubview(border) } func addLeftBorderWithColor(color: UIColor, borderWidth: CGFloat) { let border = UIView(frame: CGRectMake(0, 0, borderWidth, self.frame.size.height)) border.backgroundColor = color self.addSubview(border) } func addRightBorderWithColor(color: UIColor, borderWidth: CGFloat) { let border = UIView(frame: CGRectMake(self.frame.size.width-borderWidth, 0, borderWidth, self.frame.size.height)) border.backgroundColor = color self.addSubview(border) } func addBottomBorderWithColor(color: UIColor, borderWidth: CGFloat) { let border = UIView(frame: CGRectMake(0, self.frame.size.height-borderWidth, self.frame.size.width, borderWidth)) border.backgroundColor = color self.addSubview(border) } } // credit: http://stackoverflow.com/questions/17355280/how-to-add-a-border-just-on-the-top-side-of-a-uiview extension CALayer { func addBorder(edge: UIRectEdge, color: UIColor, thickness: CGFloat) { var border = CALayer() switch edge { case UIRectEdge.Top: border.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), thickness) break case UIRectEdge.Bottom: border.frame = CGRectMake(0, CGRectGetHeight(self.frame) - thickness, CGRectGetWidth(self.frame), thickness) break case UIRectEdge.Left: border.frame = CGRectMake(0, 0, thickness, CGRectGetHeight(self.frame)) break case UIRectEdge.Right: border.frame = CGRectMake(CGRectGetWidth(self.frame) - thickness, 0, thickness, CGRectGetHeight(self.frame)) break default: break } border.backgroundColor = color.CGColor; self.addSublayer(border) } } VerticalLayout.swift // // VerticalLayout.swift // CaseAide // // Created by Alan Perez on 7/30/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit @objc protocol VerticalLayoutDelegate: UICollectionViewDelegate { optional func layoutItemHeight(layout: VerticalLayout) -> CGFloat optional func layoutSectionEdgeInsets(layout: VerticalLayout) -> UIEdgeInsets optional func layoutSupplementalSize(layout: VerticalLayout) -> CGSize optional func layoutInterItemDistance(layout: VerticalLayout) -> CGFloat } class VerticalLayout: UICollectionViewLayout { var supplementalAttributes: [NSIndexPath: UICollectionViewLayoutAttributes]! var itemAttributes: [NSIndexPath: UICollectionViewLayoutAttributes]! static let sectionTitleSupplementaryViewKind = "sectionTitleSupplementaryViewKind" private var totalHeight: CGFloat = 0 // layout parameters private var sectionInsets: UIEdgeInsets! private var interDistance: CGFloat! private var supplementalSize: CGSize! private var itemHeight: CGFloat! // default values for layout parameters let defaultSectionInsets: UIEdgeInsets = UIEdgeInsets(top: 10, left: 20, bottom: 25, right: 10) let defaultInterDistance: CGFloat = 5.0 let defaultSupplementalSize: CGSize = CGSizeMake(0, 0) let defaultItemHeight: CGFloat = 50.0 // custom layout // section // row // row // row // section // row // row // row override func prepareLayout() { let delegate = self.collectionView!.delegate as! VerticalLayoutDelegate // load layout parameters self.itemHeight = delegate.layoutItemHeight?(self) ?? self.defaultItemHeight self.sectionInsets = delegate.layoutSectionEdgeInsets?(self) ?? self.defaultSectionInsets self.interDistance = delegate.layoutInterItemDistance?(self) ?? self.defaultInterDistance self.supplementalSize = delegate.layoutSupplementalSize?(self) ?? self.defaultSupplementalSize self.supplementalAttributes = [NSIndexPath: UICollectionViewLayoutAttributes]() self.itemAttributes = [NSIndexPath: UICollectionViewLayoutAttributes]() // iterate through items of section and set item and supplemental attributes let nSections = self.collectionView!.numberOfSections() var sectionOrigin = CGPoint(x: self.sectionInsets.left, y: self.sectionInsets.top) for s in 0..<nSections { let nItems = collectionView!.numberOfItemsInSection(s) let nItemInterSpaces = nItems - 1 var sectionHeight = CGFloat(nItems) * itemHeight + CGFloat(nItemInterSpaces) * interDistance var sectionWidth = self.collectionView!.frame.width - self.sectionInsets.left - self.sectionInsets.right let sectionSize = CGSizeMake(sectionWidth, sectionHeight) for i in 0..<nItems { let itemIndexPath = NSIndexPath(forItem: i, inSection: s) let ithItemAttributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: itemIndexPath) let xPos = sectionOrigin.x + supplementalSize.width + interDistance let yPos = sectionOrigin.y + CGFloat(i) * (itemHeight + interDistance) let height = itemHeight let width = sectionWidth - supplementalSize.width - interDistance ithItemAttributes.frame = CGRectMake(xPos, yPos, width, height) itemAttributes[itemIndexPath] = ithItemAttributes } // supplemental view attributes let supplementalIndexPath = NSIndexPath(forItem: 0, inSection: s) let sthSupplementalAttributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: VerticalLayout.sectionTitleSupplementaryViewKind, withIndexPath: supplementalIndexPath) let xPos = sectionOrigin.x let yPos = sectionOrigin.y let width = supplementalSize.width let height: CGFloat if supplementalSize.height < sectionSize.height { height = supplementalSize.height } else { height = sectionSize.height } sthSupplementalAttributes.frame = CGRectMake(xPos, yPos, width, height) supplementalAttributes[supplementalIndexPath] = sthSupplementalAttributes // shift section origin sectionOrigin.y = sectionOrigin.y + sectionHeight + sectionInsets.bottom } self.totalHeight = sectionOrigin.y } override func collectionViewContentSize() -> CGSize { return CGSizeMake(collectionView!.frame.size.width, self.totalHeight) } override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? { var layoutAttributes = Array<UICollectionViewLayoutAttributes>() for (indexPath, itemAttr) in itemAttributes { if CGRectIntersectsRect(itemAttr.frame, rect) { layoutAttributes.append(itemAttr) } } for (indexPath, supplAttr) in supplementalAttributes { if CGRectIntersectsRect(supplAttr.frame, rect) { layoutAttributes.append(supplAttr) } } return layoutAttributes } override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! { return self.itemAttributes[indexPath] } override func layoutAttributesForSupplementaryViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! { return self.supplementalAttributes[indexPath] } override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool { return true } } ViewContactController.swift // // ViewContactController.swift // CaseAide // // Created by Alan Perez on 8/19/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class ViewContactController: UIViewController, UITableViewDataSource, UITableViewDelegate { // parameters passed in by caller var contact: CAContact? var client: CAClient? // models used to reload changes to contact private var api: CAApi = CAApi.defaultApi // Segue parameters private let editContactSegueIdentifier = "editContactSegueIdentifier" private var editPageNumber = 0 // Tableview parameters private let basicContactCellReuseIdentifier = "basicContactCellReuseIdentifier" private let dateContactCellReuseIdentifier = "dateContactCellReuseIdentifier" private let noteCellReuseIdentifier = "noteCellReuseIdentifier" private let minCellHeight: CGFloat = 70.0 @IBOutlet weak var contactView: AssetView! // header view on tableview @IBOutlet weak var tableView: UITableView! let dateIndex = 0 let staffIndex = 1 let objectiveIndex = 2 let approachIndex = 3 let placeIndex = 4 let outcomeIndex = 5 let personsIncludedIndex = 6 let inSupportOfIndex = 7 let noteIndex = 8 // Date Formatting private var dateFormatter: NSDateFormatter! let contactViewDateFormat = "MMM dd, yyyy" let contactDateFormat = "MMM dd, yyyy h:mm a" let arraySeperator = ", " // Prototype cells to dynamically resize cells based on content lazy var prototypeBlobCell: BlobCell = self.createBlobCell() lazy var prototypeBasicCell: BasicCell = self.createBasicCell() lazy var prototypeDateCell: StartEndDateContactCell = self.createDateCell() // MARK: ViewContact lifecycle override func viewDidLoad() { super.viewDidLoad() navigationItem.backBarButtonItem = UIBarButtonItem.defaultCABackButton() self.dateFormatter = NSDateFormatter() self.dateFormatter.dateFormat = self.contactDateFormat self.updateContactView() } override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) if let validClient = self.client, let validContact = self.contact { api.readContact(validContact.id, clientId: validClient.id, completionHandler: { responseContact, error in if let updatedContact = responseContact { self.contact = updatedContact self.tableView.reloadData() self.updateContactView() } }) } } // MARK: Helper functions func dayOfWeekString(dow: Int) -> String { let dowString: String switch dow { case 1: dowString = "Sunday" case 2: dowString = "Monday" case 3: dowString = "Tuesday" case 4: dowString = "Wednesday" case 5: dowString = "Thursday" case 6: dowString = "Friday" case 7: dowString = "Saturday" default: dowString = "Unknown" } return dowString } func updateContactView() { self.dateFormatter.dateFormat = self.contactViewDateFormat var dayOfWeek = "N/A" var date = "N/A" if let c = self.contact { date = self.dateFormatter.stringFromDate(c.startDate) dayOfWeek = self.dayOfWeekString(c.startDate.dayOfWeek()) } self.contactView.dayLabel.text = "\(dayOfWeek.uppercaseString)," self.contactView.dateLabel.text = date.uppercaseString } func calculateHeight(cell: UITableViewCell) -> CGFloat { cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(self.tableView.bounds), CGRectGetHeight(cell.bounds)) cell.layoutIfNeeded() let size = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) // take into account cell seperator height let calculatedHeight = size.height + 1 return max(calculatedHeight, self.minCellHeight) } func configNoteCell(cell: BlobCell) { cell.fieldLabel.text = "Note" cell.valueLabel.text = self.contact?.note } func configBasicCell(cell: BasicCell, indexPath: NSIndexPath) { switch indexPath.row { case staffIndex: cell.fieldLabel.text = "Staff" cell.valueLabel.text = self.contact!.staff case objectiveIndex: cell.fieldLabel.text = "Objective" cell.valueLabel.text = self.contact!.objective case approachIndex: cell.fieldLabel.text = "Approach" cell.valueLabel.text = self.contact!.approach case placeIndex: cell.fieldLabel.text = "Place" cell.valueLabel.text = self.contact!.place case outcomeIndex: cell.fieldLabel.text = "Outcome" cell.valueLabel.text = self.contact!.outcome case personsIncludedIndex: cell.fieldLabel.text = "Persons Included" cell.valueLabel.text = arraySeperator.join(self.contact!.personsIncluded) case inSupportOfIndex: cell.fieldLabel.text = "In Support Of" cell.valueLabel.text = arraySeperator.join(self.contact!.inSupportOf) default: break; } } func configDateCell(cell: StartEndDateContactCell) { self.dateFormatter.dateFormat = self.contactDateFormat cell.startDateFieldLabel.text = "From" cell.startDateValueLabel.text = self.dateFormatter.stringFromDate(self.contact!.startDate) cell.endDateFieldLabel.text = "To" cell.endDateValueLabel.text = self.dateFormatter.stringFromDate(self.contact!.endDate) } func createBlobCell() -> BlobCell { return self.tableView.dequeueReusableCellWithIdentifier(self.noteCellReuseIdentifier) as! BlobCell } func createBasicCell() -> BasicCell { return self.tableView.dequeueReusableCellWithIdentifier(self.basicContactCellReuseIdentifier) as! BasicCell } func createDateCell() -> StartEndDateContactCell { return self.tableView.dequeueReusableCellWithIdentifier(self.dateContactCellReuseIdentifier) as! StartEndDateContactCell } // MARK: TableView datasource methods func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 9 } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { if indexPath.row == self.dateIndex { let dateCell = tableView.dequeueReusableCellWithIdentifier(self.dateContactCellReuseIdentifier, forIndexPath: indexPath) as! StartEndDateContactCell self.configDateCell(dateCell) return dateCell } else if indexPath.row == self.noteIndex { let noteCell = tableView.dequeueReusableCellWithIdentifier(self.noteCellReuseIdentifier, forIndexPath: indexPath) as! BlobCell self.configNoteCell(noteCell) return noteCell } else { let basicCell = tableView.dequeueReusableCellWithIdentifier(self.basicContactCellReuseIdentifier, forIndexPath: indexPath) as! BasicCell self.configBasicCell(basicCell, indexPath: indexPath) return basicCell } } func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return UITableViewAutomaticDimension } func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { if indexPath.row == self.dateIndex { self.configDateCell(self.prototypeDateCell) return self.calculateHeight(self.prototypeDateCell) } else if indexPath.row == self.noteIndex { self.configNoteCell(self.prototypeBlobCell) return self.calculateHeight(self.prototypeBlobCell) } else { self.configBasicCell(self.prototypeBasicCell, indexPath: indexPath) return self.calculateHeight(self.prototypeBasicCell) } } func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { tableView.deselectRowAtIndexPath(indexPath, animated: true) self.editPageNumber = self.mapRowToPageNumber(indexPath.row) self.performSegueWithIdentifier(self.editContactSegueIdentifier, sender: self) } func mapRowToPageNumber(row: Int) -> Int { // it just so happens that the rows map directly to pages in edit controller return row } @IBAction func editButtonPressed(sender: AnyObject) { self.editPageNumber = 0 self.performSegueWithIdentifier(self.editContactSegueIdentifier, sender: self) } // MARK: - Navigation override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == self.editContactSegueIdentifier { var editContactController = segue.destinationViewController as! CASetContactFormController editContactController.client = self.client println("line 251: prepair for seque: \(client?.name)") editContactController.contact = self.contact println(editContactController.contact) editContactController.startPage = self.editPageNumber } } } ViewReportController.swift // // ViewReportController.swift // CaseAide // // Created by Alan Perez on 8/19/15. // Copyright (c) 2015 Alan Perez. All rights reserved. // import UIKit class ViewReportController: UIViewController, UITableViewDataSource, UITableViewDelegate { // passed in by client var report: CAReport? var client: CAClient? // models used to reload changes to contact private var api: CAApi = CAApi.defaultApi let editReportSegueIdentifier = "editReportSegueIdentifier" let basicReportCellReuseIdentifier = "basicReportCellReuseIdentifier" let templateCellReuseIdentifier = "templateCellReuseIdentifier" private lazy var prototypeBlobCell: BlobCell = self.createBlobCell() private lazy var prototypeBasicCell: BasicCell = self.createBasicCell() private var editPageNumber = 0 let reportViewDateFormat = "MMM dd, yyyy" let reportDateFormat = "MMM dd, yyyy h:mm a" var dateFormatter: NSDateFormatter! @IBOutlet weak var tableView: UITableView! let minCellHeight: CGFloat = 70.0 @IBOutlet weak var reportView: AssetView! let arraySeperator = ", " let basicDataSection = 0 let dateIndex = 0 let reportTypeIndex = 1 let courtIndex = 2 let roomIndex = 3 let childrenIndex = 4 let templateSection = 1 override func viewDidLoad() { super.viewDidLoad() navigationItem.backBarButtonItem = UIBarButtonItem.defaultCABackButton() self.dateFormatter = NSDateFormatter() self.dateFormatter.dateFormat = self.reportDateFormat self.updateReportView() } override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) if let validClient = self.client, let validReport = self.report { api.readReport(validReport.id, clientId: validClient.id, completionHandler: { responseReport, error in if let updatedReport = responseReport { self.report = updatedReport self.tableView.reloadData() self.updateReportView() } }) } } // MARK: TableView data source methods func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 2 } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if section == self.basicDataSection { return 5 } else if section == self.templateSection { return self.report?.fields.count ?? 0 } return 0 } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { if indexPath.section == self.templateSection { let templateCell = tableView.dequeueReusableCellWithIdentifier(self.templateCellReuseIdentifier, forIndexPath: indexPath) as! BlobCell let template = self.report?.fields[indexPath.row] self.configTemplateCell(templateCell, template: template) return templateCell } else { let basicCell = tableView.dequeueReusableCellWithIdentifier(self.basicReportCellReuseIdentifier, forIndexPath: indexPath) as! BasicCell self.configBasicCell(basicCell, indexPath: indexPath, report: self.report) return basicCell } } // MARK: TableView delegate methods func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { tableView.deselectRowAtIndexPath(indexPath, animated: true) self.editPageNumber = self.mapIndexPathToPage(indexPath) self.performSegueWithIdentifier(self.editReportSegueIdentifier, sender: self) } func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { let cell: UITableViewCell if indexPath.section == basicDataSection { let basicCell = self.prototypeBasicCell self.configBasicCell(basicCell, indexPath: indexPath, report: self.report) cell = basicCell } else { let templateCell = self.prototypeBlobCell self.configTemplateCell(templateCell, template: self.report?.fields[indexPath.row]) cell = templateCell } return self.calculateHeight(cell) } func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return UITableViewAutomaticDimension } func configTemplateCell(cell: BlobCell, template: CAField?) { cell.fieldLabel.text = template?.name cell.valueLabel.text = template?.value } func configBasicCell(cell: BasicCell, indexPath: NSIndexPath, report: CAReport?) { switch indexPath.row { case dateIndex: self.dateFormatter.dateFormat = self.reportDateFormat let dateString: String if let date = self.report?.date { dateString = self.dateFormatter.stringFromDate(date) } else { dateString = "Unavailable" } cell.fieldLabel.text = "Date" cell.valueLabel.text = dateString case courtIndex: cell.fieldLabel.text = "Court" cell.valueLabel.text = self.report?.court case roomIndex: cell.fieldLabel.text = "Room" cell.valueLabel.text = self.report?.room case childrenIndex: let childrenString: String if let children = self.report?.children { childrenString = arraySeperator.join(children) } else { childrenString = "Unavailable" } cell.fieldLabel.text = "Children" cell.valueLabel.text = childrenString case reportTypeIndex: cell.fieldLabel.text = "Type" cell.valueLabel.text = self.report?.type.rawValue default: break } } func calculateHeight(cell: UITableViewCell) -> CGFloat { cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(self.tableView.bounds), CGRectGetHeight(cell.bounds)) cell.layoutIfNeeded() let size = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) // take into account cell seperator height let calculatedHeight = size.height + 1 return max(calculatedHeight, minCellHeight) } func createBlobCell() -> BlobCell { return self.tableView.dequeueReusableCellWithIdentifier(self.templateCellReuseIdentifier) as! BlobCell } func createBasicCell() -> BasicCell { return self.tableView.dequeueReusableCellWithIdentifier(self.basicReportCellReuseIdentifier) as! BasicCell } func mapIndexPathToPage(indexPath: NSIndexPath) -> Int { var pageNumber: Int = 0 if indexPath.section == self.basicDataSection { switch indexPath.row { case dateIndex, reportTypeIndex: pageNumber = 0 case courtIndex, roomIndex: pageNumber = 1 case childrenIndex: pageNumber = 2 default: break } } else { pageNumber = 3 } return pageNumber } func dayOfWeekString(dow: Int) -> String { let dowString: String switch dow { case 1: dowString = "Sunday" case 2: dowString = "Monday" case 3: dowString = "Tuesday" case 4: dowString = "Wednesday" case 5: dowString = "Thursday" case 6: dowString = "Friday" case 7: dowString = "Saturday" default: dowString = "Unknown" } return dowString } @IBAction func editButtonPressed(sender: AnyObject) { self.editPageNumber = 0 self.performSegueWithIdentifier(self.editReportSegueIdentifier, sender: self) } func updateReportView() { self.dateFormatter.dateFormat = self.reportViewDateFormat var dayOfWeek = "N/A" var date = "N/A" if let r = self.report { date = self.dateFormatter.stringFromDate(r.date) dayOfWeek = self.dayOfWeekString(r.date.dayOfWeek()) } self.reportView.dayLabel.text = "\(dayOfWeek.uppercaseString)," self.reportView.dateLabel.text = date.uppercaseString } override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == self.editReportSegueIdentifier { let editReportController = segue.destinationViewController as! CASetReportFormController editReportController.client = self.client println("View Report Controler: Segue to editviewReport \(self.client?.name)" ) editReportController.report = self.report editReportController.startPage = self.editPageNumber } } } WebViewController.swift // // WebViewController.swift // CaseAide // // Created by Alan Perez on 2/5/16. // Copyright (c) 2016 CaseAide. All rights reserved. // import UIKit class WebViewController: UIViewController, UIWebViewDelegate { @IBOutlet weak var activityIndicator: UIActivityIndicatorView! { didSet { updateActivityIndicator() } } @IBOutlet weak private var webView: UIWebView! { didSet { webView.scalesPageToFit = true webView.delegate = self } } @IBOutlet weak var navigationToolBar: UIToolbar! @IBOutlet weak var navigationToolBarHeightConstraint: NSLayoutConstraint! @IBOutlet weak var navigationBackButton: UIBarButtonItem! @IBOutlet weak var navigationForwardButton: UIBarButtonItem! private var isLoading: Bool = false { didSet { updateActivityIndicator() } } var url: NSURL? var allowNavigation: Bool = false override func viewDidLoad() { super.viewDidLoad() if let location = url { webView.loadRequest(NSURLRequest(URL: location)) } if allowNavigation { insetWebviewByNavbarHeight() } else { hideNavigationToolBar() } } func webViewDidFinishLoad(webView: UIWebView) { isLoading = false navigationBackButton.enabled = webView.canGoBack navigationForwardButton.enabled = webView.canGoForward } func webViewDidStartLoad(webView: UIWebView) { isLoading = true } func updateActivityIndicator() { if isLoading { activityIndicator.startAnimating() } else { activityIndicator.stopAnimating() } } func hideNavigationToolBar() { navigationToolBar.hidden = true navigationToolBarHeightConstraint.constant = 0 } func insetWebviewByNavbarHeight() { let navToolBarHeight = navigationToolBarHeightConstraint.constant webView.scrollView.contentInset.bottom = navToolBarHeight webView.scrollView.scrollIndicatorInsets.bottom = navToolBarHeight } @IBAction func goBack(sender: UIBarButtonItem) { webView.goBack() } @IBAction func goForward(sender: UIBarButtonItem) { webView.goForward() } }