diff --git a/code/artifact.js b/code/artifact.js index d52abc72..f816c3c0 100644 --- a/code/artifact.js +++ b/code/artifact.js @@ -6,6 +6,7 @@ // - shards: move between portals (along links) each hour. more than one can be at a portal // - targets: specific portals - one per team // the artifact data includes details for the specific portals, so can be useful +// 2014-02-06: intel site updates hint at new 'amar artifacts', likely following the same system as above window.artifact = function() {} @@ -24,9 +25,9 @@ window.artifact.setup = function() { setTimeout (artifact.requestData, 1); artifact._layer = new L.LayerGroup(); - addLayerGroup ('Artifacts (Jarvis shards)', artifact._layer, true); + addLayerGroup ('Artifacts', artifact._layer, true); - $('#toolbox').append(' Artifacts'); + $('#toolbox').append(' Artifacts'); } @@ -179,6 +180,7 @@ window.artifact.updateLayer = function() { var iconSize = 0; var opacity = 1.0; + // redundant as of 2014-02-05 - jarvis shards removed if (data.jarvis) { if (data.jarvis.target) { // target portal - show the target marker. use the count of fragments at the target to pick the right icon - it has segments that fill up @@ -193,6 +195,22 @@ window.artifact.updateLayer = function() { opacity = 0.6; // these often hide portals - let's make them semi transparent } + } + // 2014-02-06: a guess at whats needed for the new artifacts + if (data.amar) { + if (data.amar.target) { + // target portal - show the target marker. use the count of fragments at the target to pick the right icon - it has segments that fill up + + var count = data.amar.fragments ? data.amar.fragments.length : 0; + + iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/amar_shard_target_'+count+'.png'; + iconSize = 100/2; // 100 pixels - half that size works better + } else if (data.amar.fragments) { + iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/amar_shard.png'; + iconSize = 60/2; // 60 pixels - half that size works better + opacity = 0.6; // these often hide portals - let's make them semi transparent + } + } if (iconUrl) { @@ -217,15 +235,20 @@ window.artifact.updateLayer = function() { window.artifact.showArtifactList = function() { - var html = '
Artifact portals
'; + var html = ''; - var types = { 'jarvis': 'Jarvis Shards' }; + var typeNames = { 'jarvis': 'Jarvis Shards', 'amar': 'Amar Artifacts' }; - $.each(types, function(type, name) { + if (Object.keys(artifact.artifactTypes).length == 0) { + html += 'No artifacts at this time'; + } - html += '
'+types[type]+'
'; + $.each(artifact.artifactTypes, function(type,type2) { + var name = typeNames[type] || ('New artifact type: '+type); - html += ''; + html += '
'+name+'
'; + + html += '
'; html += ''; var tableRows = []; @@ -260,6 +283,11 @@ window.artifact.showArtifactList = function() { } }); + // check for no rows, and add a note to the table instead + if (tableRows.length == 0) { + html += ''; + } + // sort the rows tableRows.sort(function(a,b) { return a[0]-b[0]; @@ -268,6 +296,7 @@ window.artifact.showArtifactList = function() { // and add them to the table html += tableRows.map(function(a){return a[1];}).join(''); + html += '
PortalDetails
No portals at this time
'; }); diff --git a/code/data_cache.js b/code/data_cache.js index e6caf3b9..4002a937 100644 --- a/code/data_cache.js +++ b/code/data_cache.js @@ -2,15 +2,9 @@ // cache for map data tiles. window.DataCache = function() { - // stock site nemesis.dashboard.DataManager.CACHE_EXPIRY_MS_ = 18E4 - so should be 2 mins cache time - this.REQUEST_CACHE_FRESH_AGE = 120; // if younger than this, use data in the cache rather than fetching from the server + // stock site nemesis.dashboard.DataManager.CACHE_EXPIRY_MS_ = 18E4 - 3 minutes + this.REQUEST_CACHE_FRESH_AGE = 3*60; // if younger than this, use data in the cache rather than fetching from the server - // stale cache entries can be updated (that's what the optional 'timestampMs' field in getThinnedEntities is - // for, retrieving deltas) so use a long max age to take advantage of this - // however, there must be an overall limit on the maximum age of data from the servers, otherwise the deletedEntity - // entries would grow indefinitely. an hour seems reasonable from experience with the data, so 55 mins max cache time -// this.REQUEST_CACHE_MAX_AGE = 55*60; // maximum cache age. entries are deleted from the cache after this time -//UPDATE: this timestampMs parameter doesn't work, so reduced max age to limit RAM usage this.REQUEST_CACHE_MAX_AGE = 15*60; // maximum cache age. entries are deleted from the cache after this time if (L.Browser.mobile) { diff --git a/code/map_data_calc_tools.js b/code/map_data_calc_tools.js old mode 100644 new mode 100755 index ea89c959..15bf1bd4 --- a/code/map_data_calc_tools.js +++ b/code/map_data_calc_tools.js @@ -10,32 +10,119 @@ // http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames -window.levelToTilesPerEdge = function(level) { - var LEVEL_TO_TILES_PER_EDGE = [65536, 65536, 16384, 16384, 4096, 1536, 1024, 256, 32]; - return LEVEL_TO_TILES_PER_EDGE[level]; +window.getMapZoomTileParameters = function(zoom) { + // these arrays/constants are based on those in the stock intel site. it's essential we keep them in sync with their code + // (it may be worth reading the values from their code rather than using our own copies? it's a case of either + // breaking if they rename their variables if we do, or breaking if they change the values if we don't) + var ZOOM_TO_TILES_PER_EDGE = [32, 32, 32, 32, 256, 256, 256, 1024, 1024, 1536, 4096, 4096, 16384, 16384, 16384]; + var MAX_TILES_PER_EDGE = 65536; + var ZOOM_TO_LEVEL = [8, 8, 8, 8, 7, 7, 7, 6, 6, 5, 4, 4, 3, 2, 2, 1, 1]; + + return { + level: ZOOM_TO_LEVEL[zoom] || 0, // default to level 0 (all portals) if not in array + tilesPerEdge: ZOOM_TO_TILES_PER_EDGE[zoom] || MAX_TILES_PER_EDGE, + zoom: zoom // include the zoom level, for reference + }; } -window.lngToTile = function(lng, level) { - return Math.floor((lng + 180) / 360 * levelToTilesPerEdge(level)); +window.getDataZoomForMapZoom = function(zoom) { + // we can fetch data at a zoom level different to the map zoom. + + //NOTE: the specifics of this are tightly coupled with the above ZOOM_TO_LEVEL and ZOOM_TO_TILES_PER_EDGE arrays + + // firstly, some of IITCs zoom levels, depending on base map layer, can be higher than stock. limit zoom level + if (zoom > 18) { + zoom = 18; + } + + if (!window.CONFIG_ZOOM_DEFAULT_DETAIL_LEVEL) { + // some reasonable optimisations of data retreival + + switch(zoom) { + case 2: + case 3: + // L8 portals - fall back to the furthest out view. less detail, faster retreival. cache advantages when zooming + // (note: iitc + stock both limited so zoom 0 never possible) + zoom = 1; + break; + + case 4: + // default is L7 - but this is a crazy number of tiles. fall back to L8 (but higher detail than above) + // (the back-end does, unfortunately, rarely (never?) returns large fields with L8-only portals + zoom = 3; + break; + + case 5: + case 6: + // default L7 - pull out to furthest L7 zoom + zoom = 4; + break; + + case 8: + // default L6 - pull back to highest L6 zoom + zoom = 7; + break; + + // L5 portals - only one zoom level + + case 11: + // default L4 - pull back to lower detail L4 + zoom = 10; + break; + + // L3 portals - only one zoom level + + case 14: + // L2 portals - pull back to furthest + zoom = 13; + break; + + case 16: + // L1 portals - pull back to furthest zoom + zoom = 15; + break; + + default: + if (zoom >= 18) { + // all portals - pull back to furthest zoom + zoom = 17; + } + break; + } + } + + if (window.CONFIG_ZOOM_SHOW_MORE_PORTALS) { + if (zoom >= 15) { + //L1+ and closer zooms. the 'all portals' zoom uses the same tile size, so it's no harm to request things at that zoom level + zoom = 17; + } + } + + return zoom; } -window.latToTile = function(lat, level) { + +window.lngToTile = function(lng, params) { + return Math.floor((lng + 180) / 360 * params.tilesPerEdge); +} + +window.latToTile = function(lat, params) { return Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + - 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * levelToTilesPerEdge(level)); + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * params.tilesPerEdge); } -window.tileToLng = function(x, level) { - return x / levelToTilesPerEdge(level) * 360 - 180; +window.tileToLng = function(x, params) { + return x / params.tilesPerEdge * 360 - 180; } -window.tileToLat = function(y, level) { - var n = Math.PI - 2 * Math.PI * y / levelToTilesPerEdge(level); +window.tileToLat = function(y, params) { + var n = Math.PI - 2 * Math.PI * y / params.tilesPerEdge; return 180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))); } -window.pointToTileId = function(level, x, y) { - return level + "_" + x + "_" + y; +window.pointToTileId = function(params, x, y) { + return params.zoom + "_" + x + "_" + y; } diff --git a/code/map_data_render.js b/code/map_data_render.js index 198c376b..212c285d 100644 --- a/code/map_data_render.js +++ b/code/map_data_render.js @@ -10,9 +10,6 @@ window.Render = function() { this.CLUSTER_SIZE = L.Browser.mobile ? 10 : 4; // the map is divided into squares of this size in pixels for clustering purposes. mobile uses larger markers, so therefore larger clustering areas this.CLUSTER_PORTAL_LIMIT = 4; // no more than this many portals are drawn in each cluster square - // link length, in pixels, to be visible. use the portal cluster size, as shorter than this is likely hidden - // under the portals - this.LINK_VISIBLE_PIXEL_LENGTH = this.CLUSTER_SIZE; this.entityVisibilityZoom = undefined; @@ -398,11 +395,7 @@ window.Render.prototype.createLinkEntity = function(ent,faked) { window.links[ent[0]] = poly; - // only add the link to the layer if it's long enough to be seen - - if (this.linkVisible(poly)) { - linksFactionLayers[poly.options.team].addLayer(poly); - } + linksFactionLayers[poly.options.team].addLayer(poly); } @@ -412,7 +405,6 @@ window.Render.prototype.updateEntityVisibility = function() { this.entityVisibilityZoom = map.getZoom(); this.resetPortalClusters(); - this.resetLinkVisibility(); if (this.portalMarkerScale === undefined || this.portalMarkerScale != portalMarkerScale()) { this.portalMarkerScale = portalMarkerScale(); @@ -440,14 +432,20 @@ window.Render.prototype.resetPortalClusters = function() { if (!(cid in this.portalClusters)) this.portalClusters[cid] = []; - this.portalClusters[cid].push(p.options.guid); + this.portalClusters[cid].push(pguid); } - // now, for each cluster, sort by some arbitrary data (the guid will do), and display the first CLUSTER_PORTAL_LIMIT + // now, for each cluster, sort by some arbitrary data (the level+guid will do), and display the first CLUSTER_PORTAL_LIMIT for (var cid in this.portalClusters) { var c = this.portalClusters[cid]; - c.sort(); + c.sort(function(a,b) { + var ka = (8-portals[a].options.level)+a; + var kb = (8-portals[b].options.level)+b; + if (kakb) return 1; + else return 0; + }); for (var i=0; i= this.LINK_VISIBLE_PIXEL_LENGTH; -} - - -window.Render.prototype.resetLinkVisibility = function() { - - for (var guid in window.links) { - var link = window.links[guid]; - - var visible = this.linkVisible(link); - - if (visible) { - if (!linksFactionLayers[link.options.team].hasLayer(link)) linksFactionLayers[link.options.team].addLayer(link); - } else { - if (linksFactionLayers[link.options.team].hasLayer(link)) linksFactionLayers[link.options.team].removeLayer(link); - } - } -} diff --git a/code/map_data_request.js b/code/map_data_request.js index 4b37b308..e2f5008c 100644 --- a/code/map_data_request.js +++ b/code/map_data_request.js @@ -20,13 +20,8 @@ window.MapDataRequest = function() { // using our own queue limit ensures that other requests (e.g. chat, portal details) don't get delayed this.MAX_REQUESTS = 5; - // no more than this many tiles in one request - // as of 2013-11-11, or possibly the release before that, the stock site was changed to only request four tiles at a time - // (which matches the number successfully returned for a *long* time!) - this.MAX_TILES_PER_REQUEST = 4; - - // try to maintain at least this may tiles in each request, by reducing the number of requests as needed - this.MIN_TILES_PER_REQUEST = 4; + // this many tiles in one request + this.NUM_TILES_PER_REQUEST = 4; // number of times to retry a tile after a 'bad' error (i.e. not a timeout) this.MAX_TILE_RETRIES = 2; @@ -184,8 +179,12 @@ window.MapDataRequest.prototype.refresh = function() { var bounds = clampLatLngBounds(map.getBounds()); - var zoom = map.getZoom(); - var minPortalLevel = getMinPortalLevelForZoom(zoom); + var mapZoom = map.getZoom(); + + var dataZoom = getDataZoomForMapZoom(mapZoom); + + var tileParams = getMapZoomTileParameters(dataZoom); + //DEBUG: resize the bounds so we only retrieve some data //bounds = bounds.pad(-0.4); @@ -193,36 +192,34 @@ window.MapDataRequest.prototype.refresh = function() { //var debugrect = L.rectangle(bounds,{color: 'red', fill: false, weight: 4, opacity: 0.8}).addTo(map); //setTimeout (function(){ map.removeLayer(debugrect); }, 10*1000); - var x1 = lngToTile(bounds.getWest(), minPortalLevel); - var x2 = lngToTile(bounds.getEast(), minPortalLevel); - var y1 = latToTile(bounds.getNorth(), minPortalLevel); - var y2 = latToTile(bounds.getSouth(), minPortalLevel); + var x1 = lngToTile(bounds.getWest(), tileParams); + var x2 = lngToTile(bounds.getEast(), tileParams); + var y1 = latToTile(bounds.getNorth(), tileParams); + var y2 = latToTile(bounds.getSouth(), tileParams); // calculate the full bounds for the data - including the part of the tiles off the screen edge var dataBounds = L.latLngBounds([ - [tileToLat(y2+1,minPortalLevel), tileToLng(x1,minPortalLevel)], - [tileToLat(y1,minPortalLevel), tileToLng(x2+1,minPortalLevel)] + [tileToLat(y2+1,tileParams), tileToLng(x1,tileParams)], + [tileToLat(y1,tileParams), tileToLng(x2+1,tileParams)] ]); //var debugrect2 = L.rectangle(dataBounds,{color: 'magenta', fill: false, weight: 4, opacity: 0.8}).addTo(map); //setTimeout (function(){ map.removeLayer(debugrect2); }, 10*1000); // store the parameters used for fetching the data. used to prevent unneeded refreshes after move/zoom - this.fetchedDataParams = { bounds: dataBounds, mapZoom: map.getZoom(), minPortalLevel: minPortalLevel }; + this.fetchedDataParams = { bounds: dataBounds, mapZoom: mapZoom, dataZoom: dataZoom }; - window.runHooks ('mapDataRefreshStart', {bounds: bounds, zoom: zoom, minPortalLevel: minPortalLevel, tileBounds: dataBounds}); + window.runHooks ('mapDataRefreshStart', {bounds: bounds, mapZoom: mapZoom, dataZoom: dataZoom, minPortalLevel: tileParams.level, tileBounds: dataBounds}); this.render.startRenderPass(); - this.render.clearPortalsBelowLevel(minPortalLevel); - - + this.render.clearPortalsBelowLevel(tileParams.level); this.render.clearEntitiesOutsideBounds(dataBounds); this.render.updateEntityVisibility(); this.render.processGameEntities(artifact.getArtifactEntities()); - console.log('requesting data tiles at zoom '+zoom+' (L'+minPortalLevel+'+ portals), map zoom is '+map.getZoom()); + console.log('requesting data tiles at zoom '+dataZoom+' (L'+tileParams.level+'+ portals, '+tileParams.tilesPerEdge+' tiles per global edge), map zoom is '+mapZoom); this.cachedTileCount = 0; @@ -234,17 +231,17 @@ window.MapDataRequest.prototype.refresh = function() { var tilesToFetchDistance = {}; // map center point - for fetching center tiles first - var mapCenterPoint = map.project(map.getCenter(), zoom); + var mapCenterPoint = map.project(map.getCenter(), mapZoom); // y goes from left to right for (var y = y1; y <= y2; y++) { // x goes from bottom to top(?) for (var x = x1; x <= x2; x++) { - var tile_id = pointToTileId(minPortalLevel, x, y); - var latNorth = tileToLat(y,minPortalLevel); - var latSouth = tileToLat(y+1,minPortalLevel); - var lngWest = tileToLng(x,minPortalLevel); - var lngEast = tileToLng(x+1,minPortalLevel); + var tile_id = pointToTileId(tileParams, x, y); + var latNorth = tileToLat(y,tileParams); + var latSouth = tileToLat(y+1,tileParams); + var lngWest = tileToLng(x,tileParams); + var lngEast = tileToLng(x+1,tileParams); this.debugTiles.create(tile_id,[[latSouth,lngWest],[latNorth,lngEast]]); @@ -258,10 +255,10 @@ window.MapDataRequest.prototype.refresh = function() { // no fresh data // render the cached stale data, if we have it. this ensures *something* appears quickly when possible - var old_data = this.cache && this.cache.get(tile_id); - if (old_data) { - this.render.processTileData (old_data); - } +// var old_data = this.cache && this.cache.get(tile_id); +// if (old_data) { +// this.render.processTileData (old_data); +// } // tile needed. calculate the distance from the centre of the screen, to optimise the load order @@ -269,7 +266,7 @@ window.MapDataRequest.prototype.refresh = function() { var lngCenter = (lngEast+lngWest)/2; var tileLatLng = L.latLng(latCenter,lngCenter); - var tilePoint = map.project(tileLatLng, zoom); + var tilePoint = map.project(tileLatLng, mapZoom); var delta = mapCenterPoint.subtract(tilePoint); var distanceSquared = delta.x*delta.x + delta.y*delta.y; @@ -363,26 +360,13 @@ window.MapDataRequest.prototype.processRequestQueue = function(isFirstPass) { var requestBuckets = this.MAX_REQUESTS - this.activeRequestCount; if (pendingTiles.length > 0 && requestBuckets > 0) { - // the stock site calculates bucket grouping with the simplistic <8 tiles: 1 bucket, otherwise 4 buckets - var maxBuckets = Math.ceil(pendingTiles.length/this.MIN_TILES_PER_REQUEST); - - requestBuckets = Math.min (maxBuckets, requestBuckets); - - var lastTileIndex = Math.min(requestBuckets*this.MAX_TILES_PER_REQUEST, pendingTiles.length); - - for (var bucket=0; bucket 0) { -// console.log('-- new request: '+tiles.length+' tiles'); this.sendTileRequest(tiles); } } + } diff --git a/code/munge.js b/code/munge.js old mode 100644 new mode 100755 index ab16e207..bf7224ce --- a/code/munge.js +++ b/code/munge.js @@ -110,7 +110,7 @@ function extractMungeFromStock() { foundMunges.version_parameter = result[5]; // GET_THINNED_ENTITIES parameters - var reg = new RegExp('GET_THINNED_ENTITIES, {'+mungeRegExpLit+'[a-z]'); + var reg = new RegExp('GET_THINNED_ENTITIES, nemesis.dashboard.network.XhrController.Priority.[A-Z]+, {'+mungeRegExpLit+'[a-z]'); var result = reg.exec(nemesis.dashboard.network.DataFetcher.prototype.getGameEntities.toString()); foundMunges.quadKeys = result[1] || result[2]; @@ -119,7 +119,7 @@ function extractMungeFromStock() { var result = reg.exec(nemesis.dashboard.network.PlextStore.prototype.getPlexts.toString()); foundMunges.desiredNumItems = result[1] || result[2]; - + foundMunges.minLatE6 = result[3] || result[4]; foundMunges.minLngE6 = result[5] || result[6]; foundMunges.maxLatE6 = result[7] || result[8]; @@ -130,7 +130,7 @@ function extractMungeFromStock() { foundMunges.ascendingTimestampOrder = result[17] || result[18]; // SEND_PLEXT - var reg = new RegExp('SEND_PLEXT, {'+mungeRegExpLit+'[a-z], '+mungeRegExpLit+'[a-z], '+mungeRegExpLit+'[a-z], '+mungeRegExpLit+'[a-z]}'); + var reg = new RegExp('SEND_PLEXT, nemesis.dashboard.network.XhrController.Priority.[A-Z]+, {'+mungeRegExpLit+'[a-z], '+mungeRegExpLit+'[a-z], '+mungeRegExpLit+'[a-z], '+mungeRegExpLit+'[a-z]}'); var result = reg.exec(nemesis.dashboard.network.PlextStore.prototype.sendPlext.toString()); foundMunges.message = result[1] || result[2]; @@ -140,13 +140,12 @@ function extractMungeFromStock() { if (chatTab != foundMunges.chatTab) throw 'Error: inconsistent munge parsing for chatTab'; // GET_PORTAL_DETAILS - var reg = new RegExp('GET_PORTAL_DETAILS, {'+mungeRegExpLit+'a}'); + var reg = new RegExp('GET_PORTAL_DETAILS, nemesis.dashboard.network.XhrController.Priority.[A-Z]+, {'+mungeRegExpLit+'a}'); var result = reg.exec(nemesis.dashboard.network.DataFetcher.prototype.getPortalDetails.toString()); - foundMunges.guid = result[1] || result[2]; // SEND_INVITE_EMAIL - var reg = new RegExp('SEND_INVITE_EMAIL, {'+mungeRegExpLit+'b}'); + var reg = new RegExp('SEND_INVITE_EMAIL, nemesis.dashboard.network.XhrController.Priority.[A-Z]+, {'+mungeRegExpLit+'b}'); foundMunges.inviteeEmailAddress = result[1] || result[2]; return foundMunges; diff --git a/code/portal_detail_display.js b/code/portal_detail_display.js index 3bb64e93..1258c992 100644 --- a/code/portal_detail_display.js +++ b/code/portal_detail_display.js @@ -202,21 +202,27 @@ window.getPortalMiscDetails = function(guid,d) { // artifact details - //niantic hard-code the fact it's just jarvis shards/targets - so until more examples exist, we'll do the same - //(at some future point we can iterate through all the artifact types and add rows as needed) - var jarvisArtifact = artifact.getPortalData (guid, 'jarvis'); - if (jarvisArtifact) { - // the genFourColumnTable function below doesn't handle cases where one column is null and the other isn't - so default to *something* in both columns - var target = ['',''], shards = ['shards','(none)']; - if (jarvisArtifact.target) { - target = ['target', ''+(jarvisArtifact.target==TEAM_RES?'Resistance':'Enlightened')+'']; - } - if (jarvisArtifact.fragments) { - shards = [jarvisArtifact.fragments.length>1?'shards':'shard', '#'+jarvisArtifact.fragments.join(', #')]; - } + // 2014-02-06: stock site changed from supporting 'jarvis shards' to 'amar artifacts'(?) - so let's see what we can do to be generic... + var artifactTypes = { + 'jarvis': { 'name': 'Jarvis', 'fragmentName': 'shard(s)' }, + 'amar': { 'name': 'Amar', 'fragmentName': 'artifact(s)' }, + }; - randDetailsData.push (target, shards); - } + $.each(artifactTypes,function(type,details) { + var artdata = artifact.getPortalData (guid, type); + if (artdata) { + // the genFourColumnTable function below doesn't handle cases where one column is null and the other isn't - so default to *something* in both columns + var target = ['',''], shards = [details.fragmentName,'(none)']; + if (artdata.target) { + target = ['target', ''+(artdata.target==TEAM_RES?'Resistance':'Enlightened')+'']; + } + if (artdata.fragments) { + shards = [details.fragmentName, '#'+artdata.fragments.join(', #')]; + } + + randDetailsData.push (target, shards); + } + }); randDetails = '' + genFourColumnTable(randDetailsData) + '
'; diff --git a/code/utils_misc.js b/code/utils_misc.js index 9c07ac72..2af8c837 100644 --- a/code/utils_misc.js +++ b/code/utils_misc.js @@ -279,21 +279,11 @@ window.androidPermalink = function() { } -window.getMinPortalLevelForZoom = function(z) { - - // these values are from the stock intel map. however, they're too detailed for reasonable speed, and increasing - // detail at a higher zoom level shows enough detail still, AND speeds up IITC considerably -//var ZOOM_TO_LEVEL = [8, 8, 8, 8, 7, 7, 7, 6, 6, 5, 4, 4, 3, 2, 2, 1, 1]; - var ZOOM_TO_LEVEL = [8, 8, 8, 8, 8, 7, 7, 7, 6, 5, 4, 4, 3, 2, 2, 1, 1]; - - var l = ZOOM_TO_LEVEL[z] || 0; - return l; -} - window.getMinPortalLevel = function() { var z = map.getZoom(); - return getMinPortalLevelForZoom(z); + z = getDataZoomForMapZoom(z); + return getMapZoomTileParameters(z).level; } // returns number of pixels left to scroll down before reaching the diff --git a/external/leaflet.draw.css b/external/leaflet.draw.css index 900608fd..31e59c89 100644 --- a/external/leaflet.draw.css +++ b/external/leaflet.draw.css @@ -50,6 +50,7 @@ position: absolute; left: 26px; /* leaflet-draw-toolbar.left + leaflet-draw-toolbar.width */ top: 0; + white-space: nowrap; } .leaflet-right .leaflet-draw-actions { @@ -98,7 +99,6 @@ .leaflet-draw-actions-top { margin-top: 1px; - white-space: nowrap; } .leaflet-draw-actions-top a, diff --git a/images/marker-icon.svg.template b/images/marker-icon.svg.template new file mode 100644 index 00000000..26b8da11 --- /dev/null +++ b/images/marker-icon.svg.template @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/main.js b/main.js index 817629fe..44b86a02 100644 --- a/main.js +++ b/main.js @@ -1,7 +1,7 @@ // ==UserScript== // @id ingress-intel-total-conversion@jonatkins // @name IITC: Ingress intel map total conversion -// @version 0.16.4.@@DATETIMEVERSION@@ +// @version 0.16.5.@@DATETIMEVERSION@@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ diff --git a/mobile/AndroidManifest.xml b/mobile/AndroidManifest.xml index 8472c725..3020f113 100644 --- a/mobile/AndroidManifest.xml +++ b/mobile/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="66" + android:versionName="0.10.5"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mobile/res/values/strings.xml b/mobile/res/values/strings.xml index 68552943..f22db356 100644 --- a/mobile/res/values/strings.xml +++ b/mobile/res/values/strings.xml @@ -86,9 +86,8 @@
IITC Mobile is able to load external plugins too!

