Merge branch 'master' into release

This commit is contained in:
Jon Atkins 2014-02-22 21:54:56 +00:00
commit 18b684c95a
57 changed files with 1558 additions and 848 deletions

View File

@ -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(' <a onclick="window.artifact.showArtifactList()" title="Show artifact portal list (jarvis shards and targets)">Artifacts</a>');
$('#toolbox').append(' <a onclick="window.artifact.showArtifactList()" title="Show artifact portal list">Artifacts</a>');
}
@ -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 = '<div><b>Artifact portals</b></div>';
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 += '<i>No artifacts at this time</i>';
}
html += '<hr><div><b>'+types[type]+'</b></div>';
$.each(artifact.artifactTypes, function(type,type2) {
var name = typeNames[type] || ('New artifact type: '+type);
html += '<table class="artifact '+type+'">';
html += '<hr><div><b>'+name+'</b></div>';
html += '<table class="artifact artifact-'+type+'">';
html += '<tr><th>Portal</th><th>Details</th></tr>';
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 += '<tr><td colspan="2"><i>No portals at this time</i></td></tr>';
}
// 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 += '</table>';
});

View File

@ -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) {

113
code/map_data_calc_tools.js Normal file → Executable file
View File

@ -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;
}

View File

@ -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 (ka<kb) return -1;
else if (ka>kb) return 1;
else return 0;
});
for (var i=0; i<c.length; i++) {
var guid = c[i];
@ -513,49 +511,3 @@ window.Render.prototype.getPortalClusterID = function(portal) {
}
// link length
window.Render.prototype.getLinkPixelLength = function(link) {
var z = map.getZoom();
var latLngs = link.getLatLngs();
if (latLngs.length != 2) {
console.warn ('Link had '+latLngs.length+' points - expected 2!');
return undefined;
}
var point0 = map.project(latLngs[0]);
var point1 = map.project(latLngs[1]);
var dx = point0.x - point1.x;
var dy = point0.y - point1.y;
var lengthSquared = (dx*dx)+(dy*dy);
var length = Math.sqrt (lengthSquared);
return length;
}
window.Render.prototype.linkVisible = function(link) {
var length = this.getLinkPixelLength (link);
return length >= 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);
}
}
}

View File

@ -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<requestBuckets; bucket++) {
// create each request by taking tiles interleaved from the request
var tiles = [];
for (var i=bucket; i<lastTileIndex; i+=requestBuckets) {
tiles.push (pendingTiles[i]);
}
for (var bucket=0; bucket < requestBuckets; bucket++) {
var tiles = pendingTiles.splice(0, this.NUM_TILES_PER_REQUEST);
if (tiles.length > 0) {
// console.log('-- new request: '+tiles.length+' tiles');
this.sendTileRequest(tiles);
}
}
}

11
code/munge.js Normal file → Executable file
View File

@ -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;

View File

@ -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', '<span class="'+TEAM_TO_CSS[jarvisArtifact.target]+'">'+(jarvisArtifact.target==TEAM_RES?'Resistance':'Enlightened')+'</span>'];
}
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', '<span class="'+TEAM_TO_CSS[artdata.target]+'">'+(artdata.target==TEAM_RES?'Resistance':'Enlightened')+'</span>'];
}
if (artdata.fragments) {
shards = [details.fragmentName, '#'+artdata.fragments.join(', #')];
}
randDetailsData.push (target, shards);
}
});
randDetails = '<table id="randdetails">' + genFourColumnTable(randDetailsData) + '</table>';

View File

@ -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

View File

