/ *
* This Source Code Form is subject to the terms of the Mozilla Public
* License , v . 2.0 . If a copy of the MPL was not distributed with this
* file , You can obtain one at http : //mozilla.org/MPL/2.0/.
* /
var packages = [ ] ;
if ( getStorage ( "packages" ) != null ) {
packages = JSON . parse ( getStorage ( "packages" ) ) ;
}
/ * *
* Count how many items are still undelivered for a location .
* @ param { type } location An item in the packages array .
* @ returns { Number }
* /
function getUndeliveredCount ( location ) {
var undelivered = 0 ;
for ( var i = 0 ; i < location . items . length ; i ++ ) {
if ( ! location . items [ i ] . delivered ) {
undelivered ++ ;
}
}
return undelivered ;
}
function getPackage ( packageid ) {
for ( var i = 0 ; i < packages . length ; i ++ ) {
for ( var j = 0 ; j < packages [ i ] . items . length ; j ++ ) {
if ( packages [ i ] . items [ j ] . id == packageid ) {
return packages [ i ] . items [ j ] ;
}
}
}
}
function getIconForType ( type ) {
return SETTINGS . itemtypes [ type ] . icon ;
}
function getMapIconForItems ( items ) {
var types = { } ;
var deliveredcount = 0 ;
var type = "package" ;
for ( var i = 0 ; i < items . length ; i ++ ) {
// Don't consider delivered packages when determining icon,
// only count them to check if everything is delivered
if ( items [ i ] . delivered ) {
deliveredcount ++ ;
continue ;
}
if ( isNaN ( types [ items [ i ] . type ] ) ) {
types [ items [ i ] . type ] = 0 ;
}
types [ items [ i ] . type ] ++ ;
}
if ( deliveredcount == items . length ) {
return "check" ;
}
item _types = 0 ;
icon = "multiple-items" ;
// Count how many types we have, and set/overwrite the icon assuming we
// only have that type. If we end up with multiple types, we return that
// icon instead of a specific one.
//console.log(types);
for ( var type in types ) {
//console.log(type);
item _types ++ ;
if ( types [ type ] == 1 ) {
icon = SETTINGS . itemtypes [ type ] . mapicon ;
} else {
icon = SETTINGS . itemtypes [ type ] . pluralmapicon ;
}
//console.log(icon);
}
if ( item _types > 1 ) {
return "multiple-items" ;
}
return icon ;
}
function addPackage ( address , latitude , longitude , type , callback , deadline ) {
var added = false ;
if ( typeof type == 'undefined' ) {
type = SETTINGS . itemtypes [ 0 ] . id ;
}
if ( typeof deadline == 'undefined' ) {
deadline = false ;
}
// Extra precision makes the map stupider,
// and doesn't really increase accuracy since four decimal places ~= 11m
latitude = + ( parseFloat ( "" + latitude ) . toFixed ( 4 ) ) ;
longitude = + ( parseFloat ( "" + longitude ) . toFixed ( 4 ) ) ;
var packageID = uuidv4 ( ) ;
var coordsID = "" ;
for ( var i = 0 ; i < packages . length ; i ++ ) {
if ( packages [ i ] . coords [ 0 ] == latitude && packages [ i ] . coords [ 1 ] == longitude ) {
coordsID = packages [ i ] . id ;
packages [ i ] . items . push ( {
address : address ,
delivered : false ,
type : type ,
deadline : deadline ,
id : packageID
} ) ;
added = true ;
break ;
}
}
if ( ! added ) {
coordsID = uuidv4 ( ) ;
packages . push ( {
coords : [
latitude ,
longitude
] ,
id : coordsID ,
address : address ,
items : [
{
address : address ,
delivered : false ,
type : type ,
deadline : deadline ,
id : packageID
}
]
} ) ;
}
setStorage ( "packages" , JSON . stringify ( packages ) ) ;
playSound ( "ok" ) ;
app . toast . show ( {
text : SETTINGS . itemtypes [ type ] . name + ' Added!<br><span style="font-size: 80%;">' + address + "</span>" ,
position : "bottom" ,
destroyOnClose : true ,
closeTimeout : 1000 * 3
} ) ;
if ( map != null ) {
reloadMap ( ) ;
}
if ( typeof callback == 'function' ) {
callback ( {
coordsID : coordsID ,
packageID : packageID
} ) ;
}
addAutofillEntry ( address ) ;
}
/ * *
* Import a second package list and merge it with the existing one .
* @ param { type } newlist
* @ return { number } The number of packages that were skipped because they already exist locally .
* /
function importPackageList ( newlist ) {
skipped = 0 ;
for ( latlng in newlist ) {
var latitude = newlist [ latlng ] . coords [ 0 ] ;
var longitude = newlist [ latlng ] . coords [ 1 ] ;
latitude = + ( parseFloat ( "" + latitude ) . toFixed ( 4 ) ) ;
longitude = + ( parseFloat ( "" + longitude ) . toFixed ( 4 ) ) ;
for ( pkg in newlist [ latlng ] . items ) {
var added = false ;
for ( var i = 0 ; i < packages . length ; i ++ ) {
if ( + ( parseFloat ( "" + packages [ i ] . coords [ 0 ] ) . toFixed ( 4 ) ) == latitude && + ( parseFloat ( "" + packages [ i ] . coords [ 1 ] ) . toFixed ( 4 ) ) == longitude ) {
var newpackage = newlist [ latlng ] . items [ pkg ] ;
for ( var j in packages [ i ] . items ) {
if ( packages [ i ] . items [ j ] . id == newpackage . id ) {
// This package already exists in the local database.
added = true ;
skipped ++ ;
}
}
if ( ! added ) {
packages [ i ] . items . push ( package ) ;
added = true ;
}
break ;
}
}
if ( ! added ) {
packages . push ( newlist [ latlng ] ) ;
}
}
}
setStorage ( "packages" , JSON . stringify ( packages ) ) ;
if ( map != null ) {
reloadMap ( ) ;
}
return skipped ;
}
function markDelivered ( id , delivered ) {
for ( var i = 0 ; i < packages . length ; i ++ ) {
for ( var j = 0 ; j < packages [ i ] . items . length ; j ++ ) {
if ( packages [ i ] . items [ j ] . id == id ) {
if ( typeof delivered == 'undefined' ) {
if ( packages [ i ] . items [ j ] . delivered == false ) {
delivered = true ;
} else {
delivered = false ;
}
}
packages [ i ] . items [ j ] . delivered = delivered ;
if ( delivered ) {
packages [ i ] . items [ j ] . deliverytimestamp = Date . now ( ) ;
}
}
}
}
setStorage ( "packages" , JSON . stringify ( packages ) ) ;
}
function confirmDeletePackage ( package , callback ) {
app . dialog . confirm (
"Delete " + SETTINGS . itemtypes [ package . type ] . name . toLowerCase ( ) + " at " + package . address + "?" ,
"Confirm" ,
function ( ) {
// delete
deletePackage ( package . id , callback ) ;
} ,
function ( ) {
// cancel
}
) ;
}
function deletePackage ( id , callback ) {
for ( var i = 0 ; i < packages . length ; i ++ ) {
for ( var j = 0 ; j < packages [ i ] . items . length ; j ++ ) {
if ( packages [ i ] . items [ j ] . id == id ) {
packages [ i ] . items . splice ( j , 1 ) ;
if ( packages [ i ] . items . length == 0 ) {
packages . splice ( i , 1 ) ;
}
setStorage ( "packages" , JSON . stringify ( packages ) ) ;
loadPackageList ( ) ;
if ( typeof callback == 'function' ) {
callback ( id ) ;
}
return ;
}
}
}
}
function countRemainingPackages ( ) {
var undelivered = 0 ;
for ( var i = 0 ; i < packages . length ; i ++ ) {
for ( var j = 0 ; j < packages [ i ] . items . length ; j ++ ) {
if ( packages [ i ] . items [ j ] . delivered != true ) {
undelivered ++ ;
}
}
}
return undelivered ;
}
function countPackages ( ) {
var count = 0 ;
for ( var i = 0 ; i < packages . length ; i ++ ) {
for ( var j = 0 ; j < packages [ i ] . items . length ; j ++ ) {
count ++ ;
}
}
return count ;
}
function addPackageByAddress ( number , street , citystate , zip , type , callback ) {
var requestfinished = false ;
var searchingdialogopen = false ;
var deadline = false ;
var ajaxlookup = function ( ) {
var geocodecache = getStorage ( "geocode_cache" ) ;
if ( geocodecache == null ) {
geocodecache = "{}" ;
setStorage ( "geocode_cache" , "{}" ) ;
}
geocodecache = JSON . parse ( geocodecache ) ;
var cachekey = number + " || " + street + " || " + citystate ;
var cacheitem = geocodecache [ cachekey ] ;
var timestamp = Math . floor ( Date . now ( ) / 1000 ) ;
if ( typeof cacheitem != 'undefined' ) {
if ( cacheitem . added + SETTINGS . geocodecacheexpiry < timestamp ) {
console . log ( "Info" , "Removing expired geocode cache item " + cachekey ) ;
delete geocodecache [ cachekey ] ;
setStorage ( "geocode_cache" , JSON . stringify ( geocodecache ) ) ;
} else {
console . log ( "Info" , "Using cached geocode result" , cacheitem ) ;
addPackage ( cacheitem . address , cacheitem . latitude , cacheitem . longitude , type , callback , deadline ) ;
return ;
}
}
$ . ajax ( {
url : SETTINGS . geocodeapi ,
dataType : 'json' ,
data : {
number : number ,
street : street ,
citystate : citystate ,
zip : zip
} ,
timeout : 15 * 1000 ,
success : function ( resp ) {
if ( searchingdialogopen ) {
app . dialog . close ( ) ;
searchingdialogopen = false ;
}
requestfinished = true ;
if ( resp . status == "OK" ) {
if ( resp . accuracy . ok ) {
addPackage ( resp . address . street , resp . coords [ 0 ] , resp . coords [ 1 ] , type , callback , deadline ) ;
geocodecache [ cachekey ] = {
address : resp . address . street ,
latitude : resp . coords [ 0 ] ,
longitude : resp . coords [ 1 ] ,
added : Math . floor ( Date . now ( ) / 1000 )
} ;
setStorage ( "geocode_cache" , JSON . stringify ( geocodecache ) ) ;
} else {
playSound ( "error" ) ;
app . dialog . confirm (
"The address \"" + address + "\" couldn't be reliably located. Add it anyways?" ,
"Accuracy Warning" ,
function ( ok ) {
if ( resp . address . street == "" ) {
addPackage ( address , resp . coords [ 0 ] , resp . coords [ 1 ] , type , callback , deadline ) ;
} else {
addPackage ( resp . address . street , resp . coords [ 0 ] , resp . coords [ 1 ] , type , callback , deadline ) ;
}
}
) ;
}
} else {
playSound ( "error" ) ;
app . dialog . alert ( resp . message , "Error" ) ;
}
} ,
error : function ( jqXHR , status , errorThrown ) {
if ( searchingdialogopen ) {
app . dialog . close ( ) ;
searchingdialogopen = false ;
}
requestfinished = true ;
playSound ( "error" ) ;
app . dialog . alert ( "There was a network issue while finding the address. Please try adding the item again." , "Error" ) ;
}
} ) ;
// Open a loading message if there's a delay finding the address
setTimeout ( function ( ) {
if ( ! requestfinished ) {
app . dialog . preloader ( "Searching for address..." ) ;
searchingdialogopen = true ;
}
} , 750 ) ;
}
var prelookup = function ( ) {
if ( type == "express" ) {
if ( getStorage ( "deadlinealarm_minutes" ) == null ) {
setStorage ( "deadlinealarm_minutes" , 20 ) ;
}
var minutes = getStorage ( "deadlinealarm_minutes" ) ;
app . dialog . create ( {
title : 'Express Item' ,
text : 'Set a reminder for ' + minutes + ' minutes before:' ,
buttons : [
{
text : '10:30 AM' ,
close : true
} ,
{
text : '12:00 PM' ,
close : true
} ,
{
text : '3:00 PM' ,
close : true
} ,
{
text : "No reminder" ,
color : "red" ,
close : true
}
] ,
verticalButtons : true ,
onClick : function ( dialog , index ) {
deadline = new Date ( ) ;
switch ( index ) {
case 0 :
deadline . setMinutes ( 30 ) ;
deadline . setHours ( 10 ) ;
break ;
case 1 :
deadline . setMinutes ( 00 ) ;
deadline . setHours ( 12 ) ;
break ;
case 2 :
deadline . setMinutes ( 00 ) ;
deadline . setHours ( 12 + 3 ) ;
break ;
case 3 :
default :
deadline = false ;
break ;
}
if ( deadline != false ) {
deadline = deadline . getTime ( ) / 1000 ;
}
ajaxlookup ( ) ;
}
} ) . open ( ) ;
} else {
ajaxlookup ( ) ;
}
}
var deliverable = isDeliverable ( number , street ) ;
if ( deliverable . ok ) {
prelookup ( ) ;
} else {
app . dialog . confirm (
"A route note says this address is " + deliverable . reason + ". Add item anyways?" ,
"Confirm" ,
function ( ) {
prelookup ( ) ;
} ,
function ( ) {
// cancel
}
) ;
}
}
function checkDeadlines ( ) {
if ( getStorage ( "deadlinealarm_minutes" ) == null ) {
setStorage ( "deadlinealarm_minutes" , 20 ) ;
}
var minutes = getStorage ( "deadlinealarm_minutes" ) ;
var currentTime = new Date ( ) . getTime ( ) / 1000 ;
var deadlineTime = currentTime + ( minutes * 60 ) ;
for ( i in packages ) {
for ( j in packages [ i ] . items ) {
var item = packages [ i ] . items [ j ] ;
if ( typeof item . deadline != 'undefined' && item . deadline != false && item . delivered != true ) {
if ( ( typeof item . deadlinealarmed == 'undefined' || item . deadlinealarmed != true ) && item . deadline <= deadlineTime ) {
playSound ( "alert" ) ;
app . dialog . alert (
"Item at " + item . address + " needs to be delivered by " + timestampToTimeString ( item . deadline ) + " (" + Math . floor ( ( item . deadline - currentTime ) / 60 ) + " minutes from now)." ,
"Delivery Alarm" ,
function ( ) {
}
) ;
packages [ i ] . items [ j ] . deadlinealarmed = true ;
setStorage ( "packages" , JSON . stringify ( packages ) ) ;
}
}
}
}
}
setInterval ( checkDeadlines , 15 * 1000 ) ;