- • create %1$s
- • move *.user.js files there
- • plugins should be listed above the official plugins]]> + Add them by clicking the (+) icon at the top right. + The plugin files have to end with \'.user.js\' and are copied to %1$s
]]>
Share portal you can:
@@ -162,6 +161,7 @@ Clear Cookies Search Debug + Add external plugins Choose account to login Login failed. Search Locations @@ -177,4 +177,11 @@ Base Layer Overlay Layers + Install external plugin? + +
%1$s

+ Be careful: Javascript from external sources may contain harmful code (spyware etc.)!

+ Are you sure you want to proceed?]]> +
+ diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_FileManager.java b/mobile/src/com/cradle/iitc_mobile/IITC_FileManager.java index 6ec999eb..875348e6 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_FileManager.java +++ b/mobile/src/com/cradle/iitc_mobile/IITC_FileManager.java @@ -1,13 +1,18 @@ package com.cradle.iitc_mobile; +import android.annotation.TargetApi; import android.app.Activity; +import android.app.AlertDialog; import android.content.ActivityNotFoundException; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.AssetManager; import android.net.Uri; import android.os.Environment; +import android.os.ParcelFileDescriptor; import android.preference.PreferenceManager; +import android.text.Html; import android.util.Base64; import android.util.Base64OutputStream; import android.webkit.WebResourceResponse; @@ -22,11 +27,14 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; +import java.net.URL; +import java.net.URLConnection; import java.net.URLEncoder; import java.util.HashMap; @@ -82,6 +90,7 @@ public class IITC_FileManager { // get a list of key-value final String[] attributes = header.split(" +"); // add default values + map.put("id", "unknown"); map.put("version", "not found"); map.put("name", "unknown"); map.put("description", ""); @@ -89,6 +98,9 @@ public class IITC_FileManager { // add parsed values for (int i = 0; i < attributes.length; i++) { // search for attributes and use the value + if (attributes[i].equals("@id")) { + map.put("id", attributes[i + 1]); + } if (attributes[i].equals("@version")) { map.put("version", attributes[i + 1]); } @@ -118,15 +130,17 @@ public class IITC_FileManager { } private final AssetManager mAssetManager; - private final IITC_Mobile mIitc; + private final Activity mActivity; private final String mIitcPath; private final SharedPreferences mPrefs; + public static final String PLUGINS_PATH = Environment.getExternalStorageDirectory().getPath() + + "/IITC_Mobile/plugins/"; - public IITC_FileManager(final IITC_Mobile iitc) { - mIitc = iitc; - mIitcPath = Environment.getExternalStorageDirectory().getPath() + "/IITC_Mobile/"; - mPrefs = PreferenceManager.getDefaultSharedPreferences(iitc); - mAssetManager = mIitc.getAssets(); + public IITC_FileManager(final Activity activity) { + mActivity = activity; + mIitcPath = Environment.getExternalStorageDirectory().getPath() + "/Activity/"; + mPrefs = PreferenceManager.getDefaultSharedPreferences(activity); + mAssetManager = mActivity.getAssets(); } private InputStream getAssetFile(final String filename) throws IOException { @@ -135,10 +149,10 @@ public class IITC_FileManager { try { return new FileInputStream(file); } catch (final FileNotFoundException e) { - mIitc.runOnUiThread(new Runnable() { + mActivity.runOnUiThread(new Runnable() { @Override public void run() { - Toast.makeText(mIitc, "File " + mIitcPath + + Toast.makeText(mActivity, "File " + mIitcPath + "dev/" + filename + " not found. " + "Disable developer mode or add iitc files to the dev folder.", Toast.LENGTH_SHORT).show(); @@ -233,6 +247,77 @@ public class IITC_FileManager { return EMPTY; } + public void installPlugin(final Uri uri, final boolean invalidateHeaders) { + if (uri != null) { + String text = mActivity.getString(R.string.install_dialog_msg); + text = String.format(text, uri); + + // create alert dialog + new AlertDialog.Builder(mActivity) + .setTitle(mActivity.getString(R.string.install_dialog_top)) + .setMessage(Html.fromHtml(text)) + .setCancelable(true) + .setPositiveButton("Yes", new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, final int which) { + copyPlugin(uri, invalidateHeaders); + } + }) + .setNegativeButton("No", new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, final int which) { + dialog.cancel(); + } + }) + .create() + .show(); + } + } + + private void copyPlugin(final Uri uri, final boolean invalidateHeaders) { + final Thread thread = new Thread(new Runnable() { + @Override + public void run() { + try { + final String url = uri.toString(); + InputStream is; + String fileName; + if (uri.getScheme().contains("http")) { + URLConnection conn = new URL(url).openConnection(); + is = conn.getInputStream(); + fileName = uri.getLastPathSegment(); + } else { + // we need 2 streams since an inputStream is useless after read once + // we read it twice because we first need the script ID for the fileName and + // afterwards reading it again while copying + is = mActivity.getContentResolver().openInputStream(uri); + final InputStream isCopy = mActivity.getContentResolver().openInputStream(uri); + fileName = getScriptInfo(isCopy).get("id") + ".user.js"; + } + // create IITCm external plugins directory if it doesn't already exist + final File pluginsDirectory = new File(PLUGINS_PATH); + pluginsDirectory.mkdirs(); + + // create in and out streams and copy plugin + final File outFile = new File(pluginsDirectory, fileName); + final OutputStream os = new FileOutputStream(outFile); + IITC_FileManager.copyStream(is, os, true); + } catch (final IOException e) { + Log.w(e); + } + } + }); + thread.start(); + if (invalidateHeaders) { + try { + thread.join(); + ((IITC_PluginPreferenceActivity) mActivity).invalidateHeaders(); + } catch (final InterruptedException e) { + Log.w(e); + } + } + } + private class FileRequest extends WebResourceResponse implements ResponseHandler, Runnable { private Intent mData; private final String mFunctionName; @@ -253,21 +338,23 @@ public class IITC_FileManager { mFunctionName = uri.getPathSegments().get(0); // create the chooser Intent - final Intent target = new Intent(Intent.ACTION_GET_CONTENT); - target.setType("file/*"); - target.addCategory(Intent.CATEGORY_OPENABLE); + final Intent target = new Intent(Intent.ACTION_GET_CONTENT) + .setType("*/*") + .addCategory(Intent.CATEGORY_OPENABLE); + final IITC_Mobile iitc = (IITC_Mobile) mActivity; try { - mIitc.startActivityForResult(Intent.createChooser(target, "Choose file"), this); + iitc.startActivityForResult(Intent.createChooser(target, "Choose file"), this); } catch (final ActivityNotFoundException e) { - Toast.makeText(mIitc, "No activity to select a file found." + + Toast.makeText(mActivity, "No activity to select a file found." + "Please install a file browser of your choice!", Toast.LENGTH_LONG).show(); } } @Override public void onActivityResult(final int resultCode, final Intent data) { - mIitc.deleteResponseHandler(this); // to enable garbage collection + final IITC_Mobile iitc = (IITC_Mobile) mActivity; + iitc.deleteResponseHandler(this); // to enable garbage collection mResultCode = resultCode; mData = data; @@ -281,18 +368,18 @@ public class IITC_FileManager { try { if (mResultCode == Activity.RESULT_OK && mData != null) { final Uri uri = mData.getData(); - final File file = new File(uri.getPath()); // now create a resource that basically looks like: // someFunctionName('', ''); - mStreamOut.write( - (mFunctionName + "('" + URLEncoder.encode(file.getName(), "UTF-8") + "', '").getBytes()); + final String filename = uri.getLastPathSegment(); + final String call = mFunctionName + "('" + URLEncoder.encode(filename, "UTF-8") + "', '"; + mStreamOut.write(call.getBytes()); final Base64OutputStream encoder = new Base64OutputStream(mStreamOut, Base64.NO_CLOSE | Base64.NO_WRAP | Base64.DEFAULT); - final FileInputStream fileinput = new FileInputStream(file); + final InputStream fileinput = mActivity.getContentResolver().openInputStream(uri); copyStream(fileinput, encoder, true); @@ -310,4 +397,56 @@ public class IITC_FileManager { } } } + + @TargetApi(19) + public class FileSaveRequest implements ResponseHandler, Runnable { + private Intent mData; + private final IITC_Mobile mIitc; + private final String mContent; + + public FileSaveRequest(final String filename, final String type, final String content) { + final Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT) + .setType(type) + .addCategory(Intent.CATEGORY_OPENABLE) + .putExtra(Intent.EXTRA_TITLE, filename); + + mContent = content; + mIitc = (IITC_Mobile) mActivity; + mIitc.startActivityForResult(intent, this); + } + + @Override + public void onActivityResult(final int resultCode, final Intent data) { + mIitc.deleteResponseHandler(this); + + if (resultCode != Activity.RESULT_OK) return; + + mData = data; + + new Thread(this, "FileSaveRequest").start(); + } + + @Override + public void run() { + if (mData == null) return; + + final Uri uri = mData.getData(); + OutputStream os = null; + + try { + final ParcelFileDescriptor fd = mIitc.getContentResolver().openFileDescriptor(uri, "w"); + + try { + os = new FileOutputStream(fd.getFileDescriptor()); + os.write(mContent.getBytes()); + os.close(); + } catch (final IOException e) { + Log.w("Could not save file!", e); + } + fd.close(); + } catch (final IOException e) { + Log.w("Could not save file!", e); + } + } + } } diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_JSInterface.java b/mobile/src/com/cradle/iitc_mobile/IITC_JSInterface.java index 89a3bf66..d489ca6e 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_JSInterface.java +++ b/mobile/src/com/cradle/iitc_mobile/IITC_JSInterface.java @@ -6,16 +6,21 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.os.Environment; import android.webkit.JavascriptInterface; import android.widget.Toast; import com.cradle.iitc_mobile.IITC_NavigationHelper.Pane; import com.cradle.iitc_mobile.share.ShareActivity; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + // provide communication between IITC script and android app public class IITC_JSInterface { // context of main activity - private final IITC_Mobile mIitc; + protected final IITC_Mobile mIitc; IITC_JSInterface(final IITC_Mobile iitc) { mIitc = iitc; @@ -232,4 +237,20 @@ public class IITC_JSInterface { public void setPermalink(final String href) { mIitc.setPermalink(href); } + + @JavascriptInterface + public void saveFile(final String filename, final String type, final String content) { + try { + final File outFile = new File(Environment.getExternalStorageDirectory().getPath() + + "/IITC_Mobile/export/" + filename); + outFile.getParentFile().mkdirs(); + + final FileOutputStream outStream = new FileOutputStream(outFile); + outStream.write(content.getBytes("UTF-8")); + outStream.close(); + Toast.makeText(mIitc, "File exported to " + outFile.getPath(), Toast.LENGTH_SHORT).show(); + } catch (final IOException e) { + e.printStackTrace(); + } + } } diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_JSInterfaceKitkat.java b/mobile/src/com/cradle/iitc_mobile/IITC_JSInterfaceKitkat.java new file mode 100644 index 00000000..6c686734 --- /dev/null +++ b/mobile/src/com/cradle/iitc_mobile/IITC_JSInterfaceKitkat.java @@ -0,0 +1,17 @@ +package com.cradle.iitc_mobile; + +import android.annotation.TargetApi; +import android.webkit.JavascriptInterface; + +@TargetApi(19) +public class IITC_JSInterfaceKitkat extends IITC_JSInterface { + public IITC_JSInterfaceKitkat(final IITC_Mobile iitc) { + super(iitc); + } + + @JavascriptInterface + @Override + public void saveFile(final String filename, final String type, final String content) { + mIitc.getFileManager().new FileSaveRequest(filename, type, content); + } +} diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_Mobile.java b/mobile/src/com/cradle/iitc_mobile/IITC_Mobile.java index 8c619247..2f95caa5 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_Mobile.java +++ b/mobile/src/com/cradle/iitc_mobile/IITC_Mobile.java @@ -47,6 +47,8 @@ import java.io.IOException; import java.net.URISyntaxException; import java.util.Stack; import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class IITC_Mobile extends Activity implements OnSharedPreferenceChangeListener, NfcAdapter.CreateNdefMessageCallback { @@ -74,6 +76,7 @@ public class IITC_Mobile extends Activity private boolean mShowMapInDebug = false; private final Stack mDialogStack = new Stack(); private String mPermalink = null; + private String mSearchTerm = null; // Used for custom back stack handling private final Stack mBackStack = new Stack(); @@ -104,7 +107,9 @@ public class IITC_Mobile extends Activity mEditCommand.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(final TextView v, final int actionId, final KeyEvent event) { - if (EditorInfo.IME_ACTION_GO == actionId) { + if (EditorInfo.IME_ACTION_GO == actionId || + EditorInfo.IME_ACTION_SEND == actionId || + EditorInfo.IME_ACTION_DONE == actionId) { onBtnRunCodeClick(v); final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); @@ -193,8 +198,8 @@ public class IITC_Mobile extends Activity handleIntent(intent, false); } + // handles ingress intel url intents, search intents, geo intents and javascript file intents private void handleIntent(final Intent intent, final boolean onCreate) { - // load new iitc web view with ingress intel page final String action = intent.getAction(); if (Intent.ACTION_VIEW.equals(action) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) { final Uri uri = intent.getData(); @@ -228,6 +233,15 @@ public class IITC_Mobile extends Activity .show(); } } + + // intent MIME type and uri path may be null + final String type = intent.getType() == null ? "" : intent.getType(); + final String path = uri.getPath() == null ? "" : uri.getPath(); + if (path.endsWith(".user.js") || type.contains("javascript")) { + final Intent prefIntent = new Intent(this, IITC_PluginPreferenceActivity.class); + prefIntent.setDataAndType(uri, intent.getType()); + startActivity(prefIntent); + } } if (Intent.ACTION_SEARCH.equals(action)) { @@ -250,20 +264,20 @@ public class IITC_Mobile extends Activity private void handleGeoUri(final Uri uri) throws URISyntaxException { final String[] parts = uri.getSchemeSpecificPart().split("\\?", 2); - Double lat, lon; + Double lat = null, lon = null; Integer z = null; + String search = null; // parts[0] may contain an 'uncertainty' parameter, delimited by a semicolon final String[] pos = parts[0].split(";", 2)[0].split(",", 2); - if (pos.length != 2) throw new URISyntaxException(uri.toString(), "URI does not contain a valid position"); - - try { - lat = Double.valueOf(pos[0]); - lon = Double.valueOf(pos[1]); - } catch (final NumberFormatException e) { - final URISyntaxException use = new URISyntaxException(uri.toString(), "position could not be parsed"); - use.initCause(e); - throw use; + if (pos.length == 2) { + try { + lat = Double.valueOf(pos[0]); + lon = Double.valueOf(pos[1]); + } catch (final NumberFormatException e) { + lat = null; + lon = null; + } } if (parts.length > 1) { // query string present @@ -273,21 +287,47 @@ public class IITC_Mobile extends Activity try { z = Integer.valueOf(param.substring(2)); } catch (final NumberFormatException e) { - final URISyntaxException use = new URISyntaxException( - uri.toString(), "could not parse zoom level"); - use.initCause(e); - throw use; } - break; + } + if (param.startsWith("q=")) { + search = param.substring(2); + final Pattern pattern = Pattern.compile("^(-?\\d+(\\.\\d+)?),(-?\\d+(\\.\\d+)?)\\s*\\(.+\\)"); + final Matcher matcher = pattern.matcher(search); + if (matcher.matches()) { + try { + lat = Double.valueOf(matcher.group(1)); + lon = Double.valueOf(matcher.group(3)); + search = null; // if we have a position, we don't need the search term + } catch (final NumberFormatException e) { + lat = null; + lon = null; + } + } } } } - String url = "http://www.ingress.com/intel?ll=" + lat + "," + lon; - if (z != null) { - url += "&z=" + z; + if (lat != null && lon != null) { + String url = mIntelUrl + "?ll=" + lat + "," + lon; + if (z != null) { + url += "&z=" + z; + } + loadUrl(url); + return; } - loadUrl(url); + + if (search != null) { + if (mIsLoading) { + mSearchTerm = search; + loadUrl(mIntelUrl); + } else { + switchToPane(Pane.MAP); + mIitcWebView.loadUrl("javascript:search('" + search + "');"); + } + return; + } + + throw new URISyntaxException(uri.toString(), "position could not be parsed"); } @Override @@ -439,8 +479,7 @@ public class IITC_Mobile extends Activity // Get the SearchView and set the searchable configuration final SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); mSearchMenuItem = menu.findItem(R.id.menu_search); - final SearchView searchView = - (SearchView) mSearchMenuItem.getActionView(); + final SearchView searchView = (SearchView) mSearchMenuItem.getActionView(); // Assumes current activity is the searchable activity searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default @@ -450,8 +489,8 @@ public class IITC_Mobile extends Activity @Override public boolean onPrepareOptionsMenu(final Menu menu) { boolean visible = false; - if (mNavigationHelper != null) - visible = !mNavigationHelper.isDrawerOpened(); + if (mNavigationHelper != null) visible = !mNavigationHelper.isDrawerOpened(); + if (mIsLoading) visible = false; for (int i = 0; i < menu.size(); i++) { final MenuItem item = menu.getItem(i); @@ -467,6 +506,7 @@ public class IITC_Mobile extends Activity case R.id.locate: item.setVisible(visible); + item.setEnabled(!mIsLoading); item.setIcon(mUserLocation.isFollowing() ? R.drawable.ic_action_location_follow : R.drawable.ic_action_location_found); @@ -650,10 +690,20 @@ public class IITC_Mobile extends Activity public void setLoadingState(final boolean isLoading) { mIsLoading = isLoading; - mNavigationHelper.onLoadingStateChanged(); - + invalidateOptionsMenu(); updateViews(); + + if (mSearchTerm != null && !isLoading) { + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + // switchToPane(Pane.MAP); + mIitcWebView.loadUrl("javascript:search('" + mSearchTerm + "');"); + mSearchTerm = null; + } + }, 5000); + } } private void updateViews() { diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_PluginPreferenceActivity.java b/mobile/src/com/cradle/iitc_mobile/IITC_PluginPreferenceActivity.java index 8c63b348..a20ed8f0 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_PluginPreferenceActivity.java +++ b/mobile/src/com/cradle/iitc_mobile/IITC_PluginPreferenceActivity.java @@ -1,18 +1,22 @@ package com.cradle.iitc_mobile; +import android.content.ActivityNotFoundException; import android.content.Context; +import android.content.Intent; import android.content.res.AssetManager; +import android.net.Uri; import android.os.Bundle; -import android.os.Environment; import android.preference.PreferenceActivity; import android.text.TextUtils; import android.view.LayoutInflater; +import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ListAdapter; import android.widget.TextView; +import android.widget.Toast; import com.cradle.iitc_mobile.fragments.PluginsFragment; @@ -29,6 +33,8 @@ import java.util.TreeMap; public class IITC_PluginPreferenceActivity extends PreferenceActivity { + private final static int COPY_PLUGIN_REQUEST = 1; + private List
mHeaders; // we use a tree map to have a map with alphabetical order // don't initialize the asset plugin map, because it tells us if the settings are started the first time @@ -39,6 +45,8 @@ public class IITC_PluginPreferenceActivity extends PreferenceActivity { new TreeMap>(); private static int mDeletedPlugins = 0; + private IITC_FileManager mFileManager; + @Override public void setListAdapter(final ListAdapter adapter) { if (adapter == null) { @@ -78,6 +86,13 @@ public class IITC_PluginPreferenceActivity extends PreferenceActivity { getIntent() .putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, PluginsFragment.class.getName()); } + + mFileManager = new IITC_FileManager(this); + + final Uri uri = getIntent().getData(); + if (uri != null) { + mFileManager.installPlugin(uri, true); + } super.onCreate(savedInstanceState); } @@ -103,17 +118,52 @@ public class IITC_PluginPreferenceActivity extends PreferenceActivity { } } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.plugins, menu); + return super.onCreateOptionsMenu(menu); + } + @Override public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case android.R.id.home: // exit settings when home button (iitc icon) is pressed onBackPressed(); return true; + case R.id.menu_plugins_add: + // create the chooser Intent + final Intent target = new Intent(Intent.ACTION_GET_CONTENT); + // iitcm only parses *.user.js scripts + target.setType("file/*"); + target.addCategory(Intent.CATEGORY_OPENABLE); + + try { + startActivityForResult(Intent.createChooser(target, "Choose file"), COPY_PLUGIN_REQUEST); + } catch (final ActivityNotFoundException e) { + Toast.makeText(this, "No activity to select a file found." + + "Please install a file browser of your choice!", Toast.LENGTH_LONG).show(); + } default: return super.onOptionsItemSelected(item); } } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch(requestCode) { + case COPY_PLUGIN_REQUEST: + if (data != null && data.getData() != null) { + mFileManager.installPlugin(data.getData(), true); + return; + } + break; + default: + super.onActivityResult(requestCode, resultCode, data); + break; + + } + } + @Override protected boolean isValidFragment(final String s) { return true; @@ -141,9 +191,7 @@ public class IITC_PluginPreferenceActivity extends PreferenceActivity { } private File[] getUserPlugins() { - final String iitc_path = Environment.getExternalStorageDirectory().getPath() - + "/IITC_Mobile/"; - final File directory = new File(iitc_path + "plugins/"); + final File directory = new File(IITC_FileManager.PLUGINS_PATH); File[] files = directory.listFiles(); if (files == null) { files = new File[0]; @@ -164,6 +212,8 @@ public class IITC_PluginPreferenceActivity extends PreferenceActivity { if ((userPlugins.length + officialPlugins.length) != (numPlugins + mDeletedPlugins)) { Log.d("new or less plugins found since last start, rebuild preferences"); sAssetPlugins.clear(); + sUserPlugins.clear(); + mDeletedPlugins = 0; setUpPluginPreferenceScreen(); } } diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_WebView.java b/mobile/src/com/cradle/iitc_mobile/IITC_WebView.java index 9963e432..c2598738 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_WebView.java +++ b/mobile/src/com/cradle/iitc_mobile/IITC_WebView.java @@ -56,7 +56,13 @@ public class IITC_WebView extends WebView { mSettings.setCacheMode(WebSettings.LOAD_DEFAULT); mSettings.setAppCachePath(getContext().getCacheDir().getAbsolutePath()); mSettings.setDatabasePath(getContext().getApplicationInfo().dataDir + "/databases/"); - mJsInterface = new IITC_JSInterface(mIitc); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + mJsInterface = new IITC_JSInterfaceKitkat(mIitc); + } else { + mJsInterface = new IITC_JSInterface(mIitc); + } + addJavascriptInterface(mJsInterface, "android"); mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(mIitc); mDefaultUserAgent = mSettings.getUserAgentString(); diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_WebViewClient.java b/mobile/src/com/cradle/iitc_mobile/IITC_WebViewClient.java index be09bb43..d9e27563 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_WebViewClient.java +++ b/mobile/src/com/cradle/iitc_mobile/IITC_WebViewClient.java @@ -95,7 +95,7 @@ public class IITC_WebViewClient extends WebViewClient { public void onReceivedLoginRequest(final WebView view, final String realm, final String account, final String args) { mIitcInjected = false; // Log.d("iitcm", "Login requested: " + realm + " " + account + " " + args); - // ((IITC_Mobile) mContext).onReceivedLoginRequest(this, view, realm, account, args); + // mIitc.onReceivedLoginRequest(this, view, realm, account, args); } /** diff --git a/mobile/src/com/cradle/iitc_mobile/fragments/MainSettings.java b/mobile/src/com/cradle/iitc_mobile/fragments/MainSettings.java index 79a46ef6..f12e5267 100644 --- a/mobile/src/com/cradle/iitc_mobile/fragments/MainSettings.java +++ b/mobile/src/com/cradle/iitc_mobile/fragments/MainSettings.java @@ -5,7 +5,6 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; -import android.preference.EditTextPreference; import android.preference.ListPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; @@ -23,39 +22,39 @@ import com.cradle.iitc_mobile.R; public class MainSettings extends PreferenceFragment { @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); // set versions - String iitcVersion = getArguments().getString("iitc_version"); + final String iitcVersion = getArguments().getString("iitc_version"); String buildVersion = "unknown"; - PackageManager pm = getActivity().getPackageManager(); + final PackageManager pm = getActivity().getPackageManager(); try { - PackageInfo info = pm.getPackageInfo(getActivity().getPackageName(), 0); + final PackageInfo info = pm.getPackageInfo(getActivity().getPackageName(), 0); buildVersion = info.versionName; - } catch (NameNotFoundException e) { + } catch (final NameNotFoundException e) { Log.w(e); } - IITC_AboutDialogPreference pref_about = (IITC_AboutDialogPreference) findPreference("pref_about"); + final IITC_AboutDialogPreference pref_about = (IITC_AboutDialogPreference) findPreference("pref_about"); pref_about.setVersions(iitcVersion, buildVersion); final ListPreference pref_user_location_mode = (ListPreference) findPreference("pref_user_location_mode"); pref_user_location_mode.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - int mode = Integer.parseInt((String) newValue); + public boolean onPreferenceChange(final Preference preference, final Object newValue) { + final int mode = Integer.parseInt((String) newValue); preference.setSummary(getResources().getStringArray(R.array.pref_user_location_titles)[mode]); return true; } }); - String value = getPreferenceManager().getSharedPreferences().getString("pref_user_location_mode", "0"); - int mode = Integer.parseInt(value); + final String value = getPreferenceManager().getSharedPreferences().getString("pref_user_location_mode", "0"); + final int mode = Integer.parseInt(value); pref_user_location_mode.setSummary(getResources().getStringArray(R.array.pref_user_location_titles)[mode]); } @@ -64,7 +63,7 @@ public class MainSettings extends PreferenceFragment { // so we need some additional hacks... // thx to http://stackoverflow.com/a/16800527/2638486 !! @Override - public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + public boolean onPreferenceTreeClick(final PreferenceScreen preferenceScreen, final Preference preference) { if (preference.getTitle().toString().equals(getString(R.string.pref_advanced_options)) || preference.getTitle().toString().equals(getString(R.string.pref_about_title))) { initializeActionBar((PreferenceScreen) preference); @@ -76,27 +75,27 @@ public class MainSettings extends PreferenceFragment { // because PreferenceScreens are dialogs which swallow // events instead of passing to the activity // Related Issue: https://code.google.com/p/android/issues/detail?id=4611 - public static void initializeActionBar(PreferenceScreen preferenceScreen) { + public static void initializeActionBar(final PreferenceScreen preferenceScreen) { final Dialog dialog = preferenceScreen.getDialog(); if (dialog != null) { if (dialog.getActionBar() != null) dialog.getActionBar().setDisplayHomeAsUpEnabled(true); - View homeBtn = dialog.findViewById(android.R.id.home); + final View homeBtn = dialog.findViewById(android.R.id.home); if (homeBtn != null) { - View.OnClickListener dismissDialogClickListener = new View.OnClickListener() { + final View.OnClickListener dismissDialogClickListener = new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { dialog.dismiss(); } }; - ViewParent homeBtnContainer = homeBtn.getParent(); + final ViewParent homeBtnContainer = homeBtn.getParent(); // The home button is an ImageView inside a FrameLayout if (homeBtnContainer instanceof FrameLayout) { - ViewGroup containerParent = (ViewGroup) homeBtnContainer.getParent(); + final ViewGroup containerParent = (ViewGroup) homeBtnContainer.getParent(); if (containerParent instanceof LinearLayout) { // This view also contains the title text, set the whole view as clickable diff --git a/mobile/src/com/cradle/iitc_mobile/share/IntentFragmentAdapter.java b/mobile/src/com/cradle/iitc_mobile/share/FragmentAdapter.java similarity index 56% rename from mobile/src/com/cradle/iitc_mobile/share/IntentFragmentAdapter.java rename to mobile/src/com/cradle/iitc_mobile/share/FragmentAdapter.java index fd803345..fa3dfbdf 100644 --- a/mobile/src/com/cradle/iitc_mobile/share/IntentFragmentAdapter.java +++ b/mobile/src/com/cradle/iitc_mobile/share/FragmentAdapter.java @@ -7,16 +7,16 @@ import android.support.v4.app.FragmentPagerAdapter; import java.util.ArrayList; import java.util.List; -public class IntentFragmentAdapter extends FragmentPagerAdapter { - private final List mTabs; +public class FragmentAdapter extends FragmentPagerAdapter { + private final List mTabs; - public IntentFragmentAdapter(FragmentManager fm) { + public FragmentAdapter(final FragmentManager fm) { super(fm); - mTabs = new ArrayList(); + mTabs = new ArrayList(); } - public void add(IntentFragment fragment) { + public void add(final IntentListFragment fragment) { mTabs.add(fragment); } @@ -26,12 +26,12 @@ public class IntentFragmentAdapter extends FragmentPagerAdapter { } @Override - public Fragment getItem(int position) { + public Fragment getItem(final int position) { return mTabs.get(position); } @Override - public CharSequence getPageTitle(int position) { + public CharSequence getPageTitle(final int position) { return mTabs.get(position).getTitle(); } } \ No newline at end of file diff --git a/mobile/src/com/cradle/iitc_mobile/share/IntentAdapter.java b/mobile/src/com/cradle/iitc_mobile/share/IntentAdapter.java new file mode 100644 index 00000000..3c3cde4a --- /dev/null +++ b/mobile/src/com/cradle/iitc_mobile/share/IntentAdapter.java @@ -0,0 +1,68 @@ +package com.cradle.iitc_mobile.share; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.drawable.Drawable; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import com.cradle.iitc_mobile.Log; +import com.cradle.iitc_mobile.R; + +import java.util.Collections; +import java.util.List; + +class IntentAdapter extends ArrayAdapter { + private static final int MDPI_PX = 36; + + private final PackageManager mPackageManager; + + public IntentAdapter(final Context context) { + super(context, android.R.layout.simple_list_item_1); + mPackageManager = getContext().getPackageManager(); + } + + @Override + public View getView(final int position, final View convertView, final ViewGroup parent) { + final LayoutInflater inflater = ((Activity) getContext()).getLayoutInflater(); + final TextView view = (TextView) inflater.inflate(android.R.layout.simple_list_item_1, parent, false); + + final Intent item = getItem(position); + + view.setText(IntentGenerator.getTitle(item)); + view.setCompoundDrawablePadding((int) getContext().getResources().getDimension(R.dimen.icon_margin)); + + // get icon and scale it manually to ensure that all have the same size + final DisplayMetrics dm = new DisplayMetrics(); + ((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(dm); + final float densityScale = dm.density; + final float scaledWidth = MDPI_PX * densityScale; + final float scaledHeight = MDPI_PX * densityScale; + + try { + final Drawable icon = mPackageManager.getActivityIcon(item); + icon.setBounds(0, 0, Math.round(scaledWidth), Math.round(scaledHeight)); + view.setCompoundDrawables(icon, null, null, null); + } catch (final NameNotFoundException e) { + Log.e(e); + } + + return view; + } + + public void setIntents(final List intents) { + Collections.sort(intents, ((ShareActivity) getContext()).getIntentComparator()); + + setNotifyOnChange(false); + clear(); + addAll(intents); + notifyDataSetChanged(); + } +} \ No newline at end of file diff --git a/mobile/src/com/cradle/iitc_mobile/share/IntentComparator.java b/mobile/src/com/cradle/iitc_mobile/share/IntentComparator.java index 6ae1d0a8..8e0dead9 100644 --- a/mobile/src/com/cradle/iitc_mobile/share/IntentComparator.java +++ b/mobile/src/com/cradle/iitc_mobile/share/IntentComparator.java @@ -1,7 +1,8 @@ package com.cradle.iitc_mobile.share; import android.app.Activity; -import android.content.pm.PackageManager; +import android.content.ComponentName; +import android.content.Intent; import android.content.pm.ResolveInfo; import com.cradle.iitc_mobile.Log; @@ -16,7 +17,112 @@ import java.io.Serializable; import java.util.Comparator; import java.util.HashMap; -public class IntentComparator implements Comparator { +public class IntentComparator implements Comparator { + private static final String INTENT_MAP_FILE = "share_intent_map"; + + private final ShareActivity mActivity; + + private HashMap mIntentMap = new HashMap(); + + IntentComparator(final ShareActivity activity) { + mActivity = activity; + load(); + } + + @SuppressWarnings("unchecked") + private void load() { + ObjectInputStream objectIn = null; + + try { + final FileInputStream fileIn = mActivity.openFileInput(INTENT_MAP_FILE); + objectIn = new ObjectInputStream(fileIn); + mIntentMap = (HashMap) objectIn.readObject(); + } catch (final FileNotFoundException e) { + // Do nothing + } catch (final IOException e) { + Log.w(e); + } catch (final ClassNotFoundException e) { + Log.w(e); + } finally { + if (objectIn != null) { + try { + objectIn.close(); + } catch (final IOException e) { + Log.w(e); + } + } + } + } + + @Override + public int compare(final Intent lhs, final Intent rhs) { + int order; + + // we might be merging multiple intents, so there could be more than one default + if (IntentGenerator.isDefault(lhs) && !IntentGenerator.isDefault(rhs)) + return -1; + if (IntentGenerator.isDefault(rhs) && !IntentGenerator.isDefault(lhs)) + return 1; + + final ComponentName lComponent = lhs.getComponent(); + final ComponentName rComponent = rhs.getComponent(); + + // Show more frequently used items in top + Integer lCount = mIntentMap.get(new Component(lComponent)); + Integer rCount = mIntentMap.get(new Component(rComponent)); + + if (lCount == null) lCount = 0; + if (rCount == null) rCount = 0; + + if (lCount > rCount) return -1; + if (lCount < rCount) return 1; + + // still no order. fall back to alphabetical order + order = IntentGenerator.getTitle(lhs).compareTo(IntentGenerator.getTitle(rhs)); + if (order != 0) return order; + + order = lComponent.getPackageName().compareTo(rComponent.getPackageName()); + if (order != 0) return order; + + order = lComponent.getClassName().compareTo(rComponent.getClassName()); + if (order != 0) return order; + + return 0; + } + + public void save() { + ObjectOutputStream objectOut = null; + try { + final FileOutputStream fileOut = mActivity.openFileOutput(INTENT_MAP_FILE, Activity.MODE_PRIVATE); + objectOut = new ObjectOutputStream(fileOut); + objectOut.writeObject(mIntentMap); + fileOut.getFD().sync(); + } catch (final IOException e) { + Log.w(e); + } finally { + if (objectOut != null) { + try { + objectOut.close(); + } catch (final IOException e) { + Log.w(e); + } + } + } + } + + public void trackIntentSelection(final Intent intent) { + final Component component = new Component(intent.getComponent()); + + Integer counter = mIntentMap.get(component); + if (counter == null) { + counter = 1; + } else { + counter++; + } + + mIntentMap.put(component, counter); + } + public static class Component implements Serializable { private static final long serialVersionUID = -5043782754318376792L; @@ -28,19 +134,24 @@ public class IntentComparator implements Comparator { packageName = null; } - public Component(ResolveInfo info) { + public Component(final ComponentName cn) { + name = cn.getClassName(); + packageName = cn.getPackageName(); + } + + public Component(final ResolveInfo info) { name = info.activityInfo.name; packageName = info.activityInfo.applicationInfo.packageName; } @Override - public boolean equals(Object o) { + public boolean equals(final Object o) { if (this == o) return true; if (o == null) return false; if (o.getClass() != this.getClass()) return false; - Component c = (Component) o; + final Component c = (Component) o; if (name == null) { if (c.name != null) return false; @@ -83,120 +194,4 @@ public class IntentComparator implements Comparator { : name); } } - - private static final String INTENT_MAP_FILE = "share_intent_map"; - - private ShareActivity mActivity; - private HashMap mIntentMap = new HashMap(); - private PackageManager mPackageManager; - - IntentComparator(ShareActivity activity) { - mActivity = activity; - mPackageManager = activity.getPackageManager(); - - load(); - } - - @SuppressWarnings("unchecked") - private void load() { - ObjectInputStream objectIn = null; - - try { - FileInputStream fileIn = mActivity.openFileInput(INTENT_MAP_FILE); - objectIn = new ObjectInputStream(fileIn); - mIntentMap = (HashMap) objectIn.readObject(); - } catch (FileNotFoundException e) { - // Do nothing - } catch (IOException e) { - Log.w(e); - } catch (ClassNotFoundException e) { - Log.w(e); - } finally { - if (objectIn != null) { - try { - objectIn.close(); - } catch (IOException e) { - Log.w(e); - } - } - } - } - - @Override - public int compare(ResolveInfo lhs, ResolveInfo rhs) { - int order; - - // we might be merging multiple intents, so there could be more than one default - if (lhs.isDefault && !rhs.isDefault) - return -1; - if (rhs.isDefault && !lhs.isDefault) - return 1; - - // Show more frequently used items in top - Integer lCount = mIntentMap.get(new Component(lhs)); - Integer rCount = mIntentMap.get(new Component(rhs)); - - if (lCount == null) lCount = 0; - if (rCount == null) rCount = 0; - - if (lCount > rCount) return -1; - if (lCount < rCount) return 1; - - // don't known how these are set (or if they can be set at all), but it sounds promising... - if (lhs.preferredOrder != rhs.preferredOrder) - return rhs.preferredOrder - lhs.preferredOrder; - if (lhs.priority != rhs.priority) - return rhs.priority - lhs.priority; - - // still no order. fall back to alphabetical order - order = lhs.loadLabel(mPackageManager).toString().compareTo( - rhs.loadLabel(mPackageManager).toString()); - if (order != 0) return order; - - if (lhs.nonLocalizedLabel != null && rhs.nonLocalizedLabel != null) { - order = lhs.nonLocalizedLabel.toString().compareTo(rhs.nonLocalizedLabel.toString()); - if (order != 0) return order; - } - - order = lhs.activityInfo.packageName.compareTo(rhs.activityInfo.packageName); - if (order != 0) return order; - - order = lhs.activityInfo.name.compareTo(rhs.activityInfo.name); - if (order != 0) return order; - - return 0; - } - - public void save() { - ObjectOutputStream objectOut = null; - try { - FileOutputStream fileOut = mActivity.openFileOutput(INTENT_MAP_FILE, Activity.MODE_PRIVATE); - objectOut = new ObjectOutputStream(fileOut); - objectOut.writeObject(mIntentMap); - fileOut.getFD().sync(); - } catch (IOException e) { - Log.w(e); - } finally { - if (objectOut != null) { - try { - objectOut.close(); - } catch (IOException e) { - Log.w(e); - } - } - } - } - - public void trackIntentSelection(ResolveInfo info) { - Component component = new Component(info); - - Integer counter = mIntentMap.get(component); - if (counter == null) { - counter = 1; - } else { - counter++; - } - - mIntentMap.put(component, counter); - } } \ No newline at end of file diff --git a/mobile/src/com/cradle/iitc_mobile/share/IntentGenerator.java b/mobile/src/com/cradle/iitc_mobile/share/IntentGenerator.java new file mode 100644 index 00000000..35e4793a --- /dev/null +++ b/mobile/src/com/cradle/iitc_mobile/share/IntentGenerator.java @@ -0,0 +1,148 @@ +package com.cradle.iitc_mobile.share; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; + +import com.cradle.iitc_mobile.Log; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +public class IntentGenerator { + private static final String EXTRA_FLAG_IS_DEFAULT = "IITCM_IS_DEFAULT"; + private static final String EXTRA_FLAG_TITLE = "IITCM_TITLE"; + private static final HashSet KNOWN_COPY_HANDLERS = new HashSet(); + + static { + if (KNOWN_COPY_HANDLERS.isEmpty()) { + + KNOWN_COPY_HANDLERS.add(new ComponentName( + "com.google.android.apps.docs", + "com.google.android.apps.docs.app.SendTextToClipboardActivity")); + + KNOWN_COPY_HANDLERS.add(new ComponentName( + "com.aokp.romcontrol", + "com.aokp.romcontrol.ShareToClipboard")); + } + } + + public static String getTitle(final Intent intent) { + if (intent.hasExtra(EXTRA_FLAG_TITLE)) + return intent.getStringExtra(EXTRA_FLAG_TITLE); + + throw new IllegalArgumentException("Got an intent not generated by IntentGenerator"); + } + + public static boolean isDefault(final Intent intent) { + return intent.hasExtra(EXTRA_FLAG_IS_DEFAULT) && intent.getBooleanExtra(EXTRA_FLAG_IS_DEFAULT, false); + } + + private final Context mContext; + + private final PackageManager mPackageManager; + + public IntentGenerator(final Context context) { + mContext = context; + mPackageManager = mContext.getPackageManager(); + } + + private boolean containsCopyIntent(final List targets) { + for (final Intent intent : targets) { + for (final ComponentName handler : KNOWN_COPY_HANDLERS) { + if (handler.equals(intent.getComponent())) return true; + } + } + return false; + } + + private ArrayList resolveTargets(final Intent intent) { + final String packageName = mContext.getPackageName(); + final List activityList = mPackageManager.queryIntentActivities(intent, 0); + final ResolveInfo defaultTarget = mPackageManager.resolveActivity(intent, 0); + + final ArrayList list = new ArrayList(activityList.size()); + + for (final ResolveInfo resolveInfo : activityList) { + final ActivityInfo activity = resolveInfo.activityInfo; + final ComponentName component = new ComponentName(activity.packageName, activity.name); + + // remove IITCm from list + if (activity.packageName.equals(packageName)) continue; + + final Intent targetIntent = new Intent(intent) + .setComponent(component) + .putExtra(EXTRA_FLAG_TITLE, activity.loadLabel(mPackageManager)); + + if (resolveInfo.activityInfo.name.equals(defaultTarget.activityInfo.name) && + resolveInfo.activityInfo.packageName.equals(defaultTarget.activityInfo.packageName)) { + targetIntent.putExtra(EXTRA_FLAG_IS_DEFAULT, true); + } + + list.add(targetIntent); + } + + return list; + } + + public void cleanup(final Intent intent) { + intent.removeExtra(EXTRA_FLAG_IS_DEFAULT); + intent.removeExtra(EXTRA_FLAG_TITLE); + } + + public ArrayList getBrowserIntents(final String title, final String url) { + final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + + return resolveTargets(intent); + } + + public ArrayList getGeoIntents(final String title, final String mLl, final int mZoom) { + final Intent intent = new Intent(android.content.Intent.ACTION_VIEW, + Uri.parse(String.format("geo:%s?z=%d", mLl, mZoom))) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + + final ArrayList targets = resolveTargets(intent); + + // According to https://developer.android.com/guide/components/intents-common.html, markers can be labeled. + // Unfortunately, only Google Maps supports this, most other apps fail + for (final Intent target : targets) { + final ComponentName cn = target.getComponent(); + if ("com.google.android.apps.maps".equals(cn.getPackageName())) { + try { + final String encodedTitle = URLEncoder.encode(title, "UTF-8"); + target.setData(Uri.parse(String.format("geo:0,0?q=%s%%20(%s)&z=%d", mLl, encodedTitle, mZoom))); + } catch (final UnsupportedEncodingException e) { + Log.w(e); + } + break; + } + } + + return targets; + } + + public ArrayList getShareIntents(final String title, final String text) { + final Intent intent = new Intent(Intent.ACTION_SEND) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) + .setType("text/plain") + .putExtra(Intent.EXTRA_TEXT, text) + .putExtra(Intent.EXTRA_SUBJECT, title); + + final ArrayList targets = resolveTargets(intent); + + if (!containsCopyIntent(targets)) { + // add SendToClipboard intent in case Drive is not installed + targets.add(new Intent(intent).setComponent(new ComponentName(mContext, SendToClipboard.class))); + } + + return targets; + } +} diff --git a/mobile/src/com/cradle/iitc_mobile/share/IntentFragment.java b/mobile/src/com/cradle/iitc_mobile/share/IntentListFragment.java similarity index 54% rename from mobile/src/com/cradle/iitc_mobile/share/IntentFragment.java rename to mobile/src/com/cradle/iitc_mobile/share/IntentListFragment.java index ef3aaaeb..f2456dd1 100644 --- a/mobile/src/com/cradle/iitc_mobile/share/IntentFragment.java +++ b/mobile/src/com/cradle/iitc_mobile/share/IntentListFragment.java @@ -10,12 +10,14 @@ import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; import java.util.ArrayList; -public class IntentFragment extends Fragment implements OnScrollListener, OnItemClickListener { +public class IntentListFragment extends Fragment implements OnScrollListener, OnItemClickListener { + private IntentAdapter mAdapter; private ArrayList mIntents; - private IntentListView mListView; + private ListView mListView; private int mScrollIndex, mScrollTop; public int getIcon() { @@ -27,12 +29,16 @@ public class IntentFragment extends Fragment implements OnScrollListener, OnItem } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - Bundle args = getArguments(); + public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { + final Bundle args = getArguments(); mIntents = args.getParcelableArrayList("intents"); - mListView = new IntentListView(getActivity()); - mListView.setIntents(mIntents); + + mAdapter = new IntentAdapter(getActivity()); + mAdapter.setIntents(mIntents); + + mListView = new ListView(getActivity()); + mListView.setAdapter(mAdapter); if (mScrollIndex != -1 && mScrollTop != -1) { mListView.setSelectionFromTop(mScrollIndex, mScrollTop); } @@ -43,23 +49,18 @@ public class IntentFragment extends Fragment implements OnScrollListener, OnItem } @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - ((ShareActivity) getActivity()).getIntentComparator().trackIntentSelection(mListView.getItem(position)); - - Intent intent = mListView.getTargetIntent(position); - startActivity(intent); - - getActivity().finish(); + public void onItemClick(final AdapterView parent, final View view, final int position, final long id) { + ((ShareActivity) getActivity()).launch(mAdapter.getItem(position)); } @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + public void onScroll(final AbsListView lv, final int firstItem, final int visibleItems, final int totalItems) { mScrollIndex = mListView.getFirstVisiblePosition(); - View v = mListView.getChildAt(0); + final View v = mListView.getChildAt(0); mScrollTop = (v == null) ? 0 : v.getTop(); } @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { + public void onScrollStateChanged(final AbsListView view, final int scrollState) { } } \ No newline at end of file diff --git a/mobile/src/com/cradle/iitc_mobile/share/IntentListView.java b/mobile/src/com/cradle/iitc_mobile/share/IntentListView.java deleted file mode 100644 index a0af6dcc..00000000 --- a/mobile/src/com/cradle/iitc_mobile/share/IntentListView.java +++ /dev/null @@ -1,209 +0,0 @@ -package com.cradle.iitc_mobile.share; - -import android.app.Activity; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.util.Pair; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ListView; -import android.widget.TextView; - -import com.cradle.iitc_mobile.R; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; - -public class IntentListView extends ListView { - private static class CopyHandler extends Pair { - public CopyHandler(ResolveInfo resolveInfo) { - super(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name); - } - - public CopyHandler(String packageName, String name) { - super(packageName, name); - } - } - - private class IntentAdapter extends ArrayAdapter { - - // actually the mdpi pixel size is 48, but this looks ugly...so scale icons down for listView - private static final int MDPI_PX = 36; - - private IntentAdapter() { - super(IntentListView.this.getContext(), android.R.layout.simple_list_item_1); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - LayoutInflater inflater = ((Activity) getContext()).getLayoutInflater(); - TextView view = (TextView) inflater.inflate(android.R.layout.simple_list_item_1, parent, false); - - ActivityInfo info = getItem(position).activityInfo; - CharSequence label = info.loadLabel(mPackageManager); - - // get icon and scale it manually to ensure that all have the same size - Drawable icon = info.loadIcon(mPackageManager); - DisplayMetrics dm = new DisplayMetrics(); - ((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(dm); - float densityScale = dm.density; - float scaledWidth = MDPI_PX * densityScale; - float scaledHeight = MDPI_PX * densityScale; - icon.setBounds(0,0,Math.round(scaledWidth),Math.round(scaledHeight)); - - view.setText(label); - view.setCompoundDrawablePadding((int) getResources().getDimension(R.dimen.icon_margin)); - view.setCompoundDrawables(icon, null, null, null); - - return view; - } - } - - private static final HashSet KNOWN_COPY_HANDLERS = new HashSet(); - - static { - if (KNOWN_COPY_HANDLERS.isEmpty()) { - - KNOWN_COPY_HANDLERS.add(new CopyHandler( - "com.google.android.apps.docs", - "com.google.android.apps.docs.app.SendTextToClipboardActivity")); - - KNOWN_COPY_HANDLERS.add(new CopyHandler( - "com.aokp.romcontrol", - "com.aokp.romcontrol.ShareToClipboard")); - } - } - - private HashMap mActivities = new HashMap(); - - private IntentAdapter mAdapter; - private PackageManager mPackageManager; - - public IntentListView(Context context) { - super(context); - init(); - } - - public IntentListView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - public IntentListView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(); - } - - private void init() { - mPackageManager = getContext().getPackageManager(); - mAdapter = new IntentAdapter(); - setAdapter(mAdapter); - } - - public ResolveInfo getItem(int position) { - return mAdapter.getItem(position); - } - - public Intent getTargetIntent(int position) { - ActivityInfo activity = mAdapter.getItem(position).activityInfo; - - ComponentName activityId = new ComponentName(activity.packageName, activity.name); - - Intent intentType = mActivities.get(activityId); - - Intent intent = new Intent(intentType) - .setComponent(activityId) - .setPackage(activity.packageName); - - return intent; - } - - // wrapper method for single intents - public void setIntent(Intent intent) { - ArrayList intentList = new ArrayList(1); - intentList.add(intent); - setIntents(intentList); - } - - public void setIntents(ArrayList intents) { - mAdapter.setNotifyOnChange(false); - mAdapter.clear(); - - String packageName = getContext().getPackageName(); - - ArrayList allActivities = new ArrayList(); - - for (Intent intent : intents) { - List activityList = mPackageManager.queryIntentActivities(intent, 0); - - ResolveInfo defaultTarget = mPackageManager.resolveActivity(intent, 0); - - boolean hasCopyIntent = false; - for (ResolveInfo resolveInfo : activityList) { // search for "Copy to clipboard" handler - CopyHandler handler = new CopyHandler(resolveInfo); - - if (KNOWN_COPY_HANDLERS.contains(handler)) { - hasCopyIntent = true; - } - } - - // use traditional loop since list may change during iteration - for (int i = 0; i < activityList.size(); i++) { - ResolveInfo info = activityList.get(i); - ActivityInfo activity = info.activityInfo; - - // fix bug in PackageManager - a replaced package name might cause non-exported intents to appear - if (!activity.exported && !activity.packageName.equals(packageName)) { - activityList.remove(i); - i--; - continue; - } - - // remove all IITCm intents, except for SendToClipboard in case Drive is not installed - if (activity.packageName.equals(packageName)) { - if (hasCopyIntent || !activity.name.equals(SendToClipboard.class.getCanonicalName())) { - activityList.remove(i); - i--; - continue; - } - } - } - - // add to activity hash map if they doesn't exist - for (ResolveInfo resolveInfo : activityList) { - - ActivityInfo activity = resolveInfo.activityInfo; - ComponentName activityId = new ComponentName(activity.packageName, activity.name); - - // ResolveInfo.isDefault usually means "The target would like to be considered a default action that the - // user can perform on this data." It is set by the package manager, but we overwrite it to store - // whether this app is the default for the given intent - resolveInfo.isDefault = resolveInfo.activityInfo.name.equals(defaultTarget.activityInfo.name) - && resolveInfo.activityInfo.packageName.equals(defaultTarget.activityInfo.packageName); - - if (!mActivities.containsKey(activityId)) { - mActivities.put(activityId, intent); - allActivities.add(resolveInfo); - } - } - } - - Collections.sort(allActivities, ((ShareActivity) getContext()).getIntentComparator()); - - mAdapter.addAll(allActivities); - mAdapter.setNotifyOnChange(true); - mAdapter.notifyDataSetChanged(); - } -} diff --git a/mobile/src/com/cradle/iitc_mobile/share/ShareActivity.java b/mobile/src/com/cradle/iitc_mobile/share/ShareActivity.java index ffae8ae7..0578a7dc 100644 --- a/mobile/src/com/cradle/iitc_mobile/share/ShareActivity.java +++ b/mobile/src/com/cradle/iitc_mobile/share/ShareActivity.java @@ -4,7 +4,6 @@ import android.app.ActionBar; import android.app.FragmentTransaction; import android.content.Intent; import android.content.SharedPreferences; -import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.app.FragmentActivity; @@ -12,16 +11,14 @@ import android.support.v4.app.NavUtils; import android.support.v4.view.ViewPager; import android.view.MenuItem; -import com.cradle.iitc_mobile.Log; import com.cradle.iitc_mobile.R; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; import java.util.ArrayList; public class ShareActivity extends FragmentActivity implements ActionBar.TabListener { private IntentComparator mComparator; - private IntentFragmentAdapter mFragmentAdapter; + private FragmentAdapter mFragmentAdapter; + private IntentGenerator mGenerator; private boolean mIsPortal; private String mLl; private SharedPreferences mSharedPrefs = null; @@ -29,9 +26,9 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList private ViewPager mViewPager; private int mZoom; - private void addTab(ArrayList intents, int label, int icon) { - IntentFragment fragment = new IntentFragment(); - Bundle args = new Bundle(); + private void addTab(final ArrayList intents, final int label, final int icon) { + final IntentListFragment fragment = new IntentListFragment(); + final Bundle args = new Bundle(); args.putParcelableArrayList("intents", intents); args.putString("title", getString(label)); args.putInt("icon", icon); @@ -39,13 +36,7 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList mFragmentAdapter.add(fragment); } - private void addTab(Intent intent, int label, int icon) { - ArrayList intents = new ArrayList(1); - intents.add(intent); - addTab(intents, label, icon); - } - - private String getUrl() { + private String getIntelUrl() { String url = "http://www.ingress.com/intel?ll=" + mLl + "&z=" + mZoom; if (mIsPortal) { url += "&pll=" + mLl; @@ -53,7 +44,7 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList return url; } - private void setSelected(int position) { + private void setSelected(final int position) { // Activity not fully loaded yet (may occur during tab creation) if (mSharedPrefs == null) return; @@ -63,53 +54,20 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList .apply(); } - private void setupIntents() { - setupShareIntent(getUrl()); - - // we merge gmaps intents with geo intents since it is not possible - // anymore to set a labeled marker on geo intents - ArrayList intents = new ArrayList(); - String gMapsUri; - try { - gMapsUri = "http://maps.google.com/?q=loc:" + mLl - + "%20(" + URLEncoder.encode(mTitle, "UTF-8") + ")&z=" + mZoom; - } catch (UnsupportedEncodingException e) { - gMapsUri = "http://maps.google.com/?ll=" + mLl + "&z=" + mZoom; - Log.w(e); - } - Intent gMapsIntent = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse(gMapsUri)); - intents.add(gMapsIntent); - String geoUri = "geo:" + mLl; - Intent geoIntent = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse(geoUri)); - intents.add(geoIntent); - addTab(intents, R.string.tab_map, R.drawable.ic_action_place); - - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(getUrl())); - addTab(intent, R.string.tab_browser, R.drawable.ic_action_web_site); - } - - private void setupShareIntent(String str) { - Intent intent = new Intent(Intent.ACTION_SEND); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_TEXT, str); - intent.putExtra(Intent.EXTRA_SUBJECT, mTitle); - addTab(intent, R.string.tab_share, R.drawable.ic_action_share); - } - @Override - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_share); mComparator = new IntentComparator(this); + mGenerator = new IntentGenerator(this); - mFragmentAdapter = new IntentFragmentAdapter(getSupportFragmentManager()); + mFragmentAdapter = new FragmentAdapter(getSupportFragmentManager()); final ActionBar actionBar = getActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); - Intent intent = getIntent(); + final Intent intent = getIntent(); // from portallinks/permalinks we build 3 intents (share / geo / vanilla-intel-link) if (!intent.getBooleanExtra("onlyShare", false)) { mTitle = intent.getStringExtra("title"); @@ -118,10 +76,21 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList mIsPortal = intent.getBooleanExtra("isPortal", false); actionBar.setTitle(mTitle); - setupIntents(); + + addTab(mGenerator.getShareIntents(mTitle, getIntelUrl()), + R.string.tab_share, + R.drawable.ic_action_share); + addTab(mGenerator.getGeoIntents(mTitle, mLl, mZoom), + R.string.tab_map, + R.drawable.ic_action_place); + addTab(mGenerator.getBrowserIntents(mTitle, getIntelUrl()), + R.string.tab_browser, + R.drawable.ic_action_web_site); } else { mTitle = getString(R.string.app_name); - setupShareIntent(intent.getStringExtra("shareString")); + final String shareString = intent.getStringExtra("shareString"); + + addTab(mGenerator.getShareIntents(mTitle, shareString), R.string.tab_share, R.drawable.ic_action_share); } mViewPager = (ViewPager) findViewById(R.id.pager); @@ -129,7 +98,7 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override - public void onPageSelected(int position) { + public void onPageSelected(final int position) { if (actionBar.getNavigationMode() != ActionBar.NAVIGATION_MODE_STANDARD) { actionBar.setSelectedNavigationItem(position); } @@ -138,7 +107,7 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList }); for (int i = 0; i < mFragmentAdapter.getCount(); i++) { - IntentFragment fragment = (IntentFragment) mFragmentAdapter.getItem(i); + final IntentListFragment fragment = (IntentListFragment) mFragmentAdapter.getItem(i); actionBar.addTab(actionBar .newTab() @@ -152,7 +121,7 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList } mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); - int selected = mSharedPrefs.getInt("pref_share_selected_tab", 0); + final int selected = mSharedPrefs.getInt("pref_share_selected_tab", 0); if (selected < mFragmentAdapter.getCount()) { mViewPager.setCurrentItem(selected); if (actionBar.getNavigationMode() != ActionBar.NAVIGATION_MODE_STANDARD) { @@ -171,8 +140,15 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList return mComparator; } + public void launch(final Intent intent) { + mComparator.trackIntentSelection(intent); + mGenerator.cleanup(intent); + startActivity(intent); + finish(); + } + @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case android.R.id.home: NavUtils.navigateUpFromSameTask(this); @@ -182,17 +158,17 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList } @Override - public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { + public void onTabReselected(final ActionBar.Tab tab, final FragmentTransaction fragmentTransaction) { } @Override - public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { - int position = tab.getPosition(); + public void onTabSelected(final ActionBar.Tab tab, final FragmentTransaction fragmentTransaction) { + final int position = tab.getPosition(); mViewPager.setCurrentItem(position); setSelected(position); } @Override - public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { + public void onTabUnselected(final ActionBar.Tab tab, final FragmentTransaction fragmentTransaction) { } } diff --git a/plugins/bookmarks-by-zaso.user.js b/plugins/bookmarks-by-zaso.user.js index e15075f9..f50e578c 100644 --- a/plugins/bookmarks-by-zaso.user.js +++ b/plugins/bookmarks-by-zaso.user.js @@ -2,7 +2,7 @@ // @id iitc-plugin-bookmarks@ZasoGD // @name IITC plugin: Bookmarks for maps and portals // @category Controls -// @version 0.2.7.@@DATETIMEVERSION@@ +// @version 0.2.8.@@DATETIMEVERSION@@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ @@ -151,18 +151,11 @@ } if(newStatus === 1) { - $('#bookmarksBox').show(); - $('#bkmrksTrigger').hide(); + $('#bookmarksBox').css('height', 'auto'); + $('#bkmrksTrigger').css('height', '0'); } else { - $('#bookmarksBox').hide(); - $('#bkmrksTrigger').show(); - } - - if(window.plugin.bookmarks.isSmart) { - var button = $('#bkmrksTrigger'); - button.toggleClass('open'); - if(button.hasClass('open')) { button.text('[-] Bookmarks'); } - else{ button.text('[+] Bookmarks'); } + $('#bkmrksTrigger').css('height', '64px'); + $('#bookmarksBox').css('height', '0'); } window.plugin.bookmarks.statusBox['show'] = newStatus; @@ -433,6 +426,11 @@ } } + window.plugin.bookmarks.deleteMode = function() { + $('#bookmarksBox').toggleClass('deleteMode'); + } + + /***************************************************************************************************************************************************************/ // Saved the new sort of the folders (in the localStorage) @@ -489,6 +487,7 @@ items:"li.bookmarkFolder:not(.othersBookmarks)", handle:".bookmarksAnchor", placeholder:"sortable-placeholder", + helper:'clone', // fix accidental click in firefox forcePlaceholderSize:true, update:function(event, ui) { var typeList = $('#'+ui.item.context.id).parent().parent('.bookmarkList').attr('id'); @@ -501,6 +500,7 @@ connectWith:".bookmarkList ul ul", handle:".bookmarksLink", placeholder:"sortable-placeholder", + helper:'clone', // fix accidental click in firefox forcePlaceholderSize:true, update:function(event, ui) { var typeList = $('#'+ui.item.context.id).parent().parent().parent().parent('.bookmarkList').attr('id'); @@ -520,11 +520,6 @@ title: 'Bookmarks Options' }); - if(window.plugin.bookmarks.isAndroid()) { - $('a:contains(\'Save box\'), a:contains(\'Reset box\')').addClass('disabled'); - } else { - $('a:contains(\'Share all\')').addClass('disabled'); - } window.runHooks('pluginBkmrksOpenOpt'); } @@ -534,7 +529,7 @@ } window.plugin.bookmarks.optCopy = function() { - if(typeof android !== 'undefined' && android && android.intentPosLink) { + if(typeof android !== 'undefined' && android && android.shareString) { return android.shareString(localStorage[window.plugin.bookmarks.KEY_STORAGE]); } else { dialog({ @@ -545,20 +540,49 @@ } } - window.plugin.bookmarks.optPaste = function() { - var promptAction = prompt('Press CTRL+V to paste it.', ''); - if(promptAction !== null && promptAction !== '') { - localStorage[window.plugin.bookmarks.KEY_STORAGE] = promptAction; - window.plugin.bookmarks.refreshBkmrks(); - window.runHooks('pluginBkmrksEdit', {"target": "all", "action": "import"}); - console.log('BOOKMARKS: reset and imported bookmarks'); - window.plugin.bookmarks.optAlert('Successful. '); + window.plugin.bookmarks.optExport = function() { + if(typeof android !== 'undefined' && android && android.saveFile) { + android.saveFile("IITC-bookmarks.json", "application/json", localStorage[window.plugin.bookmarks.KEY_STORAGE]); } } + window.plugin.bookmarks.optPaste = function() { + var promptAction = prompt('Press CTRL+V to paste it.', ''); + if(promptAction !== null && promptAction !== '') { + try { + JSON.parse(promptAction); // try to parse JSON first + localStorage[window.plugin.bookmarks.KEY_STORAGE] = promptAction; + window.plugin.bookmarks.refreshBkmrks(); + window.runHooks('pluginBkmrksEdit', {"target": "all", "action": "import"}); + console.log('BOOKMARKS: reset and imported bookmarks'); + window.plugin.bookmarks.optAlert('Successful. '); + } catch(e) { + console.warn('BOOKMARKS: failed to import data: '+e); + window.plugin.bookmarks.optAlert('Import failed '); + } + } + } + + window.plugin.bookmarks.optImport = function() { + if (window.requestFile === undefined) return; + window.requestFile(function(filename, content) { + try { + JSON.parse(content); // try to parse JSON first + localStorage[window.plugin.bookmarks.KEY_STORAGE] = content; + window.plugin.bookmarks.refreshBkmrks(); + window.runHooks('pluginBkmrksEdit', {"target": "all", "action": "import"}); + console.log('BOOKMARKS: reset and imported bookmarks'); + window.plugin.bookmarks.optAlert('Successful. '); + } catch(e) { + console.warn('BOOKMARKS: failed to import data: '+e); + window.plugin.bookmarks.optAlert('Import failed '); + } + }); + } + window.plugin.bookmarks.optReset = function() { - var promptAction = prompt('All bookmarks will be deleted. Are you sure? [Y/N]', ''); - if(promptAction !== null && (promptAction === 'Y' || promptAction === 'y')) { + var promptAction = confirm('All bookmarks will be deleted. Are you sure?', ''); + if(promptAction) { delete localStorage[window.plugin.bookmarks.KEY_STORAGE]; window.plugin.bookmarks.createStorage(); window.plugin.bookmarks.loadStorage(); @@ -618,6 +642,8 @@ if(latlngs.length >= 2 && latlngs.length <= 3) { // TODO: add an API to draw-tools rather than assuming things about it's internals var newItem; + window.plugin.drawTools.setOptions(); + if(latlngs.length == 2) { newItem = L.geodesicPolyline(latlngs, window.plugin.drawTools.lineOptions); } else { @@ -885,6 +911,7 @@ +'
' +'-' +'
...
' + +'Show/Hide "X" button' +'
' +'
' +'
Maps
' @@ -904,19 +931,27 @@ +'+ Folder' +'
' +'' + +'
' +''; plugin.bookmarks.htmlDisabledMessage = '
Plugin Bookmarks disabled*.
'; plugin.bookmarks.htmlStar = ''; plugin.bookmarks.htmlCalldrawBox = 'Auto draw'; plugin.bookmarks.htmlCallSetBox = 'Bookmarks Opt'; - plugin.bookmarks.htmlSetbox = ''; + + var actions = ''; + actions += 'Reset bookmarks'; + actions += 'Copy bookmarks'; + actions += 'Paste bookmarks'; + + if(plugin.bookmarks.isAndroid()) { + actions += 'Import bookmarks'; + actions += 'Export bookmarks'; + } else { + actions += 'Save box position'; + actions += 'Reset box position'; + } + plugin.bookmarks.htmlSetbox = '
' + actions + '
'; } /***************************************************************************************************************************************************************/ diff --git a/plugins/bookmarks-css.css b/plugins/bookmarks-css.css index 9c160667..a8630974 100644 --- a/plugins/bookmarks-css.css +++ b/plugins/bookmarks-css.css @@ -9,13 +9,20 @@ line-height:22px; text-indent:0; text-decoration:none; + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; } + #bookmarksBox{ display:block; position:absolute !important; z-index:4001; top:100px; left:100px; + width:231px; + height:auto; + overflow:hidden; } #bookmarksBox .addForm, #bookmarksBox #bookmarksTypeBar, @@ -25,33 +32,40 @@ color:#fff; font-size:14px; } -#bookmarksBox #topBar, -#bookmarksBox #topBar *{ +#bookmarksBox #topBar{ height:15px !important; } +#bookmarksBox #topBar *{ + height: 14px !important; +} #bookmarksBox #topBar *{ float:left !important; } #bookmarksBox .handle{ - text-indent:-20px; - width:209px; + width:80%; text-align:center; color:#fff; - line-height:8px; + line-height:6px; cursor:move; } #bookmarksBox #topBar .btn{ display:block; - width:19px; + width:10%; cursor:pointer; color:#20a8b1; -} -#bookmarksBox #topBar #bookmarksMin{ + font-weight:bold; text-align:center; - line-height:14px; + line-height:13px; font-size:18px; } + +#bookmarksBox #topBar #bookmarksDel{ + overflow:hidden; + text-indent:-999px; + background:#B42E2E; +} + #bookmarksBox #topBar #bookmarksMin:hover{ color:#ffce00; } @@ -59,8 +73,9 @@ clear:both; } #bookmarksBox h5{ - padding:4px 0; - width:114px; + padding:4px 0 23px; + width:50%; + height:93px !important; text-align:center; color:#788; } @@ -73,9 +88,8 @@ color:#ffce00; background:rgba(0,0,0,0); } -#bookmarksBox #topBar .btn, +#bookmarksBox #topBar, #bookmarksBox .addForm, -#bookmarksBox .handle, #bookmarksBox #bookmarksTypeBar, #bookmarksBox .bookmarkList li.bookmarksEmpty, #bookmarksBox .bookmarkList li.bkmrk a, @@ -90,16 +104,16 @@ #bookmarksBox .addForm *{ display:block; float:left; - padding:4px 8px 3px; + height:28px !important; } #bookmarksBox .addForm a{ cursor:pointer; color:#20a8b1; font-size:12px; - width:65px; + width:35%; text-align:center; line-height:20px; - padding:4px 0 3px; + padding:4px 0 23px; } #bookmarksBox .addForm a:hover{ background:#ffce00; @@ -109,15 +123,19 @@ #bookmarksBox .addForm input{ font-size:11px !important; color:#ffce00; - width:81px; - line-height:11px; + height:28px; + padding:4px 8px 1px; + line-height:12px; font-size:12px; - -webkit-box-sizing:content-box; - -moz-box-sizing:content-box; - box-sizing:content-box; } #bookmarksBox #bkmrk_portals .addForm input{ - width:147px; + width:65%; +} +#bookmarksBox #bkmrk_maps .addForm input{ + width:42%; +} +#bookmarksBox #bkmrk_maps .addForm a{ + width:29%; } #bookmarksBox .addForm input:hover, #bookmarksBox .addForm input:focus{ @@ -125,11 +143,12 @@ background:rgba(0,0,0,.6); } #bookmarksBox .bookmarkList > ul{ - width:231px; clear:both; list-style-type:none; color:#fff; overflow:hidden; + overflow-y:auto; + max-height:580px; } #bookmarksBox .sortable-placeholder{ background:rgba(8,48,78,.55); @@ -161,12 +180,12 @@ color:#eee; } #bookmarksBox ul .bookmarksRemoveFrom{ - width:19px; + width:10%; text-align:center; color:#fff; } #bookmarksBox ul .bookmarksLink{ - width:171px; + width:90%; padding:0 10px 0 8px; color:#ffce00; } @@ -174,7 +193,7 @@ color:#03fe03; } #bookmarksBox ul .othersBookmarks .bookmarksLink{ - width:190px; + width:90%; } #bookmarksBox ul .bookmarksLink:hover{ color:#03fe03; @@ -183,6 +202,8 @@ color:#fff; background:#e22 !important; } + +/*---- UI border -----*/ #bookmarksBox, #bookmarksBox *{ border-color:#20a8b1; @@ -193,40 +214,44 @@ #bookmarksBox ul .bookmarkFolder{ border-top-width:1px; } + #bookmarksBox #topBar, #bookmarksBox #bookmarksTypeBar, #bookmarksBox .addForm, #bookmarksBox ul .bookmarkFolder .folderLabel, -#bookmarksBox ul li.bkmrk{ +#bookmarksBox ul li.bkmrk a { border-bottom-width:1px; } -#bookmarksBox ul .bookmarkFolder, -#bookmarksBox ul .bookmarksRemoveFrom{ +#bookmarksBox ul .bookmarkFolder{ border-right-width:1px; border-left-width:1px; } #bookmarksBox #topBar *, #bookmarksBox #bookmarksTypeBar *, -#bookmarksBox .addForm *{ +#bookmarksBox .addForm *, +#bookmarksBox ul li.bkmrk{ border-left-width:1px; } #bookmarksBox #topBar, #bookmarksBox #bookmarksTypeBar, -#bookmarksBox .addForm{ +#bookmarksBox .addForm, +#bookmarksBox ul .bookmarksRemoveFrom{ border-right-width:1px; } -#bookmarksBox ul .othersBookmarks .bookmarksRemoveFrom, +#bookmarksBox ul .bookmarkFolder.othersBookmarks li.bkmrk, #bookmarksBox ul .bookmarkFolder .folderLabel .bookmarksRemoveFrom{ border-left-width:0; } #bkmrksTrigger{ - display:none; + display:block !important; position:absolute; + overflow:hidden; top:0; left:277px; width:47px; margin-top:-36px; height:64px; + height:0; cursor:pointer; z-index:2999; background-position:center bottom; @@ -272,17 +297,25 @@ } #bookmarksBox .bookmarkList .bkmrk.ui-sortable-helper{ border-right-width:1px; - border-left-width:1px; + border-left-width:1px !important; } #bookmarksBox .bookmarkList ul li ul li.sortable-placeholder{ height:23px; box-shadow:inset 0 -1px 0 #20a8b1,inset 1px 0 0 #20a8b1; } + #bookmarksBox .bookmarkList ul li.bookmarkFolder.ui-sortable-helper, -#bookmarksBox .bookmarkList ul li.othersBookmarks ul, #bookmarksBox .bookmarkList ul li.othersBookmarks ul li.sortable-placeholder{ box-shadow:inset 0 -1px 0 #20a8b1; } + +#bookmarksBox #topBar #bookmarksDel, +#bookmarksBox .bookmarkList .bookmarkFolder .folderLabel:hover .bookmarksRemoveFrom, +#bookmarksBox .bookmarkList .bookmarkFolder .folderLabel:hover .bookmarksAnchor{ + border-bottom-width:1px; +} + +/*---------*/ #bookmarksBox .bookmarkList .bookmarkFolder .folderLabel .bookmarksAnchor span, #bookmarksBox .bookmarkList .bookmarkFolder .folderLabel > span, #bookmarksBox .bookmarkList .bookmarkFolder .folderLabel > span > span, @@ -304,7 +337,7 @@ #bookmarksBox .bookmarkList .bookmarkFolder .folderLabel .bookmarksAnchor{ line-height:25px; color:#fff; - width:209px; + width:90%; } #bookmarksBox .bookmarkList .bookmarkFolder .folderLabel .bookmarksAnchor span{ float:left; @@ -336,6 +369,7 @@ #bookmarksBox .bookmarkList .bookmarkFolder.open .folderLabel > span, #bookmarksBox .bookmarkList .bookmarkFolder.open .folderLabel > span > span{ display:block; + display:none; } #bookmarksBox .bookmarkList .bookmarkFolder.open .folderLabel:hover > span > span{ border-color:transparent #036 transparent transparent; @@ -345,7 +379,7 @@ } #bookmarksBox .bookmarkList .bookmarkFolder ul{ display:none; - margin-left:19px; + margin-left:10%; } #bookmarksBox .bookmarkList .bookmarkFolder.open ul{ display:block; @@ -355,6 +389,26 @@ margin-left:0; } +/*---- Width for deleteMode -----*/ +#bookmarksBox .bookmarksRemoveFrom{ + display:none !important; +} +#bookmarksBox.deleteMode .bookmarksRemoveFrom{ + display:block !important; +} + +#bookmarksBox .bookmarkList .bookmarkFolder .folderLabel .bookmarksAnchor, +#bookmarksBox ul .bookmarksLink, +#bookmarksBox ul .othersBookmarks .bookmarksLink{ + width:100% !important; +} + +#bookmarksBox.deleteMode .bookmarkList .bookmarkFolder .folderLabel .bookmarksAnchor, +#bookmarksBox.deleteMode ul .bookmarksLink, +#bookmarksBox.deleteMode ul .othersBookmarks .bookmarksLink{ + width:90% !important; +} + /********************************************** MOBILE **********************************************/ @@ -367,7 +421,8 @@ margin: 0 !important; padding: 0 !important; border: 0 !important; - background: transparent !important;; + background: transparent !important; + overflow:auto !important; } #bookmarksBox.mobile .bookmarkList ul, #bookmarksBox.mobile .bookmarkList ul li, @@ -380,14 +435,27 @@ box-shadow:none !important; border-width:0 !important; } -#bookmarksBox.mobile #topBar{ +#bookmarksBox.mobile #topBar #bookmarksMin, +#bookmarksBox.mobile #topBar .handle{ display:none !important; } + +#bookmarksBox.mobile #topBar #bookmarksDel{ + width:100%; + height:34px !important; + font-size:13px; + color:#fff; + font-weight:normal; + padding-top:11px; + text-indent:0; +} + #bookmarksBox.mobile #bookmarksTypeBar h5{ cursor:pointer; text-align:center; float:left; width:50%; + height:auto !important; padding:7px 0; } #bookmarksBox.mobile #bookmarksTypeBar h5.current{ @@ -413,14 +481,14 @@ #bookmarksBox.mobile .bookmarkList li.bookmarkFolder a.bookmarksRemoveFrom, #bookmarksBox.mobile .bookmarkList li.bkmrk a.bookmarksRemoveFrom{ box-shadow:inset 0 1px 0 #20a8b1,inset -1px 0 0 #20a8b1 !important; - width:15%; + width:10%; background:none !important; } #bookmarksBox.mobile .bookmarkList li.bookmarkFolder a.bookmarksAnchor, #bookmarksBox.mobile .bookmarkList li.bkmrk a.bookmarksLink{ text-indent:10px; - width:85%; - height:21px; + height:36px; + line-height:24px; overflow:hidden; } #bookmarksBox.mobile .bookmarkList ul li.bookmarkFolder ul{ @@ -428,7 +496,6 @@ } #bookmarksBox.mobile .bookmarkList > ul{ border-bottom:1px solid #20a8b1 !important; - border-right:1px solid #20a8b1 !important; } #bookmarksBox.mobile .bookmarkList .bookmarkFolder.othersBookmarks ul{ @@ -440,14 +507,14 @@ } #bookmarksBox.mobile .bookmarkList > ul{ max-height:none; - width:85% !important; +/* width:85% !important;*/ } #bookmarksBox.mobile .bookmarkList li.bookmarkFolder .folderLabel{ box-shadow:0 1px 0 #20a8b1 !important; } #bookmarksBox.mobile .bookmarkList ul li.bookmarkFolder ul{ - width:85% !important; - margin-left:15% !important; + width:90% !important; + margin-left:10% !important; } #bookmarksBox.mobile .bookmarkList ul li.bookmarkFolder.othersBookmarks ul{ width:100% !important; @@ -461,7 +528,7 @@ } #bookmarksBox.mobile .addForm, #bookmarksBox.mobile .addForm *{ - height:35px; + height:35px !important; padding:0; } #bookmarksBox.mobile .addForm a{ @@ -469,14 +536,14 @@ } #bookmarksBox.mobile .addForm a{ - width:25% !important; +/* width:25% !important;*/ } #bookmarksBox.mobile .addForm input{ - width:50% !important; +/* width:50% !important;*/ text-indent:10px; } #bookmarksBox.mobile #bkmrk_portals .addForm input{ - width:75% !important; +/* width:75% !important;*/ } #bookmarksBox.mobile #bookmarksTypeBar h5, #bookmarksBox.mobile .bookmarkList .addForm a{ @@ -530,10 +597,10 @@ #bookmarksBox.mobile .bookmarkList .bookmarkFolder.open .folderLabel > span > span{ display:block !important; } + /********************************************** DIALOG BOX **********************************************/ - /*---- Auto Drawer -----*/ #bkmrksAutoDrawer, #bkmrksAutoDrawer p, @@ -597,4 +664,4 @@ width:96%; height:120px; resize:vertical; -} +} \ No newline at end of file diff --git a/plugins/default-intel-detail.user.js b/plugins/default-intel-detail.user.js index 8bcc0ef2..107792a9 100644 --- a/plugins/default-intel-detail.user.js +++ b/plugins/default-intel-detail.user.js @@ -2,7 +2,7 @@ // @id iitc-plugin-default-intel-detail@jonatkins // @name IITC plugin: Default intel detail level // @category Tweaks -// @version 0.1.0.@@DATETIMEVERSION@@ +// @version 0.2.0.@@DATETIMEVERSION@@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ @@ -24,23 +24,9 @@ window.plugin.defaultIntelDetail = function() {}; window.plugin.defaultIntelDetail.setup = function() { - var stockIntelDetail = nemesis.dashboard.zoomlevel.ZOOM_TO_LOD_; - - // save the original function - so we can chain to it for levels we don't modify - var origGetMinPortalLevelForZoom = window.getMinPortalLevelForZoom; - - // replace the window.getMinPortalLevelForZoom function - modify behaviour when L1+ or L3+ portals are shown - - window.getMinPortalLevelForZoom = function(z) { - // for the further out zoom levels, use the stock intel site detail levels - if (z <= 11) { - return stockIntelDetail[z]; - } - // for the closer zoom levels, stock intel and IITC default is the same. falling back - // in this case allows this plugin to work alongside show-more-portals - return origGetMinPortalLevelForZoom(z); - } - +// NOTE: the logic required is closely tied to the IITC+stock map detail level code - so the logic is moved there now +// and just enabled by this flag + window.CONFIG_ZOOM_DEFAULT_DETAIL_LEVEL=true; }; diff --git a/plugins/draw-tools.user.js b/plugins/draw-tools.user.js index 55aaf01b..865a71e8 100644 --- a/plugins/draw-tools.user.js +++ b/plugins/draw-tools.user.js @@ -34,8 +34,27 @@ window.plugin.drawTools.loadExternals = function() { $('head').append(''); } -window.plugin.drawTools.currentColor = '#a24ac3'; +window.plugin.drawTools.getMarkerIcon = function(color) { + if (typeof(color) === 'undefined') { + console.warn('Color is not set or not a valid color. Using default color as fallback.'); + color = '#a24ac3'; + } + var svgIcon = window.plugin.drawTools.markerTemplate.replace(/%COLOR%/g, color); + + return L.divIcon({ + iconSize: new L.Point(25, 41), + iconAnchor: new L.Point(12, 41), + html: svgIcon, + className: 'leaflet-iitc-custom-icon', + // L.divIcon does not use the option color, but we store it here to + // be able to simply retrieve the color for serializing markers + color: color + }); +} + +window.plugin.drawTools.currentColor = '#a24ac3'; +window.plugin.drawTools.markerTemplate = '@@INCLUDESTRING:images/marker-icon.svg.template@@'; window.plugin.drawTools.setOptions = function() { @@ -60,7 +79,7 @@ window.plugin.drawTools.setOptions = function() { delete window.plugin.drawTools.editOptions.color; window.plugin.drawTools.markerOptions = { - icon: new L.Icon.Default(), + icon: window.plugin.drawTools.currentMarker, zIndexOffset: 2000 }; @@ -68,11 +87,13 @@ window.plugin.drawTools.setOptions = function() { window.plugin.drawTools.setDrawColor = function(color) { window.plugin.drawTools.currentColor = color; + window.plugin.drawTools.currentMarker = window.plugin.drawTools.getMarkerIcon(color); window.plugin.drawTools.drawControl.setDrawingOptions({ 'polygon': { 'shapeOptions': { color: color } }, 'polyline': { 'shapeOptions': { color: color } }, 'circle': { 'shapeOptions': { color: color } }, + 'marker': { 'icon': window.plugin.drawTools.currentMarker }, }); } @@ -113,14 +134,15 @@ window.plugin.drawTools.addDrawControl = function() { snapPoint: window.plugin.drawTools.getSnapLatLng, }, - marker: { + // Options for marker (icon, zIndexOffset) are not set via shapeOptions, + // so we have merge them here! + marker: L.extend({}, window.plugin.drawTools.markerOptions, { title: 'Add a marker.\n\n' + 'Click on the button, then click on the map where\n' + 'you want the marker to appear.', - shapeOptions: window.plugin.drawTools.markerOptions, snapPoint: window.plugin.drawTools.getSnapLatLng, - repeatMode: true, - }, + repeatMode: true + }), }, @@ -190,6 +212,7 @@ window.plugin.drawTools.save = function() { } else if (layer instanceof L.Marker) { item.type = 'marker'; item.latLng = layer.getLatLng(); + item.color = layer.options.icon.options.color; } else { console.warn('Unknown layer type when saving draw tools layer'); return; //.eachLayer 'continue' @@ -233,7 +256,9 @@ window.plugin.drawTools.import = function(data) { layer = L.geodesicCircle(item.latLng, item.radius, L.extend({},window.plugin.drawTools.polygonOptions,extraOpt)); break; case 'marker': - layer = L.marker(item.latLng, window.plugin.drawTools.markerOptions); + var extraMarkerOpt = {}; + if (item.color) extraMarkerOpt.icon = window.plugin.drawTools.getMarkerIcon(item.color); + layer = L.marker(item.latLng, L.extend({},window.plugin.drawTools.markerOptions,extraMarkerOpt)); break; default: console.warn('unknown layer type "'+item.type+'" when loading draw tools layer'); @@ -256,8 +281,12 @@ window.plugin.drawTools.manualOpt = function() { //TODO: add line style choosers: thickness, maybe dash styles? + '' + '
' - + 'Copy/Export Drawn Items' - + 'Paste/Import Drawn Items' + + 'Copy Drawn Items' + + 'Paste Drawn Items' + + (window.requestFile != undefined + ? 'Import Drawn Items' : '') + + ((typeof android !== 'undefined' && android && android.saveFile) + ? 'Export Drawn Items' : '') + 'Reset Drawn Items' + '
'; @@ -302,6 +331,12 @@ window.plugin.drawTools.optCopy = function() { } } +window.plugin.drawTools.optExport = function() { + if(typeof android !== 'undefined' && android && android.saveFile) { + android.saveFile('IITC-drawn-items.json', 'application/json', localStorage['plugin-draw-tools-layer']); + } +} + window.plugin.drawTools.optPaste = function() { var promptAction = prompt('Press CTRL+V to paste it.', ''); if(promptAction !== null && promptAction !== '') { @@ -318,10 +353,28 @@ window.plugin.drawTools.optPaste = function() { console.warn('DRAWTOOLS: failed to import data: '+e); window.plugin.drawTools.optAlert('Import failed'); } - } } +window.plugin.drawTools.optImport = function() { + if (window.requestFile === undefined) return; + window.requestFile(function(filename, content) { + try { + var data = JSON.parse(content); + window.plugin.drawTools.drawnItems.clearLayers(); + window.plugin.drawTools.import(data); + console.log('DRAWTOOLS: reset and imported drawn tiems'); + window.plugin.drawTools.optAlert('Import Successful.'); + + // to write back the data to localStorage + window.plugin.drawTools.save(); + } catch(e) { + console.warn('DRAWTOOLS: failed to import data: '+e); + window.plugin.drawTools.optAlert('Import failed'); + } + }); +} + window.plugin.drawTools.optReset = function() { var promptAction = confirm('All drawn items will be deleted. Are you sure?', ''); if(promptAction) { @@ -334,6 +387,8 @@ window.plugin.drawTools.optReset = function() { } window.plugin.drawTools.boot = function() { + window.plugin.drawTools.currentMarker = window.plugin.drawTools.getMarkerIcon(window.plugin.drawTools.currentColor); + window.plugin.drawTools.setOptions(); //create a leaflet FeatureGroup to hold drawn items diff --git a/plugins/favorite-portals.user.js b/plugins/favorite-portals.user.js index f82526df..d3b9c888 100644 --- a/plugins/favorite-portals.user.js +++ b/plugins/favorite-portals.user.js @@ -3,7 +3,7 @@ // @name IITC plugin: Favorite Portals // @category Deleted // @version 0.2.0.@@DATETIMEVERSION@@ -// @description PLUGIN CURRENTLY UNAVAILABLE +// @description Plugin replaced by the "Bookmarks for maps and portals" plugin // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ diff --git a/plugins/fix-googlemap-china-offset.user.js b/plugins/fix-googlemap-china-offset.user.js new file mode 100644 index 00000000..a25b271a --- /dev/null +++ b/plugins/fix-googlemap-china-offset.user.js @@ -0,0 +1,198 @@ +// ==UserScript== +// @id iitc-plugin-fix-googlemap-china-offset@breezewish +// @name IITC plugin: Fix Google Map offsets in China +// @category Tweaks +// @version 0.0.1.@@DATETIMEVERSION@@ +// @namespace https://github.com/jonatkins/ingress-intel-total-conversion +// @updateURL @@UPDATEURL@@ +// @downloadURL @@DOWNLOADURL@@ +// @description [@@BUILDNAME@@-@@BUILDDATE@@] Show correct Google Map for China user by applying offset tweaks. +// @include https://www.ingress.com/intel* +// @include http://www.ingress.com/intel* +// @match https://www.ingress.com/intel* +// @match http://www.ingress.com/intel* +// @grant none +// ==/UserScript== + +@@PLUGINSTART@@ + +// PLUGIN START //////////////////////////////////////////////////////// + +// use own namespace for plugin +window.plugin.fixChinaOffset = {}; + +// Before understanding how this plugin works, you should know 3 points: +// +// Point1. +// The coordinate system of Ingress is WGS-84. +// However, the tiles of Google maps (except satellite map) in China have +// offsets (base on GCJ-02 coordinate system) by China policy. +// That means, if you request map tiles by giving GCJ-02 position, you +// will get the correct map. +// +// Point2. +// Currently there are no easy algorithm to transform from GCJ-02 to WGS-84, +// but we can easily transform data from WGS-84 to GCJ-02. +// +// Point3. +// When using Google maps in IITC, the layer structure looks like this: +// ---------------------- +// | Other Leaflet layers | (Including portals, links, fields, and so on) +// ---------------------- +// | L.Google | (Only for controling) +// ---------------------- +// | Google Map layer | (Generated by Google Map APIs, for rendering maps) +// ---------------------- +// +// When users are interacting with L.Google (for example, dragging, zooming), +// L.Google will perform the same action on the Google Map layer using Google +// Map APIs. +// +// So, here is the internal of the plugin: +// +// The plugin overwrites behaviours of L.Google. When users are dragging the map, +// L.Google will pass offseted positions to Google Map APIs (WGS-84 to GCJ-02). +// So Google Map APIs will render a correct map. +// +// The offset between Google maps and Ingress objects can also be fixed by applying +// WGS-84 to GCJ-02 transformation on Ingress objects. However we cannot simply know +// the requesting bounds of Ingress objects because we cannot transform GCJ-02 to +// WGS-84. As a result, the Ingress objects on maps would be incomplete. +// +// The algorithm of transforming WGS-84 to GCJ-02 comes from: +// https://on4wp7.codeplex.com/SourceControl/changeset/view/21483#353936 +// There is no official algorithm because it is classified information. + +/////////// begin WGS84 to GCJ-02 transformer ///////// +var WGS84transformer = window.plugin.fixChinaOffset.WGS84transformer = function() {}; +// Krasovsky 1940 +// +// a = 6378245.0, 1/f = 298.3 +// b = a * (1 - f) +// ee = (a^2 - b^2) / a^2; +WGS84transformer.prototype.a = 6378245.0; +WGS84transformer.prototype.ee = 0.00669342162296594323; + +WGS84transformer.prototype.transform = function(wgLat, wgLng) { + + if(this.isOutOfChina(wgLat, wgLng)) + return {lat: wgLat, lng: wgLng}; + + dLat = this.transformLat(wgLng - 105.0, wgLat - 35.0); + dLng = this.transformLng(wgLng - 105.0, wgLat - 35.0); + radLat = wgLat / 180.0 * Math.PI; + magic = Math.sin(radLat); + magic = 1 - this.ee * magic * magic; + sqrtMagic = Math.sqrt(magic); + dLat = (dLat * 180.0) / ((this.a * (1 - this.ee)) / (magic * sqrtMagic) * Math.PI); + dLng = (dLng * 180.0) / (this.a / sqrtMagic * Math.cos(radLat) * Math.PI); + mgLat = wgLat + dLat; + mgLng = wgLng + dLng; + + return {lat: mgLat, lng: mgLng}; + +}; + +WGS84transformer.prototype.isOutOfChina = function(lat, lng) { + + if(lng < 72.004 || lng > 137.8347) return true; + if(lat < 0.8293 || lat > 55.8271) return true; + + return false; + +}; + +WGS84transformer.prototype.transformLat = function(x, y) { + + var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); + ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(y * Math.PI) + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0; + ret += (160.0 * Math.sin(y / 12.0 * Math.PI) + 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0; + + return ret; + +}; + +WGS84transformer.prototype.transformLng = function(x, y) { + + var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)); + ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(x * Math.PI) + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0; + ret += (150.0 * Math.sin(x / 12.0 * Math.PI) + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0; + + return ret; + +}; +/////////// end WGS84 to GCJ-02 transformer ///////// + +var WGS84toGCJ02 = new WGS84transformer(); + +/////////// begin overwrited L.Google ///////// +window.plugin.fixChinaOffset.L = {}; +window.plugin.fixChinaOffset.L.Google = { + + _update: function(e) { + + if(!this._google) return; + this._resize(); + + var center = e && e.latlng ? e.latlng : this._map.getCenter(); + + ///// modified here /// + var _center = window.plugin.fixChinaOffset.getLatLng(center, this._type); + /////////////////////// + + this._google.setCenter(_center); + this._google.setZoom(this._map.getZoom()); + + this._checkZoomLevels(); + + }, + + _handleZoomAnim: function (e) { + + var center = e.center; + + ///// modified here /// + var _center = window.plugin.fixChinaOffset.getLatLng(center, this._type); + /////////////////////// + + this._google.setCenter(_center); + this._google.setZoom(e.zoom); + + } + +} +/////////// end overwrited L.Google ///////// + +window.plugin.fixChinaOffset.getLatLng = function(pos, type) { + + // No offsets in satellite and hybrid maps + if(type !== 'SATELLITE' && type !== 'HYBRID') { + var newPos = WGS84toGCJ02.transform(pos.lat, pos.lng); + return new google.maps.LatLng(newPos.lat, newPos.lng); + } else { + return new google.maps.LatLng(pos.lat, pos.lng); + } + +}; + +window.plugin.fixChinaOffset.overwrite = function(dest, src) { + + for(var key in src) { + if(src.hasOwnProperty(key)) { + dest[key] = src[key]; + } + } + +} + +var setup = function() { + + window.plugin.fixChinaOffset.overwrite(L.Google.prototype, window.plugin.fixChinaOffset.L.Google); + +} + +// PLUGIN END ////////////////////////////////////////////////////////// + +@@PLUGINEND@@ diff --git a/plugins/portal-highlighter-bad-deployment-distance.user.js b/plugins/portal-highlighter-bad-deployment-distance.user.js index 3ddf3a66..1d8dd459 100644 --- a/plugins/portal-highlighter-bad-deployment-distance.user.js +++ b/plugins/portal-highlighter-bad-deployment-distance.user.js @@ -6,7 +6,7 @@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ -// @description PLUGIN CURRENTLY UNAVAILABLE +// @description This plugin is no longer available, as Niantic optimisations have removed the data it needed. // @include https://www.ingress.com/intel* // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* diff --git a/plugins/portal-highlighter-can-make-level.user.js b/plugins/portal-highlighter-can-make-level.user.js index 1d5acc86..ef022dd1 100644 --- a/plugins/portal-highlighter-can-make-level.user.js +++ b/plugins/portal-highlighter-can-make-level.user.js @@ -6,7 +6,7 @@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ -// @description PLUGIN CURRENTLY UNAVAILABLE +// @description This plugin is no longer available, as Niantic optimisations have removed the data it needed. // @include https://www.ingress.com/intel* // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* diff --git a/plugins/portal-highlighter-imminent-decay.user.js b/plugins/portal-highlighter-imminent-decay.user.js index 14f98297..60296401 100644 --- a/plugins/portal-highlighter-imminent-decay.user.js +++ b/plugins/portal-highlighter-imminent-decay.user.js @@ -6,7 +6,7 @@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ -// @description PLUGIN CURRENTLY UNAVAILABLE +// @description This plugin is no longer available, as Niantic optimisations have removed the data it needed. // @include https://www.ingress.com/intel* // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* diff --git a/plugins/portal-highlighter-mitigation.user.js b/plugins/portal-highlighter-mitigation.user.js index 0956e19f..f3fe1c0b 100644 --- a/plugins/portal-highlighter-mitigation.user.js +++ b/plugins/portal-highlighter-mitigation.user.js @@ -6,7 +6,7 @@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ -// @description PLUGIN CURRENTLY UNAVAILABLE +// @description This plugin is no longer available, as Niantic optimisations have removed the data it needed. // @include https://www.ingress.com/intel* // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* diff --git a/plugins/portal-highlighter-mods.user.js b/plugins/portal-highlighter-mods.user.js index 46e19f2a..4f1664cd 100644 --- a/plugins/portal-highlighter-mods.user.js +++ b/plugins/portal-highlighter-mods.user.js @@ -6,7 +6,7 @@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ -// @description PLUGIN CURRENTLY UNAVAILABLE +// @description This plugin is no longer available, as Niantic optimisations have removed the data it needed. // @include https://www.ingress.com/intel* // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* diff --git a/plugins/portal-highlighter-my-8-portals.user.js b/plugins/portal-highlighter-my-8-portals.user.js index 12cfbeb7..02e4118d 100644 --- a/plugins/portal-highlighter-my-8-portals.user.js +++ b/plugins/portal-highlighter-my-8-portals.user.js @@ -6,7 +6,7 @@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ -// @description PLUGIN CURRENTLY UNAVAILABLE +// @description This plugin is no longer available, as Niantic optimisations have removed the data it needed. // @include https://www.ingress.com/intel* // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* diff --git a/plugins/portal-highlighter-my-portals.user.js b/plugins/portal-highlighter-my-portals.user.js index 7679a824..bac86774 100644 --- a/plugins/portal-highlighter-my-portals.user.js +++ b/plugins/portal-highlighter-my-portals.user.js @@ -6,7 +6,7 @@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ -// @description PLUGIN CURRENTLY UNAVAILABLE +// @description This plugin is no longer available, as Niantic optimisations have removed the data it needed. // @include https://www.ingress.com/intel* // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* diff --git a/plugins/portal-highlighter-portal-ap-energy-relative.user.js b/plugins/portal-highlighter-portal-ap-energy-relative.user.js index be4c81eb..ddf59847 100644 --- a/plugins/portal-highlighter-portal-ap-energy-relative.user.js +++ b/plugins/portal-highlighter-portal-ap-energy-relative.user.js @@ -6,7 +6,7 @@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ -// @description PLUGIN CURRENTLY UNAVAILABLE +// @description This plugin is no longer available, as Niantic optimisations have removed the data it needed. // @include https://www.ingress.com/intel* // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* diff --git a/plugins/portal-highlighter-portal-ap-relative.user.js b/plugins/portal-highlighter-portal-ap-relative.user.js index f5865636..e119c626 100644 --- a/plugins/portal-highlighter-portal-ap-relative.user.js +++ b/plugins/portal-highlighter-portal-ap-relative.user.js @@ -6,7 +6,7 @@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ -// @description PLUGIN CURRENTLY UNAVAILABLE +// @description This plugin is no longer available, as Niantic optimisations have removed the data it needed. // @include https://www.ingress.com/intel* // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* diff --git a/plugins/portal-highlighter-portal-ap.user.js b/plugins/portal-highlighter-portal-ap.user.js index 6d8180c8..49387887 100644 --- a/plugins/portal-highlighter-portal-ap.user.js +++ b/plugins/portal-highlighter-portal-ap.user.js @@ -6,7 +6,7 @@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ -// @description PLUGIN CURRENTLY UNAVAILABLE +// @description This plugin is no longer available, as Niantic optimisations have removed the data it needed. // @include https://www.ingress.com/intel* // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* diff --git a/plugins/portal-highlighter-portals-upgrade.user.js b/plugins/portal-highlighter-portals-upgrade.user.js index 2c3f5cef..fca2ca08 100644 --- a/plugins/portal-highlighter-portals-upgrade.user.js +++ b/plugins/portal-highlighter-portals-upgrade.user.js @@ -6,7 +6,7 @@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ -// @description PLUGIN CURRENTLY UNAVAILABLE +// @description This plugin is no longer available, as Niantic optimisations have removed the data it needed. // @include https://www.ingress.com/intel* // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* diff --git a/plugins/portal-highlighter-with-lvl8-resonators.user.js b/plugins/portal-highlighter-with-lvl8-resonators.user.js index d7f09d96..01f28340 100644 --- a/plugins/portal-highlighter-with-lvl8-resonators.user.js +++ b/plugins/portal-highlighter-with-lvl8-resonators.user.js @@ -6,7 +6,7 @@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ -// @description PLUGIN CURRENTLY UNAVAILABLE +// @description This plugin is no longer available, as Niantic optimisations have removed the data it needed. // @include https://www.ingress.com/intel* // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* diff --git a/plugins/portal-names.user.js b/plugins/portal-names.user.js index d6ba283c..74322119 100644 --- a/plugins/portal-names.user.js +++ b/plugins/portal-names.user.js @@ -150,13 +150,13 @@ window.plugin.portalNames.updatePortalLabels = function() { // ass calculating portal marker visibility can take some time when there's lots of portals shown, we'll do it on // a short timer. this way it doesn't get repeated so much -window.plugin.portalNames.delayedUpdatePortalLabels = function() { +window.plugin.portalNames.delayedUpdatePortalLabels = function(wait) { if (window.plugin.portalNames.timer === undefined) { window.plugin.portalNames.timer = setTimeout ( function() { window.plugin.portalNames.timer = undefined; window.plugin.portalNames.updatePortalLabels(); - }, 0.5*1000); + }, wait*1000); } } @@ -168,9 +168,9 @@ var setup = function() { window.plugin.portalNames.labelLayerGroup = new L.LayerGroup(); window.addLayerGroup('Portal Names', window.plugin.portalNames.labelLayerGroup, true); - window.addHook('requestFinished', window.plugin.portalNames.delayedUpdatePortalLabels); - window.addHook('mapDataRefreshEnd', window.plugin.portalNames.delayedUpdatePortalLabels); - window.map.on('overlayadd overlayremove', window.plugin.portalNames.delayedUpdatePortalLabels); + window.addHook('requestFinished', function() { setTimeout(function(){window.plugin.portalNames.delayedUpdatePortalLabels(3.0);},1); }); + window.addHook('mapDataRefreshEnd', function() { window.plugin.portalNames.delayedUpdatePortalLabels(0.5); }); + window.map.on('overlayadd overlayremove', function() { setTimeout(function(){window.plugin.portalNames.delayedUpdatePortalLabels(1.0);},1); }); } // PLUGIN END ////////////////////////////////////////////////////////// diff --git a/plugins/scoreboard.user.js b/plugins/scoreboard.user.js index b3237809..52ee4d9b 100644 --- a/plugins/scoreboard.user.js +++ b/plugins/scoreboard.user.js @@ -6,7 +6,7 @@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ -// @description PLUGIN CURRENTLY UNAVAILABLE +// @description This plugin is no longer available, as Niantic optimisations have removed the data it needed. // @include https://www.ingress.com/intel* // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* diff --git a/plugins/show-more-portals.user.js b/plugins/show-more-portals.user.js index 11cc5d99..0dde84dd 100644 --- a/plugins/show-more-portals.user.js +++ b/plugins/show-more-portals.user.js @@ -2,7 +2,7 @@ // @id iitc-plugin-show-more-portals@jonatkins // @name IITC plugin: Show more portals // @category Tweaks -// @version 0.1.6.@@DATETIMEVERSION@@ +// @version 0.2.0.@@DATETIMEVERSION@@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ @@ -24,27 +24,9 @@ window.plugin.showMorePortals = function() {}; window.plugin.showMorePortals.setup = function() { - // save the original function - so we can chain to it for levels we don't modify - var origGetMinPortalLevelForZoom = window.getMinPortalLevelForZoom; - - // replace the window.getMinPortalLevelForZoom function - modify behaviour when L1+ or L3+ portals are shown - - window.getMinPortalLevelForZoom = function(z) { - var level = origGetMinPortalLevelForZoom(z); - - // as of 2013-10-16... - - // the stock site uses the same tile size for both L1+ portals and all portals - // therefore, changing the L1+ zoom levels into all portals zoom level is not unfriendly to the servers - // and the same applies for L2+ and L3+ detail levels - // (in some ways it's nicer, as IITC caches better) - - if (level == 1) level = 0; - if (level == 3) level = 2; - - return level; - } - +// NOTE: the logic required is closely tied to the IITC+stock map detail level code - so the logic is moved there now +// and just enabled by this flag + window.CONFIG_ZOOM_SHOW_MORE_PORTALS=true; }; diff --git a/website/page/faq.php b/website/page/faq.php index cf706b6b..6ab7ab79 100644 --- a/website/page/faq.php +++ b/website/page/faq.php @@ -158,19 +158,17 @@ From here you can remove/disable individual plugins or IITC itself. END ), -'mobile-plugins' => Array ( "IITC Mobile: Is it possible to add other plugins to IITC Mobile?", +'mobile-plugins' => Array ( "IITC Mobile: Is it possible to add external plugins to IITC Mobile?", <<<'END' Yes it is!
    -
  • Create a folder named "IITC_Mobile" in your home directory.
  • -
  • Inside this folder, create a new folder named "plugins".
  • -
  • Copy all your additional plugins to this folder.
  • -
  • You should see your plugins listed above the official plugins.
  • +
  • Navigate to the IITC Plugins preference screen and click the (+) icon at the top right. You can select the script using a file explorer of your choice.
  • +
  • IITCm creates a new folder in your home directory, named "IITC_Mobile". Inside this folder you'll find a "plugins" folder where all external plugins are copied to.