@ -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,

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
version="1.1" baseProfile="full"
width="25px" height="41px" viewBox="0 0 25 41">
<path d="M1.36241844765,18.67488124675 A12.5,12.5 0 1,1 23.63758155235,18.67488124675 L12.5,40.5336158073 Z" style="stroke:none; fill: %COLOR%;" />
<path d="M1.80792170975,18.44788599685 A12,12 0 1,1 23.19207829025,18.44788599685 L12.5,39.432271175 Z" style="stroke:#000000; stroke-width:1px; stroke-opacity: 0.15; fill: none;" />
<path d="M2.921679865,17.8803978722 A10.75,10.75 0 1,1 22.078320135,17.8803978722 L12.5,36.6789095943 Z" style="stroke:#ffffff; stroke-width:1.5px; stroke-opacity: 0.35; fill: none;" />
<path d="M19.86121593215,17.25 L12.5,21.5 L5.13878406785,17.25 L5.13878406785,8.75 L12.5,4.5 L19.86121593215,8.75 Z M7.7368602792,10.25 L17.2631397208,10.25 L12.5,18.5 Z M12.5,13 L7.7368602792,10.25 M12.5,13 L17.2631397208,10.25 M12.5,13 L12.5,18.5 M19.86121593215,17.25 L16.39711431705,15.25 M5.13878406785,17.25 L8.60288568295,15.25 M12.5,4.5 L12.5,8.5" style="stroke:#ffffff; stroke-width:1.25px; stroke-opacity: 1; fill: none;" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -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@@

View File

@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cradle.iitc_mobile"
android:installLocation="auto"
android:versionCode="65"
android:versionName="0.10.4">
android:versionCode="66"
android:versionName="0.10.5">
<uses-sdk
android:minSdkVersion="14"
@ -72,6 +72,45 @@
<data android:scheme="geo"/>
</intent-filter>
<!-- Handles external user plugins -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="application/x-javascript" />
<data android:mimeType="text/javascript" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="*"
android:scheme="file"
android:pathPattern=".*\\.user.js"/>
<data
android:host="*"
android:scheme="content"
android:pathPattern=".*\\.user.js"/>
<data
android:host="*"
android:scheme="http"
android:pathPattern=".*\\.user.js"/>
<data
android:host="*"
android:scheme="https"
android:pathPattern=".*\\.user.js"/>
</intent-filter>
<!-- Points to searchable meta data. -->
<meta-data
android:name="android.app.searchable"

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_plugins_add"
android:icon="@drawable/ic_action_new"
android:orderInCategory="10"
android:showAsAction="ifRoom|collapseActionView"
android:title="@string/menu_plugins_add"/>
</menu>

View File

@ -86,9 +86,8 @@
<string name="notice_extplugins">
<![CDATA[Hint:<br><br>
IITC Mobile is able to load external plugins too!<br><br>
• create <b>%1$s</b><br>
• move *.user.js files there<br>
• 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 <b>%1$s</b><br>]]>
</string>
<string name="notice_sharing">
<![CDATA[With <em>Share portal</em> you can:<br>
@ -162,6 +161,7 @@
<string name="menu_clear_cookies">Clear Cookies</string>
<string name="menu_search">Search</string>
<string name="menu_debug">Debug</string>
<string name="menu_plugins_add">Add external plugins</string>
<string name="choose_account_to_login">Choose account to login</string>
<string name="login_failed">Login failed.</string>
<string name="search_hint">Search Locations</string>
@ -177,4 +177,11 @@
<string name="label_base_layer">Base Layer</string>
<string name="label_overlay_layers">Overlay Layers</string>
<string name="install_dialog_top">Install external plugin?</string>
<string name="install_dialog_msg">
<![CDATA[IITCm was requested to install the following javascript file:<br><br>%1$s<br><br>
<b>Be careful:</b> Javascript from external sources may contain harmful code (spyware etc.)!<br><br>
Are you sure you want to proceed?]]>
</string>
</resources>

View File

@ -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('<url encoded filename>', '<base64 encoded content>');
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);
}
}
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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<String> mDialogStack = new Stack<String>();
private String mPermalink = null;
private String mSearchTerm = null;
// Used for custom back stack handling
private final Stack<Pane> mBackStack = new Stack<IITC_NavigationHelper.Pane>();
@ -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() {

View File

@ -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<Header> 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<String, ArrayList<IITC_PluginPreference>>();
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();
}
}

View File

@ -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();

View File

