kv : section.entrySet()) {
+ byte[] key = kv.getKey().getBytes(StandardCharsets.US_ASCII);
+ out.writeByte(key.length);
+ out.write(key);
+ write(kv.getValue());
+ }
+ }
+
+ private void writeVarint(long i) throws IOException {
+ if (i <= 63) {
+ out.writeByte(((int) i << 2) | Section.PORTABLE_RAW_SIZE_MARK_BYTE);
+ } else if (i <= 16383) {
+ out.writeShort(((int) i << 2) | Section.PORTABLE_RAW_SIZE_MARK_WORD);
+ } else if (i <= 1073741823) {
+ out.writeInt(((int) i << 2) | Section.PORTABLE_RAW_SIZE_MARK_DWORD);
+ } else {
+ if (i > 4611686018427387903L)
+ throw new IllegalArgumentException();
+ out.writeLong((i << 2) | Section.PORTABLE_RAW_SIZE_MARK_INT64);
+ }
+ }
+
+ private void write(Object object) throws IOException {
+ if (object instanceof byte[]) {
+ byte[] value = (byte[]) object;
+ out.writeByte(Section.SERIALIZE_TYPE_STRING);
+ writeVarint(value.length);
+ out.write(value);
+ } else if (object instanceof String) {
+ byte[] value = ((String) object)
+ .getBytes(StandardCharsets.US_ASCII);
+ out.writeByte(Section.SERIALIZE_TYPE_STRING);
+ writeVarint(value.length);
+ out.write(value);
+ } else if (object instanceof Integer) {
+ out.writeByte(Section.SERIALIZE_TYPE_UINT32);
+ out.writeInt((int) object);
+ } else if (object instanceof Long) {
+ out.writeByte(Section.SERIALIZE_TYPE_UINT64);
+ out.writeLong((long) object);
+ } else if (object instanceof Byte) {
+ out.writeByte(Section.SERIALIZE_TYPE_UINT8);
+ out.writeByte((byte) object);
+ } else if (object instanceof Section) {
+ writeSection((Section) object);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+}
diff --git a/app/src/main/java/com/m2049r/levin/util/LittleEndianDataInputStream.java b/app/src/main/java/com/m2049r/levin/util/LittleEndianDataInputStream.java
new file mode 100644
index 00000000..3924eebd
--- /dev/null
+++ b/app/src/main/java/com/m2049r/levin/util/LittleEndianDataInputStream.java
@@ -0,0 +1,564 @@
+/*
+ * Copyright (c) 2018 m2049r
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.m2049r.levin.util;
+
+import java.io.DataInput;
+import java.io.EOFException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UTFDataFormatException;
+
+/**
+ * A little endian java.io.DataInputStream (without readLine())
+ */
+
+public class LittleEndianDataInputStream extends FilterInputStream implements
+ DataInput {
+
+ /**
+ * Creates a DataInputStream that uses the specified underlying InputStream.
+ *
+ * @param in the specified input stream
+ */
+ public LittleEndianDataInputStream(InputStream in) {
+ super(in);
+ }
+
+ @Deprecated
+ public final String readLine() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Reads some number of bytes from the contained input stream and stores
+ * them into the buffer array b
. The number of bytes actually
+ * read is returned as an integer. This method blocks until input data is
+ * available, end of file is detected, or an exception is thrown.
+ *
+ *
+ * If b
is null, a NullPointerException
is thrown.
+ * If the length of b
is zero, then no bytes are read and
+ * 0
is returned; otherwise, there is an attempt to read at
+ * least one byte. If no byte is available because the stream is at end of
+ * file, the value -1
is returned; otherwise, at least one byte
+ * is read and stored into b
.
+ *
+ *
+ * The first byte read is stored into element b[0]
, the next
+ * one into b[1]
, and so on. The number of bytes read is, at
+ * most, equal to the length of b
. Let k
be the
+ * number of bytes actually read; these bytes will be stored in elements
+ * b[0]
through b[k-1]
, leaving elements
+ * b[k]
through b[b.length-1]
unaffected.
+ *
+ *
+ * The read(b)
method has the same effect as:
+ *
+ *
+ * read(b, 0, b.length)
+ *
+ *
+ *
+ *
+ * @param b the buffer into which the data is read.
+ * @return the total number of bytes read into the buffer, or
+ * -1
if there is no more data because the end of the
+ * stream has been reached.
+ * @throws IOException if the first byte cannot be read for any reason other than
+ * end of file, the stream has been closed and the underlying
+ * input stream does not support reading after close, or
+ * another I/O error occurs.
+ * @see FilterInputStream#in
+ * @see InputStream#read(byte[], int, int)
+ */
+ public final int read(byte b[]) throws IOException {
+ return in.read(b, 0, b.length);
+ }
+
+ /**
+ * Reads up to len
bytes of data from the contained input
+ * stream into an array of bytes. An attempt is made to read as many as
+ * len
bytes, but a smaller number may be read, possibly zero.
+ * The number of bytes actually read is returned as an integer.
+ *
+ *
+ * This method blocks until input data is available, end of file is
+ * detected, or an exception is thrown.
+ *
+ *
+ * If len
is zero, then no bytes are read and 0
is
+ * returned; otherwise, there is an attempt to read at least one byte. If no
+ * byte is available because the stream is at end of file, the value
+ * -1
is returned; otherwise, at least one byte is read and
+ * stored into b
.
+ *
+ *
+ * The first byte read is stored into element b[off]
, the next
+ * one into b[off+1]
, and so on. The number of bytes read is,
+ * at most, equal to len
. Let k be the number of bytes
+ * actually read; these bytes will be stored in elements b[off]
+ * through b[off+
k-1]
, leaving elements
+ * b[off+
k]
through
+ * b[off+len-1]
unaffected.
+ *
+ *
+ * In every case, elements b[0]
through b[off]
and
+ * elements b[off+len]
through b[b.length-1]
are
+ * unaffected.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset in the destination array b
+ * @param len the maximum number of bytes read.
+ * @return the total number of bytes read into the buffer, or
+ * -1
if there is no more data because the end of the
+ * stream has been reached.
+ * @throws NullPointerException If b
is null
.
+ * @throws IndexOutOfBoundsException If off
is negative, len
is
+ * negative, or len
is greater than
+ * b.length - off
+ * @throws IOException if the first byte cannot be read for any reason other than
+ * end of file, the stream has been closed and the underlying
+ * input stream does not support reading after close, or
+ * another I/O error occurs.
+ * @see FilterInputStream#in
+ * @see InputStream#read(byte[], int, int)
+ */
+ public final int read(byte b[], int off, int len) throws IOException {
+ return in.read(b, off, len);
+ }
+
+ /**
+ * See the general contract of the readFully
method of
+ * DataInput
.
+ *
+ * Bytes for this operation are read from the contained input stream.
+ *
+ * @param b the buffer into which the data is read.
+ * @throws EOFException if this input stream reaches the end before reading all
+ * the bytes.
+ * @throws IOException the stream has been closed and the contained input stream
+ * does not support reading after close, or another I/O error
+ * occurs.
+ * @see FilterInputStream#in
+ */
+ public final void readFully(byte b[]) throws IOException {
+ readFully(b, 0, b.length);
+ }
+
+ /**
+ * See the general contract of the readFully
method of
+ * DataInput
.
+ *
+ * Bytes for this operation are read from the contained input stream.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset of the data.
+ * @param len the number of bytes to read.
+ * @throws EOFException if this input stream reaches the end before reading all
+ * the bytes.
+ * @throws IOException the stream has been closed and the contained input stream
+ * does not support reading after close, or another I/O error
+ * occurs.
+ * @see FilterInputStream#in
+ */
+ public final void readFully(byte b[], int off, int len) throws IOException {
+ if (len < 0)
+ throw new IndexOutOfBoundsException();
+ int n = 0;
+ while (n < len) {
+ int count = in.read(b, off + n, len - n);
+ if (count < 0)
+ throw new EOFException();
+ n += count;
+ }
+ }
+
+ /**
+ * See the general contract of the skipBytes
method of
+ * DataInput
.
+ *
+ * Bytes for this operation are read from the contained input stream.
+ *
+ * @param n the number of bytes to be skipped.
+ * @return the actual number of bytes skipped.
+ * @throws IOException if the contained input stream does not support seek, or
+ * the stream has been closed and the contained input stream
+ * does not support reading after close, or another I/O error
+ * occurs.
+ */
+ public final int skipBytes(int n) throws IOException {
+ int total = 0;
+ int cur = 0;
+
+ while ((total < n) && ((cur = (int) in.skip(n - total)) > 0)) {
+ total += cur;
+ }
+
+ return total;
+ }
+
+ /**
+ * See the general contract of the readBoolean
method of
+ * DataInput
.
+ *
+ * Bytes for this operation are read from the contained input stream.
+ *
+ * @return the boolean
value read.
+ * @throws EOFException if this input stream has reached the end.
+ * @throws IOException the stream has been closed and the contained input stream
+ * does not support reading after close, or another I/O error
+ * occurs.
+ * @see FilterInputStream#in
+ */
+ public final boolean readBoolean() throws IOException {
+ int ch = in.read();
+ if (ch < 0)
+ throw new EOFException();
+ return (ch != 0);
+ }
+
+ /**
+ * See the general contract of the readByte
method of
+ * DataInput
.
+ *
+ * Bytes for this operation are read from the contained input stream.
+ *
+ * @return the next byte of this input stream as a signed 8-bit
+ * byte
.
+ * @throws EOFException if this input stream has reached the end.
+ * @throws IOException the stream has been closed and the contained input stream
+ * does not support reading after close, or another I/O error
+ * occurs.
+ * @see FilterInputStream#in
+ */
+ public final byte readByte() throws IOException {
+ int ch = in.read();
+ if (ch < 0)
+ throw new EOFException();
+ return (byte) (ch);
+ }
+
+ /**
+ * See the general contract of the readUnsignedByte
method of
+ * DataInput
.
+ *
+ * Bytes for this operation are read from the contained input stream.
+ *
+ * @return the next byte of this input stream, interpreted as an unsigned
+ * 8-bit number.
+ * @throws EOFException if this input stream has reached the end.
+ * @throws IOException the stream has been closed and the contained input stream
+ * does not support reading after close, or another I/O error
+ * occurs.
+ * @see FilterInputStream#in
+ */
+ public final int readUnsignedByte() throws IOException {
+ int ch = in.read();
+ if (ch < 0)
+ throw new EOFException();
+ return ch;
+ }
+
+ /**
+ * See the general contract of the readShort
method of
+ * DataInput
.
+ *
+ * Bytes for this operation are read from the contained input stream.
+ *
+ * @return the next two bytes of this input stream, interpreted as a signed
+ * 16-bit number.
+ * @throws EOFException if this input stream reaches the end before reading two
+ * bytes.
+ * @throws IOException the stream has been closed and the contained input stream
+ * does not support reading after close, or another I/O error
+ * occurs.
+ * @see FilterInputStream#in
+ */
+ public final short readShort() throws IOException {
+ int ch1 = in.read();
+ int ch2 = in.read();
+ if ((ch1 | ch2) < 0)
+ throw new EOFException();
+ return (short) ((ch1 << 0) + (ch2 << 8));
+ }
+
+ /**
+ * See the general contract of the readUnsignedShort
method of
+ * DataInput
.
+ *
+ * Bytes for this operation are read from the contained input stream.
+ *
+ * @return the next two bytes of this input stream, interpreted as an
+ * unsigned 16-bit integer.
+ * @throws EOFException if this input stream reaches the end before reading two
+ * bytes.
+ * @throws IOException the stream has been closed and the contained input stream
+ * does not support reading after close, or another I/O error
+ * occurs.
+ * @see FilterInputStream#in
+ */
+ public final int readUnsignedShort() throws IOException {
+ int ch1 = in.read();
+ int ch2 = in.read();
+ if ((ch1 | ch2) < 0)
+ throw new EOFException();
+ return (ch1 << 0) + (ch2 << 8);
+ }
+
+ /**
+ * See the general contract of the readChar
method of
+ * DataInput
.
+ *
+ * Bytes for this operation are read from the contained input stream.
+ *
+ * @return the next two bytes of this input stream, interpreted as a
+ * char
.
+ * @throws EOFException if this input stream reaches the end before reading two
+ * bytes.
+ * @throws IOException the stream has been closed and the contained input stream
+ * does not support reading after close, or another I/O error
+ * occurs.
+ * @see FilterInputStream#in
+ */
+ public final char readChar() throws IOException {
+ int ch1 = in.read();
+ int ch2 = in.read();
+ if ((ch1 | ch2) < 0)
+ throw new EOFException();
+ return (char) ((ch1 << 0) + (ch2 << 8));
+ }
+
+ /**
+ * See the general contract of the readInt
method of
+ * DataInput
.
+ *
+ * Bytes for this operation are read from the contained input stream.
+ *
+ * @return the next four bytes of this input stream, interpreted as an
+ * int
.
+ * @throws EOFException if this input stream reaches the end before reading four
+ * bytes.
+ * @throws IOException the stream has been closed and the contained input stream
+ * does not support reading after close, or another I/O error
+ * occurs.
+ * @see FilterInputStream#in
+ */
+ public final int readInt() throws IOException {
+ int ch1 = in.read();
+ int ch2 = in.read();
+ int ch3 = in.read();
+ int ch4 = in.read();
+ if ((ch1 | ch2 | ch3 | ch4) < 0)
+ throw new EOFException();
+ return ((ch1 << 0) + (ch2 << 8) + (ch3 << 16) + (ch4 << 24));
+ }
+
+ private byte readBuffer[] = new byte[8];
+
+ /**
+ * See the general contract of the readLong
method of
+ * DataInput
.
+ *
+ * Bytes for this operation are read from the contained input stream.
+ *
+ * @return the next eight bytes of this input stream, interpreted as a
+ * long
.
+ * @throws EOFException if this input stream reaches the end before reading eight
+ * bytes.
+ * @throws IOException the stream has been closed and the contained input stream
+ * does not support reading after close, or another I/O error
+ * occurs.
+ * @see FilterInputStream#in
+ */
+ public final long readLong() throws IOException {
+ readFully(readBuffer, 0, 8);
+ return (((long) readBuffer[7] << 56)
+ + ((long) (readBuffer[6] & 255) << 48)
+ + ((long) (readBuffer[5] & 255) << 40)
+ + ((long) (readBuffer[4] & 255) << 32)
+ + ((long) (readBuffer[3] & 255) << 24)
+ + ((readBuffer[2] & 255) << 16) + ((readBuffer[1] & 255) << 8) + ((readBuffer[0] & 255) << 0));
+ }
+
+ /**
+ * See the general contract of the readFloat
method of
+ * DataInput
.
+ *
+ * Bytes for this operation are read from the contained input stream.
+ *
+ * @return the next four bytes of this input stream, interpreted as a
+ * float
.
+ * @throws EOFException if this input stream reaches the end before reading four
+ * bytes.
+ * @throws IOException the stream has been closed and the contained input stream
+ * does not support reading after close, or another I/O error
+ * occurs.
+ * @see java.io.DataInputStream#readInt()
+ * @see Float#intBitsToFloat(int)
+ */
+ public final float readFloat() throws IOException {
+ return Float.intBitsToFloat(readInt());
+ }
+
+ /**
+ * See the general contract of the readDouble
method of
+ * DataInput
.
+ *
+ * Bytes for this operation are read from the contained input stream.
+ *
+ * @return the next eight bytes of this input stream, interpreted as a
+ * double
.
+ * @throws EOFException if this input stream reaches the end before reading eight
+ * bytes.
+ * @throws IOException the stream has been closed and the contained input stream
+ * does not support reading after close, or another I/O error
+ * occurs.
+ * @see java.io.DataInputStream#readLong()
+ * @see Double#longBitsToDouble(long)
+ */
+ public final double readDouble() throws IOException {
+ return Double.longBitsToDouble(readLong());
+ }
+
+ /**
+ * See the general contract of the readUTF
method of
+ * DataInput
.
+ *
+ * Bytes for this operation are read from the contained input stream.
+ *
+ * @return a Unicode string.
+ * @throws EOFException if this input stream reaches the end before reading all
+ * the bytes.
+ * @throws IOException the stream has been closed and the contained input stream
+ * does not support reading after close, or another I/O error
+ * occurs.
+ * @throws UTFDataFormatException if the bytes do not represent a valid modified UTF-8
+ * encoding of a string.
+ * @see java.io.DataInputStream#readUTF(DataInput)
+ */
+ public final String readUTF() throws IOException {
+ return readUTF(this);
+ }
+
+ /**
+ * working arrays initialized on demand by readUTF
+ */
+ private byte bytearr[] = new byte[80];
+ private char chararr[] = new char[80];
+
+ /**
+ * Reads from the stream in
a representation of a Unicode
+ * character string encoded in modified UTF-8 format; this
+ * string of characters is then returned as a String
. The
+ * details of the modified UTF-8 representation are exactly the same as for
+ * the readUTF
method of DataInput
.
+ *
+ * @param in a data input stream.
+ * @return a Unicode string.
+ * @throws EOFException if the input stream reaches the end before all the bytes.
+ * @throws IOException the stream has been closed and the contained input stream
+ * does not support reading after close, or another I/O error
+ * occurs.
+ * @throws UTFDataFormatException if the bytes do not represent a valid modified UTF-8
+ * encoding of a Unicode string.
+ * @see java.io.DataInputStream#readUnsignedShort()
+ */
+ public final static String readUTF(DataInput in) throws IOException {
+ int utflen = in.readUnsignedShort();
+ byte[] bytearr = null;
+ char[] chararr = null;
+ if (in instanceof LittleEndianDataInputStream) {
+ LittleEndianDataInputStream dis = (LittleEndianDataInputStream) in;
+ if (dis.bytearr.length < utflen) {
+ dis.bytearr = new byte[utflen * 2];
+ dis.chararr = new char[utflen * 2];
+ }
+ chararr = dis.chararr;
+ bytearr = dis.bytearr;
+ } else {
+ bytearr = new byte[utflen];
+ chararr = new char[utflen];
+ }
+
+ int c, char2, char3;
+ int count = 0;
+ int chararr_count = 0;
+
+ in.readFully(bytearr, 0, utflen);
+
+ while (count < utflen) {
+ c = (int) bytearr[count] & 0xff;
+ if (c > 127)
+ break;
+ count++;
+ chararr[chararr_count++] = (char) c;
+ }
+
+ while (count < utflen) {
+ c = (int) bytearr[count] & 0xff;
+ switch (c >> 4) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ /* 0xxxxxxx */
+ count++;
+ chararr[chararr_count++] = (char) c;
+ break;
+ case 12:
+ case 13:
+ /* 110x xxxx 10xx xxxx */
+ count += 2;
+ if (count > utflen)
+ throw new UTFDataFormatException(
+ "malformed input: partial character at end");
+ char2 = (int) bytearr[count - 1];
+ if ((char2 & 0xC0) != 0x80)
+ throw new UTFDataFormatException(
+ "malformed input around byte " + count);
+ chararr[chararr_count++] = (char) (((c & 0x1F) << 6) | (char2 & 0x3F));
+ break;
+ case 14:
+ /* 1110 xxxx 10xx xxxx 10xx xxxx */
+ count += 3;
+ if (count > utflen)
+ throw new UTFDataFormatException(
+ "malformed input: partial character at end");
+ char2 = (int) bytearr[count - 2];
+ char3 = (int) bytearr[count - 1];
+ if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))
+ throw new UTFDataFormatException(
+ "malformed input around byte " + (count - 1));
+ chararr[chararr_count++] = (char) (((c & 0x0F) << 12)
+ | ((char2 & 0x3F) << 6) | ((char3 & 0x3F) << 0));
+ break;
+ default:
+ /* 10xx xxxx, 1111 xxxx */
+ throw new UTFDataFormatException("malformed input around byte "
+ + count);
+ }
+ }
+ // The number of chars produced may be less than utflen
+ return new String(chararr, 0, chararr_count);
+ }
+}
diff --git a/app/src/main/java/com/m2049r/levin/util/LittleEndianDataOutputStream.java b/app/src/main/java/com/m2049r/levin/util/LittleEndianDataOutputStream.java
new file mode 100644
index 00000000..fbf7e0cc
--- /dev/null
+++ b/app/src/main/java/com/m2049r/levin/util/LittleEndianDataOutputStream.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (c) 2018 m2049r
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.m2049r.levin.util;
+
+import java.io.DataOutput;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UTFDataFormatException;
+
+/**
+ * A little endian java.io.DataOutputStream
+ */
+
+public class LittleEndianDataOutputStream extends FilterOutputStream implements
+ DataOutput {
+
+ /**
+ * The number of bytes written to the data output stream so far. If this
+ * counter overflows, it will be wrapped to Integer.MAX_VALUE.
+ */
+ protected int written;
+
+ /**
+ * Creates a new data output stream to write data to the specified
+ * underlying output stream. The counter written
is set to
+ * zero.
+ *
+ * @param out the underlying output stream, to be saved for later use.
+ * @see FilterOutputStream#out
+ */
+ public LittleEndianDataOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ /**
+ * Increases the written counter by the specified value until it reaches
+ * Integer.MAX_VALUE.
+ */
+ private void incCount(int value) {
+ int temp = written + value;
+ if (temp < 0) {
+ temp = Integer.MAX_VALUE;
+ }
+ written = temp;
+ }
+
+ /**
+ * Writes the specified byte (the low eight bits of the argument
+ * b
) to the underlying output stream. If no exception is
+ * thrown, the counter written
is incremented by 1
+ * .
+ *
+ * Implements the write
method of OutputStream
.
+ *
+ * @param b the byte
to be written.
+ * @throws IOException if an I/O error occurs.
+ * @see FilterOutputStream#out
+ */
+ public synchronized void write(int b) throws IOException {
+ out.write(b);
+ incCount(1);
+ }
+
+ /**
+ * Writes len
bytes from the specified byte array starting at
+ * offset off
to the underlying output stream. If no exception
+ * is thrown, the counter written
is incremented by
+ * len
.
+ *
+ * @param b the data.
+ * @param off the start offset in the data.
+ * @param len the number of bytes to write.
+ * @throws IOException if an I/O error occurs.
+ * @see FilterOutputStream#out
+ */
+ public synchronized void write(byte b[], int off, int len)
+ throws IOException {
+ out.write(b, off, len);
+ incCount(len);
+ }
+
+ /**
+ * Flushes this data output stream. This forces any buffered output bytes to
+ * be written out to the stream.
+ *
+ * The flush
method of DataOutputStream
calls the
+ * flush
method of its underlying output stream.
+ *
+ * @throws IOException if an I/O error occurs.
+ * @see FilterOutputStream#out
+ * @see OutputStream#flush()
+ */
+ public void flush() throws IOException {
+ out.flush();
+ }
+
+ /**
+ * Writes a boolean
to the underlying output stream as a 1-byte
+ * value. The value true
is written out as the value
+ * (byte)1
; the value false
is written out as the
+ * value (byte)0
. If no exception is thrown, the counter
+ * written
is incremented by 1
.
+ *
+ * @param v a boolean
value to be written.
+ * @throws IOException if an I/O error occurs.
+ * @see FilterOutputStream#out
+ */
+ public final void writeBoolean(boolean v) throws IOException {
+ out.write(v ? 1 : 0);
+ incCount(1);
+ }
+
+ /**
+ * Writes out a byte
to the underlying output stream as a
+ * 1-byte value. If no exception is thrown, the counter written
+ * is incremented by 1
.
+ *
+ * @param v a byte
value to be written.
+ * @throws IOException if an I/O error occurs.
+ * @see FilterOutputStream#out
+ */
+ public final void writeByte(int v) throws IOException {
+ out.write(v);
+ incCount(1);
+ }
+
+ /**
+ * Writes a short
to the underlying output stream as two bytes,
+ * low byte first. If no exception is thrown, the counter
+ * written
is incremented by 2
.
+ *
+ * @param v a short
to be written.
+ * @throws IOException if an I/O error occurs.
+ * @see FilterOutputStream#out
+ */
+ public final void writeShort(int v) throws IOException {
+ out.write((v >>> 0) & 0xFF);
+ out.write((v >>> 8) & 0xFF);
+ incCount(2);
+ }
+
+ /**
+ * Writes a char
to the underlying output stream as a 2-byte
+ * value, low byte first. If no exception is thrown, the counter
+ * written
is incremented by 2
.
+ *
+ * @param v a char
value to be written.
+ * @throws IOException if an I/O error occurs.
+ * @see FilterOutputStream#out
+ */
+ public final void writeChar(int v) throws IOException {
+ out.write((v >>> 0) & 0xFF);
+ out.write((v >>> 8) & 0xFF);
+ incCount(2);
+ }
+
+ /**
+ * Writes an int
to the underlying output stream as four bytes,
+ * low byte first. If no exception is thrown, the counter
+ * written
is incremented by 4
.
+ *
+ * @param v an int
to be written.
+ * @throws IOException if an I/O error occurs.
+ * @see FilterOutputStream#out
+ */
+ public final void writeInt(int v) throws IOException {
+ out.write((v >>> 0) & 0xFF);
+ out.write((v >>> 8) & 0xFF);
+ out.write((v >>> 16) & 0xFF);
+ out.write((v >>> 24) & 0xFF);
+ incCount(4);
+ }
+
+ private byte writeBuffer[] = new byte[8];
+
+ /**
+ * Writes a long
to the underlying output stream as eight
+ * bytes, low byte first. In no exception is thrown, the counter
+ * written
is incremented by 8
.
+ *
+ * @param v a long
to be written.
+ * @throws IOException if an I/O error occurs.
+ * @see FilterOutputStream#out
+ */
+ public final void writeLong(long v) throws IOException {
+ writeBuffer[7] = (byte) (v >>> 56);
+ writeBuffer[6] = (byte) (v >>> 48);
+ writeBuffer[5] = (byte) (v >>> 40);
+ writeBuffer[4] = (byte) (v >>> 32);
+ writeBuffer[3] = (byte) (v >>> 24);
+ writeBuffer[2] = (byte) (v >>> 16);
+ writeBuffer[1] = (byte) (v >>> 8);
+ writeBuffer[0] = (byte) (v >>> 0);
+ out.write(writeBuffer, 0, 8);
+ incCount(8);
+ }
+
+ /**
+ * Converts the float argument to an int
using the
+ * floatToIntBits
method in class Float
, and then
+ * writes that int
value to the underlying output stream as a
+ * 4-byte quantity, low byte first. If no exception is thrown, the counter
+ * written
is incremented by 4
.
+ *
+ * @param v a float
value to be written.
+ * @throws IOException if an I/O error occurs.
+ * @see FilterOutputStream#out
+ * @see Float#floatToIntBits(float)
+ */
+ public final void writeFloat(float v) throws IOException {
+ writeInt(Float.floatToIntBits(v));
+ }
+
+ /**
+ * Converts the double argument to a long
using the
+ * doubleToLongBits
method in class Double
, and
+ * then writes that long
value to the underlying output stream
+ * as an 8-byte quantity, low byte first. If no exception is thrown, the
+ * counter written
is incremented by 8
.
+ *
+ * @param v a double
value to be written.
+ * @throws IOException if an I/O error occurs.
+ * @see FilterOutputStream#out
+ * @see Double#doubleToLongBits(double)
+ */
+ public final void writeDouble(double v) throws IOException {
+ writeLong(Double.doubleToLongBits(v));
+ }
+
+ /**
+ * Writes out the string to the underlying output stream as a sequence of
+ * bytes. Each character in the string is written out, in sequence, by
+ * discarding its high eight bits. If no exception is thrown, the counter
+ * written
is incremented by the length of s
.
+ *
+ * @param s a string of bytes to be written.
+ * @throws IOException if an I/O error occurs.
+ * @see FilterOutputStream#out
+ */
+ public final void writeBytes(String s) throws IOException {
+ int len = s.length();
+ for (int i = 0; i < len; i++) {
+ out.write((byte) s.charAt(i));
+ }
+ incCount(len);
+ }
+
+ /**
+ * Writes a string to the underlying output stream as a sequence of
+ * characters. Each character is written to the data output stream as if by
+ * the writeChar
method. If no exception is thrown, the counter
+ * written
is incremented by twice the length of s
+ * .
+ *
+ * @param s a String
value to be written.
+ * @throws IOException if an I/O error occurs.
+ * @see java.io.DataOutputStream#writeChar(int)
+ * @see FilterOutputStream#out
+ */
+ public final void writeChars(String s) throws IOException {
+ int len = s.length();
+ for (int i = 0; i < len; i++) {
+ int v = s.charAt(i);
+ out.write((v >>> 0) & 0xFF);
+ out.write((v >>> 8) & 0xFF);
+ }
+ incCount(len * 2);
+ }
+
+ /**
+ * Writes a string to the underlying output stream using modified UTF-8 encoding in a
+ * machine-independent manner.
+ *
+ * First, two bytes are written to the output stream as if by the
+ * writeShort
method giving the number of bytes to follow. This
+ * value is the number of bytes actually written out, not the length of the
+ * string. Following the length, each character of the string is output, in
+ * sequence, using the modified UTF-8 encoding for the character. If no
+ * exception is thrown, the counter written
is incremented by
+ * the total number of bytes written to the output stream. This will be at
+ * least two plus the length of str
, and at most two plus
+ * thrice the length of str
.
+ *
+ * @param str a string to be written.
+ * @throws IOException if an I/O error occurs.
+ */
+ public final void writeUTF(String str) throws IOException {
+ writeUTF(str, this);
+ }
+
+ /**
+ * bytearr is initialized on demand by writeUTF
+ */
+ private byte[] bytearr = null;
+
+ /**
+ * Writes a string to the specified DataOutput using modified UTF-8 encoding in a
+ * machine-independent manner.
+ *
+ * First, two bytes are written to out as if by the writeShort
+ * method giving the number of bytes to follow. This value is the number of
+ * bytes actually written out, not the length of the string. Following the
+ * length, each character of the string is output, in sequence, using the
+ * modified UTF-8 encoding for the character. If no exception is thrown, the
+ * counter written
is incremented by the total number of bytes
+ * written to the output stream. This will be at least two plus the length
+ * of str
, and at most two plus thrice the length of
+ * str
.
+ *
+ * @param str a string to be written.
+ * @param out destination to write to
+ * @return The number of bytes written out.
+ * @throws IOException if an I/O error occurs.
+ */
+ static int writeUTF(String str, DataOutput out) throws IOException {
+ int strlen = str.length();
+ int utflen = 0;
+ int c, count = 0;
+
+ /* use charAt instead of copying String to char array */
+ for (int i = 0; i < strlen; i++) {
+ c = str.charAt(i);
+ if ((c >= 0x0001) && (c <= 0x007F)) {
+ utflen++;
+ } else if (c > 0x07FF) {
+ utflen += 3;
+ } else {
+ utflen += 2;
+ }
+ }
+
+ if (utflen > 65535)
+ throw new UTFDataFormatException("encoded string too long: "
+ + utflen + " bytes");
+
+ byte[] bytearr = null;
+ if (out instanceof LittleEndianDataOutputStream) {
+ LittleEndianDataOutputStream dos = (LittleEndianDataOutputStream) out;
+ if (dos.bytearr == null || (dos.bytearr.length < (utflen + 2)))
+ dos.bytearr = new byte[(utflen * 2) + 2];
+ bytearr = dos.bytearr;
+ } else {
+ bytearr = new byte[utflen + 2];
+ }
+
+ bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF);
+ bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF);
+
+ int i = 0;
+ for (i = 0; i < strlen; i++) {
+ c = str.charAt(i);
+ if (!((c >= 0x0001) && (c <= 0x007F)))
+ break;
+ bytearr[count++] = (byte) c;
+ }
+
+ for (; i < strlen; i++) {
+ c = str.charAt(i);
+ if ((c >= 0x0001) && (c <= 0x007F)) {
+ bytearr[count++] = (byte) c;
+
+ } else if (c > 0x07FF) {
+ bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
+ bytearr[count++] = (byte) (0x80 | ((c >> 6) & 0x3F));
+ bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F));
+ } else {
+ bytearr[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F));
+ bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F));
+ }
+ }
+ out.write(bytearr, 0, utflen + 2);
+ return utflen + 2;
+ }
+
+ /**
+ * Returns the current value of the counter written
, the number
+ * of bytes written to this data output stream so far. If the counter
+ * overflows, it will be wrapped to Integer.MAX_VALUE.
+ *
+ * @return the value of the written
field.
+ * @see java.io.DataOutputStream#written
+ */
+ public final int size() {
+ return written;
+ }
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java
index 0dd4dd3a..0ded608b 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java
@@ -85,15 +85,15 @@ public class GenerateFragment extends Fragment {
View view = inflater.inflate(R.layout.fragment_generate, container, false);
- etWalletName = (TextInputLayout) view.findViewById(R.id.etWalletName);
- etWalletPassword = (TextInputLayout) view.findViewById(R.id.etWalletPassword);
- llFingerprintAuth = (LinearLayout) view.findViewById(R.id.llFingerprintAuth);
- etWalletMnemonic = (TextInputLayout) view.findViewById(R.id.etWalletMnemonic);
- etWalletAddress = (TextInputLayout) view.findViewById(R.id.etWalletAddress);
- etWalletViewKey = (TextInputLayout) view.findViewById(R.id.etWalletViewKey);
- etWalletSpendKey = (TextInputLayout) view.findViewById(R.id.etWalletSpendKey);
- etWalletRestoreHeight = (TextInputLayout) view.findViewById(R.id.etWalletRestoreHeight);
- bGenerate = (Button) view.findViewById(R.id.bGenerate);
+ etWalletName = view.findViewById(R.id.etWalletName);
+ etWalletPassword = view.findViewById(R.id.etWalletPassword);
+ llFingerprintAuth = view.findViewById(R.id.llFingerprintAuth);
+ etWalletMnemonic = view.findViewById(R.id.etWalletMnemonic);
+ etWalletAddress = view.findViewById(R.id.etWalletAddress);
+ etWalletViewKey = view.findViewById(R.id.etWalletViewKey);
+ etWalletSpendKey = view.findViewById(R.id.etWalletSpendKey);
+ etWalletRestoreHeight = view.findViewById(R.id.etWalletRestoreHeight);
+ bGenerate = view.findViewById(R.id.bGenerate);
etWalletMnemonic.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT);
etWalletAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java
index 987a7eff..59a330fa 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java
@@ -91,22 +91,22 @@ public class GenerateReviewFragment extends Fragment {
View view = inflater.inflate(R.layout.fragment_review, container, false);
- scrollview = (ScrollView) view.findViewById(R.id.scrollview);
- pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
- tvWalletPassword = (TextView) view.findViewById(R.id.tvWalletPassword);
- tvWalletAddress = (TextView) view.findViewById(R.id.tvWalletAddress);
- tvWalletViewKey = (TextView) view.findViewById(R.id.tvWalletViewKey);
- tvWalletSpendKey = (TextView) view.findViewById(R.id.tvWalletSpendKey);
- tvWalletMnemonic = (TextView) view.findViewById(R.id.tvWalletMnemonic);
- bCopyAddress = (ImageButton) view.findViewById(R.id.bCopyAddress);
- bAdvancedInfo = (Button) view.findViewById(R.id.bAdvancedInfo);
- llAdvancedInfo = (LinearLayout) view.findViewById(R.id.llAdvancedInfo);
- llPassword = (LinearLayout) view.findViewById(R.id.llPassword);
- llMnemonic = (LinearLayout) view.findViewById(R.id.llMnemonic);
- llSpendKey = (LinearLayout) view.findViewById(R.id.llSpendKey);
- llViewKey = (LinearLayout) view.findViewById(R.id.llViewKey);
+ scrollview = view.findViewById(R.id.scrollview);
+ pbProgress = view.findViewById(R.id.pbProgress);
+ tvWalletPassword = view.findViewById(R.id.tvWalletPassword);
+ tvWalletAddress = view.findViewById(R.id.tvWalletAddress);
+ tvWalletViewKey = view.findViewById(R.id.tvWalletViewKey);
+ tvWalletSpendKey = view.findViewById(R.id.tvWalletSpendKey);
+ tvWalletMnemonic = view.findViewById(R.id.tvWalletMnemonic);
+ bCopyAddress = view.findViewById(R.id.bCopyAddress);
+ bAdvancedInfo = view.findViewById(R.id.bAdvancedInfo);
+ llAdvancedInfo = view.findViewById(R.id.llAdvancedInfo);
+ llPassword = view.findViewById(R.id.llPassword);
+ llMnemonic = view.findViewById(R.id.llMnemonic);
+ llSpendKey = view.findViewById(R.id.llSpendKey);
+ llViewKey = view.findViewById(R.id.llViewKey);
- bAccept = (Button) view.findViewById(R.id.bAccept);
+ bAccept = view.findViewById(R.id.bAccept);
boolean allowCopy = WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet;
tvWalletMnemonic.setTextIsSelectable(allowCopy);
@@ -481,13 +481,13 @@ public class GenerateReviewFragment extends Fragment {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
alertDialogBuilder.setView(promptsView);
- final TextInputLayout etPasswordA = (TextInputLayout) promptsView.findViewById(R.id.etWalletPasswordA);
+ final TextInputLayout etPasswordA = promptsView.findViewById(R.id.etWalletPasswordA);
etPasswordA.setHint(getString(R.string.prompt_changepw, walletName));
- final TextInputLayout etPasswordB = (TextInputLayout) promptsView.findViewById(R.id.etWalletPasswordB);
+ final TextInputLayout etPasswordB = promptsView.findViewById(R.id.etWalletPasswordB);
etPasswordB.setHint(getString(R.string.prompt_changepwB, walletName));
- LinearLayout llFingerprintAuth = (LinearLayout) promptsView.findViewById(R.id.llFingerprintAuth);
+ LinearLayout llFingerprintAuth = promptsView.findViewById(R.id.llFingerprintAuth);
final Switch swFingerprintAllowed = (Switch) llFingerprintAuth.getChildAt(0);
if (FingerprintHelper.isDeviceSupported(getActivity())) {
llFingerprintAuth.setVisibility(View.VISIBLE);
diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java
index d4fb6686..52150045 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java
@@ -43,7 +43,8 @@ import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
-import com.m2049r.xmrwallet.data.WalletNode;
+import com.m2049r.xmrwallet.data.Node;
+import com.m2049r.xmrwallet.data.NodeInfo;
import com.m2049r.xmrwallet.dialog.AboutFragment;
import com.m2049r.xmrwallet.dialog.CreditsFragment;
import com.m2049r.xmrwallet.dialog.HelpFragment;
@@ -64,25 +65,136 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.net.Socket;
-import java.net.SocketAddress;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
-import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
import timber.log.Timber;
public class LoginActivity extends BaseActivity
implements LoginFragment.Listener, GenerateFragment.Listener,
GenerateReviewFragment.Listener, GenerateReviewFragment.AcceptListener,
- ReceiveFragment.Listener {
+ ReceiveFragment.Listener, NodeFragment.Listener {
private static final String GENERATE_STACK = "gen";
- static final int DAEMON_TIMEOUT = 500; // deamon must respond in 500ms
+ private static final String NODES_PREFS_NAME = "nodes";
+ private static final String PREF_DAEMON_STAGENET = "daemon_stagenet";
+ private static final String PREF_DAEMON_MAINNET = "daemon_mainnet";
+
+ private NodeInfo node = null;
+
+ Set favouriteNodes = new HashSet<>();
+
+ @Override
+ public void setNode(NodeInfo node) {
+ if ((node != null) && (node.getNetworkType() != WalletManager.getInstance().getNetworkType()))
+ throw new IllegalArgumentException("network type does not match");
+ this.node = node;
+ WalletManager.getInstance().setDaemon(node);
+ }
+
+ @Override
+ public Set getFavouriteNodes() {
+ return favouriteNodes;
+ }
+
+ @Override
+ public void setFavouriteNodes(Set nodes) {
+ Timber.d("adding %d nodes", nodes.size());
+ favouriteNodes.clear();
+ for (NodeInfo node : nodes) {
+ Timber.d("adding %s %b", node, node.isFavourite());
+ if (node.isFavourite())
+ favouriteNodes.add(node);
+ }
+ if (favouriteNodes.isEmpty() && (!nodes.isEmpty())) { // no favourites - pick best ones
+ List nodeList = new ArrayList<>(nodes);
+ Collections.sort(nodeList, NodeInfo.BestNodeComparator);
+ int i = 0;
+ for (NodeInfo node : nodeList) {
+ Timber.d("adding %s", node);
+ node.setFavourite(true);
+ favouriteNodes.add(node);
+ if (++i >= 3) break; // add max first 3 nodes
+ }
+ Toast.makeText(this, getString(R.string.node_nobookmark, i), Toast.LENGTH_LONG).show();
+ }
+ saveFavourites();
+ }
+
+ private void loadFavouritesWithNetwork() {
+ Helper.runWithNetwork(new Helper.Action() {
+ @Override
+ public boolean run() {
+ loadFavourites();
+ return true;
+ }
+ });
+ }
+
+ private void loadFavourites() {
+ Timber.d("loadFavourites");
+ favouriteNodes.clear();
+ Map storedNodes = getSharedPreferences(NODES_PREFS_NAME, Context.MODE_PRIVATE).getAll();
+ for (Map.Entry nodeEntry : storedNodes.entrySet()) {
+ if (nodeEntry != null) // just in case, ignore possible future errors
+ addFavourite((String) nodeEntry.getValue());
+ }
+ if (storedNodes.isEmpty()) { // try to load legacy list & remove it (i.e. migrate the data once)
+ SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
+ switch (WalletManager.getInstance().getNetworkType()) {
+ case NetworkType_Mainnet:
+ loadLegacyList(sharedPref.getString(PREF_DAEMON_MAINNET, null));
+ sharedPref.edit().remove(PREF_DAEMON_MAINNET).apply();
+ break;
+ case NetworkType_Stagenet:
+ loadLegacyList(sharedPref.getString(PREF_DAEMON_STAGENET, null));
+ sharedPref.edit().remove(PREF_DAEMON_STAGENET).apply();
+ break;
+ default:
+ throw new IllegalStateException("unsupported net " + WalletManager.getInstance().getNetworkType());
+ }
+ }
+ }
+
+ private void saveFavourites() {
+ List favourites = new ArrayList<>();
+ Timber.d("SAVE");
+ SharedPreferences.Editor editor = getSharedPreferences(NODES_PREFS_NAME, Context.MODE_PRIVATE).edit();
+ editor.clear();
+ int i = 1;
+ for (Node info : favouriteNodes) {
+ String nodeString = info.toNodeString();
+ editor.putString(Integer.toString(i), nodeString);
+ Timber.d("saved %d:%s", i, nodeString);
+ i++;
+ }
+ editor.apply();
+ }
+
+ private void addFavourite(String nodeString) {
+ NodeInfo nodeInfo = NodeInfo.fromString(nodeString);
+ if (nodeInfo != null) {
+ nodeInfo.setFavourite(true);
+ favouriteNodes.add(nodeInfo);
+ } else
+ Timber.w("nodeString invalid: %s", nodeString);
+ }
+
+ private void loadLegacyList(final String legacyListString) {
+ if (legacyListString == null) return;
+ final String[] nodeStrings = legacyListString.split(";");
+ for (final String nodeString : nodeStrings) {
+ addFavourite(nodeString);
+ }
+ }
private Toolbar toolbar;
@@ -120,7 +232,7 @@ public class LoginActivity extends BaseActivity
}
setContentView(R.layout.activity_login);
- toolbar = (Toolbar) findViewById(R.id.toolbar);
+ toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayShowTitleEnabled(false);
@@ -138,12 +250,15 @@ public class LoginActivity extends BaseActivity
CreditsFragment.display(getSupportFragmentManager());
break;
case Toolbar.BUTTON_NONE:
+ break;
default:
Timber.e("Button " + type + "pressed - how can this be?");
}
}
});
+ loadFavouritesWithNetwork();
+
if (Helper.getWritePermission(this)) {
if (savedInstanceState == null) startLoginFragment();
} else {
@@ -162,15 +277,14 @@ public class LoginActivity extends BaseActivity
}
@Override
- public boolean onWalletSelected(String walletName, String daemon, boolean streetmode) {
- if (daemon.length() == 0) {
+ public boolean onWalletSelected(String walletName, boolean streetmode) {
+ if (node == null) {
Toast.makeText(this, getString(R.string.prompt_daemon_missing), Toast.LENGTH_SHORT).show();
return false;
}
if (checkServiceRunning()) return false;
try {
- WalletNode aWalletNode = new WalletNode(walletName, daemon, WalletManager.getInstance().getNetworkType());
- new AsyncOpenWallet(streetmode).execute(aWalletNode);
+ new AsyncOpenWallet(walletName, node, streetmode).execute();
} catch (IllegalArgumentException ex) {
Timber.e(ex.getLocalizedMessage());
Toast.makeText(this, ex.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
@@ -211,7 +325,7 @@ public class LoginActivity extends BaseActivity
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setMessage(getString(R.string.details_alert_message))
+ AlertDialog diag = builder.setMessage(getString(R.string.details_alert_message))
.setPositiveButton(getString(R.string.details_alert_yes), dialogClickListener)
.setNegativeButton(getString(R.string.details_alert_no), dialogClickListener)
.show();
@@ -298,8 +412,8 @@ public class LoginActivity extends BaseActivity
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
alertDialogBuilder.setView(promptsView);
- final EditText etRename = (EditText) promptsView.findViewById(R.id.etRename);
- final TextView tvRenameLabel = (TextView) promptsView.findViewById(R.id.tvRenameLabel);
+ final EditText etRename = promptsView.findViewById(R.id.etRename);
+ final TextView tvRenameLabel = promptsView.findViewById(R.id.tvRenameLabel);
tvRenameLabel.setText(getString(R.string.prompt_rename, walletName));
@@ -488,13 +602,16 @@ public class LoginActivity extends BaseActivity
startGenerateFragment(type);
}
+ @Override
+ public void onNodePrefs() {
+ Timber.d("node prefs");
+ if (checkServiceRunning()) return;
+ startNodeFragment();
+ }
+
////////////////////////////////////////
// LoginFragment.Listener
////////////////////////////////////////
- @Override
- public SharedPreferences getPrefs() {
- return getPreferences(Context.MODE_PRIVATE);
- }
@Override
public File getStorageRoot() {
@@ -506,9 +623,13 @@ public class LoginActivity extends BaseActivity
@Override
public void showNet() {
- switch (WalletManager.getInstance().getNetworkType()) {
+ showNet(WalletManager.getInstance().getNetworkType());
+ }
+
+ private void showNet(NetworkType net) {
+ switch (net) {
case NetworkType_Mainnet:
- toolbar.setSubtitle(getString(R.string.connect_mainnet));
+ toolbar.setSubtitle(null);
toolbar.setBackgroundResource(R.drawable.backgound_toolbar_mainnet);
break;
case NetworkType_Testnet:
@@ -520,7 +641,7 @@ public class LoginActivity extends BaseActivity
toolbar.setBackgroundResource(R.color.colorPrimaryDark);
break;
default:
- throw new IllegalStateException("NetworkType unknown: " + WalletManager.getInstance().getNetworkType());
+ throw new IllegalStateException("NetworkType unknown: " + net);
}
}
@@ -609,7 +730,8 @@ public class LoginActivity extends BaseActivity
}
@Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
+ public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
+ @NonNull int[] grantResults) {
Timber.d("onRequestPermissionsResult()");
switch (requestCode) {
case Helper.PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE:
@@ -660,6 +782,11 @@ public class LoginActivity extends BaseActivity
Timber.d("GenerateReviewFragment placed");
}
+ void startNodeFragment() {
+ replaceFragment(new NodeFragment(), null, null);
+ Timber.d("NodeFragment placed");
+ }
+
void startReceiveFragment(Bundle extras) {
replaceFragment(new ReceiveFragment(), null, extras);
Timber.d("ReceiveFragment placed");
@@ -826,7 +953,8 @@ public class LoginActivity extends BaseActivity
}
@Override
- public void onGenerateLedger(final String name, final String password, final long restoreHeight) {
+ public void onGenerateLedger(final String name, final String password,
+ final long restoreHeight) {
createWallet(name, password,
new WalletCreator() {
@Override
@@ -921,7 +1049,8 @@ public class LoginActivity extends BaseActivity
}
}
- boolean copyWallet(File srcWallet, File dstWallet, boolean overwrite, boolean ignoreCacheError) {
+ boolean copyWallet(File srcWallet, File dstWallet, boolean overwrite,
+ boolean ignoreCacheError) {
if (walletExists(dstWallet, true) && !overwrite) return false;
boolean success = false;
File srcDir = srcWallet.getParentFile();
@@ -1029,6 +1158,12 @@ public class LoginActivity extends BaseActivity
if (((GenerateReviewFragment) f).backOk()) {
super.onBackPressed();
}
+ } else if (f instanceof NodeFragment) {
+ if (!((NodeFragment) f).isRefreshing()) {
+ super.onBackPressed();
+ } else {
+ Toast.makeText(LoginActivity.this, getString(R.string.node_refresh_wait), Toast.LENGTH_LONG).show();
+ }
} else if (f instanceof LoginFragment) {
if (((LoginFragment) f).isFabOpen()) {
((LoginFragment) f).animateFAB();
@@ -1070,101 +1205,64 @@ public class LoginActivity extends BaseActivity
case R.id.action_help_list:
HelpFragment.display(getSupportFragmentManager(), R.string.help_list);
return true;
+ case R.id.action_help_node:
+ HelpFragment.display(getSupportFragmentManager(), R.string.help_node);
+ return true;
case R.id.action_privacy_policy:
PrivacyFragment.display(getSupportFragmentManager());
return true;
case R.id.action_language:
onChangeLocale();
return true;
- case R.id.action_stagenet:
- try {
- LoginFragment loginFragment = (LoginFragment)
- getSupportFragmentManager().findFragmentById(R.id.fragment_container);
- item.setChecked(loginFragment.onStagenetMenuItem());
- } catch (ClassCastException ex) {
- // never mind then
- }
- return true;
default:
return super.onOptionsItemSelected(item);
}
}
- public void setNetworkType(NetworkType networkType) {
- WalletManager.getInstance().setNetworkType(networkType);
- }
-
- private class AsyncOpenWallet extends AsyncTask {
+ // an AsyncTask which tests the node before trying to open the wallet
+ private class AsyncOpenWallet extends AsyncTask {
final static int OK = 0;
final static int TIMEOUT = 1;
final static int INVALID = 2;
final static int IOEX = 3;
- private WalletNode walletNode;
+ private final String walletName;
+ private final NodeInfo node;
private final boolean streetmode;
- public AsyncOpenWallet(boolean streetmode) {
+ AsyncOpenWallet(String walletName, NodeInfo node, boolean streetmode) {
+ this.walletName = walletName;
+ this.node = node;
this.streetmode = streetmode;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
- showProgressDialog(R.string.open_progress, DAEMON_TIMEOUT / 4);
}
@Override
- protected Integer doInBackground(WalletNode... params) {
- if (params.length != 1) return INVALID;
- this.walletNode = params[0];
- if (!walletNode.isValid()) return INVALID;
-
- Timber.d("checking %s", walletNode.getAddress());
-
- try {
- long timeDA = new Date().getTime();
- SocketAddress address = walletNode.getSocketAddress();
- long timeDB = new Date().getTime();
- Timber.d("Resolving " + walletNode.getAddress() + " took " + (timeDB - timeDA) + "ms.");
- Socket socket = new Socket();
- long timeA = new Date().getTime();
- socket.connect(address, LoginActivity.DAEMON_TIMEOUT);
- socket.close();
- long timeB = new Date().getTime();
- long time = timeB - timeA;
- Timber.d("Daemon " + walletNode.getAddress() + " is " + time + "ms away.");
- return (time < LoginActivity.DAEMON_TIMEOUT ? OK : TIMEOUT);
- } catch (IOException ex) {
- Timber.d("Cannot reach daemon %s because %s", walletNode.getAddress(), ex.getMessage());
- return IOEX;
- } catch (IllegalArgumentException ex) {
- Timber.d("Cannot reach daemon %s because %s", walletNode.getAddress(), ex.getMessage());
- return INVALID;
- }
+ protected Boolean doInBackground(Void... params) {
+ Timber.d("checking %s", node.getAddress());
+ return node.testRpcService();
}
@Override
- protected void onPostExecute(Integer result) {
+ protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (isDestroyed()) {
return;
}
- dismissProgressDialog();
- switch (result) {
- case OK:
- Timber.d("selected wallet is .%s.", walletNode.getName());
- // now it's getting real, onValidateFields if wallet exists
- promptAndStart(walletNode, streetmode);
- break;
- case TIMEOUT:
- Toast.makeText(LoginActivity.this, getString(R.string.status_wallet_connect_timeout), Toast.LENGTH_LONG).show();
- break;
- case INVALID:
+ if (result) {
+ Timber.d("selected wallet is .%s.", node.getName());
+ // now it's getting real, onValidateFields if wallet exists
+ promptAndStart(walletName, node, streetmode);
+ } else {
+ if (node.getResponseCode() == 0) { // IOException
Toast.makeText(LoginActivity.this, getString(R.string.status_wallet_node_invalid), Toast.LENGTH_LONG).show();
- break;
- case IOEX:
+ } else { // connected but broken
Toast.makeText(LoginActivity.this, getString(R.string.status_wallet_connect_ioex), Toast.LENGTH_LONG).show();
- break;
+ }
}
}
}
@@ -1191,23 +1289,20 @@ public class LoginActivity extends BaseActivity
return false;
}
- void promptAndStart(WalletNode walletNode, final boolean streetmode) {
- File walletFile = Helper.getWalletFile(this, walletNode.getName());
+ void promptAndStart(String walletName, Node node, final boolean streetmode) {
+ File walletFile = Helper.getWalletFile(this, walletName);
if (WalletManager.getInstance().walletExists(walletFile)) {
- WalletManager.getInstance().setDaemon(walletNode);
- Helper.promptPassword(LoginActivity.this, walletNode.getName(), false,
+ Helper.promptPassword(LoginActivity.this, walletName, false,
new Helper.PasswordAction() {
@Override
public void action(String walletName, String password, boolean fingerprintUsed) {
if (checkDevice(walletName, password))
startWallet(walletName, password, fingerprintUsed, streetmode);
-
}
});
} else { // this cannot really happen as we prefilter choices
Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show();
}
-
}
// USB Stuff - (Ledger)
diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java
index 13443e0f..22d93147 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java
@@ -17,14 +17,13 @@
package com.m2049r.xmrwallet;
import android.content.Context;
-import android.content.SharedPreferences;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.Fragment;
import android.support.v7.widget.RecyclerView;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -33,28 +32,24 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
-import android.view.inputmethod.EditorInfo;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
+import com.m2049r.xmrwallet.data.NodeInfo;
+import com.m2049r.xmrwallet.layout.NodeInfoAdapter;
import com.m2049r.xmrwallet.layout.WalletInfoAdapter;
-import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.KeyStoreHelper;
-import com.m2049r.xmrwallet.util.NodeList;
import com.m2049r.xmrwallet.util.Notice;
-import com.m2049r.xmrwallet.widget.DropDownEditText;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.io.File;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -68,20 +63,19 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
private List walletList = new ArrayList<>();
private List displayedList = new ArrayList<>();
- private EditText etDummy;
private ImageView ivGunther;
- private DropDownEditText etDaemonAddress;
- private ArrayAdapter nodeAdapter;
+ private TextView tvNodeName;
+ private TextView tvNodeAddress;
+ private View pbNode;
+ private View llNode;
private Listener activityCallback;
// Container Activity must implement this interface
public interface Listener {
- SharedPreferences getPrefs();
-
File getStorageRoot();
- boolean onWalletSelected(String wallet, String daemon, boolean streetmode);
+ boolean onWalletSelected(String wallet, boolean streetmode);
void onWalletDetails(String wallet);
@@ -95,13 +89,17 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
void onAddWallet(String type);
+ void onNodePrefs();
+
void showNet();
void setToolbarButton(int type);
void setTitle(String title);
- void setNetworkType(NetworkType networkType);
+ void setNode(NodeInfo node);
+
+ Set getFavouriteNodes();
boolean hasLedger();
}
@@ -120,17 +118,17 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
@Override
public void onPause() {
Timber.d("onPause()");
- savePrefs();
super.onPause();
}
@Override
public void onResume() {
super.onResume();
- Timber.d("onResume()");
+ Timber.d("onResume() %s", activityCallback.getFavouriteNodes().size());
activityCallback.setTitle(null);
activityCallback.setToolbarButton(Toolbar.BUTTON_CREDITS);
activityCallback.showNet();
+ findBestNode();
}
@Override
@@ -139,20 +137,20 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
Timber.d("onCreateView");
View view = inflater.inflate(R.layout.fragment_login, container, false);
- ivGunther = (ImageView) view.findViewById(R.id.ivGunther);
- fabScreen = (FrameLayout) view.findViewById(R.id.fabScreen);
- fab = (FloatingActionButton) view.findViewById(R.id.fab);
- fabNew = (FloatingActionButton) view.findViewById(R.id.fabNew);
- fabView = (FloatingActionButton) view.findViewById(R.id.fabView);
- fabKey = (FloatingActionButton) view.findViewById(R.id.fabKey);
- fabSeed = (FloatingActionButton) view.findViewById(R.id.fabSeed);
- fabLedger = (FloatingActionButton) view.findViewById(R.id.fabLedger);
+ ivGunther = view.findViewById(R.id.ivGunther);
+ fabScreen = view.findViewById(R.id.fabScreen);
+ fab = view.findViewById(R.id.fab);
+ fabNew = view.findViewById(R.id.fabNew);
+ fabView = view.findViewById(R.id.fabView);
+ fabKey = view.findViewById(R.id.fabKey);
+ fabSeed = view.findViewById(R.id.fabSeed);
+ fabLedger = view.findViewById(R.id.fabLedger);
- fabNewL = (RelativeLayout) view.findViewById(R.id.fabNewL);
- fabViewL = (RelativeLayout) view.findViewById(R.id.fabViewL);
- fabKeyL = (RelativeLayout) view.findViewById(R.id.fabKeyL);
- fabSeedL = (RelativeLayout) view.findViewById(R.id.fabSeedL);
- fabLedgerL = (RelativeLayout) view.findViewById(R.id.fabLedgerL);
+ fabNewL = view.findViewById(R.id.fabNewL);
+ fabViewL = view.findViewById(R.id.fabViewL);
+ fabKeyL = view.findViewById(R.id.fabKeyL);
+ fabSeedL = view.findViewById(R.id.fabSeedL);
+ fabLedgerL = view.findViewById(R.id.fabLedgerL);
fab_pulse = AnimationUtils.loadAnimation(getContext(), R.anim.fab_pulse);
fab_open_screen = AnimationUtils.loadAnimation(getContext(), R.anim.fab_open_screen);
@@ -169,71 +167,48 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
fabLedger.setOnClickListener(this);
fabScreen.setOnClickListener(this);
- RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list);
+ RecyclerView recyclerView = view.findViewById(R.id.list);
registerForContextMenu(recyclerView);
this.adapter = new WalletInfoAdapter(getActivity(), this);
recyclerView.setAdapter(adapter);
- etDummy = (EditText) view.findViewById(R.id.etDummy);
-
- ViewGroup llNotice = (ViewGroup) view.findViewById(R.id.llNotice);
+ ViewGroup llNotice = view.findViewById(R.id.llNotice);
Notice.showAll(llNotice, ".*_login");
- etDaemonAddress = (DropDownEditText) view.findViewById(R.id.etDaemonAddress);
- nodeAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_dropdown_item_1line);
- etDaemonAddress.setAdapter(nodeAdapter);
+ pbNode = view.findViewById(R.id.pbNode);
+ llNode = view.findViewById(R.id.llNode);
+ llNode.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (activityCallback.getFavouriteNodes().isEmpty())
+ activityCallback.onNodePrefs();
+ else
+ findBestNode();
+ }
+ });
+ tvNodeName = view.findViewById(R.id.tvNodeName);
+ tvNodeAddress = view.findViewById(R.id.tvNodeAddress);
+ view.findViewById(R.id.ibOption).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (activityCallback != null)
+ activityCallback.onNodePrefs();
+ }
+ });
Helper.hideKeyboard(getActivity());
- etDaemonAddress.setThreshold(0);
- etDaemonAddress.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- etDaemonAddress.showDropDown();
- Helper.showKeyboard(getActivity());
- }
- });
-
- etDaemonAddress.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus && !getActivity().isFinishing() && etDaemonAddress.isLaidOut()) {
- etDaemonAddress.showDropDown();
- Helper.showKeyboard(getActivity());
- }
- }
- });
-
- etDaemonAddress.setOnEditorActionListener(new TextView.OnEditorActionListener() {
- public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
- || (actionId == EditorInfo.IME_ACTION_DONE)) {
- Helper.hideKeyboard(getActivity());
- etDummy.requestFocus();
- return true;
- }
- return false;
- }
- });
-
- etDaemonAddress.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView> parent, View arg1, int pos, long id) {
- Helper.hideKeyboard(getActivity());
- etDummy.requestFocus();
-
- }
- });
-
- loadPrefs();
+ loadList();
return view;
}
// Callbacks from WalletInfoAdapter
+
+ // Wallet touched
@Override
public void onInteraction(final View view, final WalletManager.WalletInfo infoItem) {
- String addressPrefix = addressPrefix();
+ String addressPrefix = WalletManager.getInstance().addressPrefix();
if (addressPrefix.indexOf(infoItem.address.charAt(0)) < 0) {
Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show();
return;
@@ -242,9 +217,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
}
private void openWallet(String name, boolean streetmode) {
- if (activityCallback.onWalletSelected(name, getDaemon(), streetmode)) {
- savePrefs();
- }
+ activityCallback.onWalletSelected(name, streetmode);
}
@Override
@@ -274,22 +247,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
return true;
}
- private String addressPrefix() {
- switch (WalletManager.getInstance().getNetworkType()) {
- case NetworkType_Testnet:
- return "9A-";
- case NetworkType_Mainnet:
- return "4-";
- case NetworkType_Stagenet:
- return "5-";
- default:
- throw new IllegalStateException("Unsupported Network: " + WalletManager.getInstance().getNetworkType());
- }
- }
-
private void filterList() {
displayedList.clear();
- String addressPrefix = addressPrefix();
+ String addressPrefix = WalletManager.getInstance().addressPrefix();
for (WalletManager.WalletInfo s : walletList) {
if (addressPrefix.indexOf(s.address.charAt(0)) >= 0) displayedList.add(s);
}
@@ -348,94 +308,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.list_menu, menu);
- menu.findItem(R.id.action_stagenet).setChecked(stagenetCheckMenu);
super.onCreateOptionsMenu(menu, inflater);
}
- private boolean stagenetCheckMenu = BuildConfig.DEBUG;
-
- public boolean onStagenetMenuItem() {
- boolean lastState = stagenetCheckMenu;
- setNet(!lastState, true); // set and save
- return !lastState;
- }
-
- public void setNet(boolean stagenetChecked, boolean save) {
- this.stagenetCheckMenu = stagenetChecked;
- NetworkType net = stagenetChecked ? NetworkType.NetworkType_Stagenet : NetworkType.NetworkType_Mainnet;
- activityCallback.setNetworkType(net);
- activityCallback.showNet();
- if (save) {
- savePrefs(true); // use previous state as we just clicked it
- }
- if (stagenetChecked) {
- setDaemon(daemonStageNet);
- } else {
- setDaemon(daemonMainNet);
- }
- loadList();
- }
-
- private static final String PREF_DAEMON_STAGENET = "daemon_stagenet";
- private static final String PREF_DAEMON_MAINNET = "daemon_mainnet";
-
- private static final String PREF_DAEMONLIST_MAINNET =
- "node.moneroworld.com:18089;node.xmrbackb.one;node.xmr.be";
-
- private static final String PREF_DAEMONLIST_STAGENET =
- "stagenet.monerujo.io;stagenet.xmr-tw.org";
-
- private NodeList daemonStageNet;
- private NodeList daemonMainNet;
-
- void loadPrefs() {
- SharedPreferences sharedPref = activityCallback.getPrefs();
-
- daemonMainNet = new NodeList(sharedPref.getString(PREF_DAEMON_MAINNET, PREF_DAEMONLIST_MAINNET));
- daemonStageNet = new NodeList(sharedPref.getString(PREF_DAEMON_STAGENET, PREF_DAEMONLIST_STAGENET));
- setNet(stagenetCheckMenu, false);
- }
-
- void savePrefs() {
- savePrefs(false);
- }
-
- void savePrefs(boolean usePreviousNetState) {
- Timber.d("SAVE / %s", usePreviousNetState);
- // save the daemon address for the net
- boolean stagenet = stagenetCheckMenu ^ usePreviousNetState;
- String daemon = getDaemon();
- if (stagenet) {
- daemonStageNet.setRecent(daemon);
- } else {
- daemonMainNet.setRecent(daemon);
- }
-
- SharedPreferences sharedPref = activityCallback.getPrefs();
- SharedPreferences.Editor editor = sharedPref.edit();
- editor.putString(PREF_DAEMON_MAINNET, daemonMainNet.toString());
- editor.putString(PREF_DAEMON_STAGENET, daemonStageNet.toString());
- editor.apply();
- }
-
- String getDaemon() {
- return etDaemonAddress.getText().toString().trim();
- }
-
- void setDaemon(NodeList nodeList) {
- Timber.d("setDaemon() %s", nodeList.toString());
- String[] nodes = nodeList.getNodes().toArray(new String[0]);
- nodeAdapter.clear();
- nodeAdapter.addAll(nodes);
- etDaemonAddress.getText().clear();
- if (nodes.length > 0) {
- etDaemonAddress.setText(nodes[0]);
- }
- etDaemonAddress.dismissDropDown();
- etDummy.requestFocus();
- Helper.hideKeyboard(getActivity());
- }
-
private boolean isFabOpen = false;
private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed, fabLedger;
private FrameLayout fabScreen;
@@ -534,4 +409,71 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
break;
}
}
+
+ public void findBestNode() {
+ new AsyncFindBestNode().execute();
+ }
+
+ private class AsyncFindBestNode extends AsyncTask {
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ pbNode.setVisibility(View.VISIBLE);
+ llNode.setVisibility(View.INVISIBLE);
+ activityCallback.setNode(null);
+ }
+
+ @Override
+ protected NodeInfo doInBackground(Void... params) {
+ List nodesToTest = new ArrayList<>(activityCallback.getFavouriteNodes());
+ Timber.d("testing best node from %d", nodesToTest.size());
+ if (nodesToTest.isEmpty()) return null;
+ for (NodeInfo node : nodesToTest) {
+ node.testRpcService(); // TODO: do this in parallel?
+ // no: it's better if it looks like it's doing something
+ }
+ Collections.sort(nodesToTest, NodeInfo.BestNodeComparator);
+ NodeInfo bestNode = nodesToTest.get(0);
+ if (bestNode.isValid())
+ return nodesToTest.get(0);
+ else
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(NodeInfo result) {
+ if (!isAdded()) return;
+ pbNode.setVisibility(View.INVISIBLE);
+ llNode.setVisibility(View.VISIBLE);
+ activityCallback.setNode(result);
+ if (result != null) {
+ Timber.d("found a good node %s", result.toString());
+ showNode(result);
+ } else {
+ if (!activityCallback.getFavouriteNodes().isEmpty()) {
+ tvNodeName.setText(getResources().getText(R.string.node_refresh_hint));
+ tvNodeName.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_refresh_black_24dp, 0, 0, 0);
+ tvNodeAddress.setText(null);
+ tvNodeAddress.setVisibility(View.GONE);
+ } else {
+ tvNodeName.setText(getResources().getText(R.string.node_create_hint));
+ tvNodeName.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
+ tvNodeAddress.setText(null);
+ tvNodeAddress.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ @Override
+ protected void onCancelled(NodeInfo result) { //TODO: cancel this on exit from fragment
+ Timber.d("cancelled with %s", result);
+ }
+ }
+
+ private void showNode(NodeInfo nodeInfo) {
+ tvNodeName.setText(nodeInfo.getName());
+ tvNodeName.setCompoundDrawablesWithIntrinsicBounds(NodeInfoAdapter.getPingIcon(nodeInfo), 0, 0, 0);
+ tvNodeAddress.setText(nodeInfo.getAddress());
+ tvNodeAddress.setVisibility(View.VISIBLE);
+ }
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/NodeFragment.java b/app/src/main/java/com/m2049r/xmrwallet/NodeFragment.java
new file mode 100644
index 00000000..13fefa75
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/NodeFragment.java
@@ -0,0 +1,550 @@
+/*
+ * Copyright (c) 2018 m2049r
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.m2049r.xmrwallet;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.design.widget.TextInputLayout;
+import android.support.v4.app.Fragment;
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.support.v7.widget.RecyclerView;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.m2049r.levin.scanner.Dispatcher;
+import com.m2049r.xmrwallet.data.Node;
+import com.m2049r.xmrwallet.data.NodeInfo;
+import com.m2049r.xmrwallet.layout.NodeInfoAdapter;
+import com.m2049r.xmrwallet.model.NetworkType;
+import com.m2049r.xmrwallet.model.WalletManager;
+import com.m2049r.xmrwallet.util.Helper;
+import com.m2049r.xmrwallet.util.Notice;
+import com.m2049r.xmrwallet.widget.Toolbar;
+
+import java.io.File;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.text.NumberFormat;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import timber.log.Timber;
+
+public class NodeFragment extends Fragment
+ implements NodeInfoAdapter.OnInteractionListener, View.OnClickListener {
+
+ static private int NODES_TO_FIND = 10;
+
+ static private NumberFormat FORMATTER = NumberFormat.getInstance();
+
+ private SwipeRefreshLayout pullToRefresh;
+ private TextView tvPull;
+ private View fab;
+
+ private Set nodeList = new HashSet<>();
+
+ private NodeInfoAdapter nodesAdapter;
+
+ private Listener activityCallback;
+
+ public interface Listener {
+ File getStorageRoot();
+
+ void setToolbarButton(int type);
+
+ void setSubtitle(String title);
+
+ Set getFavouriteNodes();
+
+ void setFavouriteNodes(Set favouriteNodes);
+ }
+
+ void filterFavourites() {
+ for (Iterator iter = nodeList.iterator(); iter.hasNext(); ) {
+ Node node = iter.next();
+ if (!node.isFavourite()) iter.remove();
+ }
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ if (context instanceof Listener) {
+ this.activityCallback = (Listener) context;
+ } else {
+ throw new ClassCastException(context.toString()
+ + " must implement Listener");
+ }
+ }
+
+ @Override
+ public void onPause() {
+ Timber.d("onPause() %d", nodeList.size());
+ if (asyncFindNodes != null)
+ asyncFindNodes.cancel(true);
+ if (activityCallback != null)
+ activityCallback.setFavouriteNodes(nodeList);
+ super.onPause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ Timber.d("onResume()");
+ activityCallback.setSubtitle(getString(R.string.label_nodes));
+ updateRefreshElements();
+ }
+
+ boolean isRefreshing() {
+ return asyncFindNodes != null;
+ }
+
+ void updateRefreshElements() {
+ if (isRefreshing()) {
+ activityCallback.setToolbarButton(Toolbar.BUTTON_NONE);
+ fab.setVisibility(View.GONE);
+ } else {
+ activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
+ fab.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ Timber.d("onCreateView");
+ View view = inflater.inflate(R.layout.fragment_node, container, false);
+
+ fab = view.findViewById(R.id.fab);
+ fab.setOnClickListener(this);
+
+ RecyclerView recyclerView = view.findViewById(R.id.list);
+ nodesAdapter = new NodeInfoAdapter(getActivity(), this);
+ recyclerView.setAdapter(nodesAdapter);
+
+ tvPull = view.findViewById(R.id.tvPull);
+
+ pullToRefresh = view.findViewById(R.id.pullToRefresh);
+ pullToRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
+ @Override
+ public void onRefresh() {
+ if (WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) {
+ refresh();
+ } else {
+ Toast.makeText(getActivity(), getString(R.string.node_wrong_net), Toast.LENGTH_LONG).show();
+ pullToRefresh.setRefreshing(false);
+ }
+ }
+ });
+
+ Helper.hideKeyboard(getActivity());
+
+ nodeList = new HashSet<>(activityCallback.getFavouriteNodes());
+ nodesAdapter.setNodes(nodeList);
+
+ ViewGroup llNotice = view.findViewById(R.id.llNotice);
+ Notice.showAll(llNotice, ".*_nodes");
+
+ return view;
+ }
+
+ private AsyncFindNodes asyncFindNodes = null;
+
+ private void refresh() {
+ if (asyncFindNodes != null) return; // ignore refresh request as one is ongoing
+ asyncFindNodes = new AsyncFindNodes();
+ updateRefreshElements();
+ asyncFindNodes.execute();
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.node_menu, menu);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ // Callbacks from NodeInfoAdapter
+ @Override
+ public void onInteraction(final View view, final NodeInfo nodeItem) {
+ Timber.d("onInteraction");
+ EditDialog diag = createEditDialog(nodeItem);
+ if (diag != null) {
+ diag.show();
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ int id = v.getId();
+ switch (id) {
+ case R.id.fab:
+ EditDialog diag = createEditDialog(null);
+ if (diag != null) {
+ diag.show();
+ }
+ break;
+ }
+ }
+
+ private class AsyncFindNodes extends AsyncTask {
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ filterFavourites();
+ nodesAdapter.setNodes(null);
+ nodesAdapter.allowClick(false);
+ tvPull.setText(getString(R.string.node_scanning));
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ Timber.d("scanning");
+ Set seedList = new HashSet<>();
+ seedList.addAll(nodeList);
+ nodeList.clear();
+ Timber.d("seed %d", seedList.size());
+ Dispatcher d = new Dispatcher(new Dispatcher.Listener() {
+ @Override
+ public void onGet(NodeInfo info) {
+ publishProgress(info);
+ }
+ });
+ d.seedPeers(seedList);
+ d.awaitTermination(NODES_TO_FIND);
+
+ // we didn't find enough because we didn't ask around enough? ask more!
+ if ((d.getRpcNodes().size() < NODES_TO_FIND) &&
+ (d.getPeerCount() < NODES_TO_FIND + seedList.size())) {
+ // try again
+ publishProgress((NodeInfo[]) null);
+ d = new Dispatcher(new Dispatcher.Listener() {
+ @Override
+ public void onGet(NodeInfo info) {
+ publishProgress(info);
+ }
+ });
+ // also seed with monero seed nodes (see p2p/net_node.inl:410 in monero src)
+ seedList.add(new NodeInfo(new InetSocketAddress("107.152.130.98", 18080)));
+ seedList.add(new NodeInfo(new InetSocketAddress("212.83.175.67", 18080)));
+ seedList.add(new NodeInfo(new InetSocketAddress("5.9.100.248", 18080)));
+ seedList.add(new NodeInfo(new InetSocketAddress("163.172.182.165", 18080)));
+ seedList.add(new NodeInfo(new InetSocketAddress("161.67.132.39", 18080)));
+ seedList.add(new NodeInfo(new InetSocketAddress("198.74.231.92", 18080)));
+ seedList.add(new NodeInfo(new InetSocketAddress("195.154.123.123", 18080)));
+ seedList.add(new NodeInfo(new InetSocketAddress("212.83.172.165", 18080)));
+ d.seedPeers(seedList);
+ d.awaitTermination(NODES_TO_FIND);
+ }
+ // final (filtered) result
+ nodeList.addAll(d.getRpcNodes());
+ return true;
+ }
+
+ @Override
+ protected void onProgressUpdate(NodeInfo... values) {
+ Timber.d("onProgressUpdate");
+ if (!isCancelled())
+ if (values != null)
+ nodesAdapter.addNode(values[0]);
+ else
+ nodesAdapter.setNodes(null);
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ Timber.d("done scanning");
+ complete();
+ }
+
+ @Override
+ protected void onCancelled(Boolean result) {
+ Timber.d("cancelled scanning");
+ complete();
+ }
+
+ private void complete() {
+ asyncFindNodes = null;
+ if (!isAdded()) return;
+ //if (isCancelled()) return;
+ tvPull.setText(getString(R.string.node_pull_hint));
+ pullToRefresh.setRefreshing(false);
+ nodesAdapter.setNodes(nodeList);
+ nodesAdapter.allowClick(true);
+ updateRefreshElements();
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ Timber.d("detached");
+ super.onDetach();
+ }
+
+ private EditDialog editDialog = null; // for preventing opening of multiple dialogs
+
+ private EditDialog createEditDialog(final NodeInfo nodeInfo) {
+ if (editDialog != null) return null; // we are already open
+ editDialog = new EditDialog(nodeInfo);
+ return editDialog;
+ }
+
+ class EditDialog {
+ final NodeInfo nodeInfo;
+ final NodeInfo nodeBackup;
+
+ private boolean applyChanges() {
+ nodeInfo.clear();
+ showTestResult();
+
+ final String portString = etNodePort.getEditText().getText().toString().trim();
+ int port;
+ if (portString.isEmpty()) {
+ port = Node.getDefaultRpcPort();
+ } else {
+ try {
+ port = Integer.parseInt(portString);
+ } catch (NumberFormatException ex) {
+ etNodePort.setError(getString(R.string.node_port_numeric));
+ return false;
+ }
+ }
+ etNodePort.setError(null);
+ if ((port <= 0) || (port > 65535)) {
+ etNodePort.setError(getString(R.string.node_port_range));
+ return false;
+ }
+
+ final String host = etNodeHost.getEditText().getText().toString().trim();
+ if (host.isEmpty()) {
+ etNodeHost.setError(getString(R.string.node_host_empty));
+ return false;
+ }
+ final boolean setHostSuccess = Helper.runWithNetwork(new Helper.Action() {
+ @Override
+ public boolean run() {
+ try {
+ nodeInfo.setHost(host);
+ return true;
+ } catch (UnknownHostException ex) {
+ etNodeHost.setError(getString(R.string.node_host_unresolved));
+ return false;
+ }
+ }
+ });
+ if (!setHostSuccess) {
+ etNodeHost.setError(getString(R.string.node_host_unresolved));
+ return false;
+ }
+ etNodeHost.setError(null);
+ nodeInfo.setRpcPort(port);
+ // setName() may trigger reverse DNS
+ Helper.runWithNetwork(new Helper.Action() {
+ @Override
+ public boolean run() {
+ nodeInfo.setName(etNodeName.getEditText().getText().toString().trim());
+ return true;
+ }
+ });
+ nodeInfo.setUsername(etNodeUser.getEditText().getText().toString().trim());
+ nodeInfo.setPassword(etNodePass.getEditText().getText().toString()); // no trim for pw
+ return true;
+ }
+
+ private boolean shutdown = false;
+
+ private void apply() {
+ if (applyChanges()) {
+ closeDialog();
+ if (nodeBackup == null) { // this is a (FAB) new node
+ nodeInfo.setFavourite(true);
+ nodeList.add(nodeInfo);
+ }
+ shutdown = true;
+ new AsyncTestNode().execute();
+ }
+ }
+
+ private void closeDialog() {
+ if (editDialog == null) throw new IllegalStateException();
+ Helper.hideKeyboardAlways(getActivity());
+ editDialog.dismiss();
+ editDialog = null;
+ NodeFragment.this.editDialog = null;
+ }
+
+ private void undoChanges() {
+ if (nodeBackup != null)
+ nodeInfo.overwriteWith(nodeBackup);
+ }
+
+ private void show() {
+ editDialog.show();
+ }
+
+ private void test() {
+ if (applyChanges())
+ new AsyncTestNode().execute();
+ }
+
+ private void showKeyboard() {
+ Helper.showKeyboard(editDialog);
+ }
+
+ AlertDialog editDialog = null;
+
+ TextInputLayout etNodeName;
+ TextInputLayout etNodeHost;
+ TextInputLayout etNodePort;
+ TextInputLayout etNodeUser;
+ TextInputLayout etNodePass;
+ TextView tvResult;
+
+ void showTestResult() {
+ if (nodeInfo.isSuccessful()) {
+ tvResult.setText(getString(R.string.node_result,
+ FORMATTER.format(nodeInfo.getHeight()), nodeInfo.getMajorVersion(),
+ nodeInfo.getResponseTime(), nodeInfo.getHostAddress()));
+ } else {
+ tvResult.setText(NodeInfoAdapter.getResponseErrorText(getActivity(), nodeInfo.getResponseCode()));
+ }
+ }
+
+ EditDialog(final NodeInfo nodeInfo) {
+ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
+ LayoutInflater li = LayoutInflater.from(alertDialogBuilder.getContext());
+ View promptsView = li.inflate(R.layout.prompt_editnode, null);
+ alertDialogBuilder.setView(promptsView);
+
+ etNodeName = promptsView.findViewById(R.id.etNodeName);
+ etNodeHost = promptsView.findViewById(R.id.etNodeHost);
+ etNodePort = promptsView.findViewById(R.id.etNodePort);
+ etNodeUser = promptsView.findViewById(R.id.etNodeUser);
+ etNodePass = promptsView.findViewById(R.id.etNodePass);
+ tvResult = promptsView.findViewById(R.id.tvResult);
+
+ if (nodeInfo != null) {
+ this.nodeInfo = nodeInfo;
+ nodeBackup = new NodeInfo(nodeInfo);
+ etNodeName.getEditText().setText(nodeInfo.getName());
+ etNodeHost.getEditText().setText(nodeInfo.getHost());
+ etNodePort.getEditText().setText(Integer.toString(nodeInfo.getRpcPort()));
+ etNodeUser.getEditText().setText(nodeInfo.getUsername());
+ etNodePass.getEditText().setText(nodeInfo.getPassword());
+ showTestResult();
+ } else {
+ this.nodeInfo = new NodeInfo();
+ nodeBackup = null;
+ }
+
+ // set dialog message
+ alertDialogBuilder
+ .setCancelable(false)
+ .setPositiveButton(getString(R.string.label_ok), null)
+ .setNeutralButton(getString(R.string.label_test), null)
+ .setNegativeButton(getString(R.string.label_cancel),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ undoChanges();
+ closeDialog();
+ nodesAdapter.dataSetChanged(); // to refresh test results
+ }
+ });
+
+ editDialog = alertDialogBuilder.create();
+ // these need to be here, since we don't always close the dialog
+ editDialog.setOnShowListener(new DialogInterface.OnShowListener() {
+ @Override
+ public void onShow(final DialogInterface dialog) {
+ Button testButton = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEUTRAL);
+ testButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ test();
+ }
+ });
+
+ Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
+ button.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ apply();
+ }
+ });
+ }
+ });
+
+ etNodePass.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ editDialog.getButton(DialogInterface.BUTTON_NEUTRAL).requestFocus();
+ test();
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+ private class AsyncTestNode extends AsyncTask {
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ nodeInfo.clear();
+ tvResult.setText(getString(R.string.node_testing, nodeInfo.getHostAddress()));
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ nodeInfo.testRpcService();
+ return true;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ if (editDialog != null) {
+ showTestResult();
+ }
+ if (shutdown) {
+ if (nodeBackup == null) {
+ nodesAdapter.addNode(nodeInfo);
+ } else {
+ nodesAdapter.dataSetChanged();
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java b/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java
index 795bd414..fb48a05c 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java
@@ -92,17 +92,17 @@ public class ReceiveFragment extends Fragment {
View view = inflater.inflate(R.layout.fragment_receive, container, false);
- pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
- tvAddressLabel = (TextView) view.findViewById(R.id.tvAddressLabel);
- tvAddress = (TextView) view.findViewById(R.id.tvAddress);
- etNotes = (TextInputLayout) view.findViewById(R.id.etNotes);
- evAmount = (ExchangeView) view.findViewById(R.id.evAmount);
- qrCode = (ImageView) view.findViewById(R.id.qrCode);
- tvQrCode = (TextView) view.findViewById(R.id.tvQrCode);
- qrCodeFull = (ImageView) view.findViewById(R.id.qrCodeFull);
- etDummy = (EditText) view.findViewById(R.id.etDummy);
- bCopyAddress = (ImageButton) view.findViewById(R.id.bCopyAddress);
- bSubaddress = (Button) view.findViewById(R.id.bSubaddress);
+ pbProgress = view.findViewById(R.id.pbProgress);
+ tvAddressLabel = view.findViewById(R.id.tvAddressLabel);
+ tvAddress = view.findViewById(R.id.tvAddress);
+ etNotes = view.findViewById(R.id.etNotes);
+ evAmount = view.findViewById(R.id.evAmount);
+ qrCode = view.findViewById(R.id.qrCode);
+ tvQrCode = view.findViewById(R.id.tvQrCode);
+ qrCodeFull = view.findViewById(R.id.qrCodeFull);
+ etDummy = view.findViewById(R.id.etDummy);
+ bCopyAddress = view.findViewById(R.id.bCopyAddress);
+ bSubaddress = view.findViewById(R.id.bSubaddress);
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
@@ -438,7 +438,7 @@ public class ReceiveFragment extends Fragment {
Bitmap logoBitmap = Bitmap.createBitmap(qrWidth, qrHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(logoBitmap);
canvas.drawBitmap(qrBitmap, 0, 0, null);
- canvas.save(Canvas.ALL_SAVE_FLAG);
+ canvas.save();
// figure out how to scale the logo
float scaleSize = 1.0f;
while ((logoWidth / scaleSize) > (qrWidth / 5) || (logoHeight / scaleSize) > (qrHeight / 5)) {
@@ -482,7 +482,6 @@ public class ReceiveFragment extends Fragment {
if (context instanceof GenerateReviewFragment.ProgressListener) {
this.progressCallback = (GenerateReviewFragment.ProgressListener) context;
}
-
}
@Override
diff --git a/app/src/main/java/com/m2049r/xmrwallet/SecureActivity.java b/app/src/main/java/com/m2049r/xmrwallet/SecureActivity.java
index 36728956..c3e9b4c9 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/SecureActivity.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/SecureActivity.java
@@ -31,7 +31,7 @@ public abstract class SecureActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
// set FLAG_SECURE to prevent screenshots in Release Mode
- if (!BuildConfig.DEBUG) {
+ if (!BuildConfig.DEBUG && !BuildConfig.FLAVOR_type.equals("alpha")) {
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
}
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java b/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java
index ea86aa55..137e6a56 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java
@@ -85,22 +85,22 @@ public class TxFragment extends Fragment {
View view = inflater.inflate(R.layout.fragment_tx_info, container, false);
cvXmrTo = view.findViewById(R.id.cvXmrTo);
- tvTxXmrToKey = (TextView) view.findViewById(R.id.tvTxXmrToKey);
- tvDestinationBtc = (TextView) view.findViewById(R.id.tvDestinationBtc);
- tvTxAmountBtc = (TextView) view.findViewById(R.id.tvTxAmountBtc);
+ tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey);
+ tvDestinationBtc = view.findViewById(R.id.tvDestinationBtc);
+ tvTxAmountBtc = view.findViewById(R.id.tvTxAmountBtc);
- tvAccount = (TextView) view.findViewById(R.id.tvAccount);
- tvTxTimestamp = (TextView) view.findViewById(R.id.tvTxTimestamp);
- tvTxId = (TextView) view.findViewById(R.id.tvTxId);
- tvTxKey = (TextView) view.findViewById(R.id.tvTxKey);
- tvDestination = (TextView) view.findViewById(R.id.tvDestination);
- tvTxPaymentId = (TextView) view.findViewById(R.id.tvTxPaymentId);
- tvTxBlockheight = (TextView) view.findViewById(R.id.tvTxBlockheight);
- tvTxAmount = (TextView) view.findViewById(R.id.tvTxAmount);
- tvTxFee = (TextView) view.findViewById(R.id.tvTxFee);
- tvTxTransfers = (TextView) view.findViewById(R.id.tvTxTransfers);
- etTxNotes = (TextView) view.findViewById(R.id.etTxNotes);
- bTxNotes = (Button) view.findViewById(R.id.bTxNotes);
+ tvAccount = view.findViewById(R.id.tvAccount);
+ tvTxTimestamp = view.findViewById(R.id.tvTxTimestamp);
+ tvTxId = view.findViewById(R.id.tvTxId);
+ tvTxKey = view.findViewById(R.id.tvTxKey);
+ tvDestination = view.findViewById(R.id.tvDestination);
+ tvTxPaymentId = view.findViewById(R.id.tvTxPaymentId);
+ tvTxBlockheight = view.findViewById(R.id.tvTxBlockheight);
+ tvTxAmount = view.findViewById(R.id.tvTxAmount);
+ tvTxFee = view.findViewById(R.id.tvTxFee);
+ tvTxTransfers = view.findViewById(R.id.tvTxTransfers);
+ etTxNotes = view.findViewById(R.id.etTxNotes);
+ bTxNotes = view.findViewById(R.id.bTxNotes);
etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java
index 045c523b..cfb48161 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java
@@ -290,36 +290,18 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
}
private void onDisableStreetMode() {
- DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
+ Helper.promptPassword(WalletActivity.this, getWallet().getName(), false, new Helper.PasswordAction() {
@Override
- public void onClick(DialogInterface dialog, int which) {
- switch (which) {
- case DialogInterface.BUTTON_POSITIVE:
- Helper.promptPassword(WalletActivity.this, getWallet().getName(), true, new Helper.PasswordAction() {
- @Override
- public void action(String walletName, String password, boolean fingerprintUsed) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- enableStreetMode(false);
- updateStreetMode();
- }
- });
- }
- });
- break;
- case DialogInterface.BUTTON_NEGATIVE:
- // do nothing
- break;
- }
+ public void action(String walletName, String password, boolean fingerprintUsed) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ enableStreetMode(false);
+ updateStreetMode();
+ }
+ });
}
- };
-
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setMessage(getString(R.string.details_alert_message))
- .setPositiveButton(getString(R.string.details_alert_yes), dialogClickListener)
- .setNegativeButton(getString(R.string.details_alert_no), dialogClickListener)
- .show();
+ });
}
@@ -349,7 +331,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
}
setContentView(R.layout.activity_wallet);
- toolbar = (Toolbar) findViewById(R.id.toolbar);
+ toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayShowTitleEnabled(false);
@@ -378,13 +360,13 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
}
});
- drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+ drawer = findViewById(R.id.drawer_layout);
drawerToggle = new ActionBarDrawerToggle(this, drawer, toolbar, 0, 0);
drawer.addDrawerListener(drawerToggle);
drawerToggle.syncState();
setDrawerEnabled(false); // disable until synced
- accountsView = (NavigationView) findViewById(R.id.accounts_nav);
+ accountsView = findViewById(R.id.accounts_nav);
accountsView.setNavigationItemSelectedListener(this);
showNet();
@@ -1074,7 +1056,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
void updateAccountsHeader() {
final Wallet wallet = getWallet();
- final TextView tvName = (TextView) accountsView.getHeaderView(0).findViewById(R.id.tvName);
+ final TextView tvName = accountsView.getHeaderView(0).findViewById(R.id.tvName);
tvName.setText(wallet.getName());
}
@@ -1115,8 +1097,8 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
alertDialogBuilder.setView(promptsView);
- final EditText etRename = (EditText) promptsView.findViewById(R.id.etRename);
- final TextView tvRenameLabel = (TextView) promptsView.findViewById(R.id.tvRenameLabel);
+ final EditText etRename = promptsView.findViewById(R.id.etRename);
+ final TextView tvRenameLabel = promptsView.findViewById(R.id.tvRenameLabel);
final Wallet wallet = getWallet();
tvRenameLabel.setText(getString(R.string.prompt_rename, wallet.getAccountLabel()));
diff --git a/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java b/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java
index d7178b81..389996dc 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java
@@ -21,6 +21,7 @@ import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;
+import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.util.LocaleHelper;
import timber.log.Timber;
@@ -46,4 +47,17 @@ public class XmrWalletApplication extends Application {
LocaleHelper.updateSystemDefaultLocale(configuration.locale);
LocaleHelper.setLocale(XmrWalletApplication.this, LocaleHelper.getLocale(XmrWalletApplication.this));
}
+
+ static public NetworkType getNetworkType() {
+ switch (BuildConfig.FLAVOR_net) {
+ case "mainnet":
+ return NetworkType.NetworkType_Mainnet;
+ case "stagenet":
+ return NetworkType.NetworkType_Stagenet;
+ case "testnet":
+ return NetworkType.NetworkType_Testnet;
+ default:
+ throw new IllegalStateException("unknown net flavor " + BuildConfig.FLAVOR_net);
+ }
+ }
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/Node.java b/app/src/main/java/com/m2049r/xmrwallet/data/Node.java
new file mode 100644
index 00000000..4eb1d335
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/data/Node.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (c) 2018 m2049r
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.m2049r.xmrwallet.data;
+
+import com.m2049r.xmrwallet.model.NetworkType;
+import com.m2049r.xmrwallet.model.WalletManager;
+
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.net.UnknownHostException;
+
+import timber.log.Timber;
+
+public class Node {
+ static public final String MAINNET = "mainnet";
+ static public final String STAGENET = "stagenet";
+ static public final String TESTNET = "testnet";
+
+ private String name = null;
+ final private NetworkType networkType;
+ InetAddress hostAddress;
+ private String host;
+ int rpcPort = 0;
+ private int levinPort = 0;
+ private String username = "";
+ private String password = "";
+ private boolean favourite = false;
+
+ @Override
+ public int hashCode() {
+ return hostAddress.hashCode();
+ }
+
+ // Nodes are equal if they are the same host address & are on the same network
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof Node)) return false;
+ final Node anotherNode = (Node) other;
+ return (hostAddress.equals(anotherNode.hostAddress) && (networkType == anotherNode.networkType));
+ }
+
+ static public Node fromString(String nodeString) {
+ try {
+ return new Node(nodeString);
+ } catch (IllegalArgumentException ex) {
+ Timber.w(ex);
+ return null;
+ }
+ }
+
+ Node(String nodeString) {
+ if ((nodeString == null) || nodeString.isEmpty())
+ throw new IllegalArgumentException("daemon is empty");
+ String daemonAddress;
+ String a[] = nodeString.split("@");
+ if (a.length == 1) { // no credentials
+ daemonAddress = a[0];
+ username = "";
+ password = "";
+ } else if (a.length == 2) { // credentials
+ String userPassword[] = a[0].split(":");
+ if (userPassword.length != 2)
+ throw new IllegalArgumentException("User:Password invalid");
+ username = userPassword[0];
+ if (!username.isEmpty()) {
+ password = userPassword[1];
+ } else {
+ password = "";
+ }
+ daemonAddress = a[1];
+ } else {
+ throw new IllegalArgumentException("Too many @");
+ }
+
+ String daParts[] = daemonAddress.split("/");
+ if ((daParts.length > 3) || (daParts.length < 1))
+ throw new IllegalArgumentException("Too many '/' or too few");
+
+ daemonAddress = daParts[0];
+ String da[] = daemonAddress.split(":");
+ if ((da.length > 2) || (da.length < 1))
+ throw new IllegalArgumentException("Too many ':' or too few");
+ String host = da[0];
+
+ if (daParts.length == 1) {
+ networkType = NetworkType.NetworkType_Mainnet;
+ } else {
+ switch (daParts[1]) {
+ case MAINNET:
+ networkType = NetworkType.NetworkType_Mainnet;
+ break;
+ case STAGENET:
+ networkType = NetworkType.NetworkType_Stagenet;
+ break;
+ case TESTNET:
+ networkType = NetworkType.NetworkType_Testnet;
+ break;
+ default:
+ throw new IllegalArgumentException("invalid net: " + daParts[1]);
+ }
+ }
+ if (networkType != WalletManager.getInstance().getNetworkType())
+ throw new IllegalArgumentException("wrong net: " + networkType);
+
+ String name = host;
+ if (daParts.length == 3) {
+ try {
+ name = URLDecoder.decode(daParts[2], "UTF-8");
+ } catch (UnsupportedEncodingException ex) {
+ Timber.w(ex); // if we can't encode it, we don't use it
+ }
+ }
+ this.name = name;
+
+ int port;
+ if (da.length == 2) {
+ try {
+ port = Integer.parseInt(da[1]);
+ } catch (NumberFormatException ex) {
+ throw new IllegalArgumentException("Port not numeric");
+ }
+ } else {
+ port = getDefaultRpcPort();
+ }
+ try {
+ setHost(host);
+ } catch (UnknownHostException ex) {
+ throw new IllegalArgumentException("cannot resolve host " + host);
+ }
+ this.rpcPort = port;
+ this.levinPort = getDefaultLevinPort();
+ }
+
+ public String toNodeString() {
+ return toString();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (!username.isEmpty() && !password.isEmpty()) {
+ sb.append(username).append(":").append(password).append("@");
+ }
+ sb.append(host).append(":").append(rpcPort);
+ sb.append("/");
+ switch (networkType) {
+ case NetworkType_Mainnet:
+ sb.append(MAINNET);
+ break;
+ case NetworkType_Stagenet:
+ sb.append(STAGENET);
+ break;
+ case NetworkType_Testnet:
+ sb.append(TESTNET);
+ break;
+ }
+ if (name != null)
+ try {
+ sb.append("/").append(URLEncoder.encode(name, "UTF-8"));
+ } catch (UnsupportedEncodingException ex) {
+ Timber.w(ex); // if we can't encode it, we don't store it
+ }
+ return sb.toString();
+ }
+
+ public Node() {
+ this.networkType = WalletManager.getInstance().getNetworkType();
+ }
+
+ // constructor used for created nodes from retrieved peer lists
+ public Node(InetSocketAddress socketAddress) {
+ this();
+ this.hostAddress = socketAddress.getAddress();
+ this.host = socketAddress.getHostString();
+ this.rpcPort = 0; // unknown
+ this.levinPort = socketAddress.getPort();
+ this.username = "";
+ this.password = "";
+ //this.name = socketAddress.getHostName(); // triggers DNS so we don't do it by default
+ }
+
+ public String getAddress() {
+ return getHostAddress() + ":" + rpcPort;
+ }
+
+ public String getHostAddress() {
+ return hostAddress.getHostAddress();
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public int getRpcPort() {
+ return rpcPort;
+ }
+
+ public void setHost(String host) throws UnknownHostException {
+ if ((host == null) || (host.isEmpty()))
+ throw new UnknownHostException("loopback not supported (yet?)");
+ this.host = host;
+ this.hostAddress = InetAddress.getByName(host);
+ }
+
+ public void setUsername(String user) {
+ username = user;
+ }
+
+ public void setPassword(String pass) {
+ password = pass;
+ }
+
+ public void setRpcPort(int port) {
+ this.rpcPort = port;
+ }
+
+ public void setName() {
+ if (name == null)
+ this.name = hostAddress.getHostName();
+ }
+
+ public void setName(String name) {
+ if ((name == null) || (name.isEmpty()))
+ this.name = hostAddress.getHostName();
+ else
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public NetworkType getNetworkType() {
+ return networkType;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public boolean isFavourite() {
+ return favourite;
+ }
+
+ public void setFavourite(boolean favourite) {
+ this.favourite = favourite;
+ }
+
+ public void toggleFavourite() {
+ favourite = !favourite;
+ }
+
+ public Node(Node anotherNode) {
+ networkType = anotherNode.networkType;
+ overwriteWith(anotherNode);
+ }
+
+ public void overwriteWith(Node anotherNode) {
+ if (networkType != anotherNode.networkType)
+ throw new IllegalStateException("network types do not match");
+ name = anotherNode.name;
+ hostAddress = anotherNode.hostAddress;
+ host = anotherNode.host;
+ rpcPort = anotherNode.rpcPort;
+ levinPort = anotherNode.levinPort;
+ username = anotherNode.username;
+ password = anotherNode.password;
+ favourite = anotherNode.favourite;
+ }
+
+ static private int DEFAULT_LEVIN_PORT = 0;
+
+ // every node knows its network, but they are all the same
+ static public int getDefaultLevinPort() {
+ if (DEFAULT_LEVIN_PORT > 0) return DEFAULT_LEVIN_PORT;
+ switch (WalletManager.getInstance().getNetworkType()) {
+ case NetworkType_Mainnet:
+ DEFAULT_LEVIN_PORT = 18080;
+ break;
+ case NetworkType_Testnet:
+ DEFAULT_LEVIN_PORT = 28080;
+ break;
+ case NetworkType_Stagenet:
+ DEFAULT_LEVIN_PORT = 38080;
+ break;
+ default:
+ throw new IllegalStateException("unsupported net " + WalletManager.getInstance().getNetworkType());
+ }
+ return DEFAULT_LEVIN_PORT;
+ }
+
+ static private int DEFAULT_RPC_PORT = 0;
+
+ // every node knows its network, but they are all the same
+ static public int getDefaultRpcPort() {
+ if (DEFAULT_RPC_PORT > 0) return DEFAULT_RPC_PORT;
+ switch (WalletManager.getInstance().getNetworkType()) {
+ case NetworkType_Mainnet:
+ DEFAULT_RPC_PORT = 18081;
+ break;
+ case NetworkType_Testnet:
+ DEFAULT_RPC_PORT = 28081;
+ break;
+ case NetworkType_Stagenet:
+ DEFAULT_RPC_PORT = 38081;
+ break;
+ default:
+ throw new IllegalStateException("unsupported net " + WalletManager.getInstance().getNetworkType());
+ }
+ return DEFAULT_RPC_PORT;
+ }
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/NodeInfo.java b/app/src/main/java/com/m2049r/xmrwallet/data/NodeInfo.java
new file mode 100644
index 00000000..63f00d7b
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/data/NodeInfo.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2018 m2049r
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.m2049r.xmrwallet.data;
+
+import com.burgstaller.okhttp.AuthenticationCacheInterceptor;
+import com.burgstaller.okhttp.CachingAuthenticatorDecorator;
+import com.burgstaller.okhttp.digest.CachingAuthenticator;
+import com.burgstaller.okhttp.digest.Credentials;
+import com.burgstaller.okhttp.digest.DigestAuthenticator;
+import com.m2049r.levin.scanner.Dispatcher;
+import com.m2049r.xmrwallet.util.OkHttpHelper;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import okhttp3.HttpUrl;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import timber.log.Timber;
+
+public class NodeInfo extends Node {
+ final static public int MIN_MAJOR_VERSION = 9;
+
+ private long height = 0;
+ private long timestamp = 0;
+ private int majorVersion = 0;
+ private double responseTime = Double.MAX_VALUE;
+ private int responseCode = 0;
+
+ public void clear() {
+ height = 0;
+ majorVersion = 0;
+ responseTime = Double.MAX_VALUE;
+ responseCode = 0;
+ timestamp = 0;
+ }
+
+ static public NodeInfo fromString(String nodeString) {
+ try {
+ return new NodeInfo(nodeString);
+ } catch (IllegalArgumentException ex) {
+ Timber.w(ex);
+ return null;
+ }
+ }
+
+ public NodeInfo(NodeInfo anotherNode) {
+ super(anotherNode);
+ overwriteWith(anotherNode);
+ }
+
+ private SocketAddress levinSocketAddress = null;
+
+ synchronized public SocketAddress getLevinSocketAddress() {
+ if (levinSocketAddress == null) {
+ // use default peer port if not set - very few peers use nonstandard port
+ levinSocketAddress = new InetSocketAddress(hostAddress, getDefaultLevinPort());
+ }
+ return levinSocketAddress;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return super.equals(other);
+ }
+
+ public NodeInfo(String nodeString) {
+ super(nodeString);
+ }
+
+ public NodeInfo(InetSocketAddress socketAddress) {
+ super(socketAddress);
+ }
+
+ public NodeInfo() {
+ super();
+ }
+
+ public NodeInfo(InetSocketAddress peerAddress, long height, int majorVersion, double respTime) {
+ super(peerAddress);
+ this.height = height;
+ this.majorVersion = majorVersion;
+ this.responseTime = respTime;
+ }
+
+ public long getHeight() {
+ return height;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public int getMajorVersion() {
+ return majorVersion;
+ }
+
+ public double getResponseTime() {
+ return responseTime;
+ }
+
+ public int getResponseCode() {
+ return responseCode;
+ }
+
+ public boolean isSuccessful() {
+ return (responseCode >= 200) && (responseCode < 300);
+ }
+
+ public boolean isUnauthorized() {
+ return responseCode == HttpURLConnection.HTTP_UNAUTHORIZED;
+ }
+
+ public boolean isValid() {
+ return isSuccessful() && (majorVersion >= MIN_MAJOR_VERSION) && (responseTime < Double.MAX_VALUE);
+ }
+
+ static public Comparator BestNodeComparator = new Comparator() {
+ @Override
+ public int compare(NodeInfo o1, NodeInfo o2) {
+ if (o1.isValid()) {
+ if (o2.isValid()) { // both are valid
+ // higher node wins
+ int heightDiff = (int) (o2.height - o1.height);
+ if (Math.abs(heightDiff) > Dispatcher.HEIGHT_WINDOW)
+ return heightDiff;
+ // if they are (nearly) equal, faster node wins
+ return (int) Math.signum(o1.responseTime - o2.responseTime);
+ } else {
+ return -1;
+ }
+ } else {
+ return 1;
+ }
+ }
+ };
+
+ public void overwriteWith(NodeInfo anotherNode) {
+ super.overwriteWith(anotherNode);
+ height = anotherNode.height;
+ timestamp = anotherNode.timestamp;
+ majorVersion = anotherNode.majorVersion;
+ responseTime = anotherNode.responseTime;
+ responseCode = anotherNode.responseCode;
+ }
+
+ public String toNodeString() {
+ return super.toString();
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(super.toString());
+ sb.append("?rc=").append(responseCode);
+ sb.append("?v=").append(majorVersion);
+ sb.append("&h=").append(height);
+ sb.append("&ts=").append(timestamp);
+ if (responseTime < Double.MAX_VALUE) {
+ sb.append("&t=").append(responseTime).append("ms");
+ }
+ return sb.toString();
+ }
+
+ private static final int HTTP_TIMEOUT = OkHttpHelper.HTTP_TIMEOUT;
+ public static final double PING_GOOD = HTTP_TIMEOUT / 3; //ms
+ public static final double PING_MEDIUM = 2 * PING_GOOD; //ms
+ public static final double PING_BAD = HTTP_TIMEOUT;
+
+ public boolean testRpcService() {
+ return testRpcService(rpcPort);
+ }
+
+ private boolean testRpcService(int port) {
+ clear();
+ try {
+ OkHttpClient client = OkHttpHelper.getEagerClient();
+ if (!getUsername().isEmpty()) {
+ final DigestAuthenticator authenticator =
+ new DigestAuthenticator(new Credentials(getUsername(), getPassword()));
+ final Map authCache = new ConcurrentHashMap<>();
+ client = client.newBuilder()
+ .authenticator(new CachingAuthenticatorDecorator(authenticator, authCache))
+ .addInterceptor(new AuthenticationCacheInterceptor(authCache))
+ .build();
+ }
+ HttpUrl url = new HttpUrl.Builder()
+ .scheme("http")
+ .host(getHostAddress())
+ .port(port)
+ .addPathSegment("json_rpc")
+ .build();
+ final RequestBody reqBody = RequestBody
+ .create(MediaType.parse("application/json"),
+ "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"getlastblockheader\"}");
+ Request request = OkHttpHelper.getPostRequest(url, reqBody);
+ long ta = System.nanoTime();
+ try (Response response = client.newCall(request).execute()) {
+ responseTime = (System.nanoTime() - ta) / 1000000.0;
+ responseCode = response.code();
+ if (response.isSuccessful()) {
+ ResponseBody respBody = response.body(); // closed through Response object
+ if ((respBody != null) && (respBody.contentLength() < 1000)) { // sanity check
+ final JSONObject json = new JSONObject(
+ respBody.string());
+ final JSONObject header = json.getJSONObject(
+ "result").getJSONObject("block_header");
+ height = header.getLong("height");
+ timestamp = header.getLong("timestamp");
+ majorVersion = header.getInt("major_version");
+ return true; // success
+ }
+ }
+ }
+ } catch (IOException | JSONException ex) {
+ // failure
+ Timber.d(ex.getMessage());
+ }
+ return false;
+ }
+
+ static final private int[] TEST_PORTS = {18089}; // check only opt-in port
+
+ public boolean findRpcService() {
+ // if already have an rpcPort, use that
+ if (rpcPort > 0) return testRpcService(rpcPort);
+ // otherwise try to find one
+ for (int port : TEST_PORTS) {
+ if (testRpcService(port)) { // found a service
+ this.rpcPort = port;
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/WalletNode.java b/app/src/main/java/com/m2049r/xmrwallet/data/WalletNode.java
deleted file mode 100644
index 42705ab1..00000000
--- a/app/src/main/java/com/m2049r/xmrwallet/data/WalletNode.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (c) 2018 m2049r
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.m2049r.xmrwallet.data;
-
-import com.m2049r.xmrwallet.model.NetworkType;
-
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
-
-public class WalletNode {
- private final String name;
- private final String host;
- private final int port;
- private final String user;
- private final String password;
- private final NetworkType networkType;
-
- public WalletNode(String walletName, String daemon, NetworkType networkType) {
- if ((daemon == null) || daemon.isEmpty())
- throw new IllegalArgumentException("daemon is empty");
- this.name = walletName;
- String daemonAddress;
- String a[] = daemon.split("@");
- if (a.length == 1) { // no credentials
- daemonAddress = a[0];
- user = "";
- password = "";
- } else if (a.length == 2) { // credentials
- String userPassword[] = a[0].split(":");
- if (userPassword.length != 2)
- throw new IllegalArgumentException("User:Password invalid");
- user = userPassword[0];
- if (!user.isEmpty()) {
- password = userPassword[1];
- } else {
- password = "";
- }
- daemonAddress = a[1];
- } else {
- throw new IllegalArgumentException("Too many @");
- }
-
- String da[] = daemonAddress.split(":");
- if ((da.length > 2) || (da.length < 1))
- throw new IllegalArgumentException("Too many ':' or too few");
- host = da[0];
- if (da.length == 2) {
- try {
- port = Integer.parseInt(da[1]);
- } catch (NumberFormatException ex) {
- throw new IllegalArgumentException("Port not numeric");
- }
- } else {
- switch (networkType) {
- case NetworkType_Mainnet:
- port = 18081;
- break;
- case NetworkType_Testnet:
- port = 28081;
- break;
- case NetworkType_Stagenet:
- port = 38081;
- break;
- default:
- port = 0;
- }
- }
- this.networkType = networkType;
- }
-
- public String getName() {
- return name;
- }
-
- public NetworkType getNetworkType() {
- return networkType;
- }
-
- public String getAddress() {
- return host + ":" + port;
- }
-
- public String getUsername() {
- return user;
- }
-
- public String getPassword() {
- return password;
- }
-
- public SocketAddress getSocketAddress() {
- return new InetSocketAddress(host, port);
- }
-
- public boolean isValid() {
- return !host.isEmpty();
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/m2049r/xmrwallet/dialog/ProgressDialog.java b/app/src/main/java/com/m2049r/xmrwallet/dialog/ProgressDialog.java
index aa73f740..73713b06 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/dialog/ProgressDialog.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/dialog/ProgressDialog.java
@@ -57,10 +57,10 @@ public class ProgressDialog extends AlertDialog {
protected void onCreate(Bundle savedInstanceState) {
final View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_ledger_progress, null);
pbCircle = view.findViewById(R.id.pbCircle);
- tvMessage = (TextView) view.findViewById(R.id.tvMessage);
+ tvMessage = view.findViewById(R.id.tvMessage);
rlProgressBar = view.findViewById(R.id.rlProgressBar);
- pbBar = (ProgressBar) view.findViewById(R.id.pbBar);
- tvProgress = (TextView) view.findViewById(R.id.tvProgress);
+ pbBar = view.findViewById(R.id.pbBar);
+ tvProgress = view.findViewById(R.id.tvProgress);
setView(view);
//setTitle("blabla");
//super.setMessage("bubbu");
diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAddressWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAddressWizardFragment.java
index 939f8beb..587d59f5 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAddressWizardFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAddressWizardFragment.java
@@ -106,10 +106,10 @@ public class SendAddressWizardFragment extends SendWizardFragment {
tvPaymentIdIntegrated = view.findViewById(R.id.tvPaymentIdIntegrated);
llPaymentId = view.findViewById(R.id.llPaymentId);
llXmrTo = view.findViewById(R.id.llXmrTo);
- tvXmrTo = (TextView) view.findViewById(R.id.tvXmrTo);
+ tvXmrTo = view.findViewById(R.id.tvXmrTo);
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto)));
- etAddress = (TextInputLayout) view.findViewById(R.id.etAddress);
+ etAddress = view.findViewById(R.id.etAddress);
etAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
@@ -168,7 +168,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
}
});
- etPaymentId = (TextInputLayout) view.findViewById(R.id.etPaymentId);
+ etPaymentId = view.findViewById(R.id.etPaymentId);
etPaymentId.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etPaymentId.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
@@ -197,7 +197,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
}
});
- bPaymentId = (Button) view.findViewById(R.id.bPaymentId);
+ bPaymentId = view.findViewById(R.id.bPaymentId);
bPaymentId.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -205,7 +205,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
}
});
- etNotes = (TextInputLayout) view.findViewById(R.id.etNotes);
+ etNotes = view.findViewById(R.id.etNotes);
etNotes.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT);
etNotes.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
@@ -219,7 +219,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
}
});
- cvScan = (CardView) view.findViewById(R.id.bScan);
+ cvScan = view.findViewById(R.id.bScan);
cvScan.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -228,7 +228,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
});
- etDummy = (EditText) view.findViewById(R.id.etDummy);
+ etDummy = view.findViewById(R.id.etDummy);
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etDummy.requestFocus();
Helper.hideKeyboard(getActivity());
diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAmountWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAmountWizardFragment.java
index c7b901f5..c0b5fb5b 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAmountWizardFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAmountWizardFragment.java
@@ -74,9 +74,9 @@ public class SendAmountWizardFragment extends SendWizardFragment {
View view = inflater.inflate(R.layout.fragment_send_amount, container, false);
- tvFunds = (TextView) view.findViewById(R.id.tvFunds);
+ tvFunds = view.findViewById(R.id.tvFunds);
- evAmount = (ExchangeTextView) view.findViewById(R.id.evAmount);
+ evAmount = view.findViewById(R.id.evAmount);
((NumberPadView) view.findViewById(R.id.numberPad)).setListener(evAmount);
rlSweep = view.findViewById(R.id.rlSweep);
@@ -88,7 +88,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
}
});
- ibSweep = (ImageButton) view.findViewById(R.id.ibSweep);
+ ibSweep = view.findViewById(R.id.ibSweep);
ibSweep.setOnClickListener(new View.OnClickListener() {
@Override
diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcAmountWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcAmountWizardFragment.java
index d7e4dd25..d8708b61 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcAmountWizardFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcAmountWizardFragment.java
@@ -30,7 +30,7 @@ import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.data.TxDataBtc;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper;
-import com.m2049r.xmrwallet.util.OkHttpClientSingleton;
+import com.m2049r.xmrwallet.util.OkHttpHelper;
import com.m2049r.xmrwallet.widget.ExchangeBtcTextView;
import com.m2049r.xmrwallet.widget.NumberPadView;
import com.m2049r.xmrwallet.widget.SendProgressView;
@@ -44,7 +44,6 @@ import com.m2049r.xmrwallet.xmrto.network.XmrToApiImpl;
import java.text.NumberFormat;
import java.util.Locale;
-import okhttp3.HttpUrl;
import timber.log.Timber;
public class SendBtcAmountWizardFragment extends SendWizardFragment {
@@ -80,15 +79,15 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
View view = inflater.inflate(R.layout.fragment_send_btc_amount, container, false);
- tvFunds = (TextView) view.findViewById(R.id.tvFunds);
+ tvFunds = view.findViewById(R.id.tvFunds);
- evParams = (SendProgressView) view.findViewById(R.id.evXmrToParms);
+ evParams = view.findViewById(R.id.evXmrToParms);
llXmrToParms = view.findViewById(R.id.llXmrToParms);
- tvXmrToParms = (TextView) view.findViewById(R.id.tvXmrToParms);
+ tvXmrToParms = view.findViewById(R.id.tvXmrToParms);
- evAmount = (ExchangeBtcTextView) view.findViewById(R.id.evAmount);
- numberPad = (NumberPadView) view.findViewById(R.id.numberPad);
+ evAmount = view.findViewById(R.id.evAmount);
+ numberPad = view.findViewById(R.id.numberPad);
numberPad.setListener(evAmount);
Helper.hideKeyboard(getActivity());
@@ -263,7 +262,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
if (xmrToApi == null) {
synchronized (this) {
if (xmrToApi == null) {
- xmrToApi = new XmrToApiImpl(OkHttpClientSingleton.getOkHttpClient(),
+ xmrToApi = new XmrToApiImpl(OkHttpHelper.getOkHttpClient(),
Helper.getXmrToBaseUrl());
}
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcConfirmWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcConfirmWizardFragment.java
index 2697b336..6e8a2009 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcConfirmWizardFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcConfirmWizardFragment.java
@@ -38,7 +38,7 @@ import com.m2049r.xmrwallet.data.TxDataBtc;
import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper;
-import com.m2049r.xmrwallet.util.OkHttpClientSingleton;
+import com.m2049r.xmrwallet.util.OkHttpHelper;
import com.m2049r.xmrwallet.widget.SendProgressView;
import com.m2049r.xmrwallet.xmrto.XmrToError;
import com.m2049r.xmrwallet.xmrto.XmrToException;
@@ -51,7 +51,6 @@ import com.m2049r.xmrwallet.xmrto.network.XmrToApiImpl;
import java.text.NumberFormat;
import java.util.Locale;
-import okhttp3.HttpUrl;
import timber.log.Timber;
public class SendBtcConfirmWizardFragment extends SendWizardFragment implements SendConfirm {
@@ -95,21 +94,21 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
View view = inflater.inflate(
R.layout.fragment_send_btc_confirm, container, false);
- tvTxBtcAddress = (TextView) view.findViewById(R.id.tvTxBtcAddress);
- tvTxBtcAmount = ((TextView) view.findViewById(R.id.tvTxBtcAmount));
- tvTxBtcRate = (TextView) view.findViewById(R.id.tvTxBtcRate);
- tvTxXmrToKey = (TextView) view.findViewById(R.id.tvTxXmrToKey);
+ tvTxBtcAddress = view.findViewById(R.id.tvTxBtcAddress);
+ tvTxBtcAmount = view.findViewById(R.id.tvTxBtcAmount);
+ tvTxBtcRate = view.findViewById(R.id.tvTxBtcRate);
+ tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey);
- tvTxFee = (TextView) view.findViewById(R.id.tvTxFee);
- tvTxTotal = (TextView) view.findViewById(R.id.tvTxTotal);
+ tvTxFee = view.findViewById(R.id.tvTxFee);
+ tvTxTotal = view.findViewById(R.id.tvTxTotal);
llStageA = view.findViewById(R.id.llStageA);
- evStageA = (SendProgressView) view.findViewById(R.id.evStageA);
+ evStageA = view.findViewById(R.id.evStageA);
llStageB = view.findViewById(R.id.llStageB);
- evStageB = (SendProgressView) view.findViewById(R.id.evStageB);
+ evStageB = view.findViewById(R.id.evStageB);
llStageC = view.findViewById(R.id.llStageC);
- evStageC = (SendProgressView) view.findViewById(R.id.evStageC);
+ evStageC = view.findViewById(R.id.evStageC);
tvTxXmrToKey.setOnClickListener(new View.OnClickListener() {
@Override
@@ -122,7 +121,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
llConfirmSend = view.findViewById(R.id.llConfirmSend);
pbProgressSend = view.findViewById(R.id.pbProgressSend);
- bSend = (Button) view.findViewById(R.id.bSend);
+ bSend = view.findViewById(R.id.bSend);
bSend.setEnabled(false);
bSend.setOnClickListener(new View.OnClickListener() {
@@ -350,7 +349,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
android.app.AlertDialog.Builder alertDialogBuilder = new android.app.AlertDialog.Builder(activity);
alertDialogBuilder.setView(promptsView);
- final TextInputLayout etPassword = (TextInputLayout) promptsView.findViewById(R.id.etPassword);
+ final TextInputLayout etPassword = promptsView.findViewById(R.id.etPassword);
etPassword.setHint(getString(R.string.prompt_send_password));
etPassword.getEditText().addTextChangedListener(new TextWatcher() {
@@ -671,7 +670,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
if (xmrToApi == null) {
synchronized (this) {
if (xmrToApi == null) {
- xmrToApi = new XmrToApiImpl(OkHttpClientSingleton.getOkHttpClient(),
+ xmrToApi = new XmrToApiImpl(OkHttpHelper.getOkHttpClient(),
Helper.getXmrToBaseUrl());
}
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcSuccessWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcSuccessWizardFragment.java
index ca56e2a7..fc698b42 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcSuccessWizardFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcSuccessWizardFragment.java
@@ -30,7 +30,7 @@ import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.PendingTx;
import com.m2049r.xmrwallet.data.TxDataBtc;
import com.m2049r.xmrwallet.util.Helper;
-import com.m2049r.xmrwallet.util.OkHttpClientSingleton;
+import com.m2049r.xmrwallet.util.OkHttpHelper;
import com.m2049r.xmrwallet.xmrto.XmrToException;
import com.m2049r.xmrwallet.xmrto.api.QueryOrderStatus;
import com.m2049r.xmrwallet.xmrto.api.XmrToApi;
@@ -40,7 +40,6 @@ import com.m2049r.xmrwallet.xmrto.network.XmrToApiImpl;
import java.text.NumberFormat;
import java.util.Locale;
-import okhttp3.HttpUrl;
import timber.log.Timber;
public class SendBtcSuccessWizardFragment extends SendWizardFragment {
@@ -80,7 +79,7 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
View view = inflater.inflate(
R.layout.fragment_send_btc_success, container, false);
- bCopyTxId = (ImageButton) view.findViewById(R.id.bCopyTxId);
+ bCopyTxId = view.findViewById(R.id.bCopyTxId);
bCopyTxId.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -89,27 +88,26 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
}
});
- tvXmrToAmount = (TextView) view.findViewById(R.id.tvXmrToAmount);
- tvXmrToStatus = (TextView) view.findViewById(R.id.tvXmrToStatus);
- ivXmrToStatus = (ImageView) view.findViewById(R.id.ivXmrToStatus);
- ivXmrToStatusBig = (ImageView) view.findViewById(R.id.ivXmrToStatusBig);
+ tvXmrToAmount = view.findViewById(R.id.tvXmrToAmount);
+ tvXmrToStatus = view.findViewById(R.id.tvXmrToStatus);
+ ivXmrToStatus = view.findViewById(R.id.ivXmrToStatus);
+ ivXmrToStatusBig = view.findViewById(R.id.ivXmrToStatusBig);
- tvTxId = (TextView) view.findViewById(R.id.tvTxId);
- tvTxAddress = (TextView) view.findViewById(R.id.tvTxAddress);
- tvTxPaymentId = (TextView) view.findViewById(R.id.tvTxPaymentId);
- tvTxAmount = ((TextView) view.findViewById(R.id.tvTxAmount));
- tvTxFee = (TextView) view.findViewById(R.id.tvTxFee);
+ tvTxId = view.findViewById(R.id.tvTxId);
+ tvTxAddress = view.findViewById(R.id.tvTxAddress);
+ tvTxPaymentId = view.findViewById(R.id.tvTxPaymentId);
+ tvTxAmount = view.findViewById(R.id.tvTxAmount);
+ tvTxFee = view.findViewById(R.id.tvTxFee);
- pbXmrto = (ProgressBar) view.findViewById(R.id.pbXmrto);
+ pbXmrto = view.findViewById(R.id.pbXmrto);
pbXmrto.getIndeterminateDrawable().setColorFilter(0x61000000, android.graphics.PorterDuff.Mode.MULTIPLY);
- tvTxXmrToKey = (TextView) view.findViewById(R.id.tvTxXmrToKey);
+ tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey);
tvTxXmrToKey.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString());
Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show();
- ;
}
});
@@ -257,7 +255,7 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
if (xmrToApi == null) {
synchronized (this) {
if (xmrToApi == null) {
- xmrToApi = new XmrToApiImpl(OkHttpClientSingleton.getOkHttpClient(),
+ xmrToApi = new XmrToApiImpl(OkHttpHelper.getOkHttpClient(),
Helper.getXmrToBaseUrl());
}
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirmWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirmWizardFragment.java
index 5299d3bc..f0eba4db 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirmWizardFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirmWizardFragment.java
@@ -87,12 +87,12 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
View view = inflater.inflate(
R.layout.fragment_send_confirm, container, false);
- tvTxAddress = (TextView) view.findViewById(R.id.tvTxAddress);
- tvTxPaymentId = (TextView) view.findViewById(R.id.tvTxPaymentId);
- tvTxNotes = (TextView) view.findViewById(R.id.tvTxNotes);
- tvTxAmount = ((TextView) view.findViewById(R.id.tvTxAmount));
- tvTxFee = (TextView) view.findViewById(R.id.tvTxFee);
- tvTxTotal = (TextView) view.findViewById(R.id.tvTxTotal);
+ tvTxAddress = view.findViewById(R.id.tvTxAddress);
+ tvTxPaymentId = view.findViewById(R.id.tvTxPaymentId);
+ tvTxNotes = view.findViewById(R.id.tvTxNotes);
+ tvTxAmount = view.findViewById(R.id.tvTxAmount);
+ tvTxFee = view.findViewById(R.id.tvTxFee);
+ tvTxTotal = view.findViewById(R.id.tvTxTotal);
llProgress = view.findViewById(R.id.llProgress);
pbProgressSend = view.findViewById(R.id.pbProgressSend);
@@ -231,7 +231,7 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
android.app.AlertDialog.Builder alertDialogBuilder = new android.app.AlertDialog.Builder(activity);
alertDialogBuilder.setView(promptsView);
- final TextInputLayout etPassword = (TextInputLayout) promptsView.findViewById(R.id.etPassword);
+ final TextInputLayout etPassword = promptsView.findViewById(R.id.etPassword);
etPassword.setHint(getString(R.string.prompt_send_password));
etPassword.getEditText().addTextChangedListener(new TextWatcher() {
diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendFragment.java
index 9c7f8e18..a9f0a541 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendFragment.java
@@ -112,18 +112,18 @@ public class SendFragment extends Fragment
final View view = inflater.inflate(R.layout.fragment_send, container, false);
llNavBar = view.findViewById(R.id.llNavBar);
- bDone = (Button) view.findViewById(R.id.bDone);
+ bDone = view.findViewById(R.id.bDone);
- dotBar = (DotBar) view.findViewById(R.id.dotBar);
- bPrev = (Button) view.findViewById(R.id.bPrev);
- bNext = (Button) view.findViewById(R.id.bNext);
+ dotBar = view.findViewById(R.id.dotBar);
+ bPrev = view.findViewById(R.id.bPrev);
+ bNext = view.findViewById(R.id.bNext);
arrowPrev = getResources().getDrawable(R.drawable.ic_navigate_prev_white_24dp);
arrowNext = getResources().getDrawable(R.drawable.ic_navigate_next_white_24dp);
- ViewGroup llNotice = (ViewGroup) view.findViewById(R.id.llNotice);
+ ViewGroup llNotice = view.findViewById(R.id.llNotice);
Notice.showAll(llNotice, ".*_send");
- spendViewPager = (SpendViewPager) view.findViewById(R.id.pager);
+ spendViewPager = view.findViewById(R.id.pager);
pagerAdapter = new SpendPagerAdapter(getChildFragmentManager());
spendViewPager.setOffscreenPageLimit(pagerAdapter.getCount()); // load & keep all pages in cache
spendViewPager.setAdapter(pagerAdapter);
@@ -183,7 +183,7 @@ public class SendFragment extends Fragment
updatePosition(0);
- etDummy = (EditText) view.findViewById(R.id.etDummy);
+ etDummy = view.findViewById(R.id.etDummy);
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etDummy.requestFocus();
Helper.hideKeyboard(getActivity());
diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendSuccessWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendSuccessWizardFragment.java
index 2d2588a7..0e09a0c6 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendSuccessWizardFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendSuccessWizardFragment.java
@@ -72,7 +72,7 @@ public class SendSuccessWizardFragment extends SendWizardFragment {
View view = inflater.inflate(
R.layout.fragment_send_success, container, false);
- bCopyTxId = (ImageButton) view.findViewById(R.id.bCopyTxId);
+ bCopyTxId = view.findViewById(R.id.bCopyTxId);
bCopyTxId.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -81,11 +81,11 @@ public class SendSuccessWizardFragment extends SendWizardFragment {
}
});
- tvTxId = (TextView) view.findViewById(R.id.tvTxId);
- tvTxAddress = (TextView) view.findViewById(R.id.tvTxAddress);
- tvTxPaymentId = (TextView) view.findViewById(R.id.tvTxPaymentId);
- tvTxAmount = ((TextView) view.findViewById(R.id.tvTxAmount));
- tvTxFee = (TextView) view.findViewById(R.id.tvTxFee);
+ tvTxId = view.findViewById(R.id.tvTxId);
+ tvTxAddress = view.findViewById(R.id.tvTxAddress);
+ tvTxPaymentId = view.findViewById(R.id.tvTxPaymentId);
+ tvTxAmount = view.findViewById(R.id.tvTxAmount);
+ tvTxFee = view.findViewById(R.id.tvTxFee);
return view;
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/NodeInfoAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/layout/NodeInfoAdapter.java
new file mode 100644
index 00000000..25b7dacf
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/layout/NodeInfoAdapter.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2018 m2049r
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.m2049r.xmrwallet.layout;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.m2049r.xmrwallet.R;
+import com.m2049r.xmrwallet.data.NodeInfo;
+
+import java.net.HttpURLConnection;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+public class NodeInfoAdapter extends RecyclerView.Adapter {
+ private final SimpleDateFormat TS_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+
+ public interface OnInteractionListener {
+ void onInteraction(View view, NodeInfo item);
+ }
+
+ private final List nodeItems = new ArrayList<>();
+ private final OnInteractionListener listener;
+
+ private Context context;
+
+ public NodeInfoAdapter(Context context, OnInteractionListener listener) {
+ this.context = context;
+ this.listener = listener;
+ Calendar cal = Calendar.getInstance();
+ TimeZone tz = cal.getTimeZone(); //get the local time zone.
+ TS_FORMATTER.setTimeZone(tz);
+ }
+
+ @Override
+ public @NonNull
+ ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_node, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(final @NonNull ViewHolder holder, int position) {
+ holder.bind(position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return nodeItems.size();
+ }
+
+ public void addNode(NodeInfo node) {
+ if (!nodeItems.contains(node))
+ nodeItems.add(node);
+ dataSetChanged(); // in case the nodeinfo has changed
+ }
+
+ public void dataSetChanged() {
+ Collections.sort(nodeItems, NodeInfo.BestNodeComparator);
+ notifyDataSetChanged();
+ }
+
+ public void setNodes(Collection data) {
+ nodeItems.clear();
+ if (data != null) {
+ for (NodeInfo node : data) {
+ if (!nodeItems.contains(node))
+ nodeItems.add(node);
+ }
+ }
+ dataSetChanged();
+ }
+
+ private boolean itemsClickable = true;
+
+ public void allowClick(boolean clickable) {
+ itemsClickable = clickable;
+ notifyDataSetChanged();
+ }
+
+ class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
+ final ImageButton ibBookmark;
+ final TextView tvName;
+ final TextView tvIp;
+ final ImageView ivPing;
+ NodeInfo nodeItem;
+
+ ViewHolder(View itemView) {
+ super(itemView);
+ ibBookmark = itemView.findViewById(R.id.ibBookmark);
+ tvName = itemView.findViewById(R.id.tvName);
+ tvIp = itemView.findViewById(R.id.tvAddress);
+ ivPing = itemView.findViewById(R.id.ivPing);
+ ibBookmark.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ nodeItem.toggleFavourite();
+ showStar();
+ }
+ });
+ }
+
+ private void showStar() {
+ if (nodeItem.isFavourite()) {
+ ibBookmark.setImageResource(R.drawable.ic_bookmark_24dp);
+ } else {
+ ibBookmark.setImageResource(R.drawable.ic_bookmark_border_24dp);
+ }
+ }
+
+ void bind(int position) {
+ nodeItem = nodeItems.get(position);
+ tvName.setText(nodeItem.getName());
+ final String ts = TS_FORMATTER.format(new Date(nodeItem.getTimestamp() * 1000));
+ ivPing.setImageResource(getPingIcon(nodeItem));
+ if (nodeItem.isValid()) {
+ tvIp.setText(context.getString(R.string.node_height, ts));
+ } else {
+ tvIp.setText(getResponseErrorText(context, nodeItem.getResponseCode()));
+ }
+ itemView.setOnClickListener(this);
+ itemView.setClickable(itemsClickable);
+ ibBookmark.setClickable(itemsClickable);
+ showStar();
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (listener != null) {
+ int position = getAdapterPosition(); // gets item position
+ if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it
+ listener.onInteraction(view, nodeItems.get(position));
+ }
+ }
+ }
+ }
+
+ static public int getPingIcon(NodeInfo nodeInfo) {
+ if (nodeInfo.isUnauthorized()) {
+ return R.drawable.ic_wifi_lock_black_24dp;
+ }
+ if (nodeInfo.isValid()) {
+ final double ping = nodeInfo.getResponseTime();
+ if (ping < NodeInfo.PING_GOOD) {
+ return R.drawable.ic_signal_wifi_4_bar_black_24dp;
+ } else if (ping < NodeInfo.PING_MEDIUM) {
+ return R.drawable.ic_signal_wifi_3_bar_black_24dp;
+ } else if (ping < NodeInfo.PING_BAD) {
+ return R.drawable.ic_signal_wifi_2_bar_black_24dp;
+ } else {
+ return R.drawable.ic_signal_wifi_1_bar_black_24dp;
+ }
+ } else {
+ return R.drawable.ic_signal_wifi_off_black_24dp;
+ }
+ }
+
+ static public String getResponseErrorText(Context ctx, int responseCode) {
+ if (responseCode == 0) {
+ return ctx.getResources().getString(R.string.node_general_error);
+ } else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
+ return ctx.getResources().getString(R.string.node_auth_error);
+ } else {
+ return ctx.getResources().getString(R.string.node_test_error, responseCode);
+ }
+ }
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java
index 0fa41094..8d1e508a 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java
@@ -112,11 +112,11 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter managedWallets;
+ public String addressPrefix() {
+ return addressPrefix(getNetworkType());
+ }
+
+ static public String addressPrefix(NetworkType networkType) {
+ switch (networkType) {
+ case NetworkType_Testnet:
+ return "9A-";
+ case NetworkType_Mainnet:
+ return "4-";
+ case NetworkType_Stagenet:
+ return "5-";
+ default:
+ throw new IllegalStateException("Unsupported Network: " + networkType);
+ }
+ }
+
private Wallet managedWallet = null;
public Wallet getWallet() {
@@ -252,23 +269,26 @@ public class WalletManager {
//TODO virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0;
private String daemonAddress = null;
- private NetworkType networkType = null;
+ private final NetworkType networkType = XmrWalletApplication.getNetworkType();
public NetworkType getNetworkType() {
return networkType;
}
- //public void setDaemon(String address, NetworkType networkType, String username, String password) {
- public void setDaemon(WalletNode walletNode) {
- this.daemonAddress = walletNode.getAddress();
- this.networkType = walletNode.getNetworkType();
- this.daemonUsername = walletNode.getUsername();
- this.daemonPassword = walletNode.getPassword();
- setDaemonAddressJ(daemonAddress);
- }
-
- public void setNetworkType(NetworkType networkType) {
- this.networkType = networkType;
+ public void setDaemon(Node node) {
+ if (node != null) {
+ this.daemonAddress = node.getAddress();
+ if (networkType != node.getNetworkType())
+ throw new IllegalArgumentException("network type does not match");
+ this.daemonUsername = node.getUsername();
+ this.daemonPassword = node.getPassword();
+ setDaemonAddressJ(daemonAddress);
+ } else {
+ this.daemonAddress = null;
+ this.daemonUsername = "";
+ this.daemonPassword = "";
+ setDaemonAddressJ("");
+ }
}
public String getDaemonAddress() {
diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/coinmarketcap/ExchangeApiImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/coinmarketcap/ExchangeApiImpl.java
index 50087158..0babfdd8 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/coinmarketcap/ExchangeApiImpl.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/coinmarketcap/ExchangeApiImpl.java
@@ -107,7 +107,7 @@ public class ExchangeApiImpl implements ExchangeApi {
final JSONObject metadata = json.getJSONObject("metadata");
if (!metadata.isNull("error")) {
final String errorMsg = metadata.getString("error");
- callback.onError(new ExchangeException(response.code(), (String) errorMsg));
+ callback.onError(new ExchangeException(response.code(), errorMsg));
} else {
final JSONObject jsonResult = json.getJSONObject("data");
reportSuccess(jsonResult, swapAssets, callback);
diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java
index 1ab98a97..6022cd04 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java
@@ -36,6 +36,7 @@ import android.os.AsyncTask;
import android.os.Build;
import android.os.CancellationSignal;
import android.os.Environment;
+import android.os.StrictMode;
import android.support.design.widget.TextInputLayout;
import android.support.v4.content.ContextCompat;
import android.system.ErrnoException;
@@ -77,7 +78,7 @@ import timber.log.Timber;
public class Helper {
static private final String FLAVOR_SUFFIX =
- (BuildConfig.FLAVOR.equals("prod") ? "" : "." + BuildConfig.FLAVOR)
+ (BuildConfig.FLAVOR.startsWith("prod") ? "" : "." + BuildConfig.FLAVOR)
+ (BuildConfig.DEBUG ? "-debug" : "");
static public final String CRYPTO = "XMR";
@@ -397,10 +398,10 @@ public class Helper {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context);
alertDialogBuilder.setView(promptsView);
- final TextInputLayout etPassword = (TextInputLayout) promptsView.findViewById(R.id.etPassword);
+ final TextInputLayout etPassword = promptsView.findViewById(R.id.etPassword);
etPassword.setHint(context.getString(R.string.prompt_password, wallet));
- final TextView tvOpenPrompt = (TextView) promptsView.findViewById(R.id.tvOpenPrompt);
+ final TextView tvOpenPrompt = promptsView.findViewById(R.id.tvOpenPrompt);
final Drawable icFingerprint = context.getDrawable(R.drawable.ic_fingerprint);
final Drawable icError = context.getDrawable(R.drawable.ic_error_red_36dp);
final Drawable icInfo = context.getDrawable(R.drawable.ic_info_green_36dp);
@@ -598,6 +599,21 @@ public class Helper {
}
static public ExchangeApi getExchangeApi() {
- return new com.m2049r.xmrwallet.service.exchange.coinmarketcap.ExchangeApiImpl(OkHttpClientSingleton.getOkHttpClient());
+ return new com.m2049r.xmrwallet.service.exchange.coinmarketcap.ExchangeApiImpl(OkHttpHelper.getOkHttpClient());
+ }
+
+ public interface Action {
+ boolean run();
+ }
+
+ static public boolean runWithNetwork(Action action) {
+ StrictMode.ThreadPolicy currentPolicy = StrictMode.getThreadPolicy();
+ StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();
+ StrictMode.setThreadPolicy(policy);
+ try {
+ return action.run();
+ } finally {
+ StrictMode.setThreadPolicy(currentPolicy);
+ }
}
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java b/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java
index 2e3521ca..4212887f 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java
@@ -102,11 +102,7 @@ public class KeyStoreHelper {
if (isArm32 != null) return isArm32;
synchronized (KeyStoreException.class) {
if (isArm32 != null) return isArm32;
- if (Build.SUPPORTED_ABIS[0].equals("armeabi-v7a")) {
- isArm32 = true;
- } else {
- isArm32 = false;
- }
+ isArm32 = Build.SUPPORTED_ABIS[0].equals("armeabi-v7a");
return isArm32;
}
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/NodeList.java b/app/src/main/java/com/m2049r/xmrwallet/util/NodeList.java
deleted file mode 100644
index c73643e1..00000000
--- a/app/src/main/java/com/m2049r/xmrwallet/util/NodeList.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (c) 2017 m2049r
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.m2049r.xmrwallet.util;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-public class NodeList {
- private static final int MAX_SIZE = 5;
-
- private List nodes = new ArrayList<>();
-
- public List getNodes() {
- return nodes;
- }
-
- public void setRecent(String aNode) {
- if (aNode.trim().isEmpty()) return;
- boolean found = false;
- for (int i = 0; i < nodes.size(); i++) {
- if (nodes.get(i).equals(aNode)) { // node is already in the list => move it to top
- nodes.remove(i);
- found = true;
- break;
- }
- }
- if (!found) {
- if (nodes.size() > MAX_SIZE) {
- nodes.remove(nodes.size() - 1); // drop last one
- }
- }
- nodes.add(0, aNode);
- }
-
- public NodeList(String aString) {
- String[] newNodes = aString.split(";");
- nodes.addAll(Arrays.asList(newNodes));
- }
-
- @Override
- public String toString() {
- StringBuffer sb = new StringBuffer();
- for (String node : this.nodes) {
- sb.append(node).append(";");
- }
- return sb.toString();
- }
-}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Notice.java b/app/src/main/java/com/m2049r/xmrwallet/util/Notice.java
index 350b5a0e..8985fba5 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/util/Notice.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/util/Notice.java
@@ -40,16 +40,16 @@ public class Notice {
private static final String NOTICE_SHOW_XMRTO_ENABLED_LOGIN = "notice_xmrto_enabled_login";
private static final String NOTICE_SHOW_XMRTO_ENABLED_SEND = "notice_xmrto_enabled_send";
private static final String NOTICE_SHOW_LEDGER = "notice_ledger_enabled_login";
- private static final String NOTICE_SHOW_STREET = "notice_streetmode_login";
+ private static final String NOTICE_SHOW_NODES = "notice_nodes";
private static void init() {
synchronized (Notice.class) {
if (notices != null) return;
notices = new ArrayList<>();
notices.add(
- new Notice(NOTICE_SHOW_STREET,
- R.string.info_streetmode_enabled,
- R.string.help_wallet,
+ new Notice(NOTICE_SHOW_NODES,
+ R.string.info_nodes_enabled,
+ R.string.help_node,
1)
);
notices.add(
@@ -115,7 +115,7 @@ public class Notice {
}
});
- ImageButton ib = (ImageButton) ll.findViewById(R.id.ibClose);
+ ImageButton ib = ll.findViewById(R.id.ibClose);
ib.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/OkHttpClientSingleton.java b/app/src/main/java/com/m2049r/xmrwallet/util/OkHttpClientSingleton.java
deleted file mode 100644
index f1dae5c9..00000000
--- a/app/src/main/java/com/m2049r/xmrwallet/util/OkHttpClientSingleton.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (c) 2017 m2049r
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.m2049r.xmrwallet.util;
-
-import okhttp3.OkHttpClient;
-
-public class OkHttpClientSingleton {
- static private OkHttpClient Singleton;
-
- static public final OkHttpClient getOkHttpClient() {
- if (Singleton == null) {
- synchronized (com.m2049r.xmrwallet.util.OkHttpClientSingleton.class) {
- if (Singleton == null) {
- Singleton = new OkHttpClient();
- }
- }
- }
- return Singleton;
- }
-}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/OkHttpHelper.java b/app/src/main/java/com/m2049r/xmrwallet/util/OkHttpHelper.java
new file mode 100644
index 00000000..ecaa5e6b
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/util/OkHttpHelper.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2017 m2049r
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.m2049r.xmrwallet.util;
+
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.HttpUrl;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+
+public class OkHttpHelper {
+ static private OkHttpClient Singleton;
+
+ static public OkHttpClient getOkHttpClient() {
+ if (Singleton == null) {
+ synchronized (OkHttpHelper.class) {
+ if (Singleton == null) {
+ Singleton = new OkHttpClient();
+ }
+ }
+ }
+ return Singleton;
+ }
+
+ public static final int HTTP_TIMEOUT = 1000; //ms
+
+ static private OkHttpClient EagerSingleton;
+
+ static public OkHttpClient getEagerClient() {
+ if (EagerSingleton == null) {
+ synchronized (OkHttpHelper.class) {
+ if (EagerSingleton == null) {
+ EagerSingleton = new OkHttpClient.Builder()
+ .connectTimeout(HTTP_TIMEOUT, TimeUnit.MILLISECONDS)
+ .writeTimeout(HTTP_TIMEOUT, TimeUnit.MILLISECONDS)
+ .readTimeout(HTTP_TIMEOUT, TimeUnit.MILLISECONDS)
+ .build();
+ }
+ }
+ }
+ return EagerSingleton;
+ }
+
+ static final public String USER_AGENT = "Monerujo/1.0";
+
+ static public Request getPostRequest(HttpUrl url, RequestBody requestBody) {
+ return new Request.Builder().url(url).post(requestBody)
+ .header("User-Agent", USER_AGENT)
+ .build();
+ }
+
+ static public Request getGetRequest(HttpUrl url) {
+ return new Request.Builder().url(url).get()
+ .header("User-Agent", USER_AGENT)
+ .build();
+ }
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeBtcTextView.java b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeBtcTextView.java
index 5a86ed1e..7f1735b7 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeBtcTextView.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeBtcTextView.java
@@ -131,10 +131,10 @@ public class ExchangeBtcTextView extends LinearLayout
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- tvAmountA = (TextView) findViewById(R.id.tvAmountA);
- tvAmountB = (TextView) findViewById(R.id.tvAmountB);
- sCurrencyA = (Spinner) findViewById(R.id.sCurrencyA);
- sCurrencyB = (Spinner) findViewById(R.id.sCurrencyB);
+ tvAmountA = findViewById(R.id.tvAmountA);
+ tvAmountB = findViewById(R.id.tvAmountB);
+ sCurrencyA = findViewById(R.id.sCurrencyA);
+ sCurrencyB = findViewById(R.id.sCurrencyB);
ArrayAdapter btcAdapter = new ArrayAdapter(getContext(),
android.R.layout.simple_spinner_item,
diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeTextView.java b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeTextView.java
index 6043c0ba..f0db87ab 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeTextView.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeTextView.java
@@ -177,12 +177,12 @@ public class ExchangeTextView extends LinearLayout
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- tvAmountA = (TextView) findViewById(R.id.tvAmountA);
- tvAmountB = (TextView) findViewById(R.id.tvAmountB);
- sCurrencyA = (Spinner) findViewById(R.id.sCurrencyA);
- sCurrencyB = (Spinner) findViewById(R.id.sCurrencyB);
- evExchange = (ImageView) findViewById(R.id.evExchange);
- pbExchange = (ProgressBar) findViewById(R.id.pbExchange);
+ tvAmountA = findViewById(R.id.tvAmountA);
+ tvAmountB = findViewById(R.id.tvAmountB);
+ sCurrencyA = findViewById(R.id.sCurrencyA);
+ sCurrencyB = findViewById(R.id.sCurrencyB);
+ evExchange = findViewById(R.id.evExchange);
+ pbExchange = findViewById(R.id.pbExchange);
// make progress circle gray
pbExchange.getIndeterminateDrawable().
diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeView.java b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeView.java
index a913baad..54d59246 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeView.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeView.java
@@ -162,16 +162,16 @@ public class ExchangeView extends LinearLayout
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- etAmount = (TextInputLayout) findViewById(R.id.etAmount);
- tvAmountB = (TextView) findViewById(R.id.tvAmountB);
- sCurrencyA = (Spinner) findViewById(R.id.sCurrencyA);
+ etAmount = findViewById(R.id.etAmount);
+ tvAmountB = findViewById(R.id.tvAmountB);
+ sCurrencyA = findViewById(R.id.sCurrencyA);
ArrayAdapter adapter = ArrayAdapter.createFromResource(getContext(), R.array.currency, R.layout.item_spinner);
adapter.setDropDownViewResource(R.layout.item_spinner_dropdown_item);
sCurrencyA.setAdapter(adapter);
- sCurrencyB = (Spinner) findViewById(R.id.sCurrencyB);
+ sCurrencyB = findViewById(R.id.sCurrencyB);
sCurrencyB.setAdapter(adapter);
- evExchange = (ImageView) findViewById(R.id.evExchange);
- pbExchange = (ProgressBar) findViewById(R.id.pbExchange);
+ evExchange = findViewById(R.id.evExchange);
+ pbExchange = findViewById(R.id.pbExchange);
// make progress circle gray
pbExchange.getIndeterminateDrawable().
diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/SendProgressView.java b/app/src/main/java/com/m2049r/xmrwallet/widget/SendProgressView.java
index 1ffcdab4..11d53559 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/widget/SendProgressView.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/widget/SendProgressView.java
@@ -57,9 +57,9 @@ public class SendProgressView extends LinearLayout {
super.onFinishInflate();
pbProgress = findViewById(R.id.pbProgress);
llMessage = findViewById(R.id.llMessage);
- tvCode = (TextView) findViewById(R.id.tvCode);
- tvMessage = (TextView) findViewById(R.id.tvMessage);
- tvSolution = (TextView) findViewById(R.id.tvSolution);
+ tvCode = findViewById(R.id.tvCode);
+ tvMessage = findViewById(R.id.tvMessage);
+ tvSolution = findViewById(R.id.tvSolution);
}
public void showProgress(String progressText) {
diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/Toolbar.java b/app/src/main/java/com/m2049r/xmrwallet/widget/Toolbar.java
index 8f96d126..6e5e9a96 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/widget/Toolbar.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/widget/Toolbar.java
@@ -78,16 +78,16 @@ public class Toolbar extends android.support.v7.widget.Toolbar {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- toolbarImage = (ImageView) findViewById(R.id.toolbarImage);
+ toolbarImage = findViewById(R.id.toolbarImage);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
// the vector image does not work well for androis < Nougat
toolbarImage.getLayoutParams().width = (int) getResources().getDimension(R.dimen.logo_width);
toolbarImage.setImageResource(R.drawable.logo_horizontol_xmrujo);
}
- toolbarTitle = (TextView) findViewById(R.id.toolbarTitle);
- toolbarSubtitle = (TextView) findViewById(R.id.toolbarSubtitle);
- bCredits = (Button) findViewById(R.id.bCredits);
+ toolbarTitle = findViewById(R.id.toolbarTitle);
+ toolbarSubtitle = findViewById(R.id.toolbarSubtitle);
+ bCredits = findViewById(R.id.bCredits);
bCredits.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (onButtonListener != null) {
diff --git a/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/XmrToApiImpl.java b/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/XmrToApiImpl.java
index 8d8946b5..3d4fa73a 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/XmrToApiImpl.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/xmrto/network/XmrToApiImpl.java
@@ -17,15 +17,15 @@
package com.m2049r.xmrwallet.xmrto.network;
import android.support.annotation.NonNull;
-import android.support.annotation.VisibleForTesting;
-import com.m2049r.xmrwallet.xmrto.api.XmrToCallback;
+import com.m2049r.xmrwallet.util.OkHttpHelper;
import com.m2049r.xmrwallet.xmrto.XmrToError;
import com.m2049r.xmrwallet.xmrto.XmrToException;
import com.m2049r.xmrwallet.xmrto.api.CreateOrder;
import com.m2049r.xmrwallet.xmrto.api.QueryOrderParameters;
import com.m2049r.xmrwallet.xmrto.api.QueryOrderStatus;
import com.m2049r.xmrwallet.xmrto.api.XmrToApi;
+import com.m2049r.xmrwallet.xmrto.api.XmrToCallback;
import org.json.JSONException;
import org.json.JSONObject;
@@ -128,16 +128,9 @@ public class XmrToApiImpl implements XmrToApi, XmrToApiCall {
if (request != null) {
final RequestBody body = RequestBody.create(
MediaType.parse("application/json"), request.toString());
-
- return new Request.Builder()
- .url(url)
- .post(body)
- .build();
+ return OkHttpHelper.getPostRequest(url, body);
} else {
- return new Request.Builder()
- .url(url)
- .get()
- .build();
+ return OkHttpHelper.getGetRequest(url);
}
}
}
diff --git a/app/src/main/res/drawable/gradient_street_efab.xml b/app/src/main/res/drawable/gradient_street_efab.xml
new file mode 100644
index 00000000..4f5b0728
--- /dev/null
+++ b/app/src/main/res/drawable/gradient_street_efab.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_bookmark_24dp.xml b/app/src/main/res/drawable/ic_bookmark_24dp.xml
new file mode 100644
index 00000000..cf64e057
--- /dev/null
+++ b/app/src/main/res/drawable/ic_bookmark_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_bookmark_border_24dp.xml b/app/src/main/res/drawable/ic_bookmark_border_24dp.xml
new file mode 100644
index 00000000..1cae0eb2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_bookmark_border_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_edit_white_24dp.xml b/app/src/main/res/drawable/ic_edit_white_24dp.xml
deleted file mode 100644
index 46462b57..00000000
--- a/app/src/main/res/drawable/ic_edit_white_24dp.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_monero_qr_24dp.xml b/app/src/main/res/drawable/ic_monero_qr_24dp.xml
deleted file mode 100644
index 54af39d1..00000000
--- a/app/src/main/res/drawable/ic_monero_qr_24dp.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_refresh_black_24dp.xml b/app/src/main/res/drawable/ic_refresh_black_24dp.xml
new file mode 100644
index 00000000..1f9072a3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_refresh_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_search_orange_24dp.xml b/app/src/main/res/drawable/ic_search_orange_24dp.xml
new file mode 100644
index 00000000..cec9af76
--- /dev/null
+++ b/app/src/main/res/drawable/ic_search_orange_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_signal_wifi_1_bar_black_24dp.xml b/app/src/main/res/drawable/ic_signal_wifi_1_bar_black_24dp.xml
new file mode 100644
index 00000000..fe9c7a93
--- /dev/null
+++ b/app/src/main/res/drawable/ic_signal_wifi_1_bar_black_24dp.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_signal_wifi_2_bar_black_24dp.xml b/app/src/main/res/drawable/ic_signal_wifi_2_bar_black_24dp.xml
new file mode 100644
index 00000000..caa79d64
--- /dev/null
+++ b/app/src/main/res/drawable/ic_signal_wifi_2_bar_black_24dp.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_signal_wifi_3_bar_black_24dp.xml b/app/src/main/res/drawable/ic_signal_wifi_3_bar_black_24dp.xml
new file mode 100644
index 00000000..24960e6a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_signal_wifi_3_bar_black_24dp.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_signal_wifi_4_bar_black_24dp.xml b/app/src/main/res/drawable/ic_signal_wifi_4_bar_black_24dp.xml
new file mode 100644
index 00000000..254f1609
--- /dev/null
+++ b/app/src/main/res/drawable/ic_signal_wifi_4_bar_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_signal_wifi_off_black_24dp.xml b/app/src/main/res/drawable/ic_signal_wifi_off_black_24dp.xml
new file mode 100644
index 00000000..c30cb7c5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_signal_wifi_off_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_wifi_lock_black_24dp.xml b/app/src/main/res/drawable/ic_wifi_lock_black_24dp.xml
new file mode 100644
index 00000000..8a643eaf
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wifi_lock_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/fragment_login.xml b/app/src/main/res/layout/fragment_login.xml
index c52d2037..8273dd5a 100644
--- a/app/src/main/res/layout/fragment_login.xml
+++ b/app/src/main/res/layout/fragment_login.xml
@@ -1,6 +1,7 @@
@@ -16,35 +17,85 @@
android:layout_height="wrap_content"
android:orientation="vertical" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
@@ -72,7 +123,7 @@
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="72dp"
- app:layoutManager="LinearLayoutManager"
+ app:layoutManager="android.support.v7.widget.LinearLayoutManager"
tools:listitem="@layout/item_wallet" />
diff --git a/app/src/main/res/layout/fragment_node.xml b/app/src/main/res/layout/fragment_node.xml
new file mode 100644
index 00000000..12b83aa7
--- /dev/null
+++ b/app/src/main/res/layout/fragment_node.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_receive.xml b/app/src/main/res/layout/fragment_receive.xml
index 7174d665..ee91ffdd 100644
--- a/app/src/main/res/layout/fragment_receive.xml
+++ b/app/src/main/res/layout/fragment_receive.xml
@@ -25,7 +25,6 @@
android:visibility="gone" />
diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml
index cd319f38..15bc942b 100644
--- a/app/src/main/res/layout/fragment_wallet.xml
+++ b/app/src/main/res/layout/fragment_wallet.xml
@@ -22,7 +22,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:text="@string/text_streetmode"
+ android:text="@string/menu_streetmode"
android:visibility="invisible" />
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/prompt_editnode.xml b/app/src/main/res/layout/prompt_editnode.xml
new file mode 100644
index 00000000..83ec991d
--- /dev/null
+++ b/app/src/main/res/layout/prompt_editnode.xml
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/drawer_view.xml b/app/src/main/res/menu/drawer_view.xml
index 197047d6..ec9557da 100644
--- a/app/src/main/res/menu/drawer_view.xml
+++ b/app/src/main/res/menu/drawer_view.xml
@@ -5,7 +5,6 @@
android:checkableBehavior="single"
android:orderInCategory="100" />
-
-
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 00b8fe76..e5f11e78 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -29,10 +29,8 @@
Fertig
Berühren für QR-Code
- Höhere Priorität = Höhere Gebühr
BTC Zahlung aktiviert - Tippe für mehr Infos.
- CrAzYpass aktiviert - Tippe für mehr Infos.
Ledger aktiviert - Tippe für mehr Infos.
%1$s BTC = %2$s XMR
(Kurs: %1$s BTC/XMR)
- Fortgeschritten:
-
Besuche XMR.TO für Support & Nachverfolgung
Geheimer Schlüssel\nXMR.TO
XMR.TO Geheimer Schlüssel
@@ -96,7 +92,6 @@
Backup wird erstellt
Archivierung wird ausgeführt
Umbenennung wird ausgeführt
- Daemon Verbindung wird getestet
Ändere Passwort
Fertigstellen …\nDies kann einen Moment dauern!
@@ -108,13 +103,11 @@
Passwort geändert
Node
- ([<user>:<pass>@]<daemon>[:<port>])
Lade Wallet …
Wallet gespeichert
Walletspeicherung fehlgeschlagen!
Verbinde …
Verbindung zum Node fehlgeschlagen!\nPrüfe Username/Passwort
- Node Zeitüberschreitung!\nNochmal oder anderen Node versuchen.
Node ungültig!\nVersuche anderen.
Kann Node nicht erreichen!\nNochmal oder anderen Node versuchen.
@@ -194,7 +187,6 @@
Kann nicht mit einem . beginnen
Erstelle Wallet
Wallet erstellt
- Walleterstellung fehlgeschlagen
Gib eine Blocknummer oder ein Datum (JJJJ-MM-TT) ein
@@ -274,7 +266,6 @@
AUSSTEHEND
FEHLGESCHLAGEN
- Zahlungs-ID (optional)
Betrag
Konnte Wallet nicht öffnen!
16 oder 64 Hex-Zeichen (0–9,a–f)
@@ -284,8 +275,6 @@
Min. 0
XMR keine Nummer
- Empfangen
-
Sensible Daten werden angezeigt.\nSchau über deine Schulter!
Ich bin sicher
Nein, doch nicht!
@@ -295,20 +284,12 @@
Ja, mach das!
Nein, danke!
-
-
- Priorität Standard
- - Priorität Gering
- - Priorität Mittel
- - Priorität Hoch
-
-
Neues Wallet erstellen
View Only Wallet wiederherstellen
Mit privaten Schlüsseln wiederherstellen
Mit 25 Wörter Seed wiederherstellen
Konto erstellen
- Konten
Neues Konto #%1$d hinzugefügt
Konto #
@@ -330,7 +311,6 @@
Bitte Ledger (wieder-)anschließen!
Erzeuge Konto
- Aktualisiere Wallet
%1$s angesteckt
%1$s abgesteckt
@@ -357,5 +337,31 @@
Details
Street Mode
- Street Mode enabled, tap for more info.
+
+ Node-o-matiC enabled, tap for more info.
+ Last block updated: %1$s
+ Nodes
+ Node Name (Optional)
+ Hostname
+ Port
+ Username (Optional)
+ Password (Optional)
+ Cannot resolve host
+ We need this!
+ Must be numeric
+ Must be 1–65535
+ Add Node
+ Touch to refresh!
+ CONNECTION ERROR %1$d
+ CONNECTION ERROR
+ AUTHENTICATION FAILED
+ Test Result:
+ Height: %1$s (v%2$d), Ping: %3$.0fms, IP: %4$s
+ Testing IP: %1$s …
+ Please wait for scan to finish
+ Touch to select or add nodes
+ Add nodes manually or pull down to scan
+ Scanning network…
+ Automatically bookmarked best %1$d nodes
+ Test
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 7307f05e..f52db02e 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -28,10 +28,8 @@
Έγινε
Πατήστε για QR κωδικό
- Υψηλότερη προτεραιότητα = Υψηλότερα Κόμιστρα
Συναλλαγή BTC ενεργοποιήθηκε, πάτα για περισσότερες πληροφορείες.
- CrAzYpass ενεργοποιήθηκε, πάτα για περισσότερες πληροφορείες.
Ledger ενεργοποιήθηκε, πάτα για περισσότερες πληροφορείες.
%1$s BTC = %2$s XMR
(Rate: %1$s BTC/XMR)
- Για προχωρημένους:
-
Επίσκεψη στο xmr.to για υποστήριξη & με εντοπισμό συναλλαγής
Μυστικό Κλειδί\nXMR.TO
XMR.TO Μυστικό Κλειδί
@@ -94,7 +90,6 @@
Δημιουργία αντίγραφου ασφαλείας σε εξέλιξη
Αρχειοθέτηση σε εξέλιξη
Μετονομασία σε εξέλιξη
- Έλεγχος σύνδεσης δαίμονα
Μάζεμα των πραγμάτων …\nΑυτό μπορεί να διαρκέσει λίγο!
@@ -103,13 +98,11 @@
Η μετονομασία απέτυχε!
Κόμβος(Δαίμονας)
- ([<χρήστης>:<κωδικός>@]<δαίμονας>[:<πόρτα>])
Φόρτωση Πορτοφολιού …
Πορτοφόλι αποθηκεύτηκε
Η αποθήκευση του πορτοφολιού απέτυχε!
Σύνδεση …
Η σύνδεση με τον κόμβο απέτυχε!\nΈλεγξε χρήστη/κωδικό
- Η σύνδεση με τον κόμβο έχει λήξει!\nΠροσπάθησε πάλι ή με άλλον.
Ο κόμβος είναι μη έγκυρος!\nΠροσπάθησε με άλλον.
Δεν είναι δυνατή η πρόσβαση στον κόμβο!\nΠροσπάθησε πάλι ή με άλλον.
@@ -169,7 +162,6 @@
Δεν γίνεται να ξεκινάει με .
Το πορτοφόλι δημιουργείται
Το Πορτοφόλι δημιουργήθηκε
- Η δημιουργία πορτοφολιού απέτυχε
Βάλε αριθμό ή ημερομηνία (YYYY-MM-DD δλδ χρονιά-μήνας-μέρα)
@@ -248,7 +240,6 @@
ΕΚΚΡΕΜΗ
ΑΠΕΤΥΧΕ
- ID Πληρωμής(Payment ID) (προαιρετικό)
Ποσό
Δεν ήταν δυνατό το άνοιγμα του πορτοφολιού!
16 ή 64 χαρακτήρες Hex (0–9,a–f)
@@ -258,8 +249,6 @@
Ελάχιστο 0
XMR δεν υπάρχει αριθμός
- Λήψη
-
Θα εμφανιστούν τώρα ευαίσθητα δεδομένα. \nΠρόσεχε ποιος είναι πίσω σου!
Είμαι ασφαλής
Πήγαινε με πίσω!
@@ -269,13 +258,6 @@
Ναι, κάνε αυτό!
Όχι ευχαριστώ!
-
- - Προεπιλεγμένη Προτεραιότητα
- - Χαμηλή Προτεραιότητα
- - Μέτρια Προτεραιότητα
- - Υψηλή Προτεραιότητα
-
-
Δημιουργία νέου πορτοφολιού
Επαναφορά πορτοφολιού προβολής-μόνο
Επαναφορά πορτοφολιού από ιδιωτικά κλειδιά
@@ -308,7 +290,6 @@
Passphrase may not be empty
Wallet Files Restore Password
Create Account
- Accounts
Added new account #%1$d
Account #
Send all confirmed funds in this account!
@@ -329,7 +310,6 @@
Please (re)connect Ledger device
Creating account
- Updating wallet
%1$s attached
%1$s detached
@@ -356,5 +336,31 @@
Λεπτομέριες
Street Mode
- Street Mode enabled, tap for more info.
+
+ Node-o-matiC enabled, tap for more info.
+ Last block updated: %1$s
+ Nodes
+ Node Name (Optional)
+ Hostname
+ Port
+ Username (Optional)
+ Password (Optional)
+ Cannot resolve host
+ We need this!
+ Must be numeric
+ Must be 1–65535
+ Add Node
+ Touch to refresh!
+ CONNECTION ERROR %1$d
+ CONNECTION ERROR
+ AUTHENTICATION FAILED
+ Test Result:
+ Height: %1$s (v%2$d), Ping: %3$.0fms, IP: %4$s
+ Testing IP: %1$s …
+ Please wait for scan to finish
+ Touch to select or add nodes
+ Add nodes manually or pull down to scan
+ Scanning network…
+ Automatically bookmarked best %1$d nodes
+ Test
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 9e753fa7..244226fd 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -29,7 +29,6 @@
Hecho
Toca para mostrar código QR
- Mayor Prioridad = Mayor Comisión
ID de pago integrado
Preparando tu transacción
@@ -44,7 +43,6 @@
Copia de seguridad en progreso
Archivado en progreso
Cambio de nombre en progreso
- Comprobando conexión con el daemon
Cambiando contraseña en progreso
Guardando todo\n¡Puede llevar un tiempo!
@@ -56,13 +54,11 @@
Contraseña cambiada
Nodo
- ([<usuario>:<contraseña>@]<daemon>[:<puerto>])
Cargando monedero…
Monedero guardada
¡Guardado de monedero fallido!
Conectando…
¡Conexión con el nodo fallida!\nComprueba el usuario/contraseña
- ¡Conexión con el nodo ha expirado!\nInténtalo de nuevo o prueba otro.
¡Nodo inválido!\nInténtalo con otro.
¡No se puede alcanzar el nodo!\nInténtalo de nuevo o prueba otro.
@@ -138,7 +134,6 @@
No puede empezar con .
Creando monedero
Monedero creada
- Creación de monedero fallida
Introduce un número o una fecha (AAAA-MM-DD)
@@ -206,7 +201,6 @@
PENDIENTE
FALLIDO
- ID de Pago (opcional)
Monto
¡No se ha podido abrir el monedero!
16 o 64 caracteres hexadecimales (0–9,a–f)
@@ -216,8 +210,6 @@
Min. 0
XMR no es un número
- Recibir
-
Se va a mostrar información delicada.\n¡Mira por encima del hombro!
Estoy seguro
¡Llévame de vuelta!
@@ -227,13 +219,6 @@
¡Sí, haz eso!
¡No gracias!
-
- - Prioridad por Defecto
- - Prioridad Baja
- - Prioridad Media
- - Prioridad Alta
-
-
Crear nuevo monedero
Restaurar monedero de sólo vista
Restaurar monedero con claves privadas
@@ -267,7 +252,6 @@
Oh-oh, parece que XMR.TO no está disponible ahora!
%1$s BTC = %2$s XMR
(Cambio: %1$s BTC/XMR)
- Avanzado
Visita https://xmr.to para soporte y rastreo
Clave secreta\nXMR.TO
Clave secreta XMR.TO
@@ -290,11 +274,9 @@
Orden XMR.TO
Pago en BTC activado, toca para más info.
- CrAzYpass activado, toca para más info.
Ledger activado, toca para más info.
Crear Cuenta
- Cuentas
Nueva cuenta agregada #%1$d
# de cuenta
@@ -316,7 +298,6 @@
Please (re)connect Ledger device
Creating account
- Updating wallet
%1$s attached
%1$s detached
@@ -343,5 +324,31 @@
Detalles
Street Mode
- Street Mode enabled, tap for more info.
+
+ Node-o-matiC enabled, tap for more info.
+ Last block updated: %1$s
+ Nodes
+ Node Name (Optional)
+ Hostname
+ Port
+ Username (Optional)
+ Password (Optional)
+ Cannot resolve host
+ We need this!
+ Must be numeric
+ Must be 1–65535
+ Add Node
+ Touch to refresh!
+ CONNECTION ERROR %1$d
+ CONNECTION ERROR
+ AUTHENTICATION FAILED
+ Test Result:
+ Height: %1$s (v%2$d), Ping: %3$.0fms, IP: %4$s
+ Testing IP: %1$s …
+ Please wait for scan to finish
+ Touch to select or add nodes
+ Add nodes manually or pull down to scan
+ Scanning network…
+ Automatically bookmarked best %1$d nodes
+ Test
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index de4f8edc..82f2c2d5 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -29,10 +29,8 @@
Tehtud
Puuduta QR koodi saamiseks
- Suurem tähtsus = kõrgemad teenustasud
Bitcoini maksed sisse lülitatud, puuduta lisainfo saamiseks.
- CrAzYpass sisse lülitatud, puuduta lisainfo saamiseks.
Ledger\'i tugi sisse lülitatud, puuduta lisainfo saamiseks.
%1$s BTC = %2$s XMR
(Kurss: %1$s BTC/XMR)
- Edasijõudnutele:
-
Külasta xmr.to lisainfo saamiseks & jälgin
Privaatvõti\nXMR.TO
XMR.TO privaatvõti
@@ -95,7 +91,6 @@
Teostan tagavarakoopiat
Arhiveerin
Nimetan ümber
- Kontrollin ühendust serveriga
Vahetan parooli
Teen ettevalmistusi …\nSee võib aega võtta!
@@ -107,13 +102,11 @@
Parool vahetatud
Server
- ([<user>:<pass>@]<daemon>[:<port>])
Laen rahakotti …
Rahakott salvestatud
Rahakoti salvestamine ebaõnnestus!
Ühendun …
Serveriga ühendumine ebaõnnestus!\nKontrolli kasutajanime/parooli
- Serveriga ühendumine võttis liiga kaua aega!\nProovi uuesti või vali teine server.
Ebasobilik server!\nVali mõni teine.
Server ei vastanud!\nProovi uuesti või vali teine server.
@@ -194,7 +187,6 @@
Nimi ei saa alata märgiga .
Loon rahakotti
Rahakott loodud
- Rahakoti loomine ebaõnnestus
Sisesta plokinumber või kuupäev (YYYY-MM-DD)
@@ -279,7 +271,6 @@
OOTEL
EBAÕNNESTUS
- Makse ID (valikuline)
Kogus
Kommentaarid (valikuline)
Rahakotti ei õnnestunud avada!
@@ -290,8 +281,6 @@
Vähemalt 0
XMR pole arv
- Küsi raha
-
Nüüd näidatakse tundlikku infot.\nPiilu oma seljataha!
Olen turvalises kohas
Vii mind tagasi!
@@ -301,20 +290,12 @@
Täpselt nii!
Ei, tänan!
-
- - Tavaline tähtsus
- - Väike tähtsus
- - Keskmine tähtsus
- - Suur tähtsus
-
-
Loo uus rahakott
Taasta rahakott vaatamiseks
Taasta rahakott privaatvõtmetest
Taasta rahakott 25-sõnalisest seemnest
Loo konto
- Kontod
Uus konto lisatud #%1$d
Konto #
@@ -336,7 +317,6 @@
Palun (taas)ühenda seade Ledger
Loon kontot
- Uuendan rahakotti
%1$s lisatud
%1$s eemaldatud
@@ -355,5 +335,31 @@
Näita salajast infot
Avalik režiim
- Avalik režiim sisse lülitatud, puuduta lisainfo saamiseks.
+
+ Node-o-matiC enabled, tap for more info.
+ Last block updated: %1$s
+ Nodes
+ Node Name (Optional)
+ Hostname
+ Port
+ Username (Optional)
+ Password (Optional)
+ Cannot resolve host
+ We need this!
+ Must be numeric
+ Must be 1–65535
+ Add Node
+ Touch to refresh!
+ CONNECTION ERROR %1$d
+ CONNECTION ERROR
+ AUTHENTICATION FAILED
+ Test Result:
+ Height: %1$s (v%2$d), Ping: %3$.0fms, IP: %4$s
+ Testing IP: %1$s …
+ Please wait for scan to finish
+ Touch to select or add nodes
+ Add nodes manually or pull down to scan
+ Scanning network…
+ Automatically bookmarked best %1$d nodes
+ Test
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index b99f7a02..da656eaf 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -29,10 +29,8 @@
Fait
Toucher pour le QR Code
- Plus Prioritaire = Plus de Frais
Paiement BTC activé, tapez pour plus d\'infos.
- CrAzYpass activé, tapez pour plus d\'infos.
Ledger activé, tapez pour plus d\'infos.
%1$s BTC = %2$s XMR
(Taux : %1$s BTC/XMR)
- Avancé :
-
Visitez xmr.to pour l\'assistance & le suivi
Clef Secrète\nXMR.TO
Clef Secrète XMR.TO
@@ -96,7 +92,6 @@
Sauvegarde en cours
Archivage en cours
Renommage en cours
- Vérification de la connexion au démon
Modification du mot de passe en cours
Remise en ordre …\nCela peut prendre un moment !
@@ -108,13 +103,11 @@
Modification du mot de passe réussie
Nœud
- ([<utilisateur>:<mdp>@]<démon>[:<port>])
Chargement du Portefeuille …
Portefeuille Sauvegardé
Sauvegarde du Portefeuille Échouée !
Connexion …
Connexion au nœud échouée !\nVérifiez utilisateur/mot de passe
- Connexion au nœud sans réponse !\nEssayez à nouveau ou un autre.
Nœud invalide !\nEssayez un autre.
Nœud injoignable !\nEssayez à nouveau ou un autre.
@@ -196,7 +189,6 @@
Ne peut pas commencer par .
Création du Portefeuille
Portefeuille créé
- Création du Portefeuille échouée
Entrer un nombre ou une date (YYYY-MM-DD)
@@ -276,7 +268,6 @@
EN ATTENTE
ÉCHOUÉ
- ID de Paiement (optionnel)
Montant
Ouverture du portefeuille impossible !
16 ou 64 caractères héxadécimaux (0–9,a–f)
@@ -286,8 +277,6 @@
Min. 0
XMR pas un nombre
- Recevoir
-
Des données sensible vont être affichées.\nRegardez autour de vous !
C\'est bon
Non merci !
@@ -297,20 +286,12 @@
Oui, procéder !
Non merci !
-
- - Priority Défaut
- - Priority Faible
- - Priority Moyenne
- - Priority Élevée
-
-
Créer un nouveau portefeuille
Restaurer un portefeuille d\'audit
Restaurer depuis la clef privée
Restaurer depuis la phrase mnémonique
Créer un Compte
- Comptes
Nouveau Compte #%1$d ajouté
Compte #
@@ -329,7 +310,6 @@
Merci de (re)connecter le Ledger
Création du compte
- Mise à jour du portefeuille
%1$s connecté
%1$s déconnecté
@@ -359,5 +339,31 @@
Montrer les secrets !
Mode Urbain
- Mode Urbain activé, tapez pour plus d\'infos.
+
+ Node-o-matiC enabled, tap for more info.
+ Last block updated: %1$s
+ Nodes
+ Node Name (Optional)
+ Hostname
+ Port
+ Username (Optional)
+ Password (Optional)
+ Cannot resolve host
+ We need this!
+ Must be numeric
+ Must be 1–65535
+ Add Node
+ Touch to refresh!
+ CONNECTION ERROR %1$d
+ CONNECTION ERROR
+ AUTHENTICATION FAILED
+ Test Result:
+ Height: %1$s (v%2$d), Ping: %3$.0fms, IP: %4$s
+ Testing IP: %1$s …
+ Please wait for scan to finish
+ Touch to select or add nodes
+ Add nodes manually or pull down to scan
+ Scanning network…
+ Automatically bookmarked best %1$d nodes
+ Test
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index a380c22a..84b3eb70 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -29,10 +29,8 @@
Kész
Koppints a QR-kódért
- Magasabb prioritás = magasabb tranzakciós díj
BTC fizetés engedélyezve, koppints ide a részletekért.
- CrAzYpass engedélyezve, koppints ide a részletekért
Ledger engedélyezve, koppints ide a részletekért
%1$s BTC = %2$s XMR
(Arány: %1$s BTC/XMR)
- Haladó:
-
Segítségért és nyomonkövetésért látogass el az XMR.TO weboldalra
Titkos kulcs\nXMR.TO
XMR.TO titkos kulcs
@@ -95,7 +91,6 @@
Biztonsági mentés folyamatban
Archiválás folyamatban
Átnevezés folyamatban
- Daemon-kapcsolat ellenőrzése
Jelszómódosítás folyamatban
Műveletek befejezése…\nEz eltarthat egy ideig!
@@ -107,13 +102,11 @@
Jelszó megváltoztatva
Csomópont
- ([<felhasználó>:<jelszó>@]<daemon>[:<port>])
Tárca betöltése…
Tárca mentve
Sikertelen mentés!
Kapcsolódás…
Sikertelen kapcsolódás.\nEllenőrizd a felhasználónevet és jelszót.
- A kapcsolódás időtúllépés miatt megszakadt.\nPróbáld újra vagy próbálkozz egy másikkal.
Érvénytelen csomópont!\nPróbálkozz egy másikkal.
Nem lehet elérni a csomópontot!\nPróbáld újra vagy próbálkozz egy másikkal.
@@ -194,7 +187,6 @@
Nem kezdődhet ponttal.
Tárca létrehozása
Tárca létrehozva
- Sikertelen tárcalétrehozás
Számot vagy dátumot (ÉÉÉÉ-HH-NN) adj meg
@@ -274,7 +266,6 @@
FÜGGŐBEN
SIKERTELEN
- Fizetési azonosító (opcionális)
Mennyiség
Nem sikerült megnyitni a tárcát!
16 vagy 64 hexadecimális karakter (0–9,a–f)
@@ -284,8 +275,6 @@
Min. 0
XMR nem egy szám
- Fogadás
-
Bizalmas adatok kerülnek megjelenítésre.\nBizonyosodj meg róla, hogy más nem látja!
Mehet
Inkább ne!
@@ -295,20 +284,12 @@
Igen, mehet!
Inkább ne!
-
- - Alapértelmezett prioritás
- - Alacsony prioritás
- - Közepes prioritás
- - Magas prioritás
-
-
Új tárca létrehozása
Figyelőtárca visszaállítása
Visszaállítás privát kulcsokkal
Visszaállítás mnemonikus maggal
Számla létrehozása
- Számlák
Új számla hozzáadva (#%1$d)
Számla #
@@ -330,7 +311,6 @@
Csatlakoztasd (újra) a Ledgert
Számla létrehozása
- Tárca frissítése
%1$s csatlakoztatva
%1$s leválasztva
@@ -357,5 +337,31 @@
Részletek
Street Mode
- Street Mode enabled, tap for more info.
+
+ Node-o-matiC enabled, tap for more info.
+ Last block updated: %1$s
+ Nodes
+ Node Name (Optional)
+ Hostname
+ Port
+ Username (Optional)
+ Password (Optional)
+ Cannot resolve host
+ We need this!
+ Must be numeric
+ Must be 1–65535
+ Add Node
+ Touch to refresh!
+ CONNECTION ERROR %1$d
+ CONNECTION ERROR
+ AUTHENTICATION FAILED
+ Test Result:
+ Height: %1$s (v%2$d), Ping: %3$.0fms, IP: %4$s
+ Testing IP: %1$s …
+ Please wait for scan to finish
+ Touch to select or add nodes
+ Add nodes manually or pull down to scan
+ Scanning network…
+ Automatically bookmarked best %1$d nodes
+ Test
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index f323f08f..4c9bfa92 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -29,10 +29,8 @@
Fatto
Tocca per il codice QR
- Priorità più alta = Commissioni più alte
Pagamento BTC abilitato, tocca per maggiori informazioni.
- CrAzYpass abilitato, tocca per maggiori informazioni.
Ledger abilitato, tocca per maggiori informazioni.
%1$s BTC = %2$s XMR
(Tasso: %1$s BTC/XMR)
- Avanzate:
-
Visita xmr.to per supporto e tracciamento
Chiave segreta\nXMR.TO
Chiave segreta XMR.TO
@@ -96,7 +92,6 @@
Backup in corso
Archiviazione in corso
Rinomina in corso
- Controllando connessione con daemon
Modifica password in corso
Rimettendo le cose a posto …\nPuò richiedere del tempo!
@@ -108,13 +103,11 @@
Password cambiata
Nodo
- ([<utente>:<password>@]<daemon>[:<porta>])
Caricando portafoglio …
Portafoglio salvato
Salvataggio portafoglio fallito!
In connessione …
Connessione con il nodo fallita!\nControlla username/password
- Connessione con il nodo scaduta!\nProva di nuovo o provane un altro.
Nodo invalido!\nProvane un altro.
Impossibile raggiungere il nodo!\nProva di nuovo o provane un altro.
@@ -195,7 +188,6 @@
Non può iniziare con .
Portafoglio in creazione
Portafoglio creato
- Creazione del portafoglio fallita
Inserisci un numero o una data (AAAA-MM-GG)
@@ -275,7 +267,6 @@
IN ATTESA
FALLITA
- ID pagamento (opzionale)
Ammontare
Impossibile aprire il portafoglio!
16 o 64 caratteri esadecimali (0–9,a–f)
@@ -285,8 +276,6 @@
Min. 0
XMR non è un numero
- Ricevi
-
Verranno ora mostrati dati sensibili.\nGuardati alle spalle!
Sono al sicuro
Torna indietro!
@@ -296,20 +285,12 @@
Sì, procedi!
No grazie!
-
- - Priority Default
- - Priority Bassa
- - Priority Media
- - Priority Alta
-
-
Crea un nuovo portafoglio
Recupera un portafoglio solo-visualizzazione
Recupera un portafoglio dalle chiavi private
Recupera un portafoglio da un seed di 25 parole
Crea Account
- Accounts
Aggiunto nuovo account #%1$d
Account #
@@ -331,7 +312,6 @@
(Ri)connetti il dispositivo Ledger
Creando account
- Aggiornando portafoglio
%1$s allegati
%1$s allegati
@@ -358,5 +338,31 @@
Mostra i segreti!
Modalità strada
- Modalità strada abilitata, tocca per maggiori informazioni.
+
+ Node-o-matiC enabled, tap for more info.
+ Last block updated: %1$s
+ Nodes
+ Node Name (Optional)
+ Hostname
+ Port
+ Username (Optional)
+ Password (Optional)
+ Cannot resolve host
+ We need this!
+ Must be numeric
+ Must be 1–65535
+ Add Node
+ Touch to refresh!
+ CONNECTION ERROR %1$d
+ CONNECTION ERROR
+ AUTHENTICATION FAILED
+ Test Result:
+ Height: %1$s (v%2$d), Ping: %3$.0fms, IP: %4$s
+ Testing IP: %1$s …
+ Please wait for scan to finish
+ Touch to select or add nodes
+ Add nodes manually or pull down to scan
+ Scanning network…
+ Automatically bookmarked best %1$d nodes
+ Test
diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml
index 0e651601..32cdfb16 100644
--- a/app/src/main/res/values-nb/strings.xml
+++ b/app/src/main/res/values-nb/strings.xml
@@ -29,10 +29,8 @@
Ferdig
Trykk for QR kode
- Høyere prioritet = høyere avgifter
BTC betaling tilgjengelig, trykk for mer info.
- CrAzYpass tilgjengelig, trykk for mer info.
Ledger tilgjengelig, trykk for mer info.
%1$s BTC = %2$s XMR
(Rate: %1$s BTC/XMR)
- Avansert:
-
Besøk xmr.to for støtte og sporing
Hemmelig nøkkel\nXMR.TO
XMR.TO Hemmelig nøkkel
@@ -94,7 +90,6 @@
Backup pågår
Arkivering pågår
Nytt navn gis
- Sjekker daemon-tilkobling
[Passordforandring i gang]
Gjør ting ferdig …\nDette kan ta si tid!
@@ -106,13 +101,11 @@
[Passord forandra]
Node
- ([<bruker>:<pass>@]<daemon>[:<port>])
Laster lommebok …
Lommebok lagra
Lommeboklagring feila!
Kobler til …
Node-tiklobling feila!\nSjekk brukernavn/passord
- Node-tilkobling tok for lang tid!\nPrøv på nytt eller med en annen.
Noden er ugyldig!\nPrøv en annen.
Kan ikke nå node!\nPrøv på nytt eller med en annen.
@@ -193,7 +186,6 @@
Kan ikke begynne med .
Lager lommebok
Lommebok lagd
- Klarte ikke å lage lommebok
Skriv inn nummer eller dato (ÅÅÅÅ-MM-DD)
@@ -273,7 +265,6 @@
VENTENDE
FEILA
- Betalings-ID (valgfritt)
Mengde
Kunne ikke åpne lommebok!
16 eller 64 heks-karakterer (0–9,a–f)
@@ -283,8 +274,6 @@
Min. 0
XMR ikke et tall
- Motta
-
Sensitive data vil nå bli vist.\nSe over skulderen din!
Jeg er trygg
Ta meg tilbake!
@@ -294,20 +283,12 @@
Ja, gjør det!
Nei takk!
-
- - Prioritet Standard
- - Prioritet Lav
- - Prioritet Middels
- - Prioritet Høy
-
-
Lag ny lommebok
Gjenoprett bare-se lommebok
Gjenoprett lommebok fra private nøkler
Gjenoprett lommebok fra 25-ord seed
Create Account
- Accounts
Added new account #%1$d
Account #
@@ -329,7 +310,6 @@
Please (re)connect Ledger device
Creating account
- Updating wallet
%1$s attached
%1$s detached
@@ -356,5 +336,31 @@
Detaljer
Street Mode
- Street Mode enabled, tap for more info.
+
+ Node-o-matiC enabled, tap for more info.
+ Last block updated: %1$s
+ Nodes
+ Node Name (Optional)
+ Hostname
+ Port
+ Username (Optional)
+ Password (Optional)
+ Cannot resolve host
+ We need this!
+ Must be numeric
+ Must be 1–65535
+ Add Node
+ Touch to refresh!
+ CONNECTION ERROR %1$d
+ CONNECTION ERROR
+ AUTHENTICATION FAILED
+ Test Result:
+ Height: %1$s (v%2$d), Ping: %3$.0fms, IP: %4$s
+ Testing IP: %1$s …
+ Please wait for scan to finish
+ Touch to select or add nodes
+ Add nodes manually or pull down to scan
+ Scanning network…
+ Automatically bookmarked best %1$d nodes
+ Test
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 8bf3c6d7..edf8129a 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -29,10 +29,8 @@
Klaar
Tik voor QR-code
- Hogere prioriteit = hogere kosten
BTC-betaling ingeschakeld. Tik voor meer info.
- CrAzYpass ingeschakeld. Tik voor meer info.
Ledger ingeschakeld. Tik voor meer info.
%1$s BTC = %2$s XMR
(Koers: %1$s BTC/XMR)
- Geavanceerd:
-
Ga naar xmr.to voor hulp en traceren
Geheime sleutel\nXMR.TO
Geheime sleutel XMR.TO
@@ -95,7 +91,6 @@
Back-up wordt gemaakt
Wordt gearchiveerd
Naam wordt gewijzigd
- Verbinding met node controleren
Wachtwoord wordt gewijzigd
Bezig met voltooien…\nDit kan even duren.
@@ -107,13 +102,11 @@
Wachtwoord is gewijzigd
Node
- ([<user>:<pass>@]<daemon>[:<port>])
Portemonnee laden…
Portemonnee opgeslagen
Portemonnee opslaan mislukt!
Verbinden…
Geen verbinding met node.\nControleer naam/wachtwoord.
- Time-out verbinding met node.\nProbeer opnieuw of andere.
Node ongeldig.\nProbeer een andere.
Kan node niet bereiken.\nProbeer opnieuw of andere.
@@ -191,7 +184,6 @@
Mag niet beginnen met .
Portemonnee wordt gemaakt
Portemonnee is gemaakt
- Portemonnee maken mislukt!
Voer getal of datum (JJJJ-MM-DD) in
@@ -272,7 +264,6 @@
WACHTEN
MISLUKT
- Betalings-ID (optioneel)
Bedrag
Kan portemonnee niet openen!
16 of 64 hexadecimale tekens (0–9, a–f)
@@ -282,8 +273,6 @@
Min. 0
XMR geen getal
- Ontvangen
-
Nu worden vertrouwelijke gegevens getoond.\nKijk achter je!
Het is veilig.
Ga terug!
@@ -293,20 +282,12 @@
Ja, doe dat!
Nee, niet doen!
-
- - Prioriteit Normaal
- - Prioriteit Laag
- - Prioriteit Normaal
- - Prioriteit Hoog
-
-
Nieuwe portemonnee
Alleen-lezen portemonnee herstellen
Portemonnee herstellen met privésleutels
Voer je 25 woorden in
Account maken
- Accounts
Nieuw account #%1$d toegevoegd
Accountnr.
@@ -328,7 +309,6 @@
Maak (opnieuw) verbinding met Ledger
Account maken…
- Portemonnee bijwerken…
%1$s gekoppeld
%1$s losgemaakt
@@ -354,5 +334,31 @@
Details
Street Mode
- Street Mode enabled, tap for more info.
+
+ Node-o-matiC enabled, tap for more info.
+ Last block updated: %1$s
+ Nodes
+ Node Name (Optional)
+ Hostname
+ Port
+ Username (Optional)
+ Password (Optional)
+ Cannot resolve host
+ We need this!
+ Must be numeric
+ Must be 1–65535
+ Add Node
+ Touch to refresh!
+ CONNECTION ERROR %1$d
+ CONNECTION ERROR
+ AUTHENTICATION FAILED
+ Test Result:
+ Height: %1$s (v%2$d), Ping: %3$.0fms, IP: %4$s
+ Testing IP: %1$s …
+ Please wait for scan to finish
+ Touch to select or add nodes
+ Add nodes manually or pull down to scan
+ Scanning network…
+ Automatically bookmarked best %1$d nodes
+ Test
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 4fcced95..e316346f 100755
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -29,10 +29,8 @@
Concluído
Toque para ver o código QR
- Maior Prioridade = Maiores Taxas
Pagamento em BTC ativado, toque para mais informações.
- SeNHaLoUCa ativada, toque para mais informações.
Ledger ativada, toque para mais informações.
%1$s BTC = %2$s XMR
(Cotação: %1$s BTC/XMR)
- Avançado:
-
Visite o XMR.TO para suporte & rastreio da ordem
Chave secreta\nXMR.TO
Chave secreta XMR.TO
@@ -95,7 +91,6 @@
Criando backup
Arquivando
Renomeando
- Verificando conexão
Alterando senha
Finalizando o processo …\nPode demorar um pouco
@@ -107,14 +102,12 @@
Senha alterada
Nó
- ([<usuário>:<senha>@]<daemon>[:<porta>])
Carregando …
Carteira salva
Erro ao salvar carteira
Conectando …
Erro ao conectar!\Verifique o usuário e senha
Versão do nó incompatível - por favor faça a atualização!
- Tempo limite de conexão ao nó!\nTente novamente ou escolha outro.
Nó inválido!\nEscolha outro.
Não foi possível conectar ao nó!\nTente novamente ou escolha outro.
@@ -193,7 +186,6 @@
Não pode começar com .
Criando carteira
Carteira criada
- Erro ao criar carteira
Escreva a altura ou data (AAAA-MM-DD)
@@ -274,7 +266,6 @@
PENDENTE
FALHOU
- ID do Pagamento (opcional)
Valor
Não foi possível abrir a carteira!
16 ou 64 caracteres em hexadecimal (0–9,a–f)
@@ -284,8 +275,6 @@
Mín. 0
O valor não é numérico
- Receber
-
Dados importantes serão exibidos.\nCertifique-se que ninguém está espiando!
Estou seguro
Tire-me daqui!
@@ -295,20 +284,12 @@
Sim, pode fazer!
Não, obrigado!
-
- - Prioridade padrão
- - Prioridade baixa
- - Prioridade média
- - Prioridade alta
-
-
Criar nova carteira
Restaurar carteira \"Somente leitura\"
Restaurar carteira via chaves privadas
Restaurar carteira via semente mnemônica
Criar conta
- Contas
Conta adicionada #%1$d
Conta #
@@ -330,7 +311,6 @@
Favor (re)conectar a Ledger
Criando conta
- Atualizando carteira
%1$s conectado
%1$s desconectado
@@ -347,5 +327,31 @@
Detalhes
Street Mode
- Street Mode enabled, tap for more info.
+
+ Node-o-matiC enabled, tap for more info.
+ Last block updated: %1$s
+ Nodes
+ Node Name (Optional)
+ Hostname
+ Port
+ Username (Optional)
+ Password (Optional)
+ Cannot resolve host
+ We need this!
+ Must be numeric
+ Must be 1–65535
+ Add Node
+ Touch to refresh!
+ CONNECTION ERROR %1$d
+ CONNECTION ERROR
+ AUTHENTICATION FAILED
+ Test Result:
+ Height: %1$s (v%2$d), Ping: %3$.0fms, IP: %4$s
+ Testing IP: %1$s …
+ Please wait for scan to finish
+ Touch to select or add nodes
+ Add nodes manually or pull down to scan
+ Scanning network…
+ Automatically bookmarked best %1$d nodes
+ Test
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 87cf9b72..b8c2ab44 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -29,10 +29,8 @@
Feito
Toca para código QR
- Prioridade Alta = Taxas Altas
Pagamento em BTC activado, toca para mais informação.
- passLoUCa activa, toca para mais informação.
Ledger activa, toca para mais informação.
%1$s BTC = %2$s XMR
(Rácio: %1$s BTC/XMR)
- Avançado:
-
Vai a xmr.to para suporte & seguimento
Chave secreta\nXMR.TO
XMR.TO Chave secreta
@@ -95,7 +91,6 @@
A efectuar cópia de segurança
A arquivar
A renomear
- A verificar a conecção
A altear a palavra passe
A concluir o processamento …\nIsto pode demorar!
@@ -107,13 +102,11 @@
Palavra passe alterada
Nó
- ([<utilizador>:<passe>@]<serviço>[:<porta>])
A carregar a carteira …
Carteira guardada
Erro ao gravar a carteira!
A conectar …
Erro ao conectar!\nConfirma o utilizador/palavra passe
- O tempo para a conecção expirou!\nTenta novamente ou outro nó.
Nó inválido!\nTenta outro.
Não foi possível chegar ao nó!\nTenta novamente ou outro.
@@ -193,7 +186,6 @@
Não pode começar com .
A criar carteira
Carteira criada
- Erro ao criar a carteira
Introduzir número ou data (AAAA-MM-DD)
@@ -273,7 +265,6 @@
PENDENTE
FALHADA
- ID Pagamento (opcional)
Quantidade
Não foi possível abrir a carteira!
16 ou 64 caracteres em hexadecimal (0–9,a–f)
@@ -283,8 +274,6 @@
Mín. 0
XMR não é um número
- Receber
-
Dados sensíveis vão ser mostrados.\nOlha à tua volta!
Estou seguro
Volta atrás!
@@ -294,13 +283,6 @@
Sim, faz isso!
Não obrigado!
-
- - Prioridade por Defeito
- - Prioridade Baixa
- - Prioridade Média
- - Prioridade Alta
-
-
Criar nova carteira
Restaurar carteira apenas de visualização
Restaurar carteira a partir de chaves privadas
@@ -310,7 +292,6 @@
Opening the wallet…
Create Account
- Accounts
Added new account #%1$d
Account #
@@ -332,7 +313,6 @@
Please (re)connect Ledger device
Creating account
- Updating wallet
%1$s attached
%1$s detached
@@ -359,5 +339,31 @@
Detalhes
Street Mode
- Street Mode enabled, tap for more info.
+
+ Node-o-matiC enabled, tap for more info.
+ Last block updated: %1$s
+ Nodes
+ Node Name (Optional)
+ Hostname
+ Port
+ Username (Optional)
+ Password (Optional)
+ Cannot resolve host
+ We need this!
+ Must be numeric
+ Must be 1–65535
+ Add Node
+ Touch to refresh!
+ CONNECTION ERROR %1$d
+ CONNECTION ERROR
+ AUTHENTICATION FAILED
+ Test Result:
+ Height: %1$s (v%2$d), Ping: %3$.0fms, IP: %4$s
+ Testing IP: %1$s …
+ Please wait for scan to finish
+ Touch to select or add nodes
+ Add nodes manually or pull down to scan
+ Scanning network…
+ Automatically bookmarked best %1$d nodes
+ Test
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 9dcd1e90..8e27c99c 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -28,10 +28,8 @@
Gata
Atinge pentru codul QR
- Prioritate mare = Comision mare
Plată BTC activată, apasă pentru mai multe informații.
- CrAzYpass activată, apasă pentru mai multe informații.
Ledger activată, apasă pentru mai multe informații.
%1$s BTC = %2$s XMR
(Rată: %1$s BTC/XMR)
- Avansat:
-
Vizitează xmr.to pentru suport & interogare
Cheia secretă \nXMR.TO
Cheia secretă XMR.TO
@@ -94,7 +90,6 @@
Copie de rezervă în curs
Arhivare în curs
Redenumire în curd
- Se verifică conexiunea cu nodul
Impachetăm lucrurile …\nPoate dura un pic!
@@ -103,13 +98,11 @@
Redenumirea a eșuat!
Nodul
- ([<user>:<pass>@]<daemon>[:<port>])
Se încarcă portofelul …
Portofelul a fost salvat
Salvarea portofelului a eșuat!
Se conecteaza …
Conexiunea la nod a eșuat!\nVerifică utilizator/parolă
- Conexiunea la nod a expirat!\nÎncearca din nou sau un altul.
Nodul este invalid!\nÎncearcă altul.
Nu se poate ajunge la nod!\nÎncearca din nou sau un altul.
@@ -169,7 +162,6 @@
Nu pot începe cu .
Se creează portofel
Portofel creat
- Crearea portofelului nereușită
Introdu număr sau dată (YYYY-MM-ZZ)
@@ -248,7 +240,6 @@
ÎN CURS
EȘUAT
- Payment ID (facultativ)
Sumă
Nu pot deschide portofel!
16 sau 64 caractere Hex (0–9,a–f)
@@ -258,8 +249,6 @@
Min. 0
XMR fără valoare
- Primește
-
Acum vor fi afișate date sensibile.\nUită-te peste umărul tău!
Sunt în siguranță
Du-mă înapoi!
@@ -269,13 +258,6 @@
Da, fă asta!
Nu mersi!
-
- - Prioritate Implicită
- - Prioritate mică
- - Prioritate medie
- - Prioritate mare
-
-
Creeaza portofel nou
Restaurează portofel-vizualizare
Restaurează portofel folosind cheie privată
@@ -308,7 +290,6 @@
Passphrase may not be empty
Wallet Files Restore Password
Create Account
- Accounts
Added new account #%1$d
Account #
Send all confirmed funds in this account!
@@ -329,7 +310,6 @@
Please (re)connect Ledger device
Creating account
- Updating wallet
%1$s attached
%1$s detached
@@ -356,5 +336,31 @@
Detalii
Street Mode
- Street Mode enabled, tap for more info.
+
+ Node-o-matiC enabled, tap for more info.
+ Last block updated: %1$s
+ Nodes
+ Node Name (Optional)
+ Hostname
+ Port
+ Username (Optional)
+ Password (Optional)
+ Cannot resolve host
+ We need this!
+ Must be numeric
+ Must be 1–65535
+ Add Node
+ Touch to refresh!
+ CONNECTION ERROR %1$d
+ CONNECTION ERROR
+ AUTHENTICATION FAILED
+ Test Result:
+ Height: %1$s (v%2$d), Ping: %3$.0fms, IP: %4$s
+ Testing IP: %1$s …
+ Please wait for scan to finish
+ Touch to select or add nodes
+ Add nodes manually or pull down to scan
+ Scanning network…
+ Automatically bookmarked best %1$d nodes
+ Test
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index f30c733b..b7e2f255 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -29,10 +29,8 @@
Готово
Нажмите для использования QR-кода
- Высокий приоритет = Высокие комиссии
Доступны переводы в BTC, нажмите для доп. информации
- Доступен CrAzYpass, нажмите для доп. информации
Доступен Ledger, нажмите для доп. информации
%1$s BTC = %2$s XMR
(Курс: %1$s BTC/XMR)
- Дополнительно:
-
Посетите xmr.to для получения помощи &
Секретный ключ\nXMR.TO
XMR.TO секретный ключ
@@ -96,7 +92,6 @@
Выполняется резервное копирование
Выполняется архивирование
Выполняется переименование
- Проверка подключения
Выполняется изменение пароля
Для данного действия …\nМожет потребоваться некоторое время!
@@ -108,13 +103,11 @@
Изменение пароля выполено успешно
Удаленный узел
- ([<user>:<pass>@]<daemon>[:<port>])
Загрузка кошелека …
Кошелек записан
Ошибка записи кошелека!
Подключение …
Ошибка подключения к удаленному узлу!\nПроверьте username/password
- Время ожидания соединения превышено!\nПопробуйте еще раз.
Ошибка удаленного узла!\nПопробуйте еще раз.
Не удается подключится к удаленному узлу!\nПопробуйте позже.
@@ -195,7 +188,6 @@
Не может начинаться с .
Создание кошелька
Кошелек создан
- Не удалось создать кошелек
Введите номер блока или дату (YYYY-MM-DD)
@@ -275,7 +267,6 @@
ОЖИДАНИЕ
ОШИБКА
- ID платежа (необязательно)
Сумма
Не удалось открыть кошелек!
Система счисления - 16 или 64(0–9,a–f)
@@ -285,8 +276,6 @@
Min. 0
Не числовое значение XMR
- Получить
-
Сейчас будут показаны конфиденциальные данные. Оглянись вокруг!
Я в безопасности
Верните меня обратно!
@@ -296,20 +285,12 @@
Да, сделай это!
Нет, спасибо!
-
- - Приоритет - Стандартный
- - Приоритет - Низкий
- - Приоритет - Средний
- - Приоритет - Высокий
-
-
Создать новый кошелек
Восстановить кошелек только для просмотра
Восстановить кошелек из ключей
Восстановить кошелек из мнемонической фразы
Создать учетную запись
- Учетная запись
Добавить новую учетную запись #%1$d
Учетная запись #
@@ -331,7 +312,6 @@
Подключите (переподключите) устройство Ledger
Создание аккаунта
- Обновление кошелька
%1$s прикреплен
%1$s откреплён
@@ -358,5 +338,31 @@
Информация
Street Mode
- Street Mode enabled, tap for more info.
+
+ Node-o-matiC enabled, tap for more info.
+ Last block updated: %1$s
+ Nodes
+ Node Name (Optional)
+ Hostname
+ Port
+ Username (Optional)
+ Password (Optional)
+ Cannot resolve host
+ We need this!
+ Must be numeric
+ Must be 1–65535
+ Add Node
+ Touch to refresh!
+ CONNECTION ERROR %1$d
+ CONNECTION ERROR
+ AUTHENTICATION FAILED
+ Test Result:
+ Height: %1$s (v%2$d), Ping: %3$.0fms, IP: %4$s
+ Testing IP: %1$s …
+ Please wait for scan to finish
+ Touch to select or add nodes
+ Add nodes manually or pull down to scan
+ Scanning network…
+ Automatically bookmarked best %1$d nodes
+ Test
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 8a7f287d..2ac8c872 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -29,10 +29,8 @@
Hotovo
Klepni pre QR kód
- Vyššia priorita = Vyšší poplatok
BTC platby aktivované, klepni pre viac info.
- CrAzYpass aktivovaný, klepni pre viac info.
Ledger aktivovaný, klepni pre viac info.
%1$s BTC = %2$s XMR
(Kurz: %1$s BTC/XMR)
- Pokročilé:
-
Navštív XMR.TO pre podporu & tracking
Tajný kľúč\nXMR.TO
XMR.TO Tajný kľúč
@@ -96,7 +92,6 @@
Prebieha Záloha
Prebieha Archivácia
Prebieha Premenovanie
- Skúšam spojenie so serverom
Prebieha zmena hesla
Balím si veci …\nMôže to chvíľu trvať!
@@ -108,13 +103,11 @@
Heslo zmenené
Uzol
- ([<meno>:<heslo>@]<server>[:<port>])
Načítavam peňaženku …
Peňaženka uložená
Uloženie zlyhalo!
pripájam sa …
Spojenie s uzlom zlyhalo!\nSkontroluj užívateľa/heslo
- Pripojenie uzla vypršalo!\nSkús znova, alebo skús iný uzol.
Neplatný uzol!\nSkús iný.
Neviem sa spojiť s uzlom!\nSkús znova, alebo skús iný uzol.
@@ -192,7 +185,6 @@
Neslobodno začať s .
Vytváranie peňaženky
Peňaženka vytvorená
- Vytvorenie peňaženky zlyhalo
Vlož číslo alebo dátum (YYYY-MM-DD)
@@ -272,7 +264,6 @@
ČAKAJÚCI
ZLYHAL
- ID Platby (voliteľné)
Suma
Nemohol som otvoriť peňaženku!
16 alebo 64 hexa znakov (0–9,a–f)
@@ -282,8 +273,6 @@
Min. 0
XMR nie číslo
- Prijať
-
Budú zobrazené citlivé dáta.\nPozri cez plece!
Som v bezpečí
Naspäť!
@@ -293,20 +282,12 @@
Áno, poďme na to!
Nie, díky!
-
- - Priorita Štandard
- - Priorita Nízka
- - Priorita Medium
- - Priorita Vysoká
-
-
Vytvor novú peňaženku
Obnoviť prezeraciu peňaženku
Obnoviť peňaženku zo súkromných kľúčov
Obnoviť peňaženku z 25-slovného seedu
Vytvor Účet
- Účty
Pridaný nový účet #%1$d
Účet #
@@ -328,7 +309,6 @@
(znovu) zapoj Ledger
Vytváram účet
- Aktualizujem peňaženku
%1$s pripojený
%1$s odpojený
@@ -350,11 +330,36 @@
Prekladám OpenAlias…
OpenAlias bez DNSSEC - adresa môže byť zneužitá
Prijímateľova XMR/BTC adresa alebo OpenAlias
-
- vyžaduje sa uzol V9
+
Nekompatibilný s verziou uzla - nutná aktualizácia!
Detaily
Street Mode
- Street Mode enabled, tap for more info.
+
+ Node-o-matiC enabled, tap for more info.
+ Last block updated: %1$s
+ Nodes
+ Node Name (Optional)
+ Hostname
+ Port
+ Username (Optional)
+ Password (Optional)
+ Cannot resolve host
+ We need this!
+ Must be numeric
+ Must be 1–65535
+ Add Node
+ Touch to refresh!
+ CONNECTION ERROR %1$d
+ CONNECTION ERROR
+ AUTHENTICATION FAILED
+ Test Result:
+ Height: %1$s (v%2$d), Ping: %3$.0fms, IP: %4$s
+ Testing IP: %1$s …
+ Please wait for scan to finish
+ Touch to select or add nodes
+ Add nodes manually or pull down to scan
+ Scanning network…
+ Automatically bookmarked best %1$d nodes
+ Test
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 2482ae6e..2600a7e8 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -30,10 +30,8 @@
Färdig
Tryck för QR-kod
- Högre prioritet = Högre avgift
BTC-betalning aktiverad, tryck för mer info.
- CrAzYpass aktiverat, tryck för mer info.
Ledger aktiverat, tryck för mer info.
Du har angivit en Bitcoin-adress.
Du kommer att skicka XMR och mottagaren får BTC via tjänsten XMR.TO.]]>
@@ -72,8 +70,6 @@
%1$s BTC = %2$s XMR
(Kurs: %1$s BTC/XMR)
- Avancerat:
-
Besök xmr.to för support & spårning
Hemlig nyckel\nXMR.TO
XMR.TO hemlig nyckel
@@ -88,7 +84,6 @@
Säkerhetskopiering pågår
Arkivering pågår
Namnbyte pågår
- Kontrollerar anslutning till daemon
Ändring av lösenord pågår
Gör klart några saker …\nDetta kan ta lite tid!
@@ -100,13 +95,11 @@
Lösenordet har ändrats
Nod
- ([<användare>:<lösenord>@]<daemon>[:<port>])
Läser in plånbok …
Plånbok sparad
Det gick inte att spara plånbok!
Ansluter …
Kunde inte ansluta till nod!\nKontrollera användarnamn/lösenord
- Fel: Anslutning till nod uppnådde en tidsgräns!\nFörsök igen eller en annan nod.
Ogiltig nod!\nPröva någon annan.
Noden kan inte nås!\nFörsök igen eller en annan nod.
@@ -174,7 +167,6 @@
Får inte börja med .
Skapar plånbok
Plånboken skapades
- Det gick inte att skapa plånbok
Ange ett tal eller ett datum (ÅÅÅÅ-MM-DD)
@@ -254,7 +246,6 @@
VÄNTANDE
MISSLYCKAD
- Betalnings-ID (valfritt)
Belopp
Det gick inte att öppna plånboken!
16 eller 64 hexadecimala tecken (0–9, a–f)
@@ -264,8 +255,6 @@
Min. 0
XMR är inte ett tal
- Ta emot
-
Nu kommer känsliga data att visas.\nTitta över axeln!
Det är OK
Ta mig tillbaka!
@@ -275,13 +264,6 @@
Ja, gör det!
Nej tack!
-
- - Prioritet standard
- - Prioritet låg
- - Prioritet medel
- - Prioritet hög
-
-
Skapa ny plånbok
Återställ granskningsplånbok
Återställ plånbok från privata nycklar
@@ -291,7 +273,6 @@
Det sparade lösenordet är inkorrekt.\nSkriv lösenordet manuellt.
Skapa konto
- Konton
Nytt konto skapat #%1$d
Konto #
@@ -313,7 +294,6 @@
Vänligen (åter)anslut Ledger
Skapar konto
- Uppdaterar blånbok
%1$s ansluten
%1$s frånkopplad
@@ -340,5 +320,31 @@
Detaljer
Street Mode
- Street Mode enabled, tap for more info.
+
+ Node-o-matiC enabled, tap for more info.
+ Last block updated: %1$s
+ Nodes
+ Node Name (Optional)
+ Hostname
+ Port
+ Username (Optional)
+ Password (Optional)
+ Cannot resolve host
+ We need this!
+ Must be numeric
+ Must be 1–65535
+ Add Node
+ Touch to refresh!
+ CONNECTION ERROR %1$d
+ CONNECTION ERROR
+ AUTHENTICATION FAILED
+ Test Result:
+ Height: %1$s (v%2$d), Ping: %3$.0fms, IP: %4$s
+ Testing IP: %1$s …
+ Please wait for scan to finish
+ Touch to select or add nodes
+ Add nodes manually or pull down to scan
+ Scanning network…
+ Automatically bookmarked best %1$d nodes
+ Test
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 41f97b5b..74866931 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -29,10 +29,8 @@
完成
点选显示QR码
- 较高优先权 = 较高手续费
BTC付款已启用, 点选了解更多
- CrAzYpass已启用, 点选了解更多
Ledger已启用, 点选了解更多
%1$s BTC = %2$s XMR
(汇率: %1$s BTC/XMR)
- 高级选项:
-
参访 xmr.to 以获得支援及追踪交易
私钥\nXMR.TO
XMR.TO 私钥
@@ -95,7 +91,6 @@
备份中
封存中
重新命名中
- 检查背景程序连网中
更改密码中
处理中 …\n可能会花费一点时间!
@@ -107,13 +102,11 @@
密码更改成功
节点
- ([<使用者>:<密码>@]<位置>[:<port>])
载入钱包中 …
钱包已储存
储存钱包失败!
连接中 …
连接至节点失败!\n请检查使用者/密码。
- 节点连接逾时!\n请重试或更換节点。
节点无效!\n请试试其他节点。
无法连接至节点!\n请试试其他节点。
@@ -191,7 +184,6 @@
无法使用.作为开头
建立钱包
钱包已建立
- 钱包建立失败
输入区块高度或日期(YYYY-MM-DD)
@@ -271,7 +263,6 @@
等待确认中
失败
- 付款ID (选填)
金额
无法开启钱包!
16 或 64 Hex 字元 (0–9,a–f)
@@ -281,8 +272,6 @@
最小值 0
输入的XMR不是数字
- 接收
-
将会选示敏感信息。\n请注意周遭安全!
我现在很安全
不看了!
@@ -292,20 +281,12 @@
好的!
不了!
-
- - 预设优先权
- - 低优先权
- - 中优先权
- - 高优先权
-
-
建立新钱包
恢复只读钱包
从私钥恢复钱包
从25字种子码恢复钱包
开新户口
- 户口
已新增户口 #%1$d
户口 #
@@ -327,7 +308,6 @@
Please (re)connect Ledger device
Creating account
- Updating wallet
%1$s attached
%1$s detached
@@ -354,5 +334,31 @@
详细信息
Street Mode
- Street Mode enabled, tap for more info.
+
+ Node-o-matiC enabled, tap for more info.
+ Last block updated: %1$s
+ Nodes
+ Node Name (Optional)
+ Hostname
+ Port
+ Username (Optional)
+ Password (Optional)
+ Cannot resolve host
+ We need this!
+ Must be numeric
+ Must be 1–65535
+ Add Node
+ Touch to refresh!
+ CONNECTION ERROR %1$d
+ CONNECTION ERROR
+ AUTHENTICATION FAILED
+ Test Result:
+ Height: %1$s (v%2$d), Ping: %3$.0fms, IP: %4$s
+ Testing IP: %1$s …
+ Please wait for scan to finish
+ Touch to select or add nodes
+ Add nodes manually or pull down to scan
+ Scanning network…
+ Automatically bookmarked best %1$d nodes
+ Test
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index ba4decd8..8b9a6655 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -29,10 +29,8 @@
完成
點選顯示 QR 碼
- 較高優先權 = 較高手續費
BTC 付款已啟用,點選了解更多
- CrAzYpass 已啟用,點選了解更多
Ledger 已啟用,點選了解更多
%1$s BTC = %2$s XMR
(匯率:%1$s BTC/XMR)
- 進階:
-
參訪 XMR.TO 以獲得支援及追蹤交易
私鑰\nXMR.TO
XMR.TO 私鑰
@@ -96,7 +92,6 @@
備份中
封存中
重新命名中
- 檢查背景程式連線中
更改密碼中
處理中 …\n可能需要些許時間!
@@ -108,13 +103,11 @@
密碼更改成功
節點
- ([<使用者>:<密碼>@]<位置>[:<port>])
載入錢包中 …
錢包已儲存
儲存錢包失敗!
連接中 …
連接至節點失敗!\n請檢查使用者/密碼。
- 節點連接逾時!\n請重試或更換節點。
節點無效!\n請試試其他節點。
無法連接至節點!\n請試試其他節點。
@@ -192,7 +185,6 @@
無法用.作為開頭
建立錢包
錢包已建立
- 錢包建立失敗
輸入區塊高度或日期 (YYYY-MM-DD)
@@ -272,7 +264,6 @@
等待確認中
失敗
- 付款 ID (選填)
金額
無法開啟錢包!
16 或 64 Hex 字元 (0–9,a–f)
@@ -282,8 +273,6 @@
最小值 0
輸入的 XMR 不是數字
- 接收
-
將會顯示敏感資訊。\n請注意周遭安全!
我現在很安全
不看了!
@@ -293,20 +282,12 @@
好的!
不了!
-
- - 預設優先權
- - 低優先權
- - 中優先權
- - 高優先權
-
-
建立新錢包
回復唯讀錢包
從私鑰回復錢包
從 25 字種子碼回復錢包
新增帳戶
- 帳戶
已新增帳戶 #%1$d
帳戶 #
@@ -328,7 +309,6 @@
請(重新)連接 Ledger 裝置
正在建立帳戶
- 正在更新帳戶
%1$s 已連接
%1$s 已斷開連接
@@ -355,5 +335,31 @@
詳細資訊
Street Mode
- Street Mode enabled, tap for more info.
+
+ Node-o-matiC enabled, tap for more info.
+ Last block updated: %1$s
+ Nodes
+ Node Name (Optional)
+ Hostname
+ Port
+ Username (Optional)
+ Password (Optional)
+ Cannot resolve host
+ We need this!
+ Must be numeric
+ Must be 1–65535
+ Add Node
+ Touch to refresh!
+ CONNECTION ERROR %1$d
+ CONNECTION ERROR
+ AUTHENTICATION FAILED
+ Test Result:
+ Height: %1$s (v%2$d), Ping: %3$.0fms, IP: %4$s
+ Testing IP: %1$s …
+ Please wait for scan to finish
+ Touch to select or add nodes
+ Add nodes manually or pull down to scan
+ Scanning network…
+ Automatically bookmarked best %1$d nodes
+ Test
diff --git a/app/src/main/res/values/help.xml b/app/src/main/res/values/help.xml
index ea9aa54b..fb8f69ee 100644
--- a/app/src/main/res/values/help.xml
+++ b/app/src/main/res/values/help.xml
@@ -216,16 +216,6 @@
previous step and then coming back to the \"Confirm\" screen.
]]>
-