Note:
  • The filename has to end with *.user.js.
  • -
  • If you don't know where to find your home directory: Enable dev-mode in the settings and follow the hint.
  • +
  • You can also trigger installation by clicking on http(s) links pointing to a plugin, selecting plugins with a file explorer, opening javascript e-mail attachments etc...
END ), diff --git a/website/page/home.php b/website/page/home.php index f46f56e4..399a1b46 100644 --- a/website/page/home.php +++ b/website/page/home.php @@ -13,39 +13,30 @@ offers many more features. It is available for

Latest news

-

13th January 2014

+

6th February 2014

-A new IITC release, 0.16.2 and IITC Mobile 0.10.2 have been released. These are needed to work with a change to the -standard intel site. -

-

-Additionally, the 'Compute AP Statistics' plugin has been brought back, the 'blank map' base layer has a new black option -to go with the white, and the 'Yandex' base map has had some bug fixes. Also, IITC Mobile features some changes to -the 'show my location' feature. You may need to turn this option on again for it to work. -

-

-Update 14th January 2014: An updated IITC Mobile, 0.10.3, has been released, to fix a crash issue seen by some. -Also, a minor update was made to the main IITC script which changes the order the data is loaded, to match a change made to -the standard intel site. -

- -

21st December 2013

-

-Just in time for the holidays, another IITC update. IITC 0.16.1 and IITC Mobile 0.10.1 have just been released. -Changes include +IITC 0.16.4 and IITC Mobile 0.10.4 have just been released. This version is required to fix a bug with showing portal details +due to a change made by Niantic to the intel site protocol. Also, the following changes have been made:

    -
  • Portals list plugin returns - but less data than before due to the Niantic backend changes
  • -
  • Resonators plugin returns - but only shows the selected portal
  • -
  • Mobile: +
  • Portal markers are now reduced in size when you zoom out, reducing clutter when viewing large areas of the map
  • +
  • Blocked a 3rd party plugin, arc, from running - it had spyware features hidden within it +(details here).
  • +
  • Plugins
      -
    • Some plugins moved to panes from the left-swipe menu: portals list, portal counts
    • -
    • Immersive fullscreen mode on Android 4.4 KitKat
    • -
    • Sort apps in share activity - most used at the top
    • -
    • Fix links sometimes being badly drawn on mobile
    • +
    • add-kml: support for opening files on mobile added
    • +
    • regions: new plugin to draw the scoreboard regions on the map. No support for showing scores - this needs Niantic to add it to the standard intel site first
    • +
    • score-cycle-times: new plugin to show the times of the scoreboard cycles
    • +
    • draw-tools: added basic import/export (via copy+paste), and colour choosing options (click on "DrawTools Opt" in the sidebar)
    • +
    • compute-ap-stats and portal-names: changed code to reduce the performance impact when a large number of portals are shown
  • -
  • .. and, as always, other various bug fixes, improvements, etc
  • +
  • Mobile: +
      +
    • NFC support for sharing map view/selected portal - app permissions updated for this
    • +
    +
  • +
  • .. plus various minor bugfixes and improvements