@ -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);
}
/**

View File

@ -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

View File

@ -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<IntentFragment> mTabs;
public class FragmentAdapter extends FragmentPagerAdapter {
private final List<IntentListFragment> mTabs;
public IntentFragmentAdapter(FragmentManager fm) {
public FragmentAdapter(final FragmentManager fm) {
super(fm);
mTabs = new ArrayList<IntentFragment>();
mTabs = new ArrayList<IntentListFragment>();
}
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();
}
}

View File

@ -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<Intent> {
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<Intent> intents) {
Collections.sort(intents, ((ShareActivity) getContext()).getIntentComparator());
setNotifyOnChange(false);
clear();
addAll(intents);
notifyDataSetChanged();
}
}

View File

@ -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<ResolveInfo> {
public class IntentComparator implements Comparator<Intent> {
private static final String INTENT_MAP_FILE = "share_intent_map";
private final ShareActivity mActivity;
private HashMap<Component, Integer> mIntentMap = new HashMap<Component, Integer>();
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<Component, Integer>) 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<ResolveInfo> {
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<ResolveInfo> {
: name);
}
}
private static final String INTENT_MAP_FILE = "share_intent_map";
private ShareActivity mActivity;
private HashMap<Component, Integer> mIntentMap = new HashMap<Component, Integer>();
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<Component, Integer>) 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);
}
}

View File

@ -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<ComponentName> KNOWN_COPY_HANDLERS = new HashSet<ComponentName>();
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<Intent> targets) {
for (final Intent intent : targets) {
for (final ComponentName handler : KNOWN_COPY_HANDLERS) {
if (handler.equals(intent.getComponent())) return true;
}
}
return false;
}
private ArrayList<Intent> resolveTargets(final Intent intent) {
final String packageName = mContext.getPackageName();
final List<ResolveInfo> activityList = mPackageManager.queryIntentActivities(intent, 0);
final ResolveInfo defaultTarget = mPackageManager.resolveActivity(intent, 0);
final ArrayList<Intent> list = new ArrayList<Intent>(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<Intent> 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<Intent> 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<Intent> 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<Intent> 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<Intent> 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;
}
}

View File

@ -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<Intent> 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) {
}
}

View File

@ -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<String, String> {
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<ResolveInfo> {
// 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<CopyHandler> KNOWN_COPY_HANDLERS = new HashSet<CopyHandler>();
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<ComponentName, Intent> mActivities = new HashMap<ComponentName, Intent>();
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<Intent> intentList = new ArrayList<Intent>(1);
intentList.add(intent);
setIntents(intentList);
}
public void setIntents(ArrayList<Intent> intents) {
mAdapter.setNotifyOnChange(false);
mAdapter.clear();
String packageName = getContext().getPackageName();
ArrayList<ResolveInfo> allActivities = new ArrayList<ResolveInfo>();
for (Intent intent : intents) {
List<ResolveInfo> 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();
}
}

View File

@ -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<Intent> intents, int label, int icon) {
IntentFragment fragment = new IntentFragment();
Bundle args = new Bundle();
private void addTab(final ArrayList<Intent> 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<Intent> intents = new ArrayList<Intent>(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<Intent> intents = new ArrayList<Intent>();
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) {
}
}

View File

@ -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('<span style="color: #f88">Import failed </span>');
}
}
}
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('<span style="color: #f88">Import failed </span>');
}
});
}
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 @@
+'<div id="topBar">'
+'<a id="bookmarksMin" class="btn" onclick="window.plugin.bookmarks.switchStatusBkmrksBox(0);return false;" title="Minimize">-</a>'
+'<div class="handle">...</div>'
+'<a id="bookmarksDel" class="btn" onclick="window.plugin.bookmarks.deleteMode();return false;" title="Show/Hide \'X\' button">Show/Hide "X" button</a>'
+'</div>'
+'<div id="bookmarksTypeBar">'
+'<h5 class="bkmrk_maps current" onclick="window.plugin.bookmarks.switchPageBkmrksBox(this, 0);return false">Maps</h5>'
@ -904,19 +931,27 @@
+'<a class="newFolder" onclick="window.plugin.bookmarks.addElement(this, \'folder\');return false;">+ Folder</a>'
+'</div>'
+'</div>'
+'<div style="border-bottom-width:1px;"></div>'
+'</div>';
plugin.bookmarks.htmlDisabledMessage = '<div title="Your browser do not support localStorage">Plugin Bookmarks disabled*.</div>';
plugin.bookmarks.htmlStar = '<a class="bkmrksStar" onclick="window.plugin.bookmarks.switchStarPortal();return false;" title="Save this portal in your bookmarks"><span></span></a>';
plugin.bookmarks.htmlCalldrawBox = '<a onclick="window.plugin.bookmarks.dialogDrawer();return false;" title="Draw lines/triangles between bookmarked portals">Auto draw</a>';
plugin.bookmarks.htmlCallSetBox = '<a onclick="window.plugin.bookmarks.manualOpt();return false;">Bookmarks Opt</a>';
plugin.bookmarks.htmlSetbox = '<div id="bkmrksSetbox">'
+'<a onclick="window.plugin.bookmarks.optCopy();">Copy/Export Bookmarks</a>'
+'<a onclick="window.plugin.bookmarks.optPaste();return false;">Paste/Import Bookmarks</a>'
+'<a onclick="window.plugin.bookmarks.optReset();return false;">Reset Bookmarks</a>'
+'<a onclick="window.plugin.bookmarks.optBox(\'save\');">Save box position (No IITCm)</a>'
+'<a onclick="window.plugin.bookmarks.optBox(\'reset\');">Reset box position (No IITCm)</a>'
+'</div>';
var actions = '';
actions += '<a onclick="window.plugin.bookmarks.optReset();return false;">Reset bookmarks</a>';
actions += '<a onclick="window.plugin.bookmarks.optCopy();return false;">Copy bookmarks</a>';
actions += '<a onclick="window.plugin.bookmarks.optPaste();return false;">Paste bookmarks</a>';
if(plugin.bookmarks.isAndroid()) {
actions += '<a onclick="window.plugin.bookmarks.optImport();return false;">Import bookmarks</a>';
actions += '<a onclick="window.plugin.bookmarks.optExport();return false;">Export bookmarks</a>';
} else {
actions += '<a onclick="window.plugin.bookmarks.optBox(\'save\');return false;">Save box position</a>';
actions += '<a onclick="window.plugin.bookmarks.optBox(\'reset\');return false;">Reset box position</a>';
}
plugin.bookmarks.htmlSetbox = '<div id="bkmrksSetbox">' + actions + '</div>';
}
/***************************************************************************************************************************************************************/

