From a0534c7232fe979d50109593fb0dd4fa9f9fedda Mon Sep 17 00:00:00 2001 From: simon Date: Mon, 16 Nov 2020 19:16:06 +0200 Subject: [PATCH 01/16] move started --- .../java/net/osmand/osm/io/NetworkUtils.java | 49 ++++++++ OsmAnd/build.gradle | 2 + .../builders/cards/ImageCard.java | 1 + .../osmand/plus/osmedit/opr/OpenDBAPI.java | 107 ++++++++++++++++++ 4 files changed, 159 insertions(+) create mode 100644 OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java diff --git a/OsmAnd-java/src/main/java/net/osmand/osm/io/NetworkUtils.java b/OsmAnd-java/src/main/java/net/osmand/osm/io/NetworkUtils.java index 28a1ec3fb9..f95e3b4b45 100644 --- a/OsmAnd-java/src/main/java/net/osmand/osm/io/NetworkUtils.java +++ b/OsmAnd-java/src/main/java/net/osmand/osm/io/NetworkUtils.java @@ -56,6 +56,55 @@ public class NetworkUtils { return e.getMessage(); } } + + public static String sendPostDataRequest(String urlText, InputStream data) { + try { + log.info("POST : " + urlText); + HttpURLConnection conn = getHttpURLConnection(urlText); + conn.setDoInput(true); + conn.setDoOutput(false); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Accept", "*/*"); + conn.setRequestProperty("User-Agent", "OsmAnd"); //$NON-NLS-1$ //$NON-NLS-2$ + conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); + OutputStream ous = conn.getOutputStream(); + ous.write(("--" + BOUNDARY + "\r\n").getBytes()); + ous.write(("content-disposition: form-data; name=\"" + "file" + "\"; filename=\"" + "image1" + "\"\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$ + ous.write(("Content-Type: application/octet-stream\r\n\r\n").getBytes()); //$NON-NLS-1$ + Algorithms.streamCopy(data, ous); + ous.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$ + ous.flush(); + log.info("Response code and message : " + conn.getResponseCode() + " " + conn.getResponseMessage()); + if (conn.getResponseCode() != 200) { + return null; + } + StringBuilder responseBody = new StringBuilder(); + InputStream is = conn.getInputStream(); + responseBody.setLength(0); + if (is != null) { + BufferedReader in = new BufferedReader(new InputStreamReader(is, "UTF-8")); //$NON-NLS-1$ + String s; + boolean first = true; + while ((s = in.readLine()) != null) { + if (first) { + first = false; + } else { + responseBody.append("\n"); //$NON-NLS-1$ + } + responseBody.append(s); + } + is.close(); + } + Algorithms.closeStream(is); + Algorithms.closeStream(data); + Algorithms.closeStream(ous); + return responseBody.toString(); + } catch (IOException e) { + log.error(e.getMessage(), e); + return e.getMessage(); + } + } + private static final String BOUNDARY = "CowMooCowMooCowCowCow"; //$NON-NLS-1$ public static String uploadFile(String urlText, File fileToUpload, String userNamePassword, OsmOAuthAuthorizationClient client, diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index c52dac72ad..91ec5744e0 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -526,4 +526,6 @@ dependencies { implementation 'com.jaredrummler:colorpicker:1.1.0' freehuaweiImplementation 'com.huawei.hms:iap:5.0.2.300' + + implementation "org.bouncycastle:bcpkix-jdk15on:1.56" } diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/ImageCard.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/ImageCard.java index 26c81f03a7..d7b1d69895 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/ImageCard.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/ImageCard.java @@ -509,6 +509,7 @@ public abstract class ImageCard extends AbstractCard { "Requesting location images...", false, false); try { if (!Algorithms.isEmpty(response)) { + //TODO extract place id JSONArray obj = new JSONObject(response).getJSONArray("objects"); JSONArray images = ((JSONObject) ((JSONObject) obj.get(0)).get("images")).getJSONArray("outdoor"); if (images.length() > 0) { diff --git a/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java b/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java new file mode 100644 index 0000000000..550906a508 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java @@ -0,0 +1,107 @@ +package net.osmand.plus.osmedit.opr; + +import android.net.TrafficStats; +import android.os.Build; +import com.google.gson.GsonBuilder; +import net.osmand.PlatformUtil; +import net.osmand.plus.BuildConfig; +import org.apache.commons.logging.Log; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.openplacereviews.opendb.SecUtils; +import org.openplacereviews.opendb.ops.OpOperation; +import org.openplacereviews.opendb.util.JsonFormatter; +import org.openplacereviews.opendb.util.exception.FailedVerificationException; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.security.KeyPair; +import java.security.Security; +import java.util.*; + +import static org.openplacereviews.opendb.SecUtils.*; + + +public class OpenDBAPI { + private static final Log log = PlatformUtil.getLog(SecUtils.class); + + private static final int THREAD_ID = 11200; + + public int uploadImage(String[] placeId, String privateKey, String username, String image) throws FailedVerificationException { + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + Security.removeProvider("BC"); + Security.addProvider(new BouncyCastleProvider()); + } + KeyPair kp = SecUtils.getKeyPair(ALGO_EC, privateKey, null); + String signed = username;// + ":opr-web"; + + JsonFormatter formatter = new JsonFormatter(); + OPRImage OPRImage = new GsonBuilder().create().fromJson(image, OPRImage.class); + OpOperation opOperation = new OpOperation(); + opOperation.setType("opr.place"); + List edits = new ArrayList<>(); + Map edit = new TreeMap<>(); + List imageResponseList = new ArrayList<>(); + Map imageMap = new TreeMap<>(); + imageMap.put("cid", OPRImage.cid); + imageMap.put("hash", OPRImage.hash); + imageMap.put("extension", OPRImage.extension); + imageMap.put("type", OPRImage.type); + imageResponseList.add(imageMap); + List ids = new ArrayList<>(Arrays.asList(placeId)); + Map change = new TreeMap<>(); + Map images = new TreeMap<>(); + Map outdoor = new TreeMap<>(); + outdoor.put("outdoor", imageResponseList); + images.put("append", outdoor); + change.put("version", "increment"); + change.put("images", images); + edit.put("id", ids); + edit.put("change", change); + edit.put("current", new Object()); + edits.add(edit); + opOperation.putObjectValue(OpOperation.F_EDIT, edits); + opOperation.setSignedBy(signed); + String hash = JSON_MSG_TYPE + ":" + + SecUtils.calculateHashWithAlgo(SecUtils.HASH_SHA256, null, + formatter.opToJsonNoHash(opOperation)); + byte[] hashBytes = SecUtils.getHashBytes(hash); + String signature = signMessageWithKeyBase64(kp, hashBytes, SecUtils.SIG_ALGO_SHA1_EC, null); + opOperation.addOrSetStringValue("hash", hash); + opOperation.addOrSetStringValue("signature", signature); + TrafficStats.setThreadStatsTag(THREAD_ID); + String url = BuildConfig.OPR_BASE_URL + "api/auth/process-operation?addToQueue=true&dontSignByServer=false"; + String json = formatter.opToJson(opOperation); + System.out.println("JSON: " + json); + HttpURLConnection connection; + try { + connection = (HttpURLConnection) new URL(url).openConnection(); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setConnectTimeout(10000); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + try { + DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); + wr.write(json.getBytes()); + } catch (Exception e) { + e.printStackTrace(); + } + int rc = connection.getResponseCode(); + if (rc != 200) { + log.error("ERROR HAPPENED"); + BufferedReader br = new BufferedReader(new InputStreamReader(connection.getErrorStream())); + String strCurrentLine; + while ((strCurrentLine = br.readLine()) != null) { + log.error(strCurrentLine); + } + } + return rc; + } catch (IOException e) { + log.error(e); + } + return -1; + } +} \ No newline at end of file From bc6bcbcc59cae192dd986011b5e9005380672a8c Mon Sep 17 00:00:00 2001 From: simon Date: Mon, 16 Nov 2020 19:24:06 +0200 Subject: [PATCH 02/16] opr files added --- .../net/osmand/plus/osmedit/opr/OPRImage.java | 8 + .../org/openplacereviews/opendb/SecUtils.java | 217 +++++++ .../openplacereviews/opendb/ops/OpObject.java | 537 ++++++++++++++++++ .../opendb/ops/OpOperation.java | 289 ++++++++++ .../opendb/util/JsonFormatter.java | 157 +++++ .../opendb/util/JsonObjectUtils.java | 276 +++++++++ .../FailedVerificationException.java | 17 + 7 files changed, 1501 insertions(+) create mode 100644 OsmAnd/src/net/osmand/plus/osmedit/opr/OPRImage.java create mode 100644 OsmAnd/src/org/openplacereviews/opendb/SecUtils.java create mode 100644 OsmAnd/src/org/openplacereviews/opendb/ops/OpObject.java create mode 100644 OsmAnd/src/org/openplacereviews/opendb/ops/OpOperation.java create mode 100644 OsmAnd/src/org/openplacereviews/opendb/util/JsonFormatter.java create mode 100644 OsmAnd/src/org/openplacereviews/opendb/util/JsonObjectUtils.java create mode 100644 OsmAnd/src/org/openplacereviews/opendb/util/exception/FailedVerificationException.java diff --git a/OsmAnd/src/net/osmand/plus/osmedit/opr/OPRImage.java b/OsmAnd/src/net/osmand/plus/osmedit/opr/OPRImage.java new file mode 100644 index 0000000000..dfffbf9973 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/opr/OPRImage.java @@ -0,0 +1,8 @@ +package net.osmand.plus.osmedit.opr; + +public class OPRImage { + public String type; + public String hash; + public String cid; + public String extension; +} \ No newline at end of file diff --git a/OsmAnd/src/org/openplacereviews/opendb/SecUtils.java b/OsmAnd/src/org/openplacereviews/opendb/SecUtils.java new file mode 100644 index 0000000000..183a93b9c0 --- /dev/null +++ b/OsmAnd/src/org/openplacereviews/opendb/SecUtils.java @@ -0,0 +1,217 @@ +//Revision d1a1f6e81d0716a47cbddf5754ee77fa5fc6d1d8 +package org.openplacereviews.opendb; + + +import android.util.Base64; +import org.openplacereviews.opendb.util.exception.FailedVerificationException; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.digest.DigestUtils; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.*; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.concurrent.ThreadLocalRandom; + +//This class is a copy of SecUtils class from OpenDB project with changes for android platform +public class SecUtils { + public static final String SIG_ALGO_SHA1_EC = "SHA1withECDSA"; + public static final String SIG_ALGO_NONE_EC = "NonewithECDSA"; + + public static final String SIG_ALGO_ECDSA = "ECDSA"; + public static final String ALGO_EC = "EC"; + + public static final String DECODE_BASE64 = "base64"; + public static final String HASH_SHA256 = "sha256"; + public static final String HASH_SHA1 = "sha1"; + + public static final String JSON_MSG_TYPE = "json"; + public static final String KEY_BASE64 = DECODE_BASE64; + + + public static EncodedKeySpec decodeKey(String key) { + if (key.startsWith(KEY_BASE64 + ":")) { + key = key.substring(KEY_BASE64.length() + 1); + int s = key.indexOf(':'); + if (s == -1) { + throw new IllegalArgumentException(String.format("Key doesn't contain algorithm of hashing to verify")); + } + //should use android.util.Base64 for android platform instead of Base64.getDecoder() + return getKeySpecByFormat(key.substring(0, s), + android.util.Base64.decode(key.substring(s + 1), Base64.DEFAULT)); + } + throw new IllegalArgumentException(String.format("Key doesn't contain algorithm of hashing to verify")); + } + + public static String encodeKey(String algo, PublicKey pk) { + if (algo.equals(KEY_BASE64)) { + return SecUtils.KEY_BASE64 + ":" + pk.getFormat() + ":" + encodeBase64(pk.getEncoded()); + } + throw new UnsupportedOperationException("Algorithm is not supported: " + algo); + } + + public static String encodeKey(String algo, PrivateKey pk) { + if (algo.equals(KEY_BASE64)) { + return SecUtils.KEY_BASE64 + ":" + pk.getFormat() + ":" + encodeBase64(pk.getEncoded()); + } + throw new UnsupportedOperationException("Algorithm is not supported: " + algo); + } + + public static EncodedKeySpec getKeySpecByFormat(String format, byte[] data) { + switch (format) { + case "PKCS#8": + return new PKCS8EncodedKeySpec(data); + case "X.509": + return new X509EncodedKeySpec(data); + } + throw new IllegalArgumentException(format); + } + + public static String encodeBase64(byte[] data) { + //should use android.util.Base64 for android platform instead of Base64.getDecoder() + return new String(android.util.Base64.decode(data, android.util.Base64.DEFAULT)); + } + + public static boolean validateKeyPair(String algo, PrivateKey privateKey, PublicKey publicKey) + throws FailedVerificationException { + if (!algo.equals(ALGO_EC)) { + throw new FailedVerificationException("Algorithm is not supported: " + algo); + } + // create a challenge + byte[] challenge = new byte[512]; + ThreadLocalRandom.current().nextBytes(challenge); + + try { + // sign using the private key + Signature sig = Signature.getInstance(SIG_ALGO_SHA1_EC); + sig.initSign(privateKey); + sig.update(challenge); + byte[] signature = sig.sign(); + + // verify signature using the public key + sig.initVerify(publicKey); + sig.update(challenge); + + boolean keyPairMatches = sig.verify(signature); + return keyPairMatches; + } catch (InvalidKeyException e) { + throw new FailedVerificationException(e); + } catch (NoSuchAlgorithmException e) { + throw new FailedVerificationException(e); + } catch (SignatureException e) { + throw new FailedVerificationException(e); + } + } + + public static KeyPair getKeyPair(String algo, String prKey, String pbKey) throws FailedVerificationException { + try { + KeyFactory keyFactory = KeyFactory.getInstance(algo); + PublicKey pb = null; + PrivateKey pr = null; + if (pbKey != null) { + pb = keyFactory.generatePublic(decodeKey(pbKey)); + } + if (prKey != null) { + pr = keyFactory.generatePrivate(decodeKey(prKey)); + } + return new KeyPair(pb, pr); + } catch (NoSuchAlgorithmException e) { + throw new FailedVerificationException(e); + } catch (InvalidKeySpecException e) { + throw new FailedVerificationException(e); + } + } + + public static String signMessageWithKeyBase64(KeyPair keyPair, byte[] msg, String signAlgo, ByteArrayOutputStream out) { + byte[] sigBytes; + try { + sigBytes = signMessageWithKey(keyPair, msg, signAlgo); + } catch (FailedVerificationException e) { + throw new IllegalStateException("Cannot get bytes"); + } + if (out != null) { + try { + out.write(sigBytes); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + String signature = Base64.encodeToString(sigBytes, Base64.DEFAULT).replace("\n", ""); + return signAlgo + ":" + DECODE_BASE64 + ":" + signature; + } + + public static byte[] signMessageWithKey(KeyPair keyPair, byte[] msg, String signAlgo) throws FailedVerificationException { + try { + //use BouncyCastle on android platform in order to achieve consistency between platforms + Signature sig = Signature.getInstance(getInternalSigAlgo(signAlgo), "BC"); + sig.initSign(keyPair.getPrivate()); + sig.update(msg); + byte[] signatureBytes = sig.sign(); + return signatureBytes; + } catch (NoSuchAlgorithmException e) { + throw new FailedVerificationException(e); + } catch (InvalidKeyException e) { + throw new FailedVerificationException(e); + } catch (SignatureException e) { + throw new FailedVerificationException(e); + } catch (NoSuchProviderException e) { + throw new RuntimeException(e); + } + } + + private static String getInternalSigAlgo(String sigAlgo) { + return sigAlgo.equals(SIG_ALGO_ECDSA) ? SIG_ALGO_NONE_EC : sigAlgo; + } + + public static byte[] calculateHash(String algo, byte[] b1, byte[] b2) { + byte[] m = mergeTwoArrays(b1, b2); + if (algo.equals(HASH_SHA256)) { + return DigestUtils.sha256(m); + } else if (algo.equals(HASH_SHA1)) { + return DigestUtils.sha1(m); + } + throw new UnsupportedOperationException(); + } + + public static byte[] mergeTwoArrays(byte[] b1, byte[] b2) { + byte[] m = b1 == null ? b2 : b1; + if (b2 != null && b1 != null) { + m = new byte[b1.length + b2.length]; + System.arraycopy(b1, 0, m, 0, b1.length); + System.arraycopy(b2, 0, m, b1.length, b2.length); + } + return m; + } + + public static String calculateHashWithAlgo(String algo, String salt, String msg) { + try { + //use Hex.encodeHex for android platform instead of Hex.encodeHexString + char[] hex = Hex.encodeHex(calculateHash(algo, salt == null ? null : salt.getBytes("UTF-8"), + msg == null ? null : msg.getBytes("UTF-8"))); + + return algo + ":" + new String(hex); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } + } + + public static byte[] getHashBytes(String msg) { + if (msg == null || msg.length() == 0) { + // special case for empty hash + return new byte[0]; + } + int i = msg.lastIndexOf(':'); + String s = i >= 0 ? msg.substring(i + 1) : msg; + try { + return Hex.decodeHex(s.toCharArray()); + } catch (DecoderException e) { + throw new IllegalArgumentException(e); + } + } + +} diff --git a/OsmAnd/src/org/openplacereviews/opendb/ops/OpObject.java b/OsmAnd/src/org/openplacereviews/opendb/ops/OpObject.java new file mode 100644 index 0000000000..2cc8acc839 --- /dev/null +++ b/OsmAnd/src/org/openplacereviews/opendb/ops/OpObject.java @@ -0,0 +1,537 @@ +//Revision d1a1f6e81d0716a47cbddf5754ee77fa5fc6d1d8 +package org.openplacereviews.opendb.ops; + +import com.google.gson.*; +import org.openplacereviews.opendb.util.JsonObjectUtils; +// OSMAND ANDROID CHANGE BEGIN: +// removed unused imports +// OSMAND ANDROID CHANGE END + +import java.lang.reflect.Type; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +public class OpObject { + + public static final String F_NAME = "name"; + public static final String F_ID = "id"; + public static final String F_COMMENT = "comment"; + public static final String TYPE_OP = "sys.op"; + public static final String TYPE_BLOCK = "sys.block"; + public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; + // transient info about validation timing etc + public static final String F_EVAL = "eval"; + public static final String F_VALIDATION = "validation"; + public static final String F_TIMESTAMP_ADDED = "timestamp"; + public static final String F_PARENT_TYPE = "parentType"; + public static final String F_PARENT_HASH = "parentHash"; + public static final String F_CHANGE = "change"; + public static final String F_CURRENT = "current"; + // voting + public static final String F_OP = "op"; + public static final String F_STATE = "state"; + public static final String F_OPEN = "open"; + public static final String F_FINAL = "final"; + public static final String F_VOTE = "vote"; + public static final String F_VOTES = "votes"; + public static final String F_SUBMITTED_OP_HASH = "submittedOpHash"; + public static final String F_USER = "user"; + + public static final OpObject NULL = new OpObject(true); + + public static SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); + static { + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + protected Map fields = new TreeMap<>(); + protected transient Map cacheFields; + protected boolean isImmutable; + + protected transient String parentType; + protected transient String parentHash; + protected transient boolean deleted; + + + public OpObject() {} + + public OpObject(boolean deleted) { + this.deleted = deleted; + } + + public OpObject(OpObject cp) { + this(cp, false); + } + + public OpObject(OpObject cp, boolean copyCacheFields) { + createOpObjectCopy(cp, copyCacheFields); + } + + @SuppressWarnings("unchecked") + private OpObject createOpObjectCopy(OpObject opObject, boolean copyCacheFields) { + this.parentType = opObject.parentType; + this.parentHash = opObject.parentHash; + this.deleted = opObject.deleted; + this.fields = (Map) copyingObjects(opObject.fields, copyCacheFields); + if (opObject.cacheFields != null && copyCacheFields) { + this.cacheFields = (Map) copyingObjects(opObject.cacheFields, copyCacheFields); + } + this.isImmutable = false; + + return this; + } + + public boolean isDeleted() { + return deleted; + } + + @SuppressWarnings("unchecked") + private Object copyingObjects(Object object, boolean copyCacheFields) { + if (object instanceof Number) { + return (Number) object; + } else if (object instanceof String) { + return (String) object; + } else if (object instanceof Boolean) { + return (Boolean) object; + } else if (object instanceof List) { + List copy = new ArrayList<>(); + List list = (List) object; + for (Object o : list) { + copy.add(copyingObjects(o, copyCacheFields)); + } + return copy; + } else if (object instanceof Map) { + Map copy = new LinkedHashMap<>(); + Map map = (Map) object; + for (Object o : map.keySet()) { + copy.put(o, copyingObjects(map.get(o), copyCacheFields)); + } + return copy; + } + // OSMAND ANDROID CHANGE BEGIN: + // removed instanceOf OpExprEvaluator + // OSMAND ANDROID CHANGE END: + else if (object instanceof OpObject) { + return new OpObject((OpObject) object); + } else { + throw new UnsupportedOperationException("Type of object is not supported"); + } + } + + public void setParentOp(OpOperation op) { + setParentOp(op.type, op.getRawHash()); + } + + public void setParentOp(String parentType, String parentHash) { + this.parentType = parentType; + this.parentHash = parentHash; + } + + public String getParentHash() { + return parentHash; + } + + public String getParentType() { + return parentType; + } + + public List getId() { + return getStringList(F_ID); + } + + public void setId(String id) { + addOrSetStringValue(F_ID, id); + } + + public boolean isImmutable() { + return isImmutable; + } + + public OpObject makeImmutable() { + isImmutable = true; + return this; + } + + public Object getFieldByExpr(String field) { + if (field.contains(".") || field.contains("[") || field.contains("]")) { + return JsonObjectUtils.getField(this.fields, generateFieldSequence(field)); + } + + return fields.get(field); + } + + + /** + * generateFieldSequence("a") - [a] + * generateFieldSequence("a.b") - [a, b] + * generateFieldSequence("a.b.c.de") - [a, b, c, de] + * generateFieldSequence("a.bwerq.c") - [a, bwerq, c] + * generateFieldSequence("a.bwerq...c") - [a, bwerq, c] + * generateFieldSequence("a.bwereq..c..") - [a, bwerq, c] + * generateFieldSequence("a.{b}") - [a, b] + * generateFieldSequence("a.{b.c.de}") - [a, b.c.de] + * generateFieldSequence("a.{b.c.de}") - [a, b.c.de] + * generateFieldSequence("a.{b{}}") - [a, b{}] + * generateFieldSequence("a.{b{}d.q}") - [a, b{}d.q] + */ + private static List generateFieldSequence(String field) { + int STATE_OPEN_BRACE = 1; + int STATE_OPEN = 0; + int state = STATE_OPEN; + int start = 0; + List l = new ArrayList(); + for(int i = 0; i < field.length(); i++) { + boolean split = false; + if (i == field.length() - 1) { + if (state == STATE_OPEN_BRACE) { + if(field.charAt(i) == '}') { + split = true; + } else { + throw new IllegalArgumentException("Illegal field expression: " + field); + } + } else { + if(field.charAt(i) != '.') { + i++; + } + split = true; + } + } else { + if (field.charAt(i) == '.' && state == STATE_OPEN) { + split = true; + } else if (field.charAt(i) == '}' && field.charAt(i + 1) == '.' && state == STATE_OPEN_BRACE) { + split = true; + } else if (field.charAt(i) == '{' && state == STATE_OPEN) { + if(start != i) { + throw new IllegalArgumentException("Illegal field expression (wrap {} is necessary): " + field); + } + state = STATE_OPEN_BRACE; + start = i + 1; + } + } + if(split) { + if (i != start) { + l.add(field.substring(start, i)); + } + start = i + 1; + state = STATE_OPEN; + } + } + return l; + } + + public void setFieldByExpr(String field, Object object) { + if (field.contains(".") || field.contains("[") || field.contains("]")) { + List fieldSequence = generateFieldSequence(field); + if (object == null) { + JsonObjectUtils.deleteField(this.fields, fieldSequence); + } else { + JsonObjectUtils.setField(this.fields, fieldSequence, object); + } + } else if (object == null) { + fields.remove(field); + } else { + fields.put(field, object); + } + } + + + public Object getCacheObject(String f) { + if(cacheFields == null) { + return null; + } + return cacheFields.get(f); + } + + public void putCacheObject(String f, Object o) { + if (isImmutable()) { + if (cacheFields == null) { + cacheFields = new ConcurrentHashMap(); + } + cacheFields.put(f, o); + } + } + + public void setId(String id, String id2) { + List list = new ArrayList(); + list.add(id); + list.add(id2); + putObjectValue(F_ID, list); + } + + public String getName() { + return getStringValue(F_NAME); + } + + public String getComment() { + return getStringValue(F_COMMENT); + } + + public Map getRawOtherFields() { + return fields; + } + + @SuppressWarnings("unchecked") + public Map getStringMap(String field) { + return (Map) fields.get(field); + } + + @SuppressWarnings("unchecked") + public Map> getMapStringList(String field) { + return (Map>) fields.get(field); + } + + @SuppressWarnings("unchecked") + public List> getListStringMap(String field) { + return (List>) fields.get(field); + } + + @SuppressWarnings("unchecked") + public List> getListStringObjMap(String field) { + return (List>) fields.get(field); + } + + @SuppressWarnings("unchecked") + public Map getStringObjMap(String field) { + return (Map) fields.get(field); + } + + @SuppressWarnings("unchecked") + public T getField(T def, String... fields) { + Map p = this.fields; + for(int i = 0; i < fields.length - 1 ; i++) { + p = (Map) p.get(fields[i]); + if(p == null) { + return def; + } + } + T res = (T) p.get(fields[fields.length - 1]); + if(res == null) { + return def; + } + return res; + } + + @SuppressWarnings("unchecked") + public Map, Object> getStringListObjMap(String field) { + return (Map, Object>) fields.get(field); + } + + + public long getDate(String field) { + String date = getStringValue(field); + // OSMAND ANDROID CHANGE BEGIN: + // removed check OUtils.isEmpty(date) + // OSMAND ANDROID CHANGE END + try { + return dateFormat.parse(date).getTime(); + } catch (ParseException e) { + return 0; + } + } + + + public void setDate(String field, long time) { + putStringValue(field, dateFormat.format(new Date(time))); + } + + public Number getNumberValue(String field) { + return (Number) fields.get(field); + } + + public int getIntValue(String key, int def) { + Number o = getNumberValue(key); + return o == null ? def : o.intValue(); + } + + public long getLongValue(String key, long def) { + Number o = getNumberValue(key); + return o == null ? def : o.longValue(); + } + + public String getStringValue(String field) { + Object o = fields.get(field); + if (o instanceof String || o == null) { + return (String) o; + } + return o.toString(); + } + + @SuppressWarnings("unchecked") + public List getStringList(String field) { + // cast to list if it is single value + Object o = fields.get(field); + if(o == null || o.toString().isEmpty()) { + return Collections.emptyList(); + } + if(o instanceof String) { + return Collections.singletonList(o.toString()); + } + return (List) o; + } + + public Object getObjectValue(String field) { + return fields.get(field); + } + + public void putStringValue(String key, String value) { + checkNotImmutable(); + if(value == null) { + fields.remove(key); + } else { + fields.put(key, value); + } + } + + /** + * Operates as a single value if cardinality is less than 1 + * or as a list of values if it stores > 1 value + * @param key + * @param value + */ + @SuppressWarnings("unchecked") + public void addOrSetStringValue(String key, String value) { + checkNotImmutable(); + Object o = fields.get(key); + if(o == null) { + fields.put(key, value); + } else if(o instanceof List) { + ((List) o).add(value); + } else { + List list = new ArrayList(); + list.add(o.toString()); + list.add(value); + fields.put(key, list); + } + } + + @SuppressWarnings("unchecked") + public Map getChangedEditFields() { + return (Map) fields.get(F_CHANGE); + } + + @SuppressWarnings("unchecked") + public Map getCurrentEditFields() { + return (Map) fields.get(F_CURRENT); + } + + public void putObjectValue(String key, Object value) { + checkNotImmutable(); + if(value == null) { + fields.remove(key); + } else { + fields.put(key, value); + } + } + + public void checkNotImmutable() { + if(isImmutable) { + throw new IllegalStateException("Object is immutable"); + } + + } + + public void checkImmutable() { + if(!isImmutable) { + throw new IllegalStateException("Object is mutable"); + } + } + + public Object remove(String key) { + checkNotImmutable(); + return fields.remove(key); + } + + public Map getMixedFieldsAndCacheMap() { + TreeMap mp = new TreeMap<>(fields); + if(cacheFields != null || parentType != null || parentHash != null) { + TreeMap eval = new TreeMap(); + + if(parentType != null) { + eval.put(F_PARENT_TYPE, parentType); + } + if(parentHash != null) { + eval.put(F_PARENT_HASH, parentHash); + } + if (cacheFields != null) { + Iterator> it = cacheFields.entrySet().iterator(); + while (it.hasNext()) { + Entry e = it.next(); + Object v = e.getValue(); + if (v instanceof Map || v instanceof String || v instanceof Number) { + eval.put(e.getKey(), v); + } + } + } + if(eval.size() > 0) { + mp.put(F_EVAL, eval); + } + } + return mp; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((fields == null) ? 0 : fields.hashCode()); + return result; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + fields + "]"; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + OpObject other = (OpObject) obj; + if (fields == null) { + if (other.fields != null) + return false; + } else if (!fields.equals(other.fields)) + return false; + return true; + } + + public static class OpObjectAdapter implements JsonDeserializer, + JsonSerializer { + + private boolean fullOutput; + + public OpObjectAdapter(boolean fullOutput) { + this.fullOutput = fullOutput; + } + + @Override + public OpObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + OpObject bn = new OpObject(); + bn.fields = context.deserialize(json, TreeMap.class); + // remove cache + bn.fields.remove(F_EVAL); + return bn; + } + + @Override + public JsonElement serialize(OpObject src, Type typeOfSrc, JsonSerializationContext context) { + return context.serialize(fullOutput ? src.getMixedFieldsAndCacheMap() : src.fields); + } + + + } + + + + + + +} diff --git a/OsmAnd/src/org/openplacereviews/opendb/ops/OpOperation.java b/OsmAnd/src/org/openplacereviews/opendb/ops/OpOperation.java new file mode 100644 index 0000000000..27f2b5fb91 --- /dev/null +++ b/OsmAnd/src/org/openplacereviews/opendb/ops/OpOperation.java @@ -0,0 +1,289 @@ +//Revision d1a1f6e81d0716a47cbddf5754ee77fa5fc6d1d8 +package org.openplacereviews.opendb.ops; + +import com.google.gson.*; + +import java.lang.reflect.Type; +import java.util.*; +// OSMAND ANDROID CHANGE BEGIN: +// removed dependency OUtils +// OSMAND ANDROID CHANGE END: +public class OpOperation extends OpObject { + + public static final String F_TYPE = "type"; + public static final String F_SIGNED_BY = "signed_by"; + public static final String F_HASH = "hash"; + + public static final String F_SIGNATURE = "signature"; + + public static final String F_REF = "ref"; + public static final String F_CREATE = "create"; + public static final String F_DELETE = "delete"; + public static final String F_EDIT = "edit"; + + public static final String F_NAME = "name"; + public static final String F_COMMENT = "comment"; + + private List createdObjects = new LinkedList(); + private List editedObjects = new LinkedList(); + protected String type; + + public OpOperation() { + } + + public OpOperation(OpOperation cp, boolean copyCacheFields) { + super(cp, copyCacheFields); + this.type = cp.type; + for(OpObject o : cp.createdObjects) { + this.createdObjects.add(new OpObject(o, copyCacheFields)); + } + for(OpObject o : cp.editedObjects) { + this.editedObjects.add(new OpObject(o, copyCacheFields)); + } + } + + public String getOperationType() { + return type; + } + + public void setType(String name) { + checkNotImmutable(); + type = name; + updateObjectsRef(); + } + + protected void updateObjectsRef() { + for(OpObject o : createdObjects) { + o.setParentOp(this); + } + for(OpObject o : editedObjects) { + o.setParentOp(this); + } + } + + public String getType() { + return type; + } + + public OpOperation makeImmutable() { + isImmutable = true; + for(OpObject o : createdObjects) { + o.makeImmutable(); + } + for(OpObject o : editedObjects) { + o.makeImmutable(); + } + return this; + } + + public void setSignedBy(String value) { + putStringValue(F_SIGNED_BY, value); + } + + public void addOtherSignedBy(String value) { + super.addOrSetStringValue(F_SIGNED_BY, value); + } + + public List getSignedBy() { + return getStringList(F_SIGNED_BY); + } + + public String getHash() { + return getStringValue(F_HASH); + } + + public String getRawHash() { + String rw = getStringValue(F_HASH); + // drop algorithm and everything else + if(rw != null) { + rw = rw.substring(rw.lastIndexOf(':') + 1); + } + return rw; + } + + public List getSignatureList() { + return getStringList(F_SIGNATURE); + } + + public Map> getRef() { + return getMapStringList(F_REF); + } + + @SuppressWarnings("unchecked") + public List> getDeleted() { + List> l = (List>) fields.get(F_DELETE); + if(l == null) { + return Collections.emptyList(); + } + return l; + } + + public boolean hasDeleted() { + return getDeleted().size() > 0; + } + + public void addDeleted(List id) { + if(!fields.containsKey(F_DELETE)) { + ArrayList> lst = new ArrayList<>(); + lst.add(id); + putObjectValue(F_DELETE, lst); + } else { + getDeleted().add(id); + } + } + + public List getCreated() { + return createdObjects; + } + + public void addCreated(OpObject o) { + checkNotImmutable(); + createdObjects.add(o); + if(type != null) { + o.setParentOp(this); + } + } + + public boolean hasCreated() { + return createdObjects.size() > 0; + } + + public void addEdited(OpObject o) { + checkNotImmutable(); + editedObjects.add(o); + if (type != null) { + o.setParentOp(this); + } + } + + public List getEdited() { + return editedObjects; + } + + public boolean hasEdited() { + return editedObjects.size() > 0; + } + + + public String getName() { + return getStringValue(F_NAME); + } + + public String getComment() { + return getStringValue(F_COMMENT); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((createdObjects == null) ? 0 : createdObjects.hashCode()); + result = prime * result + ((editedObjects == null) ? 0 : editedObjects.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + OpOperation other = (OpOperation) obj; + if (createdObjects == null) { + if (other.createdObjects != null) + return false; + } else if (!createdObjects.equals(other.createdObjects)) + return false; + if (editedObjects == null) { + if (other.editedObjects != null) + return false; + } else if (!editedObjects.equals(other.editedObjects)) + return false; + if (type == null) { + if (other.type != null) + return false; + } else if (!type.equals(other.type)) + return false; + return true; + } + + // OSMAND ANDROID CHANGE BEGIN: + // removed unused classes and methods + // public static OpObjectDiffBuilder createDiffOperation(OpObject o) + // private static Object diffSet(Object vl) + // public static class OpObjectDiffBuilder{} + // OSMAND ANDROID CHANGE END + + public static class OpOperationBeanAdapter implements JsonDeserializer, + JsonSerializer { + + // plain serialization to calculate hash + private boolean excludeHashAndSignature; + private boolean fullOutput; + + public OpOperationBeanAdapter(boolean fullOutput, boolean excludeHashAndSignature) { + this.excludeHashAndSignature = excludeHashAndSignature; + this.fullOutput = fullOutput; + } + + public OpOperationBeanAdapter(boolean fullOutput) { + this.fullOutput = fullOutput; + this.excludeHashAndSignature = false; + } + + @Override + public OpOperation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + JsonObject jsonObj = json.getAsJsonObject(); + OpOperation op = new OpOperation(); + JsonElement tp = jsonObj.remove(F_TYPE); + if(tp != null) { + String opType = tp.getAsString(); + op.type = opType; + } else { + op.type = ""; + } + JsonElement createdObjs = jsonObj.remove(F_CREATE); + if(createdObjs != null) { + JsonArray ar = createdObjs.getAsJsonArray(); + for(int i = 0; i < ar.size(); i++) { + op.addCreated((OpObject) context.deserialize(ar.get(i), OpObject.class)); + } + } + + JsonElement editedObjs = jsonObj.remove(F_EDIT); + if (editedObjs != null) { + for (JsonElement editElem : editedObjs.getAsJsonArray()) { + op.addEdited((OpObject) context.deserialize(editElem, OpObject.class)); + } + } + + jsonObj.remove(F_EVAL); + op.fields = context.deserialize(jsonObj, TreeMap.class); + return op; + } + + @Override + public JsonElement serialize(OpOperation src, Type typeOfSrc, JsonSerializationContext context) { + TreeMap tm = new TreeMap<>(fullOutput ? src.getMixedFieldsAndCacheMap() : src.fields); + if(excludeHashAndSignature) { + tm.remove(F_SIGNATURE); + tm.remove(F_HASH); + } + tm.put(F_TYPE, src.type); + + if (src.hasEdited()) { + tm.put(F_EDIT, context.serialize(src.editedObjects)); + } + + if(src.hasCreated()) { + tm.put(F_CREATE, context.serialize(src.createdObjects)); + } + return context.serialize(tm); + } + } + +} \ No newline at end of file diff --git a/OsmAnd/src/org/openplacereviews/opendb/util/JsonFormatter.java b/OsmAnd/src/org/openplacereviews/opendb/util/JsonFormatter.java new file mode 100644 index 0000000000..6603f4c3ab --- /dev/null +++ b/OsmAnd/src/org/openplacereviews/opendb/util/JsonFormatter.java @@ -0,0 +1,157 @@ +//Revision d1a1f6e81d0716a47cbddf5754ee77fa5fc6d1d8 +package org.openplacereviews.opendb.util; + +import com.google.gson.*; +// OSMAND ANDROID CHANGE BEGIN: +// removed dependency org.openplacereviews.opendb.ops.OpBlock; +// OSMAND ANDROID CHANGE END +import org.openplacereviews.opendb.ops.OpObject; +import org.openplacereviews.opendb.ops.OpOperation; +// OSMAND ANDROID CHANGE BEGIN: +// removed dependency org.springframework.stereotype.Component; +// OSMAND ANDROID CHANGE END + +import java.io.Reader; +import java.lang.reflect.Type; +import java.util.*; + +// OSMAND ANDROID CHANGE BEGIN: +// removed annotation @Component +// OSMAND ANDROID CHANGE END +public class JsonFormatter { + + private Gson gson; + + private Gson gsonOperationHash; + + private Gson gsonFullOutput; + + public JsonFormatter() { + GsonBuilder builder = new GsonBuilder(); + builder.disableHtmlEscaping(); + builder.registerTypeAdapter(OpOperation.class, new OpOperation.OpOperationBeanAdapter(false)); + builder.registerTypeAdapter(OpObject.class, new OpObject.OpObjectAdapter(false)); + // OSMAND ANDROID CHANGE BEGIN: + // removed OpBlock.class TypeAdapter + // OSMAND ANDROID CHANGE END + builder.registerTypeAdapter(TreeMap.class, new MapDeserializerDoubleAsIntFix()); + gson = builder.create(); + + builder = new GsonBuilder(); + builder.disableHtmlEscaping(); + builder.registerTypeAdapter(OpOperation.class, new OpOperation.OpOperationBeanAdapter(false, true)); + builder.registerTypeAdapter(OpObject.class, new OpObject.OpObjectAdapter(false)); + // OSMAND ANDROID CHANGE BEGIN: + // removed OpBlock.class TypeAdapter + // OSMAND ANDROID CHANGE END + builder.registerTypeAdapter(TreeMap.class, new MapDeserializerDoubleAsIntFix()); + gsonOperationHash = builder.create(); + + builder = new GsonBuilder(); + builder.disableHtmlEscaping(); + builder.registerTypeAdapter(OpOperation.class, new OpOperation.OpOperationBeanAdapter(true)); + builder.registerTypeAdapter(OpObject.class, new OpObject.OpObjectAdapter(true)); + builder.registerTypeAdapter(TreeMap.class, new MapDeserializerDoubleAsIntFix()); + gsonFullOutput = builder.create(); + + + } + + public static class MapDeserializerDoubleAsIntFix implements JsonDeserializer> { + + @Override @SuppressWarnings("unchecked") + public TreeMap deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return (TreeMap) read(json); + } + + public Object read(JsonElement in) { + + if(in.isJsonArray()){ + List list = new ArrayList(); + JsonArray arr = in.getAsJsonArray(); + for (JsonElement anArr : arr) { + list.add(read(anArr)); + } + return list; + }else if(in.isJsonObject()){ + Map map = new TreeMap(); + JsonObject obj = in.getAsJsonObject(); + Set> entitySet = obj.entrySet(); + for(Map.Entry entry: entitySet){ + map.put(entry.getKey(), read(entry.getValue())); + } + return map; + }else if(in.isJsonPrimitive()){ + JsonPrimitive prim = in.getAsJsonPrimitive(); + if(prim.isBoolean()){ + return prim.getAsBoolean(); + }else if(prim.isString()){ + return prim.getAsString(); + }else if(prim.isNumber()){ + Number num = prim.getAsNumber(); + // here you can handle double int/long values + // and return any type you want + // this solution will transform 3.0 float to long values + if(Math.ceil(num.doubleValue()) == num.longValue() && (!num.toString().contains(".") || num.toString().split("\\.")[1].length() <= 1)) + return num.longValue(); + else { + return num.doubleValue(); + } + } + } + return null; + } + } + +// operations to parse / format related + public OpOperation parseOperation(String opJson) { + return gson.fromJson(opJson, OpOperation.class); + } + + public OpObject parseObject(String opJson) { + return gson.fromJson(opJson, OpObject.class); + } + + // OSMAND ANDROID CHANGE BEGIN: + // removed unused methods + // public OpBlock parseBlock(String opJson) + // public String toJson(OpBlock bl) + // OSMAND ANDROID CHANGE END + + public JsonElement toJsonElement(Object o) { + return gson.toJsonTree(o); + } + + @SuppressWarnings("unchecked") + public TreeMap fromJsonToTreeMap(String json) { + return gson.fromJson(json, TreeMap.class); + } + + + public T fromJson(Reader json, Class classOfT) throws JsonSyntaxException { + return gson.fromJson(json, classOfT); + } + + public T fromJson(Reader json, Type typeOfT) throws JsonSyntaxException { + return gson.fromJson(json, typeOfT); + } + + public String fullObjectToJson(Object o) { + return gsonFullOutput.toJson(o); + } + + + public String opToJsonNoHash(OpOperation op) { + return gsonOperationHash.toJson(op); + } + + public String opToJson(OpOperation op) { + return gson.toJson(op); + } + + public String objToJson(OpObject op) { + return gson.toJson(op); + } + + +} diff --git a/OsmAnd/src/org/openplacereviews/opendb/util/JsonObjectUtils.java b/OsmAnd/src/org/openplacereviews/opendb/util/JsonObjectUtils.java new file mode 100644 index 0000000000..f372394c8b --- /dev/null +++ b/OsmAnd/src/org/openplacereviews/opendb/util/JsonObjectUtils.java @@ -0,0 +1,276 @@ +//Revision d1a1f6e81d0716a47cbddf5754ee77fa5fc6d1d8 +package org.openplacereviews.opendb.util; + +import java.util.*; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Class uses for work with Json Object represent as Map. + */ +public class JsonObjectUtils { + + + private static final int GET_OPERATION = 0; + private static final int SET_OPERATION = 1; + private static final int DELETE_OPERATION = 2; + protected static final Log LOGGER = LogFactory.getLog(JsonObjectUtils.class); + + private static class OperationAccess { + private final int operation; + private final Object value; + + private OperationAccess(int op, Object v) { + this.operation = op; + this.value = v; + } + + } + + /** + * Retrieve value from jsonMap by field sequence. + * @param jsonMap source json object deserialized in map + * @param fieldSequence Sequence to field value. + * Example: person.car.number have to be ["person", "car[2]", "number"] + * @return Field value + */ + public static Object getField(Map jsonMap, String[] fieldSequence) { + return accessField(jsonMap, fieldSequence, new OperationAccess(GET_OPERATION, null)); + } + + /** + * Set value to json field (path to field presented as sequence of string) + * + * @param jsonMap source json object deserialized in map + * @param fieldSequence Sequence to field value. + * * Example: person.car.number have to be ["person", "car[2]", "number"] + * @param field field value + * @return + */ + public static Object setField(Map jsonMap, List fieldSequence, Object field) { + return setField(jsonMap, fieldSequence.toArray(new String[fieldSequence.size()]), field); + } + + /** + * Set value to json field (path to field presented as sequence of string) + * + * @param jsonObject source json object deserialized in map + * @param fieldSequence Sequence to field value. + * * Example: person.car.number have to be ["person", "car[2]", "number"] + * @param field field value + * @return + */ + public static Object setField(Map jsonObject, String[] fieldSequence, Object field) { + return accessField(jsonObject, fieldSequence, new OperationAccess(SET_OPERATION, field)); + } + + + /** + * Retrieve value from jsonMap by field sequence. + * + * @param jsonObject source json object deserialized in map + * @param fieldSequence Sequence to field value. + * Example: person.car.number have to be ["person", "car[2]", "number"] + * @return Field value + */ + public static Object getField(Map jsonObject, List fieldSequence) { + return getField(jsonObject, fieldSequence.toArray(new String[fieldSequence.size()])); + } + + /** + * Delete field value from json Map (field path presented as sequence of string) + * + * @param jsonMap source json object deserialized in map + * @param fieldSequence Sequence to field value. + * Example: person.car.number have to be ["person", "car[2]", "number"] + * @return + */ + public static Object deleteField(Map jsonMap, List fieldSequence) { + return accessField(jsonMap, fieldSequence.toArray(new String[fieldSequence.size()]), new OperationAccess(DELETE_OPERATION, null)); + } + + + @SuppressWarnings("unchecked") + private static Object accessField(Map jsonObject, String[] fieldSequence, OperationAccess op) { + if (fieldSequence == null || fieldSequence.length == 0) { + throw new IllegalArgumentException("Field sequence is empty. Set value to root not possible."); + } + String fieldName = null; + Map jsonObjLocal = jsonObject; + List jsonListLocal = null; + int indexToAccess = -1; + for(int i = 0; i < fieldSequence.length; i++) { + boolean last = i == fieldSequence.length - 1; + fieldName = fieldSequence[i]; + int indOpArray = -1; + for(int ic = 0; ic < fieldName.length(); ) { + if(ic > 0 && (fieldName.charAt(ic) == '[' || fieldName.charAt(ic) == ']') && + fieldName.charAt(ic - 1) == '\\') { + // replace '\[' with '[' + fieldName = fieldName.substring(0, ic - 1) + fieldName.substring(ic); + } else if(fieldName.charAt(ic) == '[') { + indOpArray = ic; + break; + } else { + ic++; + } + } + jsonListLocal = null; // reset + if(indOpArray == -1) { + if(!last) { + Map fieldAccess = (Map) jsonObjLocal.get(fieldName); + if(fieldAccess == null) { + if(op.operation == GET_OPERATION) { + // don't modify during get operation + return null; + } + Map newJsonMap = new TreeMap<>(); + jsonObjLocal.put(fieldName, newJsonMap); + jsonObjLocal = newJsonMap; + } else { + jsonObjLocal = fieldAccess; + } + } + } else { + String arrayFieldName = fieldName.substring(0, indOpArray); + if(arrayFieldName.contains("]")) { + throw new IllegalArgumentException(String.format("Illegal field array modifier %s", fieldSequence[i])); + } + jsonListLocal = (List) jsonObjLocal.get(arrayFieldName); + if (jsonListLocal == null) { + if (op.operation == GET_OPERATION) { + // don't modify during get operation + return null; + } + jsonListLocal = new ArrayList(); + jsonObjLocal.put(arrayFieldName, jsonListLocal); + } + while (indOpArray != -1) { + fieldName = fieldName.substring(indOpArray + 1); + int indClArray = fieldName.indexOf("]"); + if (indClArray == -1) { + throw new IllegalArgumentException(String.format("Illegal field array modifier %s", fieldSequence[i])); + } + if(indClArray == fieldName.length() - 1) { + indOpArray = -1; + } else if(fieldName.charAt(indClArray + 1) == '[') { + indOpArray = indClArray + 1; + } else { + throw new IllegalArgumentException(String.format("Illegal field array modifier %s", fieldSequence[i])); + } + int index = Integer.parseInt(fieldName.substring(0, indClArray)); + if (last && indOpArray == -1) { + indexToAccess = index; + } else { + Object obj = null; + if (index < jsonListLocal.size() && index >= 0) { + obj = jsonListLocal.get(index); + } else if (op.operation == SET_OPERATION && (index == -1 || index == jsonListLocal.size())) { + index = jsonListLocal.size(); + jsonListLocal.add(null); + } else { + throw new IllegalArgumentException( + String.format("Illegal access to array at position %d", index)); + } + + if (obj == null) { + if (op.operation == GET_OPERATION) { + // don't modify during get operation + return null; + } + if (indOpArray == -1) { + obj = new TreeMap<>(); + } else { + obj = new ArrayList(); + } + jsonListLocal.set(index, obj); + } + if(indOpArray != -1) { + jsonListLocal = (List) obj; + } else { + jsonObjLocal = (Map) obj; + jsonListLocal = null; + } + } + } + + } + } + if(jsonListLocal != null) { + return accessListField(op, jsonListLocal, indexToAccess); + } else { + return accessObjField(op, jsonObjLocal, fieldName); + } + } + + private static Object accessObjField(OperationAccess op, Map jsonObjLocal, String fieldName) { + Object prevValue; + if (op.operation == DELETE_OPERATION) { + prevValue = jsonObjLocal.remove(fieldName); + } else if (op.operation == SET_OPERATION) { + prevValue = jsonObjLocal.put(fieldName, op.value); + } else { + prevValue = jsonObjLocal.get(fieldName); + } + return prevValue; + } + + private static Object accessListField(OperationAccess op, List jsonListLocal, int indexToAccess) { + Object prevValue; + int lastIndex = indexToAccess; + if (op.operation == DELETE_OPERATION) { + if (lastIndex >= jsonListLocal.size() || lastIndex < 0) { + prevValue = null; + } else { + prevValue = jsonListLocal.remove(lastIndex); + } + } else if (op.operation == SET_OPERATION) { + if (lastIndex == jsonListLocal.size() || lastIndex == -1) { + prevValue = null; + jsonListLocal.add(op.value); + } else if (lastIndex >= jsonListLocal.size() || lastIndex < 0) { + throw new IllegalArgumentException(String.format("Illegal access to %d position in array with size %d", + lastIndex, jsonListLocal.size())); + } else { + prevValue = jsonListLocal.set(lastIndex, op.value); + } + } else { + if (lastIndex >= jsonListLocal.size() || lastIndex < 0) { + prevValue = null; + } else { + prevValue = jsonListLocal.get(lastIndex); + } + } + return prevValue; + } + + @SuppressWarnings("unchecked") + public static List getIndexObjectByField(Object obj, List field, List res) { + if(obj == null) { + return res; + } + if(field.size() == 0) { + if(res == null) { + res = new ArrayList(); + } + res.add(obj); + return res; + } + if (obj instanceof Map) { + String fieldFirst = field.get(0); + Object value = ((Map) obj).get(fieldFirst); + return getIndexObjectByField(value, field.subList(1, field.size()), res); + } else if(obj instanceof Collection) { + for(Object o : ((Collection)obj)) { + res = getIndexObjectByField(o, field, res); + } + } else { + // we need extract but there no field + LOGGER.warn(String.format("Can't access field %s for object %s", field, obj)); + } + return res; + } + + +} diff --git a/OsmAnd/src/org/openplacereviews/opendb/util/exception/FailedVerificationException.java b/OsmAnd/src/org/openplacereviews/opendb/util/exception/FailedVerificationException.java new file mode 100644 index 0000000000..8826742e42 --- /dev/null +++ b/OsmAnd/src/org/openplacereviews/opendb/util/exception/FailedVerificationException.java @@ -0,0 +1,17 @@ +//Revision d1a1f6e81d0716a47cbddf5754ee77fa5fc6d1d8 +package org.openplacereviews.opendb.util.exception; + +public class FailedVerificationException extends Exception { + + private static final long serialVersionUID = -4936205097177668159L; + + + public FailedVerificationException(Exception e) { + super(e); + } + + + public FailedVerificationException(String msg) { + super(msg); + } +} \ No newline at end of file From 8ea9ba2925de6e5433cae3e4122b13a0d1b8578e Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 17 Nov 2020 15:42:21 +0200 Subject: [PATCH 03/16] add photo hidden --- OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java index 45c4864a2a..73301158c5 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java @@ -336,6 +336,8 @@ public class MenuBuilder { b.setTypeface(null, Typeface.BOLD); b.setText(context.getResources().getString(R.string.shared_string_add_photo)); b.setBackgroundResource(R.drawable.btn_border_light); + //TODO feature under development + b.setVisibility(View.GONE); b.setTextColor(ContextCompat.getColor(context, R.color.preference_category_title)); return b; } From 2bde2da819f7b6fd4543a58ca186c8b584780def Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 17 Nov 2020 17:07:16 +0200 Subject: [PATCH 04/16] adding image id code --- .../builders/cards/ImageCard.java | 75 ++++++++++++------- 1 file changed, 50 insertions(+), 25 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/ImageCard.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/ImageCard.java index d7b1d69895..1108692934 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/ImageCard.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/ImageCard.java @@ -21,6 +21,7 @@ import net.osmand.Location; import net.osmand.PlatformUtil; import net.osmand.data.Amenity; import net.osmand.data.LatLon; +import net.osmand.plus.BuildConfig; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.Version; @@ -39,13 +40,7 @@ import org.json.JSONObject; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; +import java.util.*; public abstract class ImageCard extends AbstractCard { @@ -408,6 +403,28 @@ public abstract class ImageCard extends AbstractCard { } } + private static String[] getIdFromResponse(String response) { + try { + JSONArray obj = new JSONObject(response).getJSONArray("objects"); + JSONArray images = (JSONArray) ((JSONObject) obj.get(0)).get("id"); + return toStringArray(images); + } catch (JSONException e) { + e.printStackTrace(); + } + return new String[0]; + } + + private static String[] toStringArray(JSONArray array) { + if (array == null) + return null; + + String[] arr = new String[array.length()]; + for (int i = 0; i < arr.length; i++) { + arr[i] = array.optString(i); + } + return arr; + } + public static class GetImageCardsTask extends AsyncTask> { private MapActivity mapActivity; @@ -425,7 +442,7 @@ public abstract class ImageCard extends AbstractCard { } public GetImageCardsTask(@NonNull MapActivity mapActivity, LatLon latLon, - @Nullable Map params, GetImageCardsListener listener) { + @Nullable Map params, GetImageCardsListener listener) { this.mapActivity = mapActivity; this.app = mapActivity.getMyApplication(); this.latLon = latLon; @@ -441,7 +458,15 @@ public abstract class ImageCard extends AbstractCard { if (o instanceof Amenity) { Amenity am = (Amenity) o; long amenityId = am.getId() >> 1; - getPicturesForPlace(result, amenityId); + String url = BuildConfig.OPR_BASE_URL + "api/objects-by-index?type=opr.place&index=osmid&key=" + amenityId; + String response = AndroidNetworkUtils.sendRequest(app, url, Collections.emptyMap(), + "Requesting location images...", false, false); + if (response != null) { + getPicturesForPlace(result, response); + String[] id = getIdFromResponse(response); + //TODO perform something with image + //listener.onOPRPlaceIdAcquired(id); + } } try { final Map pms = new LinkedHashMap<>(); @@ -503,27 +528,27 @@ public abstract class ImageCard extends AbstractCard { return result; } - private void getPicturesForPlace(List result, long l) { - String url = "https://test.openplacereviews.org/api/objects-by-index?type=opr.place&index=osmid&limit=1&key=" + l; - String response = AndroidNetworkUtils.sendRequest(app, url, Collections.emptyMap(), - "Requesting location images...", false, false); + private void getPicturesForPlace(List result, String response) { try { if (!Algorithms.isEmpty(response)) { - //TODO extract place id JSONArray obj = new JSONObject(response).getJSONArray("objects"); - JSONArray images = ((JSONObject) ((JSONObject) obj.get(0)).get("images")).getJSONArray("outdoor"); - if (images.length() > 0) { - for (int i = 0; i < images.length(); i++) { - try { - JSONObject imageObject = (JSONObject) images.get(i); - if (imageObject != JSONObject.NULL) { - ImageCard imageCard = ImageCard.createCardOpr(mapActivity, imageObject); - if (imageCard != null) { - result.add(imageCard); + JSONObject imagesWrapper = ((JSONObject) ((JSONObject) obj.get(0)).get("images")); + Iterator it = imagesWrapper.keys(); + while (it.hasNext()) { + JSONArray images = imagesWrapper.getJSONArray(it.next()); + if (images.length() > 0) { + for (int i = 0; i < images.length(); i++) { + try { + JSONObject imageObject = (JSONObject) images.get(i); + if (imageObject != JSONObject.NULL) { + ImageCard imageCard = ImageCard.createCardOpr(mapActivity, imageObject); + if (imageCard != null) { + result.add(imageCard); + } } + } catch (JSONException e) { + LOG.error(e); } - } catch (JSONException e) { - LOG.error(e); } } } From 14b0ab427a055f4699b963585ef63d0473b0ff52 Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 17 Nov 2020 18:17:53 +0200 Subject: [PATCH 05/16] add check email functions --- .../osmand/plus/osmedit/opr/OpenDBAPI.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java b/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java index 550906a508..5214ec2df5 100644 --- a/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java +++ b/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java @@ -3,7 +3,9 @@ package net.osmand.plus.osmedit.opr; import android.net.TrafficStats; import android.os.Build; import com.google.gson.GsonBuilder; +import net.osmand.AndroidNetworkUtils; import net.osmand.PlatformUtil; +import net.osmand.osm.io.NetworkUtils; import net.osmand.plus.BuildConfig; import org.apache.commons.logging.Log; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -27,9 +29,30 @@ import static org.openplacereviews.opendb.SecUtils.*; public class OpenDBAPI { private static final Log log = PlatformUtil.getLog(SecUtils.class); - + private static final String checkLoginEndpoint = "api/auth/user-check-loginkey?"; + private static final String LOGIN_SUCCESS_MESSAGE = "success"; private static final int THREAD_ID = 11200; + /* + * method for check if user is loggined in blockchain + * params + * - username: blockchain username in format "openplacereviews:test_1" + * - privatekey: "base64:PKCS#8:actualKey" + * + * Do not call on mainThread + */ + public boolean checkPrivateKeyValid(String username, String privateKey){ + String url = BuildConfig.OPR_BASE_URL + checkLoginEndpoint + + "name=" + + username + + "&" + + "privateKey=" + + privateKey; + StringBuilder response = new StringBuilder(); + return (NetworkUtils.sendGetRequest(url,null,response) == null) && + response.toString().contains(LOGIN_SUCCESS_MESSAGE); + } + public int uploadImage(String[] placeId, String privateKey, String username, String image) throws FailedVerificationException { if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { Security.removeProvider("BC"); From 5cef32531b88e54f249a072b64cfa7adf02766c6 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 18 Nov 2020 18:21:25 +0200 Subject: [PATCH 06/16] encode url bug check login fixed --- .../osmand/plus/osmedit/opr/OpenDBAPI.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java b/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java index 5214ec2df5..0d77c49d25 100644 --- a/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java +++ b/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java @@ -14,12 +14,10 @@ import org.openplacereviews.opendb.ops.OpOperation; import org.openplacereviews.opendb.util.JsonFormatter; import org.openplacereviews.opendb.util.exception.FailedVerificationException; -import java.io.BufferedReader; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; +import java.io.*; import java.net.HttpURLConnection; import java.net.URL; +import java.net.URLEncoder; import java.security.KeyPair; import java.security.Security; import java.util.*; @@ -38,16 +36,22 @@ public class OpenDBAPI { * params * - username: blockchain username in format "openplacereviews:test_1" * - privatekey: "base64:PKCS#8:actualKey" - * + * Need to encode key * Do not call on mainThread */ - public boolean checkPrivateKeyValid(String username, String privateKey){ - String url = BuildConfig.OPR_BASE_URL + checkLoginEndpoint + - "name=" + - username + - "&" + - "privateKey=" + - privateKey; + public boolean checkPrivateKeyValid(String username, String privateKey) throws UnsupportedEncodingException { + String url = null; + try { + url = BuildConfig.OPR_BASE_URL + checkLoginEndpoint + + "name=" + + username + + "&" + + "privateKey=" + + //need to encode the key + URLEncoder.encode(privateKey, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw e; + } StringBuilder response = new StringBuilder(); return (NetworkUtils.sendGetRequest(url,null,response) == null) && response.toString().contains(LOGIN_SUCCESS_MESSAGE); From c307a0e4fe21cc16d8f9f303de9db1726a5048d1 Mon Sep 17 00:00:00 2001 From: Simon Yakymovych Date: Fri, 20 Nov 2020 12:26:23 +0200 Subject: [PATCH 07/16] photo upload feature implemented --- OsmAnd/res/values/strings.xml | 2 + .../plus/mapcontextmenu/MenuBuilder.java | 127 +++++++++++++++++- .../builders/cards/ImageCard.java | 5 +- .../osmand/plus/osmedit/opr/OpenDBAPI.java | 8 +- 4 files changed, 135 insertions(+), 7 deletions(-) diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index 83862c5287..9029f205e2 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -11,6 +11,8 @@ Thx - Hardy --> + Select picture + Cannot upload image, please, try again later Motorboat Kayak Search history diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java index 73301158c5..30b7f05cd8 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java @@ -6,6 +6,8 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.Typeface; @@ -33,11 +35,14 @@ import androidx.appcompat.view.ContextThemeWrapper; import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; import net.osmand.AndroidUtils; +import net.osmand.PlatformUtil; import net.osmand.data.Amenity; import net.osmand.data.LatLon; import net.osmand.data.PointDescription; import net.osmand.data.QuadRect; +import net.osmand.osm.io.NetworkUtils; import net.osmand.plus.*; +import net.osmand.plus.activities.ActivityResultListener; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.helpers.FontCache; import net.osmand.plus.mapcontextmenu.builders.cards.AbstractCard; @@ -46,7 +51,9 @@ import net.osmand.plus.mapcontextmenu.builders.cards.ImageCard; import net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCardsTask; import net.osmand.plus.mapcontextmenu.builders.cards.NoImagesCard; import net.osmand.plus.mapcontextmenu.controllers.TransportStopController; +import net.osmand.plus.openplacereviews.OPRWebviewActivity; import net.osmand.plus.openplacereviews.OprStartFragment; +import net.osmand.plus.osmedit.opr.OpenDBAPI; import net.osmand.plus.poi.PoiUIFilter; import net.osmand.plus.render.RenderingIcons; import net.osmand.plus.transport.TransportStopRoute; @@ -57,6 +64,14 @@ import net.osmand.plus.widgets.tools.ClickableSpanTouchListener; import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; +import org.apache.commons.logging.Log; +import org.openplacereviews.opendb.util.exception.FailedVerificationException; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.text.MessageFormat; import java.util.*; import static net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCardsTask.GetImageCardsListener; @@ -92,6 +107,12 @@ public class MenuBuilder { private String preferredMapAppLang; private boolean transliterateNames; + private static final int PICK_IMAGE = 1231; + private static final Log LOG = PlatformUtil.getLog(MenuBuilder.class); + private View view; + private OpenDBAPI openDBAPI = new OpenDBAPI(); + private String[] placeId = new String[0]; + public interface CollapseExpandListener { void onCollapseExpand(boolean collapsed); } @@ -336,8 +357,103 @@ public class MenuBuilder { b.setTypeface(null, Typeface.BOLD); b.setText(context.getResources().getString(R.string.shared_string_add_photo)); b.setBackgroundResource(R.drawable.btn_border_light); + b.setOnClickListener(new OnClickListener() { + @Override + public void onClick(final View view) { + mapActivity.registerActivityResultListener(new ActivityResultListener(PICK_IMAGE, + new ActivityResultListener.OnActivityResultListener() { + @Override + public void onResult(int resultCode, Intent resultData) { + InputStream inputStream = null; + try { + inputStream = mapActivity.getContentResolver().openInputStream(resultData.getData()); + } catch (Exception e) { + LOG.error(e); + } + handleSelectedImage(view, inputStream); + } + })); + final String privateKey = OPRWebviewActivity.getPrivateKeyFromCookie(); + final String name = OPRWebviewActivity.getUsernameFromCookie(); + if (privateKey == null || privateKey.isEmpty()) { + OprStartFragment.showInstance(mapActivity.getSupportFragmentManager()); + return; + } + new Thread(new Runnable() { + @Override + public void run() { + if (openDBAPI.checkPrivateKeyValid(name,privateKey)){ + app.runInUIThread(new Runnable() { + @Override + public void run() { + Intent intent = new Intent(); + intent.setType("image/*"); + intent.setAction(Intent.ACTION_GET_CONTENT); + mapActivity.startActivityForResult(Intent.createChooser(intent, + mapActivity.getString(R.string.select_picture)), PICK_IMAGE); + } + }); + } + else { + OprStartFragment.showInstance(mapActivity.getSupportFragmentManager()); + } + } + }).start(); + } + + private void handleSelectedImage(final View view, final InputStream image) { + Thread t = new Thread(new Runnable() { + @Override + public void run() { + try { + uploadImageToPlace(view, image); + } catch (Exception e) { + LOG.error(e); + } + } + }); + t.start(); + } + + private void uploadImageToPlace(View view, InputStream image) { + //compress image + BufferedInputStream bufferedInputStream = new BufferedInputStream(image); + Bitmap bmp = BitmapFactory.decodeStream(bufferedInputStream); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + bmp.compress(Bitmap.CompressFormat.PNG, 70, os); + byte[] buff = os.toByteArray(); + InputStream serverData = new ByteArrayInputStream(buff); + String url = BuildConfig.OPR_BASE_URL + "api/ipfs/image"; + String response = NetworkUtils.sendPostDataRequest(url, serverData); + if (response != null) { + int res = 0; + try { + res = openDBAPI.uploadImage( + placeId, + OPRWebviewActivity.getPrivateKeyFromCookie() +, OPRWebviewActivity.getUsernameFromCookie(), + response); + } catch (FailedVerificationException e) { + LOG.error(e); + app.showToastMessage(view.getResources().getString(R.string.cannot_upload_image)); + } + if (res != 200) { + //image was uploaded but not added to blockchain + app.showToastMessage(view.getResources().getString(R.string.cannot_upload_image)); + } else { + String str = MessageFormat.format(view.getResources() + .getString(R.string.successfully_uploaded_pattern), 1, 1); + app.showToastMessage(str); + //refresh the image + startLoadingImagesTask(); + } + } else { + app.showToastMessage(view.getResources().getString(R.string.cannot_upload_image)); + } + } + }); //TODO feature under development - b.setVisibility(View.GONE); + //b.setVisibility(View.GONE); b.setTextColor(ContextCompat.getColor(context, R.color.preference_category_title)); return b; } @@ -357,6 +473,10 @@ public class MenuBuilder { } onlinePhotoCards = new ArrayList<>(); onlinePhotoCardsRow.setProgressCard(); + startLoadingImagesTask(); + } + + private void startLoadingImagesTask(){ execute(new GetImageCardsTask(mapActivity, getLatLon(), getAdditionalCardParams(), new GetImageCardsListener() { @Override @@ -364,6 +484,11 @@ public class MenuBuilder { processOnlinePhotosCards(cardList); } + @Override + public void onPlaceIdAcquired(String[] placeId) { + MenuBuilder.this.placeId = placeId; + } + @Override public void onFinish(List cardList) { if (!isHidden()) { diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/ImageCard.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/ImageCard.java index 1108692934..33feb6c5a3 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/ImageCard.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/ImageCard.java @@ -438,6 +438,8 @@ public abstract class ImageCard extends AbstractCard { public interface GetImageCardsListener { void onPostProcess(List cardList); + void onPlaceIdAcquired(String[] placeId); + void onFinish(List cardList); } @@ -464,8 +466,7 @@ public abstract class ImageCard extends AbstractCard { if (response != null) { getPicturesForPlace(result, response); String[] id = getIdFromResponse(response); - //TODO perform something with image - //listener.onOPRPlaceIdAcquired(id); + listener.onPlaceIdAcquired(id); } } try { diff --git a/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java b/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java index 0d77c49d25..fbaa401cf6 100644 --- a/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java +++ b/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java @@ -28,7 +28,7 @@ import static org.openplacereviews.opendb.SecUtils.*; public class OpenDBAPI { private static final Log log = PlatformUtil.getLog(SecUtils.class); private static final String checkLoginEndpoint = "api/auth/user-check-loginkey?"; - private static final String LOGIN_SUCCESS_MESSAGE = "success"; + private static final String LOGIN_SUCCESS_MESSAGE = "{\"result\":\"OK\"}"; private static final int THREAD_ID = 11200; /* @@ -39,7 +39,7 @@ public class OpenDBAPI { * Need to encode key * Do not call on mainThread */ - public boolean checkPrivateKeyValid(String username, String privateKey) throws UnsupportedEncodingException { + public boolean checkPrivateKeyValid(String username, String privateKey) { String url = null; try { url = BuildConfig.OPR_BASE_URL + checkLoginEndpoint + @@ -50,7 +50,7 @@ public class OpenDBAPI { //need to encode the key URLEncoder.encode(privateKey, "UTF-8"); } catch (UnsupportedEncodingException e) { - throw e; + return false; } StringBuilder response = new StringBuilder(); return (NetworkUtils.sendGetRequest(url,null,response) == null) && @@ -63,7 +63,7 @@ public class OpenDBAPI { Security.addProvider(new BouncyCastleProvider()); } KeyPair kp = SecUtils.getKeyPair(ALGO_EC, privateKey, null); - String signed = username;// + ":opr-web"; + String signed = username + ":opr-web"; JsonFormatter formatter = new JsonFormatter(); OPRImage OPRImage = new GsonBuilder().create().fromJson(image, OPRImage.class); From a2207239904c016a8f6d9f12082b22bf2e632434 Mon Sep 17 00:00:00 2001 From: Simon Yakymovych Date: Fri, 20 Nov 2020 14:13:10 +0200 Subject: [PATCH 08/16] ui bug fix --- OsmAnd/res/layout/fragment_opr_login.xml | 171 ++++++++++-------- .../plus/mapcontextmenu/MenuBuilder.java | 60 +++--- .../net/osmand/plus/osmedit/opr/OPRImage.java | 8 - .../osmand/plus/osmedit/opr/OpenDBAPI.java | 17 +- 4 files changed, 137 insertions(+), 119 deletions(-) delete mode 100644 OsmAnd/src/net/osmand/plus/osmedit/opr/OPRImage.java diff --git a/OsmAnd/res/layout/fragment_opr_login.xml b/OsmAnd/res/layout/fragment_opr_login.xml index 3313679aec..7bd292e977 100644 --- a/OsmAnd/res/layout/fragment_opr_login.xml +++ b/OsmAnd/res/layout/fragment_opr_login.xml @@ -1,95 +1,114 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + android:gravity="bottom" + android:orientation="vertical"> + android:text="@string/register_opr_create_new_account" + android:textColor="@color/color_white" /> + android:layout_height="wrap_content" + android:layout_gravity="bottom" + android:layout_marginLeft="@dimen/content_padding" + android:layout_marginTop="@dimen/dashPadding" + android:layout_marginRight="@dimen/content_padding" + android:background="@color/activity_background_color_light" + android:text="@string/register_opr_have_account" + android:textColor="@color/icon_color_active_light" /> - \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java index 30b7f05cd8..20db3e7d74 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java @@ -112,6 +112,32 @@ public class MenuBuilder { private View view; private OpenDBAPI openDBAPI = new OpenDBAPI(); private String[] placeId = new String[0]; + private GetImageCardsListener imageCardListener = new GetImageCardsListener() { + @Override + public void onPostProcess(List cardList) { + processOnlinePhotosCards(cardList); + } + + @Override + public void onPlaceIdAcquired(String[] placeId) { + MenuBuilder.this.placeId = placeId; + } + + @Override + public void onFinish(List cardList) { + if (!isHidden()) { + List cards = new ArrayList<>(); + cards.addAll(cardList); + if (cardList.size() == 0) { + cards.add(new NoImagesCard(mapActivity)); + } + if (onlinePhotoCardsRow != null) { + onlinePhotoCardsRow.setCards(cards); + } + onlinePhotoCards = cards; + } + } + }; public interface CollapseExpandListener { void onCollapseExpand(boolean collapsed); @@ -445,7 +471,7 @@ public class MenuBuilder { .getString(R.string.successfully_uploaded_pattern), 1, 1); app.showToastMessage(str); //refresh the image - startLoadingImagesTask(); + execute(new GetImageCardsTask(mapActivity, getLatLon(), getAdditionalCardParams(), imageCardListener)); } } else { app.showToastMessage(view.getResources().getString(R.string.cannot_upload_image)); @@ -471,39 +497,13 @@ public class MenuBuilder { if (onlinePhotoCardsRow == null) { return; } - onlinePhotoCards = new ArrayList<>(); - onlinePhotoCardsRow.setProgressCard(); startLoadingImagesTask(); } private void startLoadingImagesTask(){ - execute(new GetImageCardsTask(mapActivity, getLatLon(), getAdditionalCardParams(), - new GetImageCardsListener() { - @Override - public void onPostProcess(List cardList) { - processOnlinePhotosCards(cardList); - } - - @Override - public void onPlaceIdAcquired(String[] placeId) { - MenuBuilder.this.placeId = placeId; - } - - @Override - public void onFinish(List cardList) { - if (!isHidden()) { - List cards = new ArrayList<>(); - cards.addAll(cardList); - if (cardList.size() == 0) { - cards.add(new NoImagesCard(mapActivity)); - } - if (onlinePhotoCardsRow != null) { - onlinePhotoCardsRow.setCards(cards); - } - onlinePhotoCards = cards; - } - } - })); + onlinePhotoCards = new ArrayList<>(); + onlinePhotoCardsRow.setProgressCard(); + execute(new GetImageCardsTask(mapActivity, getLatLon(), getAdditionalCardParams(), imageCardListener)); } protected Map getAdditionalCardParams() { diff --git a/OsmAnd/src/net/osmand/plus/osmedit/opr/OPRImage.java b/OsmAnd/src/net/osmand/plus/osmedit/opr/OPRImage.java deleted file mode 100644 index dfffbf9973..0000000000 --- a/OsmAnd/src/net/osmand/plus/osmedit/opr/OPRImage.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.osmand.plus.osmedit.opr; - -public class OPRImage { - public String type; - public String hash; - public String cid; - public String extension; -} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java b/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java index fbaa401cf6..ad19add249 100644 --- a/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java +++ b/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java @@ -66,17 +66,17 @@ public class OpenDBAPI { String signed = username + ":opr-web"; JsonFormatter formatter = new JsonFormatter(); - OPRImage OPRImage = new GsonBuilder().create().fromJson(image, OPRImage.class); + OPRImage oprImage = new GsonBuilder().create().fromJson(image, OPRImage.class); OpOperation opOperation = new OpOperation(); opOperation.setType("opr.place"); List edits = new ArrayList<>(); Map edit = new TreeMap<>(); List imageResponseList = new ArrayList<>(); Map imageMap = new TreeMap<>(); - imageMap.put("cid", OPRImage.cid); - imageMap.put("hash", OPRImage.hash); - imageMap.put("extension", OPRImage.extension); - imageMap.put("type", OPRImage.type); + imageMap.put("cid", oprImage.cid); + imageMap.put("hash", oprImage.hash); + imageMap.put("extension", oprImage.extension); + imageMap.put("type", oprImage.type); imageResponseList.add(imageMap); List ids = new ArrayList<>(Arrays.asList(placeId)); Map change = new TreeMap<>(); @@ -131,4 +131,11 @@ public class OpenDBAPI { } return -1; } + + public class OPRImage { + public String type; + public String hash; + public String cid; + public String extension; + } } \ No newline at end of file From 9d5464b82c88d5f919084df8d5093dda92ac4806 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Mon, 23 Nov 2020 03:42:59 +0200 Subject: [PATCH 09/16] Add BaseSettingsListFragment --- .../fragments/BaseSettingsListFragment.java | 267 ++++++++++ .../ExportImportSettingsAdapter.java | 463 ------------------ .../fragments/ExportSettingsAdapter.java | 9 +- .../fragments/ExportSettingsFragment.java | 211 +------- .../fragments/ImportCompleteFragment.java | 6 +- .../fragments/ImportDuplicatesFragment.java | 4 +- .../fragments/ImportSettingsFragment.java | 227 ++------- 7 files changed, 341 insertions(+), 846 deletions(-) create mode 100644 OsmAnd/src/net/osmand/plus/settings/fragments/BaseSettingsListFragment.java delete mode 100644 OsmAnd/src/net/osmand/plus/settings/fragments/ExportImportSettingsAdapter.java diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/BaseSettingsListFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/BaseSettingsListFragment.java new file mode 100644 index 0000000000..d95b6cffea --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/BaseSettingsListFragment.java @@ -0,0 +1,267 @@ +package net.osmand.plus.settings.fragments; + +import android.content.Context; +import android.content.DialogInterface; +import android.os.Build; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.ExpandableListView; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.Toolbar; +import androidx.core.view.ViewCompat; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; + +import net.osmand.AndroidUtils; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.UiUtilities.DialogButtonType; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.base.BaseOsmAndFragment; +import net.osmand.plus.helpers.AndroidUiHelper; +import net.osmand.plus.settings.backend.ExportSettingsCategory; +import net.osmand.plus.settings.backend.ExportSettingsType; +import net.osmand.plus.settings.fragments.ExportSettingsAdapter.OnItemSelectedListener; +import net.osmand.plus.widgets.TextViewEx; + +import java.io.File; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public abstract class BaseSettingsListFragment extends BaseOsmAndFragment implements OnItemSelectedListener { + + protected static final String SETTINGS_LIST_TAG = "settings_list_tag"; + + protected OsmandApplication app; + + protected Map> selectedItemsMap = new EnumMap<>(ExportSettingsType.class); + protected Map dataList = new LinkedHashMap<>(); + + protected View header; + protected View continueBtn; + protected View headerShadow; + protected View headerDivider; + protected View itemsSizeContainer; + protected View availableSpaceContainer; + protected TextViewEx selectedItemsSize; + protected TextViewEx availableSpaceDescr; + protected LinearLayout buttonsContainer; + protected ExpandableListView expandableList; + protected ExportSettingsAdapter adapter; + + protected boolean nightMode; + private boolean wasDrawerDisabled; + + @Override + public int getStatusBarColorId() { + return nightMode ? R.color.status_bar_color_dark : R.color.status_bar_color_light; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + app = requireMyApplication(); + nightMode = !app.getSettings().isLightContent(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + LayoutInflater themedInflater = UiUtilities.getInflater(app, nightMode); + View root = themedInflater.inflate(R.layout.fragment_import, container, false); + AndroidUtils.addStatusBarPadding21v(app, root); + + selectedItemsSize = root.findViewById(R.id.file_size); + itemsSizeContainer = root.findViewById(R.id.file_size_container); + expandableList = root.findViewById(R.id.list); + buttonsContainer = root.findViewById(R.id.buttons_container); + + Toolbar toolbar = root.findViewById(R.id.toolbar); + setupToolbar(toolbar); + ViewCompat.setNestedScrollingEnabled(expandableList, true); + + header = themedInflater.inflate(R.layout.list_item_description_header, null); + headerDivider = header.findViewById(R.id.divider); + headerShadow = header.findViewById(R.id.card_bottom_divider); + expandableList.addHeaderView(header); + + availableSpaceContainer = themedInflater.inflate(R.layout.enough_space_warning_card, null); + availableSpaceDescr = availableSpaceContainer.findViewById(R.id.warning_descr); + + continueBtn = root.findViewById(R.id.continue_button); + UiUtilities.setupDialogButton(nightMode, continueBtn, DialogButtonType.PRIMARY, getString(R.string.shared_string_continue)); + continueBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onContinueButtonClickAction(); + } + }); + + ViewTreeObserver treeObserver = buttonsContainer.getViewTreeObserver(); + treeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (buttonsContainer != null) { + ViewTreeObserver vts = buttonsContainer.getViewTreeObserver(); + int height = buttonsContainer.getMeasuredHeight(); + expandableList.setPadding(0, 0, 0, height); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + vts.removeOnGlobalLayoutListener(this); + } else { + vts.removeGlobalOnLayoutListener(this); + } + } + } + }); + + adapter = new ExportSettingsAdapter(app, this, nightMode); + adapter.updateSettingsItems(dataList, selectedItemsMap); + expandableList.setAdapter(adapter); + updateAvailableSpace(); + + return root; + } + + protected abstract void onContinueButtonClickAction(); + + @Override + public void onResume() { + super.onResume(); + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + wasDrawerDisabled = mapActivity.isDrawerDisabled(); + if (!wasDrawerDisabled) { + mapActivity.disableDrawer(); + } + } + } + + public void onPause() { + super.onPause(); + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null && !wasDrawerDisabled) { + mapActivity.enableDrawer(); + } + } + + protected void dismissFragment() { + FragmentManager fm = getFragmentManager(); + if (fm != null && !fm.isStateSaved()) { + getFragmentManager().popBackStack(SETTINGS_LIST_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); + } + } + + public void showExitDialog() { + Context themedContext = UiUtilities.getThemedContext(getActivity(), nightMode); + AlertDialog.Builder dismissDialog = new AlertDialog.Builder(themedContext); + dismissDialog.setTitle(getString(R.string.shared_string_dismiss)); + dismissDialog.setMessage(getString(R.string.exit_without_saving)); + dismissDialog.setNegativeButton(R.string.shared_string_cancel, null); + dismissDialog.setPositiveButton(R.string.shared_string_exit, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismissFragment(); + } + }); + dismissDialog.show(); + } + + private void setupToolbar(Toolbar toolbar) { + toolbar.setNavigationIcon(getPaintedContentIcon(R.drawable.ic_action_close, nightMode + ? getResources().getColor(R.color.active_buttons_and_links_text_dark) + : getResources().getColor(R.color.active_buttons_and_links_text_light))); + toolbar.setNavigationContentDescription(R.string.shared_string_close); + toolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showExitDialog(); + } + }); + } + + protected void updateAvailableSpace() { + long calculatedSize = ExportSettingsAdapter.calculateItemsSize(adapter.getData()); + if (calculatedSize != 0) { + selectedItemsSize.setText(AndroidUtils.formatSize(app, calculatedSize)); + + File dir = app.getAppPath("").getParentFile(); + long availableSizeBytes = AndroidUtils.getAvailableSpace(dir); + if (calculatedSize > availableSizeBytes) { + String availableSize = AndroidUtils.formatSize(app, availableSizeBytes); + availableSpaceDescr.setText(getString(R.string.export_not_enough_space_descr, availableSize)); + updateWarningHeaderVisibility(true); + continueBtn.setEnabled(false); + } else { + updateWarningHeaderVisibility(false); + continueBtn.setEnabled(adapter.hasSelectedData()); + } + itemsSizeContainer.setVisibility(View.VISIBLE); + } else { + updateWarningHeaderVisibility(false); + itemsSizeContainer.setVisibility(View.INVISIBLE); + continueBtn.setEnabled(adapter.hasSelectedData()); + } + } + + private void updateWarningHeaderVisibility(boolean visible) { + if (visible) { + if (expandableList.getHeaderViewsCount() < 2) { + expandableList.addHeaderView(availableSpaceContainer); + } + AndroidUiHelper.updateVisibility(headerShadow, false); + AndroidUiHelper.updateVisibility(headerDivider, true); + } else { + expandableList.removeHeaderView(availableSpaceContainer); + AndroidUiHelper.updateVisibility(headerShadow, true); + AndroidUiHelper.updateVisibility(headerDivider, false); + } + } + + @Nullable + public MapActivity getMapActivity() { + FragmentActivity activity = getActivity(); + if (activity instanceof MapActivity) { + return (MapActivity) activity; + } else { + return null; + } + } + + @Override + public void onCategorySelected(ExportSettingsCategory category, boolean selected) { + SettingsCategoryItems categoryItems = dataList.get(category); + for (ExportSettingsType type : categoryItems.getTypes()) { + List selectedItems = selected ? categoryItems.getItemsForType(type) : new ArrayList<>(); + selectedItemsMap.put(type, selectedItems); + } + updateAvailableSpace(); + } + + @Override + public void onItemsSelected(ExportSettingsType type, List selectedItems) { + selectedItemsMap.put(type, selectedItems); + adapter.notifyDataSetChanged(); + updateAvailableSpace(); + } + + @Override + public void onTypeClicked(ExportSettingsCategory category, ExportSettingsType type) { + FragmentManager fragmentManager = getFragmentManager(); + if (fragmentManager != null && type != ExportSettingsType.GLOBAL && type != ExportSettingsType.SEARCH_HISTORY) { + List items = (List) dataList.get(category).getItemsForType(type); + List selectedItems = (List) selectedItemsMap.get(type); + ExportItemsBottomSheet.showInstance(type, selectedItems, items, fragmentManager, this); + } + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ExportImportSettingsAdapter.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ExportImportSettingsAdapter.java deleted file mode 100644 index 2643b5230d..0000000000 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ExportImportSettingsAdapter.java +++ /dev/null @@ -1,463 +0,0 @@ -package net.osmand.plus.settings.fragments; - -import android.content.res.ColorStateList; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.core.content.ContextCompat; -import androidx.core.widget.CompoundButtonCompat; - -import net.osmand.AndroidUtils; -import net.osmand.IndexConstants; -import net.osmand.PlatformUtil; -import net.osmand.map.ITileSource; -import net.osmand.plus.FavouritesDbHelper.FavoriteGroup; -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.R; -import net.osmand.plus.UiUtilities; -import net.osmand.plus.activities.OsmandBaseExpandableListAdapter; -import net.osmand.plus.audionotes.AudioVideoNotesPlugin; -import net.osmand.plus.helpers.AvoidSpecificRoads.AvoidRoadInfo; -import net.osmand.plus.helpers.FileNameTranslationHelper; -import net.osmand.plus.helpers.GpxUiHelper; -import net.osmand.plus.helpers.SearchHistoryHelper.HistoryEntry; -import net.osmand.plus.osmedit.OpenstreetmapPoint; -import net.osmand.plus.osmedit.OsmEditingPlugin; -import net.osmand.plus.osmedit.OsmNotesPoint; -import net.osmand.plus.poi.PoiUIFilter; -import net.osmand.plus.profiles.ProfileIconColors; -import net.osmand.plus.profiles.RoutingProfileDataObject.RoutingProfilesResources; -import net.osmand.plus.quickaction.QuickAction; -import net.osmand.plus.render.RenderingIcons; -import net.osmand.plus.settings.backend.ApplicationMode; -import net.osmand.plus.settings.backend.ApplicationMode.ApplicationModeBean; -import net.osmand.plus.settings.backend.ExportSettingsType; -import net.osmand.plus.settings.backend.backup.FileSettingsItem; -import net.osmand.plus.settings.backend.backup.GlobalSettingsItem; -import net.osmand.util.Algorithms; -import net.osmand.view.ThreeStateCheckbox; - -import org.apache.commons.logging.Log; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static net.osmand.plus.settings.backend.ExportSettingsType.OFFLINE_MAPS; -import static net.osmand.plus.settings.backend.backup.FileSettingsItem.FileSubtype; -import static net.osmand.view.ThreeStateCheckbox.State.CHECKED; -import static net.osmand.view.ThreeStateCheckbox.State.MISC; -import static net.osmand.view.ThreeStateCheckbox.State.UNCHECKED; - -class ExportImportSettingsAdapter extends OsmandBaseExpandableListAdapter { - - private static final Log LOG = PlatformUtil.getLog(ExportImportSettingsAdapter.class.getName()); - private OsmandApplication app; - private UiUtilities uiUtilities; - private List data; - private Map> itemsMap; - private List itemsTypes; - private boolean nightMode; - private boolean importState; - private int activeColorRes; - private int secondaryColorRes; - - ExportImportSettingsAdapter(OsmandApplication app, boolean nightMode, boolean importState) { - this.app = app; - this.nightMode = nightMode; - this.importState = importState; - this.itemsMap = new HashMap<>(); - this.itemsTypes = new ArrayList<>(); - this.data = new ArrayList<>(); - uiUtilities = app.getUIUtilities(); - activeColorRes = nightMode - ? R.color.icon_color_active_dark - : R.color.icon_color_active_light; - secondaryColorRes = nightMode - ? R.color.icon_color_secondary_dark - : R.color.icon_color_secondary_light; - } - - @Override - public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { - View group = convertView; - if (group == null) { - LayoutInflater inflater = UiUtilities.getInflater(app, nightMode); - group = inflater.inflate(R.layout.profile_data_list_item_group, parent, false); - } - - boolean isLastGroup = groupPosition == getGroupCount() - 1; - final ExportSettingsType type = itemsTypes.get(groupPosition); - - TextView titleTv = group.findViewById(R.id.title_tv); - TextView subTextTv = group.findViewById(R.id.sub_text_tv); - final ThreeStateCheckbox checkBox = group.findViewById(R.id.check_box); - FrameLayout checkBoxContainer = group.findViewById(R.id.check_box_container); - ImageView expandIv = group.findViewById(R.id.explist_indicator); - View lineDivider = group.findViewById(R.id.divider); - View cardTopDivider = group.findViewById(R.id.card_top_divider); - View cardBottomDivider = group.findViewById(R.id.card_bottom_divider); - - titleTv.setText(getGroupTitle(type)); - lineDivider.setVisibility(importState || isExpanded || isLastGroup ? View.GONE : View.VISIBLE); - cardTopDivider.setVisibility(importState ? View.VISIBLE : View.GONE); - cardBottomDivider.setVisibility(importState && !isExpanded ? View.VISIBLE : View.GONE); - - final List listItems = itemsMap.get(type); - subTextTv.setText(getSelectedItemsAmount(listItems, type)); - - if (data.containsAll(listItems)) { - checkBox.setState(CHECKED); - } else { - boolean contains = false; - for (Object object : listItems) { - if (data.contains(object)) { - contains = true; - break; - } - } - checkBox.setState(contains ? MISC : UNCHECKED); - } - int checkBoxColor = checkBox.getState() == UNCHECKED ? secondaryColorRes : activeColorRes; - CompoundButtonCompat.setButtonTintList(checkBox, ColorStateList.valueOf(ContextCompat.getColor(app, checkBoxColor))); - checkBoxContainer.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - checkBox.performClick(); - if (checkBox.getState() == CHECKED) { - for (Object object : listItems) { - if (!data.contains(object)) { - data.add(object); - } - } - } else { - data.removeAll(listItems); - } - notifyDataSetChanged(); - } - }); - adjustIndicator(app, groupPosition, isExpanded, group, nightMode); - return group; - } - - @Override - public View getChildView(int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { - View child = convertView; - if (child == null) { - LayoutInflater inflater = UiUtilities.getInflater(app, nightMode); - child = inflater.inflate(R.layout.profile_data_list_item_child, parent, false); - } - final Object currentItem = itemsMap.get(itemsTypes.get(groupPosition)).get(childPosition); - - boolean isLastGroup = groupPosition == getGroupCount() - 1; - boolean itemSelected = data.contains(currentItem); - final ExportSettingsType type = itemsTypes.get(groupPosition); - - TextView title = child.findViewById(R.id.title_tv); - TextView subText = child.findViewById(R.id.sub_title_tv); - subText.setVisibility(View.GONE); - final CheckBox checkBox = child.findViewById(R.id.check_box); - ImageView icon = child.findViewById(R.id.icon); - View lineDivider = child.findViewById(R.id.divider); - View cardBottomDivider = child.findViewById(R.id.card_bottom_divider); - - lineDivider.setVisibility(!importState && isLastChild && !isLastGroup ? View.VISIBLE : View.GONE); - cardBottomDivider.setVisibility(importState && isLastChild ? View.VISIBLE : View.GONE); - int checkBoxColor = itemSelected ? activeColorRes : secondaryColorRes; - CompoundButtonCompat.setButtonTintList(checkBox, ColorStateList.valueOf(ContextCompat.getColor(app, checkBoxColor))); - - checkBox.setChecked(itemSelected); - checkBox.setClickable(false); - child.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (data.contains(currentItem)) { - data.remove(currentItem); - } else { - data.add(currentItem); - } - notifyDataSetChanged(); - } - }); - - switch (type) { - case PROFILE: - ApplicationModeBean modeBean = (ApplicationModeBean) currentItem; - String profileName = modeBean.userProfileName; - if (Algorithms.isEmpty(profileName)) { - ApplicationMode appMode = ApplicationMode.valueOfStringKey(modeBean.stringKey, null); - profileName = app.getString(appMode.getNameKeyResource()); - } - title.setText(profileName); - String routingProfile = ""; - String routingProfileValue = modeBean.routingProfile; - if (!routingProfileValue.isEmpty()) { - try { - routingProfile = app.getString(RoutingProfilesResources.valueOf(routingProfileValue.toUpperCase()).getStringRes()); - routingProfile = Algorithms.capitalizeFirstLetterAndLowercase(routingProfile); - } catch (IllegalArgumentException e) { - routingProfile = Algorithms.capitalizeFirstLetterAndLowercase(routingProfileValue); - LOG.error("Error trying to get routing resource for " + routingProfileValue + "\n" + e); - } - } - if (!Algorithms.isEmpty(routingProfile)) { - subText.setText(String.format( - app.getString(R.string.ltr_or_rtl_combine_via_colon), - app.getString(R.string.nav_type_hint), - routingProfile)); - subText.setVisibility(View.VISIBLE); - } - int profileIconRes = AndroidUtils.getDrawableId(app, modeBean.iconName); - ProfileIconColors iconColor = modeBean.iconColor; - icon.setImageDrawable(uiUtilities.getIcon(profileIconRes, iconColor.getColor(nightMode))); - break; - case QUICK_ACTIONS: - title.setText(((QuickAction) currentItem).getName(app.getApplicationContext())); - setupIcon(icon, ((QuickAction) currentItem).getIconRes(), itemSelected); - break; - case POI_TYPES: - title.setText(((PoiUIFilter) currentItem).getName()); - int iconRes = RenderingIcons.getBigIconResourceId(((PoiUIFilter) currentItem).getIconId()); - setupIcon(icon, iconRes != 0 ? iconRes : R.drawable.ic_action_user, itemSelected); - break; - case MAP_SOURCES: - title.setText(((ITileSource) currentItem).getName()); - setupIcon(icon, R.drawable.ic_map, itemSelected); - break; - case CUSTOM_RENDER_STYLE: - String renderName = ((File) currentItem).getName(); - renderName = renderName.replace('_', ' ').replaceAll(IndexConstants.RENDERER_INDEX_EXT, ""); - title.setText(renderName); - setupIcon(icon, R.drawable.ic_action_map_style, itemSelected); - break; - case CUSTOM_ROUTING: - String routingName = ((File) currentItem).getName(); - routingName = routingName.replace('_', ' ').replaceAll(".xml", ""); - title.setText(routingName); - setupIcon(icon, R.drawable.ic_action_route_distance, itemSelected); - break; - case AVOID_ROADS: - AvoidRoadInfo avoidRoadInfo = (AvoidRoadInfo) currentItem; - title.setText(avoidRoadInfo.name); - setupIcon(icon, R.drawable.ic_action_alert, itemSelected); - break; - case MULTIMEDIA_NOTES: - File file = (File) currentItem; - title.setText(file.getName()); - int iconId = AudioVideoNotesPlugin.getIconIdForRecordingFile(file); - if (iconId == -1) { - iconId = R.drawable.ic_action_photo_dark; - } - setupIcon(icon, iconId, itemSelected); - break; - case TRACKS: - String fileName = ((File) currentItem).getName(); - title.setText(GpxUiHelper.getGpxTitle(fileName)); - setupIcon(icon, R.drawable.ic_action_route_distance, itemSelected); - break; - case GLOBAL: - String name = ((GlobalSettingsItem) currentItem).getPublicName(app); - title.setText(name); - setupIcon(icon, R.drawable.ic_action_settings, itemSelected); - break; - case OSM_NOTES: - title.setText(((OsmNotesPoint) currentItem).getText()); - setupIcon(icon, R.drawable.ic_action_osm_note_add, itemSelected); - break; - case OSM_EDITS: - title.setText(OsmEditingPlugin.getTitle((OpenstreetmapPoint) currentItem, app)); - setupIcon(icon, R.drawable.ic_action_info_dark, itemSelected); - break; - case OFFLINE_MAPS: - long size; - if (currentItem instanceof FileSettingsItem) { - FileSettingsItem currentFileItem = (FileSettingsItem) currentItem; - file = currentFileItem.getFile(); - size = currentFileItem.getSize(); - } else { - file = (File) currentItem; - size = file.length(); - } - title.setText(FileNameTranslationHelper.getFileNameWithRegion(app, file.getName())); - FileSubtype subtype = FileSubtype.getSubtypeByPath(app, file.getPath()); - setupIcon(icon, subtype.getIconId(), itemSelected); - subText.setText(AndroidUtils.formatSize(app, size)); - subText.setVisibility(View.VISIBLE); - break; - case FAVORITES: - FavoriteGroup favoriteGroup = (FavoriteGroup) currentItem; - title.setText(favoriteGroup.getDisplayName(app)); - setupIcon(icon, R.drawable.ic_action_favorite, itemSelected); - break; - case TTS_VOICE: - case VOICE: - file = (File) currentItem; - title.setText(FileNameTranslationHelper.getFileNameWithRegion(app, file.getName())); - setupIcon(icon, R.drawable.ic_action_volume_up, itemSelected); - break; - case ACTIVE_MARKERS: - title.setText(R.string.map_markers); - setupIcon(icon, R.drawable.ic_action_flag, itemSelected); - break; - case HISTORY_MARKERS: - title.setText(R.string.markers_history); - setupIcon(icon, R.drawable.ic_action_flag, itemSelected); - break; - case SEARCH_HISTORY: - HistoryEntry historyEntry = (HistoryEntry) currentItem; - title.setText(historyEntry.getName().getName()); - break; - default: - return child; - } - return child; - } - - @Override - public int getGroupCount() { - return itemsTypes.size(); - } - - @Override - public int getChildrenCount(int i) { - return itemsMap.get(itemsTypes.get(i)).size(); - } - - @Override - public Object getGroup(int i) { - return itemsMap.get(itemsTypes.get(i)); - } - - @Override - public Object getChild(int groupPosition, int childPosition) { - return itemsMap.get(itemsTypes.get(groupPosition)).get(childPosition); - } - - @Override - public long getGroupId(int i) { - return i; - } - - @Override - public long getChildId(int groupPosition, int childPosition) { - return groupPosition * 10000 + childPosition; - } - - @Override - public boolean hasStableIds() { - return false; - } - - @Override - public boolean isChildSelectable(int i, int i1) { - return true; - } - - private String getSelectedItemsAmount(List listItems, ExportSettingsType type) { - int amount = 0; - long amountSize = 0; - for (Object item : listItems) { - if (data.contains(item)) { - amount++; - if (type == OFFLINE_MAPS) { - if (item instanceof FileSettingsItem) { - amountSize += ((FileSettingsItem) item).getSize(); - } else { - amountSize += ((File) item).length(); - } - } - } - } - String itemsOf = app.getString(R.string.n_items_of_z, String.valueOf(amount), String.valueOf(listItems.size())); - return amountSize == 0 ? itemsOf : app.getString(R.string.ltr_or_rtl_combine_via_bold_point, itemsOf, - AndroidUtils.formatSize(app, amountSize)); - } - - private int getGroupTitle(ExportSettingsType type) { - switch (type) { - case PROFILE: - return R.string.shared_string_profiles; - case QUICK_ACTIONS: - return R.string.configure_screen_quick_action; - case POI_TYPES: - return R.string.poi_dialog_poi_type; - case MAP_SOURCES: - return R.string.quick_action_map_source_title; - case CUSTOM_RENDER_STYLE: - return R.string.shared_string_rendering_style; - case CUSTOM_ROUTING: - return R.string.shared_string_routing; - case AVOID_ROADS: - return R.string.avoid_road; - case TRACKS: - return R.string.shared_string_tracks; - case MULTIMEDIA_NOTES: - return R.string.audionotes_plugin_name; - case GLOBAL: - return R.string.general_settings_2; - case OSM_NOTES: - return R.string.osm_notes; - case OSM_EDITS: - return R.string.osm_edits; - case OFFLINE_MAPS: - return R.string.shared_string_maps; - case FAVORITES: - return R.string.shared_string_favorites; - case TTS_VOICE: - return R.string.local_indexes_cat_tts; - case VOICE: - return R.string.local_indexes_cat_voice; - case ACTIVE_MARKERS: - return R.string.map_markers; - case HISTORY_MARKERS: - return R.string.markers_history; - case SEARCH_HISTORY: - return R.string.shared_string_search_history; - default: - return R.string.access_empty_list; - } - } - - private void setupIcon(ImageView icon, int iconRes, boolean itemSelected) { - if (itemSelected) { - icon.setImageDrawable(uiUtilities.getIcon(iconRes, activeColorRes)); - } else { - icon.setImageDrawable(uiUtilities.getIcon(iconRes, nightMode)); - } - } - - public void updateSettingsList(Map> itemsMap) { - this.itemsMap = itemsMap; - this.itemsTypes = new ArrayList<>(itemsMap.keySet()); - Collections.sort(itemsTypes); - notifyDataSetChanged(); - } - - public void clearSettingsList() { - this.itemsMap.clear(); - this.itemsTypes.clear(); - notifyDataSetChanged(); - } - - public void selectAll(boolean selectAll) { - data.clear(); - if (selectAll) { - for (List values : itemsMap.values()) { - data.addAll(values); - } - } - notifyDataSetChanged(); - } - - List getData() { - return this.data; - } -} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ExportSettingsAdapter.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ExportSettingsAdapter.java index 452573091e..d479b78a7c 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ExportSettingsAdapter.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ExportSettingsAdapter.java @@ -38,7 +38,7 @@ import static net.osmand.view.ThreeStateCheckbox.State.UNCHECKED; public class ExportSettingsAdapter extends OsmandBaseExpandableListAdapter { - private static final Log LOG = PlatformUtil.getLog(ExportImportSettingsAdapter.class.getName()); + private static final Log LOG = PlatformUtil.getLog(ExportSettingsAdapter.class.getName()); private final OsmandApplication app; private final UiUtilities uiUtilities; @@ -240,6 +240,13 @@ public class ExportSettingsAdapter extends OsmandBaseExpandableListAdapter { notifyDataSetChanged(); } + public void clearSettingsList() { + this.itemsMap.clear(); + this.itemsTypes.clear(); + this.selectedItemsMap.clear(); + notifyDataSetChanged(); + } + public boolean hasSelectedData() { return !selectedItemsMap.isEmpty(); } diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ExportSettingsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ExportSettingsFragment.java index 77960d9cef..f831fd8fdd 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ExportSettingsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ExportSettingsFragment.java @@ -4,24 +4,16 @@ import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.widget.ExpandableListView; -import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.Toolbar; -import androidx.core.content.ContextCompat; -import androidx.core.view.ViewCompat; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; @@ -31,36 +23,23 @@ import net.osmand.AndroidUtils; import net.osmand.FileUtils; import net.osmand.IndexConstants; import net.osmand.PlatformUtil; -import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; -import net.osmand.plus.UiUtilities; -import net.osmand.plus.UiUtilities.DialogButtonType; -import net.osmand.plus.base.BaseOsmAndFragment; -import net.osmand.plus.helpers.AndroidUiHelper; import net.osmand.plus.settings.backend.ApplicationMode; -import net.osmand.plus.settings.backend.ExportSettingsCategory; -import net.osmand.plus.settings.backend.ExportSettingsType; import net.osmand.plus.settings.backend.backup.FileSettingsItem; import net.osmand.plus.settings.backend.backup.SettingsHelper.SettingsExportListener; import net.osmand.plus.settings.backend.backup.SettingsItem; -import net.osmand.plus.settings.fragments.ExportSettingsAdapter.OnItemSelectedListener; -import net.osmand.plus.widgets.TextViewEx; import org.apache.commons.logging.Log; import java.io.File; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; -import java.util.Map; import static net.osmand.plus.settings.fragments.BaseSettingsFragment.APP_MODE_KEY; -public class ExportSettingsFragment extends BaseOsmAndFragment implements OnItemSelectedListener { +public class ExportSettingsFragment extends BaseSettingsListFragment { public static final String TAG = ImportSettingsFragment.class.getSimpleName(); public static final Log LOG = PlatformUtil.getLog(ImportSettingsFragment.class.getSimpleName()); @@ -76,45 +55,19 @@ public class ExportSettingsFragment extends BaseOsmAndFragment implements OnItem private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd-MM-yy", Locale.US); - private OsmandApplication app; - - private Map> selectedItemsMap = new HashMap<>(); - private Map dataList = new LinkedHashMap<>(); - private ProgressDialog progress; private ApplicationMode appMode; private SettingsExportListener exportListener; - private View continueBtn; - private View headerShadow; - private View headerDivider; - private View itemsSizeContainer; - private View availableSpaceContainer; - private TextViewEx selectedItemsSize; - private TextViewEx availableSpaceDescr; - private LinearLayout buttonsContainer; - private ExpandableListView expandableList; - private ExportSettingsAdapter adapter; - private int progressMax; private int progressValue; - private long exportStartTime; - - private boolean nightMode; private boolean globalExport; private boolean exportingStarted; - @Override - public int getStatusBarColorId() { - return nightMode ? R.color.status_bar_color_dark : R.color.status_bar_color_light; - } - @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - app = requireMyApplication(); - nightMode = !app.getSettings().isLightContent(); if (savedInstanceState != null) { appMode = ApplicationMode.valueOfStringKey(savedInstanceState.getString(APP_MODE_KEY), null); globalExport = savedInstanceState.getBoolean(GLOBAL_EXPORT_KEY); @@ -136,64 +89,19 @@ public class ExportSettingsFragment extends BaseOsmAndFragment implements OnItem @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - LayoutInflater themedInflater = UiUtilities.getInflater(app, nightMode); - View root = themedInflater.inflate(R.layout.fragment_import, container, false); - AndroidUtils.addStatusBarPadding21v(app, root); + View view = super.onCreateView(inflater, container, savedInstanceState); - selectedItemsSize = root.findViewById(R.id.file_size); - itemsSizeContainer = root.findViewById(R.id.file_size_container); - expandableList = root.findViewById(R.id.list); - buttonsContainer = root.findViewById(R.id.buttons_container); - - Toolbar toolbar = root.findViewById(R.id.toolbar); - setupToolbar(toolbar); - ViewCompat.setNestedScrollingEnabled(expandableList, true); - - View header = themedInflater.inflate(R.layout.list_item_description_header, null); - headerDivider = header.findViewById(R.id.divider); - headerShadow = header.findViewById(R.id.card_bottom_divider); - expandableList.addHeaderView(header); - - availableSpaceContainer = themedInflater.inflate(R.layout.enough_space_warning_card, null); - availableSpaceDescr = availableSpaceContainer.findViewById(R.id.warning_descr); - - continueBtn = root.findViewById(R.id.continue_button); - UiUtilities.setupDialogButton(nightMode, continueBtn, DialogButtonType.PRIMARY, getString(R.string.shared_string_continue)); - continueBtn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - prepareFile(); - } - }); - - ViewTreeObserver treeObserver = buttonsContainer.getViewTreeObserver(); - treeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - if (buttonsContainer != null) { - ViewTreeObserver vts = buttonsContainer.getViewTreeObserver(); - int height = buttonsContainer.getMeasuredHeight(); - expandableList.setPadding(0, 0, 0, height); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - vts.removeOnGlobalLayoutListener(this); - } else { - vts.removeGlobalOnLayoutListener(this); - } - } - } - }); - - adapter = new ExportSettingsAdapter(app, this, nightMode); - adapter.updateSettingsItems(dataList, selectedItemsMap); - expandableList.setAdapter(adapter); - - CollapsingToolbarLayout toolbarLayout = root.findViewById(R.id.toolbar_layout); + CollapsingToolbarLayout toolbarLayout = view.findViewById(R.id.toolbar_layout); toolbarLayout.setTitle(getString(R.string.shared_string_export)); TextView description = header.findViewById(R.id.description); description.setText(R.string.select_data_to_export); - updateAvailableSpace(); - return root; + return view; + } + + @Override + protected void onContinueButtonClickAction() { + prepareFile(); } @Override @@ -224,105 +132,6 @@ public class ExportSettingsFragment extends BaseOsmAndFragment implements OnItem } } - private void dismissFragment() { - FragmentManager fm = getFragmentManager(); - if (fm != null && !fm.isStateSaved()) { - getFragmentManager().popBackStack(EXPORT_SETTINGS_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); - } - } - - public void showExitDialog() { - Context themedContext = UiUtilities.getThemedContext(getActivity(), nightMode); - AlertDialog.Builder dismissDialog = new AlertDialog.Builder(themedContext); - dismissDialog.setTitle(getString(R.string.shared_string_dismiss)); - dismissDialog.setMessage(getString(R.string.exit_without_saving)); - dismissDialog.setNegativeButton(R.string.shared_string_cancel, null); - dismissDialog.setPositiveButton(R.string.shared_string_exit, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dismissFragment(); - } - }); - dismissDialog.show(); - } - - private void setupToolbar(Toolbar toolbar) { - int color = ContextCompat.getColor(app, nightMode ? R.color.active_buttons_and_links_text_dark : R.color.active_buttons_and_links_text_light); - toolbar.setNavigationIcon(getPaintedContentIcon(R.drawable.ic_action_close, color)); - toolbar.setNavigationContentDescription(R.string.shared_string_close); - toolbar.setNavigationOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - showExitDialog(); - } - }); - } - - private void updateAvailableSpace() { - long calculatedSize = ExportSettingsAdapter.calculateItemsSize(adapter.getData()); - if (calculatedSize != 0) { - selectedItemsSize.setText(AndroidUtils.formatSize(app, calculatedSize)); - - File dir = app.getAppPath("").getParentFile(); - long availableSizeBytes = AndroidUtils.getAvailableSpace(dir); - if (calculatedSize > availableSizeBytes) { - String availableSize = AndroidUtils.formatSize(app, availableSizeBytes); - availableSpaceDescr.setText(getString(R.string.export_not_enough_space_descr, availableSize)); - updateWarningHeaderVisibility(true); - continueBtn.setEnabled(false); - } else { - updateWarningHeaderVisibility(false); - continueBtn.setEnabled(adapter.hasSelectedData()); - } - itemsSizeContainer.setVisibility(View.VISIBLE); - } else { - updateWarningHeaderVisibility(false); - itemsSizeContainer.setVisibility(View.INVISIBLE); - continueBtn.setEnabled(adapter.hasSelectedData()); - } - } - - private void updateWarningHeaderVisibility(boolean visible) { - if (visible) { - if (expandableList.getHeaderViewsCount() < 2) { - expandableList.addHeaderView(availableSpaceContainer); - } - AndroidUiHelper.updateVisibility(headerShadow, false); - AndroidUiHelper.updateVisibility(headerDivider, true); - } else { - expandableList.removeHeaderView(availableSpaceContainer); - AndroidUiHelper.updateVisibility(headerShadow, true); - AndroidUiHelper.updateVisibility(headerDivider, false); - } - } - - @Override - public void onCategorySelected(ExportSettingsCategory category, boolean selected) { - SettingsCategoryItems categoryItems = dataList.get(category); - for (ExportSettingsType type : categoryItems.getTypes()) { - List selectedItems = selected ? categoryItems.getItemsForType(type) : new ArrayList<>(); - selectedItemsMap.put(type, selectedItems); - } - updateAvailableSpace(); - } - - @Override - public void onItemsSelected(ExportSettingsType type, List selectedItems) { - selectedItemsMap.put(type, selectedItems); - adapter.notifyDataSetChanged(); - updateAvailableSpace(); - } - - @Override - public void onTypeClicked(ExportSettingsCategory category, ExportSettingsType type) { - FragmentManager fragmentManager = getFragmentManager(); - if (fragmentManager != null && type != ExportSettingsType.GLOBAL && type != ExportSettingsType.SEARCH_HISTORY) { - List items = (List) dataList.get(category).getItemsForType(type); - List selectedItems = (List) selectedItemsMap.get(type); - ExportItemsBottomSheet.showInstance(type, selectedItems, items, fragmentManager, this); - } - } - private void prepareFile() { if (app != null) { exportingStarted = true; @@ -467,7 +276,7 @@ public class ExportSettingsFragment extends BaseOsmAndFragment implements OnItem fragment.globalExport = globalExport; fragmentManager.beginTransaction(). replace(R.id.fragmentContainer, fragment, TAG) - .addToBackStack(EXPORT_SETTINGS_TAG) + .addToBackStack(SETTINGS_LIST_TAG) .commitAllowingStateLoss(); return true; } catch (RuntimeException e) { diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportCompleteFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportCompleteFragment.java index 68087cf71f..8893fb3f97 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportCompleteFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportCompleteFragment.java @@ -40,7 +40,7 @@ import net.osmand.plus.settings.backend.backup.SettingsItem; import java.util.List; import static net.osmand.aidlapi.OsmAndCustomizationConstants.DRAWER_SETTINGS_ID; -import static net.osmand.plus.settings.fragments.ImportSettingsFragment.IMPORT_SETTINGS_TAG; +import static net.osmand.plus.settings.fragments.BaseSettingsListFragment.SETTINGS_LIST_TAG; public class ImportCompleteFragment extends BaseOsmAndFragment { public static final String TAG = ImportCompleteFragment.class.getSimpleName(); @@ -58,7 +58,7 @@ public class ImportCompleteFragment extends BaseOsmAndFragment { fragment.setRetainInstance(true); fm.beginTransaction() .replace(R.id.fragmentContainer, fragment, TAG) - .addToBackStack(IMPORT_SETTINGS_TAG) + .addToBackStack(SETTINGS_LIST_TAG) .commitAllowingStateLoss(); } @@ -137,7 +137,7 @@ public class ImportCompleteFragment extends BaseOsmAndFragment { public void dismissFragment() { FragmentManager fm = getFragmentManager(); if (fm != null && !fm.isStateSaved()) { - fm.popBackStack(IMPORT_SETTINGS_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); + fm.popBackStack(SETTINGS_LIST_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); } } diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportDuplicatesFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportDuplicatesFragment.java index 184f71452f..4d75a27e4d 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportDuplicatesFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportDuplicatesFragment.java @@ -51,7 +51,7 @@ import java.util.ArrayList; import java.util.List; import static net.osmand.plus.settings.backend.backup.FileSettingsItem.FileSubtype; -import static net.osmand.plus.settings.fragments.ImportSettingsFragment.IMPORT_SETTINGS_TAG; +import static net.osmand.plus.settings.fragments.BaseSettingsListFragment.SETTINGS_LIST_TAG; public class ImportDuplicatesFragment extends BaseOsmAndFragment { @@ -79,7 +79,7 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment { fragment.setFile(file); fm.beginTransaction() .replace(R.id.fragmentContainer, fragment, TAG) - .addToBackStack(IMPORT_SETTINGS_TAG) + .addToBackStack(SETTINGS_LIST_TAG) .commitAllowingStateLoss(); } diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java index a6f7dd73fd..bb629308b9 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java @@ -1,18 +1,13 @@ package net.osmand.plus.settings.fragments; import android.app.Activity; -import android.content.Context; -import android.content.DialogInterface; import android.graphics.Typeface; import android.os.AsyncTask; -import android.os.Build; import android.os.Bundle; import android.text.style.StyleSpan; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.widget.ExpandableListView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; @@ -20,27 +15,21 @@ import android.widget.TextView; import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.Toolbar; -import androidx.core.view.ViewCompat; import androidx.fragment.app.FragmentManager; import com.google.android.material.appbar.CollapsingToolbarLayout; -import net.osmand.AndroidUtils; -import net.osmand.IProgress; import net.osmand.PlatformUtil; import net.osmand.map.ITileSource; import net.osmand.map.TileSourceManager.TileSourceTemplate; import net.osmand.plus.AppInitializer; import net.osmand.plus.FavouritesDbHelper.FavoriteGroup; -import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.SQLiteTileSource; import net.osmand.plus.UiUtilities; -import net.osmand.plus.UiUtilities.DialogButtonType; import net.osmand.plus.activities.MapActivity; -import net.osmand.plus.base.BaseOsmAndFragment; +import net.osmand.plus.download.ReloadIndexesTask; +import net.osmand.plus.download.ReloadIndexesTask.ReloadIndexesListener; import net.osmand.plus.helpers.AvoidSpecificRoads.AvoidRoadInfo; import net.osmand.plus.helpers.SearchHistoryHelper.HistoryEntry; import net.osmand.plus.mapmarkers.MapMarker; @@ -66,7 +55,6 @@ import net.osmand.plus.settings.backend.backup.QuickActionsSettingsItem; import net.osmand.plus.settings.backend.backup.SearchHistorySettingsItem; import net.osmand.plus.settings.backend.backup.SettingsHelper; import net.osmand.plus.settings.backend.backup.SettingsHelper.ImportAsyncTask; -import net.osmand.plus.settings.backend.backup.SettingsHelper.ImportType; import net.osmand.plus.settings.backend.backup.SettingsItem; import net.osmand.plus.settings.backend.backup.SettingsItemType; import net.osmand.util.Algorithms; @@ -76,30 +64,25 @@ import org.apache.commons.logging.Log; import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.EnumMap; -import java.util.HashMap; import java.util.List; -import java.util.Map; -public class ImportSettingsFragment extends BaseOsmAndFragment { +public class ImportSettingsFragment extends BaseSettingsListFragment { public static final String TAG = ImportSettingsFragment.class.getSimpleName(); public static final Log LOG = PlatformUtil.getLog(ImportSettingsFragment.class.getSimpleName()); + private static final String DUPLICATES_START_TIME_KEY = "duplicates_start_time"; private static final long MIN_DELAY_TIME_MS = 500; - static final String IMPORT_SETTINGS_TAG = "import_settings_tag"; - private OsmandApplication app; - private ExportImportSettingsAdapter adapter; - private ExpandableListView expandableList; - private TextView description; - private List settingsItems; + private File file; - private boolean allSelected; - private boolean nightMode; - private LinearLayout buttonsContainer; - private ProgressBar progressBar; - private CollapsingToolbarLayout toolbarLayout; private SettingsHelper settingsHelper; + private List settingsItems; + + private TextView description; + private ProgressBar progressBar; + private LinearLayout buttonsContainer; + private CollapsingToolbarLayout toolbarLayout; + private long duplicateStartTime; public static void showInstance(@NonNull FragmentManager fm, @NonNull List settingsItems, @NonNull File file) { @@ -108,7 +91,7 @@ public class ImportSettingsFragment extends BaseOsmAndFragment { fragment.setFile(file); fm.beginTransaction(). replace(R.id.fragmentContainer, fragment, TAG) - .addToBackStack(IMPORT_SETTINGS_TAG) + .addToBackStack(SETTINGS_LIST_TAG) .commitAllowingStateLoss(); } @@ -118,70 +101,14 @@ public class ImportSettingsFragment extends BaseOsmAndFragment { if (savedInstanceState != null) { duplicateStartTime = savedInstanceState.getLong(DUPLICATES_START_TIME_KEY); } - app = requireMyApplication(); settingsHelper = app.getSettingsHelper(); - nightMode = !app.getSettings().isLightContent(); requireActivity().getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { @Override public void handleOnBackPressed() { showExitDialog(); } }); - } - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - inflater = UiUtilities.getInflater(app, nightMode); - View root = inflater.inflate(R.layout.fragment_import, container, false); - Toolbar toolbar = root.findViewById(R.id.toolbar); - View continueBtn = root.findViewById(R.id.continue_button); - toolbarLayout = root.findViewById(R.id.toolbar_layout); - expandableList = root.findViewById(R.id.list); - buttonsContainer = root.findViewById(R.id.buttons_container); - progressBar = root.findViewById(R.id.progress_bar); - setupToolbar(toolbar); - ViewCompat.setNestedScrollingEnabled(expandableList, true); - View header = inflater.inflate(R.layout.list_item_description_header, null); - description = header.findViewById(R.id.description); - description.setText(R.string.select_data_to_import); - expandableList.addHeaderView(header); - UiUtilities.setupDialogButton(nightMode, continueBtn, DialogButtonType.PRIMARY, getString(R.string.shared_string_continue)); - continueBtn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (adapter.getData().isEmpty()) { - app.showShortToastMessage(getString(R.string.shared_string_nothing_selected)); - } else { - importItems(); - } - } - }); - if (Build.VERSION.SDK_INT >= 21) { - AndroidUtils.addStatusBarPadding21v(app, root); - } - ViewTreeObserver treeObserver = buttonsContainer.getViewTreeObserver(); - treeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - if (buttonsContainer != null) { - ViewTreeObserver vts = buttonsContainer.getViewTreeObserver(); - int height = buttonsContainer.getMeasuredHeight(); - expandableList.setPadding(0, 0, 0, height); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - vts.removeOnGlobalLayoutListener(this); - } else { - vts.removeGlobalOnLayoutListener(this); - } - } - } - }); - return root; - } - - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); ImportAsyncTask importTask = settingsHelper.getImportTask(); if (importTask != null) { if (settingsItems == null) { @@ -200,27 +127,24 @@ public class ImportSettingsFragment extends BaseOsmAndFragment { } } } - - adapter = new ExportImportSettingsAdapter(app, nightMode, true); - Map> itemsMap = new EnumMap<>(ExportSettingsType.class); if (settingsItems != null) { - itemsMap = SettingsHelper.getSettingsToOperate(settingsItems, false); - adapter.updateSettingsList(itemsMap); + dataList = SettingsHelper.getSettingsToOperateByCategory(settingsItems, false); } - expandableList.setAdapter(adapter); - toolbarLayout.setTitle(getString(R.string.shared_string_import)); + } - ImportType importTaskType = settingsHelper.getImportTaskType(); - if (importTaskType == ImportType.CHECK_DUPLICATES && !settingsHelper.isImportDone()) { - updateUi(R.string.shared_string_preparing, R.string.checking_for_duplicate_description); - } else if (importTaskType == ImportType.IMPORT) { - updateUi(R.string.shared_string_importing, R.string.importing_from); - } else { - toolbarLayout.setTitle(getString(R.string.shared_string_import)); - } - if (itemsMap.size() == 1 && itemsMap.containsKey(ExportSettingsType.PROFILE)) { - expandableList.expandGroup(0); - } + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = super.onCreateView(inflater, container, savedInstanceState); + + toolbarLayout = view.findViewById(R.id.toolbar_layout); + buttonsContainer = view.findViewById(R.id.buttons_container); + progressBar = view.findViewById(R.id.progress_bar); + + description = header.findViewById(R.id.description); + description.setText(R.string.select_data_to_import); + + return view; } @Override @@ -230,11 +154,11 @@ public class ImportSettingsFragment extends BaseOsmAndFragment { } @Override - public void onResume() { - super.onResume(); - Activity activity = getActivity(); - if (activity instanceof MapActivity) { - ((MapActivity) activity).closeDrawer(); + protected void onContinueButtonClickAction() { + if (adapter.getData().isEmpty()) { + app.showShortToastMessage(getString(R.string.shared_string_nothing_selected)); + } else { + importItems(); } } @@ -253,12 +177,12 @@ public class ImportSettingsFragment extends BaseOsmAndFragment { } private void importItems() { - updateUi(R.string.shared_string_preparing, R.string.checking_for_duplicate_description); List selectedItems = getSettingsItemsFromData(adapter.getData()); if (file != null && settingsItems != null) { duplicateStartTime = System.currentTimeMillis(); settingsHelper.checkDuplicates(file, settingsItems, selectedItems, getDuplicatesListener()); } + updateUi(R.string.shared_string_preparing, R.string.checking_for_duplicate_description); } public SettingsHelper.SettingsImportListener getImportListener() { @@ -283,38 +207,29 @@ public class ImportSettingsFragment extends BaseOsmAndFragment { if (item instanceof FileSettingsItem && ((FileSettingsItem) item).getSubtype().isMap()) { Activity activity = getActivity(); if (activity instanceof MapActivity) { - new ReloadIndexesTack((MapActivity) activity).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + final WeakReference mapActivityRef = new WeakReference<>((MapActivity) activity); + ReloadIndexesListener listener = new ReloadIndexesListener() { + @Override + public void reloadIndexesStarted() { + + } + + @Override + public void reloadIndexesFinished(List warnings) { + MapActivity mapActivity = mapActivityRef.get(); + if (mapActivity != null) { + mapActivity.refreshMap(); + } + } + }; + ReloadIndexesTask reloadIndexesTask = new ReloadIndexesTask(app, listener); + reloadIndexesTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } break; } } } - private static class ReloadIndexesTack extends AsyncTask { - - private final WeakReference mapActivityRef; - private final OsmandApplication app; - - ReloadIndexesTack(@NonNull MapActivity mapActivity) { - this.mapActivityRef = new WeakReference<>(mapActivity); - this.app = mapActivity.getMyApplication(); - } - - @Override - protected Void doInBackground(Void[] params) { - app.getResourceManager().reloadIndexes(IProgress.EMPTY_PROGRESS, new ArrayList()); - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - MapActivity mapActivity = mapActivityRef.get(); - if (mapActivity != null) { - mapActivity.refreshMap(); - } - } - } - private SettingsHelper.CheckDuplicatesListener getDuplicatesListener() { return new SettingsHelper.CheckDuplicatesListener() { @Override @@ -349,13 +264,6 @@ public class ImportSettingsFragment extends BaseOsmAndFragment { } } - private void dismissFragment() { - FragmentManager fm = getFragmentManager(); - if (fm != null && !fm.isStateSaved()) { - getFragmentManager().popBackStack(IMPORT_SETTINGS_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); - } - } - public void setSettingsItems(List settingsItems) { this.settingsItems = settingsItems; } @@ -523,39 +431,6 @@ public class ImportSettingsFragment extends BaseOsmAndFragment { return settingsItems; } - @Override - public int getStatusBarColorId() { - return nightMode ? R.color.status_bar_color_dark : R.color.status_bar_color_light; - } - - public void showExitDialog() { - Context themedContext = UiUtilities.getThemedContext(getActivity(), nightMode); - AlertDialog.Builder dismissDialog = new AlertDialog.Builder(themedContext); - dismissDialog.setTitle(getString(R.string.shared_string_dismiss)); - dismissDialog.setMessage(getString(R.string.exit_without_saving)); - dismissDialog.setNegativeButton(R.string.shared_string_cancel, null); - dismissDialog.setPositiveButton(R.string.shared_string_exit, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dismissFragment(); - } - }); - dismissDialog.show(); - } - - private void setupToolbar(Toolbar toolbar) { - toolbar.setNavigationIcon(getPaintedContentIcon(R.drawable.ic_action_close, nightMode - ? getResources().getColor(R.color.active_buttons_and_links_text_dark) - : getResources().getColor(R.color.active_buttons_and_links_text_light))); - toolbar.setNavigationContentDescription(R.string.shared_string_close); - toolbar.setNavigationOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - showExitDialog(); - } - }); - } - public void setFile(File file) { this.file = file; } From 5a91901eeefca5c4176940b423f2155dc358a0b2 Mon Sep 17 00:00:00 2001 From: simon Date: Mon, 23 Nov 2020 11:02:02 +0200 Subject: [PATCH 10/16] conflict resolved --- OsmAnd/res/layout/fragment_opr_login.xml | 181 +++++++++--------- OsmAnd/res/values/strings.xml | 29 ++- .../plus/mapcontextmenu/MenuBuilder.java | 150 ++++++++------- .../openplacereviews/OprStartFragment.java | 3 - 4 files changed, 183 insertions(+), 180 deletions(-) diff --git a/OsmAnd/res/layout/fragment_opr_login.xml b/OsmAnd/res/layout/fragment_opr_login.xml index b8877dc710..e941075f54 100644 --- a/OsmAnd/res/layout/fragment_opr_login.xml +++ b/OsmAnd/res/layout/fragment_opr_login.xml @@ -1,114 +1,105 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + android:layout_height="@dimen/dialog_button_height" + android:layout_marginBottom="@dimen/content_padding_small" /> + android:layout_height="@dimen/dialog_button_height" /> - \ No newline at end of file + + \ No newline at end of file diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index aaa8241710..72fb10e393 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -11,6 +11,19 @@ Thx - Hardy --> + OsmAnd shows photos from several sources:\nOpenPlaceReviews - POI photos;\nMapillary - street-level imagery;\nWeb / Wikimedia - POI photos specified in OpenStreetMap data. + Use dev.openstreetmap.org + Switch to use "dev.openstreetmap.org" instead of "openstreetmap.org" to testing uploading OSM Note / POI / GPX. + Add to OpenPlaceReviews + Add to Mapillary + Select items that will be imported. + Select groups that will be imported. + There is not enough space + Your device only has %1$s free. Please free up some space or unselect some items to export. + Needed for import + Select the data to be exported to the file. + Approximate file size + Resources Select picture Cannot upload image, please, try again later Motorboat @@ -18,10 +31,10 @@ Search history I already have an account Create new account - Log in on the open data project website OpenPlaceReviews.org to upload even more photos. + Photos are provided by open data project OpenPlaceReviews.org. In order to upload your photos you need to sign up on website. Register on\nOpenPlaceReviews.org Add photo - Log in using the safe OAuth method or use your username and password. + You can log in using the safe OAuth method or use your login and password. Comment OSM Note Close OSM Note \"Trackable\" means the trace does not show up in any public listings, but processed trackpoints with timestamps from it (that can\'t be associated with you directly) do through downloads from the public GPS API. @@ -36,15 +49,15 @@ OsmAnd Live subscription has been expired There is a problem with your subscription. Click the button to go to the Google Play subscription settings to fix your payment method. Manage subscription - Username + Login Password Account - Log in with username and password - Log in to upload new or modified changes,\n\neither with OAuth or using your username and password. + Use login and password + You need to login to upload new or modified changes. \n\nYou can log in using the safe OAuth method or use your login and password. You can view all your not yet uploaded edits or OSM bugs in %1$s. Uploaded points don’t show in OsmAnd. - Log in with OpenStreetMap - Login for OpenStreetMap.org - Login for OpenStreetMap + Sign in with OpenStreetMap + Login to OpenStreetMap.org + Login to OpenStreetMap These plugin setting are global, and apply to all profiles You need to add at least two points Travel diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java index 4d8ff2d548..8a82f4d96b 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java @@ -51,6 +51,7 @@ import net.osmand.plus.mapcontextmenu.builders.cards.ImageCard; import net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCardsTask; import net.osmand.plus.mapcontextmenu.builders.cards.NoImagesCard; import net.osmand.plus.mapcontextmenu.controllers.TransportStopController; +import net.osmand.plus.openplacereviews.AddPhotosBottomSheetDialogFragment; import net.osmand.plus.openplacereviews.OPRWebviewActivity; import net.osmand.plus.openplacereviews.OprStartFragment; import net.osmand.plus.osmedit.opr.OpenDBAPI; @@ -78,6 +79,8 @@ import static net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCa public class MenuBuilder { + private static final int PICK_IMAGE = 1231; + private static final Log LOG = PlatformUtil.getLog(MenuBuilder.class); public static final float SHADOW_HEIGHT_TOP_DP = 17f; public static final int TITLE_LIMIT = 60; protected static final String[] arrowChars = new String[] {"=>", " - "}; @@ -107,10 +110,7 @@ public class MenuBuilder { private String preferredMapAppLang; private boolean transliterateNames; - private static final int PICK_IMAGE = 1231; - private static final Log LOG = PlatformUtil.getLog(MenuBuilder.class); - private View view; - private OpenDBAPI openDBAPI = new OpenDBAPI(); + private final OpenDBAPI openDBAPI = new OpenDBAPI(); private String[] placeId = new String[0]; private GetImageCardsListener imageCardListener = new GetImageCardsListener() { @Override @@ -126,8 +126,7 @@ public class MenuBuilder { @Override public void onFinish(List cardList) { if (!isHidden()) { - List cards = new ArrayList<>(); - cards.addAll(cardList); + List cards = new ArrayList(cardList); if (cardList.size() == 0) { cards.add(new NoImagesCard(mapActivity)); } @@ -386,19 +385,7 @@ public class MenuBuilder { b.setOnClickListener(new OnClickListener() { @Override public void onClick(final View view) { - mapActivity.registerActivityResultListener(new ActivityResultListener(PICK_IMAGE, - new ActivityResultListener.OnActivityResultListener() { - @Override - public void onResult(int resultCode, Intent resultData) { - InputStream inputStream = null; - try { - inputStream = mapActivity.getContentResolver().openInputStream(resultData.getData()); - } catch (Exception e) { - LOG.error(e); - } - handleSelectedImage(view, inputStream); - } - })); + registerResultListener(view); final String privateKey = OPRWebviewActivity.getPrivateKeyFromCookie(); final String name = OPRWebviewActivity.getUsernameFromCookie(); if (privateKey == null || privateKey.isEmpty()) { @@ -408,7 +395,7 @@ public class MenuBuilder { new Thread(new Runnable() { @Override public void run() { - if (openDBAPI.checkPrivateKeyValid(name,privateKey)){ + if (openDBAPI.checkPrivateKeyValid(name, privateKey)) { app.runInUIThread(new Runnable() { @Override public void run() { @@ -419,64 +406,12 @@ public class MenuBuilder { mapActivity.getString(R.string.select_picture)), PICK_IMAGE); } }); - } - else { + } else { OprStartFragment.showInstance(mapActivity.getSupportFragmentManager()); } } }).start(); } - - private void handleSelectedImage(final View view, final InputStream image) { - Thread t = new Thread(new Runnable() { - @Override - public void run() { - try { - uploadImageToPlace(view, image); - } catch (Exception e) { - LOG.error(e); - } - } - }); - t.start(); - } - - private void uploadImageToPlace(View view, InputStream image) { - //compress image - BufferedInputStream bufferedInputStream = new BufferedInputStream(image); - Bitmap bmp = BitmapFactory.decodeStream(bufferedInputStream); - ByteArrayOutputStream os = new ByteArrayOutputStream(); - bmp.compress(Bitmap.CompressFormat.PNG, 70, os); - byte[] buff = os.toByteArray(); - InputStream serverData = new ByteArrayInputStream(buff); - String url = BuildConfig.OPR_BASE_URL + "api/ipfs/image"; - String response = NetworkUtils.sendPostDataRequest(url, serverData); - if (response != null) { - int res = 0; - try { - res = openDBAPI.uploadImage( - placeId, - OPRWebviewActivity.getPrivateKeyFromCookie() -, OPRWebviewActivity.getUsernameFromCookie(), - response); - } catch (FailedVerificationException e) { - LOG.error(e); - app.showToastMessage(view.getResources().getString(R.string.cannot_upload_image)); - } - if (res != 200) { - //image was uploaded but not added to blockchain - app.showToastMessage(view.getResources().getString(R.string.cannot_upload_image)); - } else { - String str = MessageFormat.format(view.getResources() - .getString(R.string.successfully_uploaded_pattern), 1, 1); - app.showToastMessage(str); - //refresh the image - execute(new GetImageCardsTask(mapActivity, getLatLon(), getAdditionalCardParams(), imageCardListener)); - } - } else { - app.showToastMessage(view.getResources().getString(R.string.cannot_upload_image)); - } - } }); //TODO feature under development //b.setVisibility(View.GONE); @@ -493,6 +428,73 @@ public class MenuBuilder { false, null, false); } + private void registerResultListener(final View view) { + mapActivity.registerActivityResultListener(new ActivityResultListener(PICK_IMAGE, new ActivityResultListener. + OnActivityResultListener() { + @Override + public void onResult(int resultCode, Intent resultData) { + handleSelectedImage(view, resultData.getData()); + } + })); + } + + private void handleSelectedImage(final View view, final Uri uri) { + Thread t = new Thread(new Runnable() { + @Override + public void run() { + InputStream inputStream = null; + try { + inputStream = app.getContentResolver().openInputStream(uri); + if (inputStream != null) { + uploadImageToPlace(view, inputStream); + } + } catch (Exception e) { + LOG.error(e); + } finally { + Algorithms.closeStream(inputStream); + } + } + }); + t.start(); + } + + private void uploadImageToPlace(View view, InputStream image) { + InputStream serverData = new ByteArrayInputStream(compressImage(image)); + String url = BuildConfig.OPR_BASE_URL + "api/ipfs/image"; + String response = NetworkUtils.sendPostDataRequest(url, serverData); + if (response != null) { + int res = 0; + try { + res = openDBAPI.uploadImage( + placeId, + OPRWebviewActivity.getPrivateKeyFromCookie(), + OPRWebviewActivity.getUsernameFromCookie(), + response); + } catch (FailedVerificationException e) { + LOG.error(e); + app.showToastMessage(R.string.cannot_upload_image); + } + if (res != 200) { + //image was uploaded but not added to blockchain + app.showToastMessage(R.string.cannot_upload_image); + } else { + app.showToastMessage(R.string.successfully_uploaded_pattern, 1, 1); + //refresh the image + execute(new GetImageCardsTask(mapActivity, getLatLon(), getAdditionalCardParams(), imageCardListener)); + } + } else { + app.showToastMessage(R.string.cannot_upload_image); + } + } + + private byte[] compressImage(InputStream image) { + BufferedInputStream bufferedInputStream = new BufferedInputStream(image); + Bitmap bmp = BitmapFactory.decodeStream(bufferedInputStream); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + bmp.compress(Bitmap.CompressFormat.PNG, 70, os); + return os.toByteArray(); + } + private void startLoadingImages() { if (onlinePhotoCardsRow == null) { return; @@ -500,7 +502,7 @@ public class MenuBuilder { startLoadingImagesTask(); } - private void startLoadingImagesTask(){ + private void startLoadingImagesTask() { onlinePhotoCards = new ArrayList<>(); onlinePhotoCardsRow.setProgressCard(); execute(new GetImageCardsTask(mapActivity, getLatLon(), getAdditionalCardParams(), imageCardListener)); diff --git a/OsmAnd/src/net/osmand/plus/openplacereviews/OprStartFragment.java b/OsmAnd/src/net/osmand/plus/openplacereviews/OprStartFragment.java index 19a4ba57be..2f936cf21e 100644 --- a/OsmAnd/src/net/osmand/plus/openplacereviews/OprStartFragment.java +++ b/OsmAnd/src/net/osmand/plus/openplacereviews/OprStartFragment.java @@ -12,17 +12,14 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; - import net.osmand.PlatformUtil; import net.osmand.plus.R; import net.osmand.plus.UiUtilities; import net.osmand.plus.base.BaseOsmAndFragment; - import org.apache.commons.logging.Log; public class OprStartFragment extends BaseOsmAndFragment { From 0462f635a8ebf04e5f8b64a9697eedad6481149a Mon Sep 17 00:00:00 2001 From: simon Date: Mon, 23 Nov 2020 11:17:18 +0200 Subject: [PATCH 11/16] visibility updated --- OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java index 8a82f4d96b..b0a5897ceb 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java @@ -414,7 +414,7 @@ public class MenuBuilder { } }); //TODO feature under development - //b.setVisibility(View.GONE); + b.setVisibility(View.GONE); b.setTextColor(ContextCompat.getColor(context, R.color.preference_category_title)); return b; } From 74aace6fa9c417d5f3579825920010d0e26e10fb Mon Sep 17 00:00:00 2001 From: max-klaus Date: Mon, 23 Nov 2020 15:25:12 +0300 Subject: [PATCH 12/16] Move oauth consts to no_translate --- OsmAnd/build.gradle | 2 - OsmAnd/no_translate.xml | 5 +++ .../src/net/osmand/plus/OsmAndConstants.java | 7 +-- .../openplacereviews/OPRWebviewActivity.java | 43 ++++++++++++------- .../oauth/OsmOAuthAuthorizationAdapter.java | 9 ++-- 5 files changed, 39 insertions(+), 27 deletions(-) diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index 42642fc2cb..13803c8a45 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -197,11 +197,9 @@ android { buildTypes { debug { - buildConfigField "String", "OPR_BASE_URL", "\"https://test.openplacereviews.org/\"" signingConfig signingConfigs.development } release { - buildConfigField "String", "OPR_BASE_URL", "\"https://test.openplacereviews.org/\"" signingConfig signingConfigs.publishing } } diff --git a/OsmAnd/no_translate.xml b/OsmAnd/no_translate.xml index 719b6ee8fe..20d7a424ca 100644 --- a/OsmAnd/no_translate.xml +++ b/OsmAnd/no_translate.xml @@ -41,4 +41,9 @@ items modified OsmAnd Unlimited Markers + https://test.openplacereviews.org/ + v8G8r9NLJZGMV4he5lwbQlz620FNVARKjI9Bm5UJ + jDvM95Ne1Bq2BDTmIfB6b3ZMxvdK87WGfp6DC07J + Ti2qq3fo4i4Wmuox3SiWRIGq3obZisBHnxmcM05y + lxulb3HYoMmd2cC4xxNe1dyfRMAY8dS0eNihJ0DM diff --git a/OsmAnd/src/net/osmand/plus/OsmAndConstants.java b/OsmAnd/src/net/osmand/plus/OsmAndConstants.java index 16a5312405..aee7b58bfa 100644 --- a/OsmAnd/src/net/osmand/plus/OsmAndConstants.java +++ b/OsmAnd/src/net/osmand/plus/OsmAndConstants.java @@ -12,10 +12,5 @@ public interface OsmAndConstants { public int UI_HANDLER_PROGRESS = 6000; public int UI_HANDLER_SEARCH = 7000; - - String OSM_OAUTH_DEVELOPER_KEY = "v8G8r9NLJZGMV4he5lwbQlz620FNVARKjI9Bm5UJ"; - String OSM_OAUTH_DEVELOPER_SECRET = "jDvM95Ne1Bq2BDTmIfB6b3ZMxvdK87WGfp6DC07J"; - String OSM_OAUTH_CONSUMER_KEY = "Ti2qq3fo4i4Wmuox3SiWRIGq3obZisBHnxmcM05y"; - String OSM_OAUTH_CONSUMER_SECRET = "lxulb3HYoMmd2cC4xxNe1dyfRMAY8dS0eNihJ0DM"; - + } diff --git a/OsmAnd/src/net/osmand/plus/openplacereviews/OPRWebviewActivity.java b/OsmAnd/src/net/osmand/plus/openplacereviews/OPRWebviewActivity.java index ebcf32e4ec..7cd6f5bc38 100644 --- a/OsmAnd/src/net/osmand/plus/openplacereviews/OPRWebviewActivity.java +++ b/OsmAnd/src/net/osmand/plus/openplacereviews/OPRWebviewActivity.java @@ -1,6 +1,7 @@ package net.osmand.plus.openplacereviews; +import android.content.Context; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -8,24 +9,36 @@ import android.webkit.CookieManager; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.TextView; + import androidx.appcompat.widget.Toolbar; import androidx.core.content.ContextCompat; + import net.osmand.AndroidUtils; -import net.osmand.plus.BuildConfig; import net.osmand.plus.R; import net.osmand.plus.activities.OsmandActionBarActivity; public class OPRWebviewActivity extends OsmandActionBarActivity { public static final String KEY_LOGIN = "LOGIN_KEY"; - private static final String url = BuildConfig.OPR_BASE_URL; - private static final String cookieUrl = BuildConfig.OPR_BASE_URL + "profile"; - private static final String loginUrl = BuildConfig.OPR_BASE_URL + "login"; - private static final String registerUrl = BuildConfig.OPR_BASE_URL + "signup"; - private static final String finishUrl = cookieUrl; public static String KEY_TITLE = "TITLE_KEY"; private WebView webView; private boolean isLogin = false; + public static String getCookieUrl(Context ctx) { + return ctx.getString(R.string.opr_base_url) + "profile"; + } + + public static String getLoginUrl(Context ctx) { + return ctx.getString(R.string.opr_base_url) + "login"; + } + + public static String getRegisterUrl(Context ctx) { + return ctx.getString(R.string.opr_base_url) + "signup"; + } + + public static String getFinishUrl(Context ctx) { + return getCookieUrl(ctx); + } + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_opr_webview); @@ -46,9 +59,9 @@ public class OPRWebviewActivity extends OsmandActionBarActivity { if (b != null) { isLogin = b.getBoolean(KEY_LOGIN); if (isLogin) { - webView.loadUrl(loginUrl); + webView.loadUrl(getLoginUrl(this)); } else { - webView.loadUrl(registerUrl); + webView.loadUrl(getRegisterUrl(this)); } } } @@ -59,18 +72,18 @@ public class OPRWebviewActivity extends OsmandActionBarActivity { return true; } - public static String getPrivateKeyFromCookie() { - return returnCookieByKey("opr-token"); + public static String getPrivateKeyFromCookie(Context ctx) { + return returnCookieByKey(ctx, "opr-token"); } - public static String getUsernameFromCookie() { - return returnCookieByKey("opr-nickname"); + public static String getUsernameFromCookie(Context ctx) { + return returnCookieByKey(ctx, "opr-nickname"); } - private static String returnCookieByKey(String key) { + private static String returnCookieByKey(Context ctx, String key) { String CookieValue = null; CookieManager cookieManager = CookieManager.getInstance(); - String cookies = cookieManager.getCookie(cookieUrl); + String cookies = cookieManager.getCookie(getCookieUrl(ctx)); if (cookies == null || cookies.isEmpty()) { return ""; } @@ -88,7 +101,7 @@ public class OPRWebviewActivity extends OsmandActionBarActivity { public class CloseOnSuccessWebViewClient extends WebViewClient { @Override public void onPageFinished(WebView view, String url) { - if (url.contains(finishUrl) && isLogin) { + if (url.contains(getFinishUrl(OPRWebviewActivity.this)) && isLogin) { finish(); } super.onPageFinished(view, url); diff --git a/OsmAnd/src/net/osmand/plus/osmedit/oauth/OsmOAuthAuthorizationAdapter.java b/OsmAnd/src/net/osmand/plus/osmedit/oauth/OsmOAuthAuthorizationAdapter.java index 63ca96b968..5df7bfb765 100644 --- a/OsmAnd/src/net/osmand/plus/osmedit/oauth/OsmOAuthAuthorizationAdapter.java +++ b/OsmAnd/src/net/osmand/plus/osmedit/oauth/OsmOAuthAuthorizationAdapter.java @@ -19,6 +19,7 @@ import net.osmand.PlatformUtil; import net.osmand.osm.oauth.OsmOAuthAuthorizationClient; import net.osmand.plus.OsmAndConstants; import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; import org.apache.commons.logging.Log; import org.xmlpull.v1.XmlPullParser; @@ -45,12 +46,12 @@ public class OsmOAuthAuthorizationAdapter { String secret; if (app.getSettings().USE_DEV_URL.get()) { api10a = new OsmOAuthAuthorizationClient.OsmDevApi(); - key = OsmAndConstants.OSM_OAUTH_DEVELOPER_KEY; - secret = OsmAndConstants.OSM_OAUTH_DEVELOPER_SECRET; + key = app.getString(R.string.osm_oauth_developer_key); + secret = app.getString(R.string.osm_oauth_developer_secret); } else { api10a = new OsmOAuthAuthorizationClient.OsmApi(); - key = OsmAndConstants.OSM_OAUTH_CONSUMER_KEY; - secret = OsmAndConstants.OSM_OAUTH_CONSUMER_SECRET; + key = app.getString(R.string.osm_oauth_consumer_key); + secret = app.getString(R.string.osm_oauth_consumer_secret); } client = new OsmOAuthAuthorizationClient(key, secret, api10a); restoreToken(); From ee4ea6de8dffc87c1a62dd46a5725bb726200603 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Mon, 23 Nov 2020 14:59:00 +0200 Subject: [PATCH 13/16] Fix #10233 --- .../plus/helpers/SearchHistoryHelper.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/helpers/SearchHistoryHelper.java b/OsmAnd/src/net/osmand/plus/helpers/SearchHistoryHelper.java index 0f4af1f68d..693227f434 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/SearchHistoryHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/SearchHistoryHelper.java @@ -376,7 +376,10 @@ public class SearchHistoryHelper { SQLiteConnection db = openConnection(false); if (db != null) { try { - removeQuery(e.getSerializedName(), db); + db.execSQL("DELETE FROM " + HISTORY_TABLE_NAME + " WHERE " + + HISTORY_COL_NAME + " = ? AND " + + HISTORY_COL_LAT + " = ? AND " + HISTORY_COL_LON + " = ?", + new Object[] {e.getSerializedName(), e.getLat(), e.getLon()}); } finally { db.close(); } @@ -385,11 +388,6 @@ public class SearchHistoryHelper { return false; } - private void removeQuery(String name, SQLiteConnection db) { - db.execSQL("DELETE FROM " + HISTORY_TABLE_NAME + " WHERE " + HISTORY_COL_NAME + " = ?", - new Object[]{name}); - } - public boolean removeAll() { SQLiteConnection db = openConnection(false); if (db != null) { @@ -411,9 +409,10 @@ public class SearchHistoryHelper { "UPDATE " + HISTORY_TABLE_NAME + " SET " + HISTORY_COL_TIME + "= ? " + ", " + HISTORY_COL_FREQ_INTERVALS + " = ? " + ", " + HISTORY_COL_FREQ_VALUES + "= ? WHERE " + - HISTORY_COL_NAME + " = ?", - new Object[]{e.getLastAccessTime(), e.getIntervals(), e.getIntervalsValues(), - e.getSerializedName()}); + HISTORY_COL_NAME + " = ? AND " + + HISTORY_COL_LAT + " = ? AND " + HISTORY_COL_LON + " = ?", + new Object[] {e.getLastAccessTime(), e.getIntervals(), e.getIntervalsValues(), + e.getSerializedName(), e.getLat(), e.getLon()}); } finally { db.close(); } From 5b6e0928a0e018dbe0f6f93f1704fdcbf8d1a19c Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Mon, 23 Nov 2020 15:13:44 +0200 Subject: [PATCH 14/16] Fix #10255 --- .../plus/routepreparationmenu/RouteDetailsFragment.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/routepreparationmenu/RouteDetailsFragment.java b/OsmAnd/src/net/osmand/plus/routepreparationmenu/RouteDetailsFragment.java index 780186b068..0747c650c5 100644 --- a/OsmAnd/src/net/osmand/plus/routepreparationmenu/RouteDetailsFragment.java +++ b/OsmAnd/src/net/osmand/plus/routepreparationmenu/RouteDetailsFragment.java @@ -27,8 +27,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; -import com.github.mikephil.charting.charts.LineChart; - import net.osmand.AndroidUtils; import net.osmand.GPXUtilities.GPXFile; import net.osmand.GPXUtilities.GPXTrackAnalysis; @@ -41,7 +39,6 @@ import net.osmand.data.QuadRect; import net.osmand.data.TransportRoute; import net.osmand.data.TransportStop; import net.osmand.plus.GeocodingLookupService; -import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup; import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem; import net.osmand.plus.OsmAndFormatter; import net.osmand.plus.OsmandApplication; @@ -82,8 +79,8 @@ import net.osmand.render.RenderingRulesStorage; import net.osmand.router.RouteSegmentResult; import net.osmand.router.RouteStatisticsHelper; import net.osmand.router.RouteStatisticsHelper.RouteStatistics; -import net.osmand.router.TransportRouteResult; import net.osmand.router.TransportRoutePlanner.TransportRouteResultSegment; +import net.osmand.router.TransportRouteResult; import net.osmand.util.Algorithms; import java.lang.ref.WeakReference; @@ -371,7 +368,9 @@ public class RouteDetailsFragment extends ContextMenuFragment protected void calculateLayout(View view, boolean initLayout) { super.calculateLayout(view, initLayout); if (!initLayout && getCurrentMenuState() != MenuState.FULL_SCREEN) { - refreshMapCallback.refreshMap(false); + if (refreshMapCallback != null) { + refreshMapCallback.refreshMap(false); + } } } From c7f7a9efdca4a93e2a1054f0c751242a8a4144ff Mon Sep 17 00:00:00 2001 From: simon Date: Mon, 23 Nov 2020 15:49:19 +0200 Subject: [PATCH 15/16] condition added --- .../plus/mapcontextmenu/MenuBuilder.java | 62 +++++++++---------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java index b0a5897ceb..0a6fa296d0 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java @@ -6,11 +6,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.ColorStateList; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.Typeface; +import android.graphics.*; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.net.Uri; @@ -64,7 +60,6 @@ import net.osmand.plus.widgets.TextViewEx; import net.osmand.plus.widgets.tools.ClickableSpanTouchListener; import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; - import org.apache.commons.logging.Log; import org.openplacereviews.opendb.util.exception.FailedVerificationException; @@ -72,7 +67,6 @@ import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; -import java.text.MessageFormat; import java.util.*; import static net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCardsTask.GetImageCardsListener; @@ -385,32 +379,36 @@ public class MenuBuilder { b.setOnClickListener(new OnClickListener() { @Override public void onClick(final View view) { - registerResultListener(view); - final String privateKey = OPRWebviewActivity.getPrivateKeyFromCookie(); - final String name = OPRWebviewActivity.getUsernameFromCookie(); - if (privateKey == null || privateKey.isEmpty()) { - OprStartFragment.showInstance(mapActivity.getSupportFragmentManager()); - return; - } - new Thread(new Runnable() { - @Override - public void run() { - if (openDBAPI.checkPrivateKeyValid(name, privateKey)) { - app.runInUIThread(new Runnable() { - @Override - public void run() { - Intent intent = new Intent(); - intent.setType("image/*"); - intent.setAction(Intent.ACTION_GET_CONTENT); - mapActivity.startActivityForResult(Intent.createChooser(intent, - mapActivity.getString(R.string.select_picture)), PICK_IMAGE); - } - }); - } else { - OprStartFragment.showInstance(mapActivity.getSupportFragmentManager()); - } + if (false) { + AddPhotosBottomSheetDialogFragment.showInstance(mapActivity.getSupportFragmentManager()); + } else { + registerResultListener(view); + final String privateKey = OPRWebviewActivity.getPrivateKeyFromCookie(); + final String name = OPRWebviewActivity.getUsernameFromCookie(); + if (privateKey == null || privateKey.isEmpty()) { + OprStartFragment.showInstance(mapActivity.getSupportFragmentManager()); + return; } - }).start(); + new Thread(new Runnable() { + @Override + public void run() { + if (openDBAPI.checkPrivateKeyValid(name, privateKey)) { + app.runInUIThread(new Runnable() { + @Override + public void run() { + Intent intent = new Intent(); + intent.setType("image/*"); + intent.setAction(Intent.ACTION_GET_CONTENT); + mapActivity.startActivityForResult(Intent.createChooser(intent, + mapActivity.getString(R.string.select_picture)), PICK_IMAGE); + } + }); + } else { + OprStartFragment.showInstance(mapActivity.getSupportFragmentManager()); + } + } + }).start(); + } } }); //TODO feature under development From 21447af2c3d23e8c38268418a721431fd2f82471 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Mon, 23 Nov 2020 17:05:16 +0200 Subject: [PATCH 16/16] Revert "Plan Route Fix UI margins" --- OsmAnd/res/layout/custom_radio_buttons.xml | 22 +++++--- .../res/layout/fragment_measurement_tool.xml | 33 ++++-------- OsmAnd/src/net/osmand/plus/UiUtilities.java | 33 +++++------- .../other/HorizontalSelectionAdapter.java | 2 - .../other/TrackDetailsMenu.java | 8 +-- .../MeasurementToolFragment.java | 54 +++++-------------- 6 files changed, 51 insertions(+), 101 deletions(-) diff --git a/OsmAnd/res/layout/custom_radio_buttons.xml b/OsmAnd/res/layout/custom_radio_buttons.xml index 460356e875..d768e58004 100644 --- a/OsmAnd/res/layout/custom_radio_buttons.xml +++ b/OsmAnd/res/layout/custom_radio_buttons.xml @@ -26,18 +26,27 @@ android:background="?attr/selectableItemBackground" android:gravity="center" android:textSize="@dimen/default_desc_text_size" - android:textColor="@drawable/radio_flat_text_selector_light" osmand:typeface="@string/font_roboto_medium" tools:text="@string/shared_string_left"/> + android:layout_weight="1" + android:visibility="gone"> + + @@ -53,9 +62,8 @@ android:layout_height="match_parent" android:background="?attr/selectableItemBackground" android:gravity="center" - android:textColor="@drawable/radio_flat_text_selector_light" - osmand:typeface="@string/font_roboto_medium" android:textSize="@dimen/default_desc_text_size" + osmand:typeface="@string/font_roboto_medium" tools:text="@string/shared_string_right"/> diff --git a/OsmAnd/res/layout/fragment_measurement_tool.xml b/OsmAnd/res/layout/fragment_measurement_tool.xml index 82cd085ec9..2fe4dc1416 100644 --- a/OsmAnd/res/layout/fragment_measurement_tool.xml +++ b/OsmAnd/res/layout/fragment_measurement_tool.xml @@ -30,23 +30,22 @@ + tools:src="@drawable/ic_action_ruler"/> - - - + + @@ -160,11 +150,6 @@ - - items, - final AdapterView.OnItemClickListener listener) { + View v, int minWidth, + List items, + final AdapterView.OnItemClickListener listener) { int contentPadding = themedCtx.getResources().getDimensionPixelSize(R.dimen.content_padding); int contentPaddingHalf = themedCtx.getResources().getDimensionPixelSize(R.dimen.content_padding_half); int defaultListTextSize = themedCtx.getResources().getDimensionPixelSize(R.dimen.default_list_text_size); diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java index 28cea2eb1e..06783d6f50 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java @@ -64,8 +64,6 @@ public class HorizontalSelectionAdapter extends RecyclerView.Adapter