Older news diff --git a/website/page/news.php b/website/page/news.php index c14bc1c3..20c36b14 100644 --- a/website/page/news.php +++ b/website/page/news.php @@ -1,5 +1,31 @@

News

+

6th February 2014

+

+IITC 0.16.4 and IITC Mobile 0.10.4 have just been released. This version is required to fix a bug with showing portal details +due to a change made by Niantic to the intel site protocol. Also, the following changes have been made: +

+
    +
  • Portal markers are now reduced in size when you zoom out, reducing clutter when viewing large areas of the map
  • +
  • Blocked a 3rd party plugin, arc, from running - it had spyware features hidden within it +(details here).
  • +
  • Plugins +
      +
    • add-kml: support for opening files on mobile added
    • +
    • regions: new plugin to draw the scoreboard regions on the map. No support for showing scores - this needs Niantic to add it to the standard intel site first
    • +
    • score-cycle-times: new plugin to show the times of the scoreboard cycles
    • +
    • draw-tools: added basic import/export (via copy+paste), and colour choosing options (click on "DrawTools Opt" in the sidebar)
    • +
    • compute-ap-stats and portal-names: changed code to reduce the performance impact when a large number of portals are shown
    • +
    +
  • +
  • Mobile: +
      +
    • NFC support for sharing map view/selected portal - app permissions updated for this
    • +
    +
  • +
  • .. plus various minor bugfixes and improvements
  • +
+

13th January 2014

A new IITC release, 0.16.2 and IITC Mobile 0.10.2 have been released. These are needed to work with a change to the