View File

@ -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;
}
}

View File

@ -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;
};

View File

@ -34,8 +34,27 @@ window.plugin.drawTools.loadExternals = function() {
$('head').append('<style>@@INCLUDESTRING:external/spectrum/spectrum.css@@</style>');
}
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?
+ '</div>'
+ '<div class="drawtoolsSetbox">'
+ '<a onclick="window.plugin.drawTools.optCopy();">Copy/Export Drawn Items</a>'
+ '<a onclick="window.plugin.drawTools.optPaste();return false;">Paste/Import Drawn Items</a>'
+ '<a onclick="window.plugin.drawTools.optCopy();">Copy Drawn Items</a>'
+ '<a onclick="window.plugin.drawTools.optPaste();return false;">Paste Drawn Items</a>'
+ (window.requestFile != undefined
? '<a onclick="window.plugin.drawTools.optImport();return false;">Import Drawn Items</a>' : '')
+ ((typeof android !== 'undefined' && android && android.saveFile)
? '<a onclick="window.plugin.drawTools.optExport();return false;">Export Drawn Items</a>' : '')
+ '<a onclick="window.plugin.drawTools.optReset();return false;">Reset Drawn Items</a>'
+ '</div>';
@ -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('<span style="color: #f88">Import failed</span>');
}
}
}
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('<span style="color: #f88">Import failed</span>');
}
});
}
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

View File

@ -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@@

View File

@ -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@@

View File

@ -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*

View File

@ -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*

View File

@ -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*

View File

@ -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*

View File

@ -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*

View File

@ -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*

View File

@ -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*

View File

@ -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*

View File

@ -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*

View File

@ -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*

View File

