Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Contact Logs & Call States #28

Merged
merged 14 commits into from
Feb 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions FiveCalls/FiveCalls.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion FiveCalls/FiveCalls/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}

if let font = UIFont(name: "RobotoCondensed-Bold", size: 18.0) {
UINavigationBar.appearance().titleTextAttributes = [NSFontAttributeName: font]
UINavigationBar.appearance().titleTextAttributes = [NSFontAttributeName: font,
NSForegroundColorAttributeName : UIColor.white]
}
}

Expand Down
76 changes: 48 additions & 28 deletions FiveCalls/FiveCalls/CallScriptViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@ class CallScriptViewController : UIViewController, IssueShareable {
var issuesManager: IssuesManager!
var issue: Issue!
var contact: Contact!
var logs = ContactLogs.load()

@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var footer: UIView!
@IBOutlet weak var resultUnavailableButton: ContactButton!
@IBOutlet weak var resultVoicemailButton: ContactButton!
@IBOutlet weak var resultContactedButton: ContactButton!
@IBOutlet weak var resultSkipButton: ContactButton!
@IBOutlet weak var resultNextButton: ContactButton!
@IBOutlet weak var footerLabel: UILabel!
@IBOutlet weak var footerHeightContraint: NSLayoutConstraint!
var dropdown: DropDown?

override var preferredStatusBarStyle: UIStatusBarStyle {
Expand All @@ -29,10 +34,36 @@ class CallScriptViewController : UIViewController, IssueShareable {
override func viewDidLoad() {
super.viewDidLoad()

navigationController?.navigationBar.tintColor = .white

navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(IssueDetailViewController.shareButtonPressed(_ :)))

tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension

contactChanged()
}

func contactChanged() {
if let issue = issue, let issueIndex = issue.contacts.index(where:{$0.id == contact.id}) {
title = "Contact \(issueIndex+1) of \(issue.contacts.count)"
}
let hasCompleted = logs.hasCompleted(issue: issue.id, allContacts: issue.contacts)
let hasContacted = logs.hasContacted(contactId: contact.id, forIssue: issue.id)
if hasCompleted {
self.footerHeightContraint.constant = 30
self.footerLabel.text = "You've contacted everyone, great work!"
} else {
self.footerHeightContraint.constant = hasContacted ? 75 : 118
self.footerLabel.text = hasContacted ? "You've already contacted \(self.contact.name)." : "Enter your call result to get the next call."
}
self.resultNextButton.isHidden = hasCompleted || !hasContacted
self.resultUnavailableButton.isHidden = hasCompleted || hasContacted
self.resultVoicemailButton.isHidden = hasCompleted || hasContacted
self.resultContactedButton.isHidden = hasCompleted || hasContacted
self.resultSkipButton.isHidden = hasCompleted || hasContacted
footer.setNeedsUpdateConstraints()
tableView.reloadData()
}

func callButtonPressed(_ button: UIButton) {
Expand All @@ -48,9 +79,10 @@ class CallScriptViewController : UIViewController, IssueShareable {
}
}

func reportCallOutcome(_ outcomeType: String) {
if outcomeType.characters.count > 0 {
let operation = ReportOutcomeOperation(issue: issue, contact: contact, result: outcomeType)
func reportCallOutcome(_ log: ContactLog) {
if log.outcome.characters.count > 0 {
logs.add(log: log)
let operation = ReportOutcomeOperation(log:log)
OperationQueue.main.addOperation(operation)
}
}
Expand All @@ -62,41 +94,32 @@ class CallScriptViewController : UIViewController, IssueShareable {
outcomeType = "contacted"
break
case resultSkipButton:
outcomeType = "" // Do we have a report for "skip" ?
outcomeType = "skip"
break
case resultVoicemailButton:
outcomeType = "vm"
break
case resultUnavailableButton:
outcomeType = "unavailable"
break
case resultNextButton:
print("find next contact")
break
default:
print("unknown button pressed")
}
reportCallOutcome(outcomeType)
//validate that a call button was actually pressed at some point?
let log = ContactLog(issueId: issue.id, contactId: contact.id, phone: contact.phone, outcome: outcomeType, date: Date())
reportCallOutcome(log)

// Struct passing around problems. This needs to be refactored / cleaned up.
contact.hasContacted = true
let oldContact: Contact! = contact
for i in 0..<issuesManager.issuesList!.issues.count {
if issuesManager.issuesList!.issues[i].id == self.issue.id {
for j in 0..<issuesManager.issuesList!.issues[i].contacts.count {
if issuesManager.issuesList!.issues[i].contacts[j].id == oldContact.id {
issuesManager.issuesList!.issues[i].contacts[j].hasContacted = true
}
if issuesManager.issuesList!.issues[i].contacts[j].hasContacted == false {
contact = issuesManager.issuesList!.issues[i].contacts[j]
tableView.reloadData()
}
}
for contact in issue.contacts {
if !logs.hasContacted(contactId: contact.id, forIssue: issue.id) {
self.contact = contact
contactChanged()
break
}
}
if (contact.hasContacted) {
for i in 0..<issuesManager.issuesList!.issues.count {
if issuesManager.issuesList!.issues[i].id == self.issue.id {
issuesManager.issuesList!.issues[i].madeCalls = true
}
}
if logs.hasCompleted(issue: issue.id, allContacts: issue.contacts) {
_ = navigationController?.popToRootViewController(animated: true)
}
}
Expand All @@ -121,7 +144,6 @@ extension CallScriptViewController : UITableViewDataSource {
switch indexPath.row {

case CallScriptRows.contact.rawValue:

let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactDetailCell
cell.callButton.setTitle(contact.phone, for: .normal)
cell.callButton.addTarget(self, action: #selector(callButtonPressed(_:)), for: .touchUpInside)
Expand All @@ -147,13 +169,11 @@ extension CallScriptViewController : UITableViewDataSource {
return cell

case CallScriptRows.script.rawValue:

let cell = tableView.dequeueReusableCell(withIdentifier: "scriptCell", for: indexPath) as! IssueDetailCell
cell.issueLabel.text = issue.script
return cell

default:

return UITableViewCell()

}
Expand Down
17 changes: 10 additions & 7 deletions FiveCalls/FiveCalls/Contact.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ struct Contact {
let party: String
let phone: String
let photoURL: URL?
let reason: String
let state: String
let reason: String?
let state: String?
let fieldOffices: [AreaOffice]
var hasContacted: Bool
}

extension Contact : JSONSerializable {
Expand All @@ -45,16 +44,20 @@ extension Contact : JSONSerializable {
let phone = dictionary["phone"] as? String,
let photoURLString = dictionary["photoURL"] as? String,
let reason = dictionary["reason"] as? String,
let state = dictionary["state"] as? String,
let fieldOfficesJSON = dictionary["field_offices"] as? [JSONDictionary]
let state = dictionary["state"] as? String
else {
print("Unable to parse Contact from JSON: \(dictionary)")
return nil
}

let fieldOffices = fieldOfficesJSON.flatMap(AreaOffice.init)
let fieldOffices: [AreaOffice]
if let fieldOfficesJSON = dictionary["field_offices"] as? [JSONDictionary] {
fieldOffices = fieldOfficesJSON.flatMap(AreaOffice.init)
} else {
fieldOffices = []
}

let photoURL = URL(string: photoURLString)
self.init(id: id, area: area, name: name, party: party, phone: phone, photoURL: photoURL, reason: reason, state: state, fieldOffices: fieldOffices, hasContacted: false)
self.init(id: id, area: area, name: name, party: party, phone: phone, photoURL: photoURL, reason: reason, state: state, fieldOffices: fieldOffices)
}
}
104 changes: 104 additions & 0 deletions FiveCalls/FiveCalls/ContactLog.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//
// ContactLog.swift
// FiveCalls
//
// Created by Ben Scheirman on 2/5/17.
// Copyright © 2017 5calls. All rights reserved.
//

import Foundation
import Pantry

struct ContactLog {

let issueId: String
let contactId: String
let phone: String
let outcome: String
let date: Date

static var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
return formatter
}()

}

extension ContactLog : Storable {

init(warehouse: Warehouseable) {
issueId = warehouse.get("issueId") ?? ""
contactId = warehouse.get("contactId") ?? ""
phone = warehouse.get("phone") ?? ""
outcome = warehouse.get("outcome") ?? ""
date = ContactLog.dateFormatter.date(from: warehouse.get("date") ?? "") ?? Date()
}

func toDictionary() -> [String : Any] {
return [
"issueId": issueId,
"contactId": contactId,
"phone": phone,
"outcome": outcome,
"date": ContactLog.dateFormatter.string(from: date)
]
}
}

extension ContactLog : Hashable {
var hashValue: Int {
return (issueId + contactId + phone + outcome).hash
}

static func ==(lhs: ContactLog, rhs: ContactLog) -> Bool {
return lhs.issueId == rhs.issueId &&
lhs.contactId == rhs.contactId &&
lhs.phone == rhs.phone &&
lhs.outcome == rhs.outcome
}
}

struct ContactLogs {
private static let persistenceKey = "ContactLogs"

var all: [ContactLog]

init() {
all = []
}

private init(logs: [ContactLog]) {
all = logs
}

mutating func add(log: ContactLog) {
all.append(log)
save()
}

func save() {
Pantry.pack(all, key: ContactLogs.persistenceKey)
}

static func load() -> ContactLogs {
return Pantry.unpack(persistenceKey).flatMap(ContactLogs.init) ?? ContactLogs()
}

func hasContacted(contactId: String, forIssue issueId: String) -> Bool {
if let _ = all.filter({$0.contactId == contactId && $0.issueId == issueId}).first {
return true
}
return false
}

func hasCompleted(issue: String, allContacts: [Contact]) -> Bool {
for contact in allContacts {
if !hasContacted(contactId: contact.id, forIssue: issue) {
return false
}
}
return true
}
}
5 changes: 3 additions & 2 deletions FiveCalls/FiveCalls/EditLocationViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ class EditLocationViewController : UIViewController, CLLocationManagerDelegate {
super.viewWillAppear(animated)
zipCodeTextField.becomeFirstResponder()
// FIXME
if let zip = UserDefaults.standard.string(forKey: UserDefaultsKeys.zipCode.rawValue) {
zipCodeTextField.text = zip

if case .zipCode? = UserLocation.current.locationType {
zipCodeTextField.text = UserLocation.current.locationValue
}
}

Expand Down
5 changes: 2 additions & 3 deletions FiveCalls/FiveCalls/Issue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ struct Issue {
let name: String
let reason: String
let script: String
var contacts: [Contact]
var madeCalls: Bool
let contacts: [Contact]
}

extension Issue : JSONSerializable {
Expand All @@ -43,6 +42,6 @@ extension Issue : JSONSerializable {

let contacts = contactsJSON.flatMap({ Contact(dictionary: $0) })

self.init(id: id, name: name, reason: reason, script: script, contacts: contacts, madeCalls: false)
self.init(id: id, name: name, reason: reason, script: script, contacts: contacts)
}
}
28 changes: 19 additions & 9 deletions FiveCalls/FiveCalls/IssueDetailViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,14 @@ class IssueDetailViewController : UIViewController, IssueShareable {

var issuesManager: IssuesManager!
var issue: Issue!
var logs: ContactLogs?

@IBOutlet weak var tableView: UITableView!

override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(false, animated: true)
}

override func awakeFromNib() {
super.awakeFromNib()
}
Expand All @@ -39,6 +35,21 @@ class IssueDetailViewController : UIViewController, IssueShareable {
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let indexPath = tableView.indexPathForSelectedRow {
tableView.deselectRow(at: indexPath, animated: true)
}
logs = ContactLogs.load()

navigationController?.setNavigationBarHidden(false, animated: true)
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tableView.reloadRows(at: tableView.indexPathsForVisibleRows ?? [], with: .none)
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let nav = segue.destination as? UINavigationController,
Expand Down Expand Up @@ -88,7 +99,6 @@ extension IssueDetailViewController : UITableViewDataSource {
case IssueSections.header.rawValue:
return headerCell(at: indexPath)
default:

if issue.contacts.isEmpty {
let cell = tableView.dequeueReusableCell(withIdentifier: "setLocationCell", for: indexPath)
return cell
Expand All @@ -103,8 +113,9 @@ extension IssueDetailViewController : UITableViewDataSource {
} else {
cell.avatarImageView.image = cell.avatarImageView.defaultImage
}
// This wont work while issue is a struct unless you return to root
cell.hasContacted = contact.hasContacted
if let hasContacted = logs?.hasContacted(contactId: contact.id, forIssue: issue.id) {
cell.hasContacted = hasContacted
}
return cell
}
}
Expand All @@ -114,7 +125,6 @@ extension IssueDetailViewController : UITableViewDataSource {
if section == IssueSections.contacts.rawValue {
return "Call your representatives"
}

return nil
}

Expand Down
Loading