@ -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*

View File

@ -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*

View File

@ -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 //////////////////////////////////////////////////////////

View File

@ -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*

View File

@ -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;
};

View File

@ -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!
<ul>
<li>Create a folder named "IITC_Mobile" in your home directory.</li>
<li>Inside this folder, create a new folder named "plugins".</li>
<li>Copy all your additional plugins to this folder.</li>
<li>You should see your plugins listed above the official plugins.</li>
<li>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.</li>
<li>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.</li>
</ul>
Note:
<ul>
<li>The filename has to end with *.user.js.</li>
<li>If you don't know where to find your home directory: Enable dev-mode in the settings and follow the hint.</li>
<li>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...
</ul>
END
),

View File

@ -13,39 +13,30 @@ offers many more features. It is available for
<h3>Latest news</h3>
<h4>13th January 2014</h4>
<h4>6th February 2014</h4>
<p>
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.
</p>
<p>
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.
</p>
<p>
<b>Update 14th January 2014</b>: 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.
</p>
<h4>21st December 2013</h4>
<p>
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:
</p>
<ul>
<li>Portals list plugin returns - but less data than before due to the Niantic backend changes</li>
<li>Resonators plugin returns - but only shows the selected portal</li>
<li>Mobile:
<li>Portal markers are now reduced in size when you zoom out, reducing clutter when viewing large areas of the map</li>
<li>Blocked a 3rd party plugin, arc, from running - it had spyware features hidden within it
(<a href="https://plus.google.com/105383756361375410867/posts/4b2EjP3Du42">details here</a>).</li>
<li>Plugins
<ul>
<li>Some plugins moved to panes from the left-swipe menu: portals list, portal counts</li>
<li>Immersive fullscreen mode on Android 4.4 KitKat</li>
<li>Sort apps in share activity - most used at the top</li>
<li>Fix links sometimes being badly drawn on mobile</li>
<li>add-kml: support for opening files on mobile added</li>
<li>regions: new plugin to draw the scoreboard regions on the map. <i>No support for showing scores - this needs Niantic to add it to the standard intel site first</i></li>
<li>score-cycle-times: new plugin to show the times of the scoreboard cycles</li>
<li>draw-tools: added basic import/export (via copy+paste), and colour choosing options (click on "DrawTools Opt" in the sidebar)</li>
<li>compute-ap-stats and portal-names: changed code to reduce the performance impact when a large number of portals are shown</li>
</ul>
</li>
<li>.. and, as always, other various bug fixes, improvements, etc</li>
<li>Mobile:
<ul>
<li>NFC support for sharing map view/selected portal - app permissions updated for this</li>
</ul>
</li>
<li>.. plus various minor bugfixes and improvements</li>
</ul>
<a class="btn btn-default btn-sm" href="?page=news">Older news</a>

View File

@ -1,5 +1,31 @@
<h2>News</h2>
<h4>6th February 2014</h4>
<p>
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:
</p>
<ul>
<li>Portal markers are now reduced in size when you zoom out, reducing clutter when viewing large areas of the map</li>
<li>Blocked a 3rd party plugin, arc, from running - it had spyware features hidden within it
(<a href="https://plus.google.com/105383756361375410867/posts/4b2EjP3Du42">details here</a>).</li>
<li>Plugins
<ul>
<li>add-kml: support for opening files on mobile added</li>
<li>regions: new plugin to draw the scoreboard regions on the map. <i>No support for showing scores - this needs Niantic to add it to the standard intel site first</i></li>
<li>score-cycle-times: new plugin to show the times of the scoreboard cycles</li>
<li>draw-tools: added basic import/export (via copy+paste), and colour choosing options (click on "DrawTools Opt" in the sidebar)</li>
<li>compute-ap-stats and portal-names: changed code to reduce the performance impact when a large number of portals are shown</li>
</ul>
</li>
<li>Mobile:
<ul>
<li>NFC support for sharing map view/selected portal - app permissions updated for this</li>
</ul>
</li>
<li>.. plus various minor bugfixes and improvements</li>
</ul>
<h4>13th January 2014</h4>
<p>
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