diff --git a/TODO b/TODO index 59e0079..25fe22c 100644 --- a/TODO +++ b/TODO @@ -7,8 +7,6 @@ Tasks: - Show logged-in status in persistent notification - Intro and setup - More layouts (tune for each screen size) -- About page -- Help page - Subclass EmailListFragment for each default mailbox - Cache Identicons for speed - Refactor code diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ab67e4c..95553a4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -138,6 +138,14 @@ android:name="android.support.PARENT_ACTIVITY" android:value="i2p.bote.android.config.SettingsActivity"/> + + + + * Copyright (C) 2013 Mohammed Lakkadshaw + * + * 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 org.sufficientlysecure.htmltextview; + +import java.util.Vector; + +import org.xml.sax.XMLReader; + +import android.text.Editable; +import android.text.Html; +import android.text.Layout; +import android.text.Spannable; +import android.text.style.AlignmentSpan; +import android.text.style.BulletSpan; +import android.text.style.LeadingMarginSpan; +import android.text.style.TypefaceSpan; +import android.util.Log; + +/** + * Some parts of this code are based on android.text.Html + */ +public class HtmlTagHandler implements Html.TagHandler { + private int mListItemCount = 0; + private Vector mListParents = new Vector(); + + private static class Code { + } + + private static class Center { + } + + @Override + public void handleTag(final boolean opening, final String tag, Editable output, final XMLReader xmlReader) { + if (opening) { + // opening tag + if (HtmlTextView.DEBUG) { + Log.d(HtmlTextView.TAG, "opening, output: " + output.toString()); + } + + if (tag.equalsIgnoreCase("ul") || tag.equalsIgnoreCase("ol") || tag.equalsIgnoreCase("dd")) { + mListParents.add(tag); + mListItemCount = 0; + } else if (tag.equalsIgnoreCase("code")) { + start(output, new Code()); + } else if (tag.equalsIgnoreCase("center")) { + start(output, new Center()); + } + } else { + // closing tag + if (HtmlTextView.DEBUG) { + Log.d(HtmlTextView.TAG, "closing, output: " + output.toString()); + } + + if (tag.equalsIgnoreCase("ul") || tag.equalsIgnoreCase("ol") || tag.equalsIgnoreCase("dd")) { + mListParents.remove(tag); + mListItemCount = 0; + } else if (tag.equalsIgnoreCase("li")) { + handleListTag(output); + } else if (tag.equalsIgnoreCase("code")) { + end(output, Code.class, new TypefaceSpan("monospace"), false); + } else if (tag.equalsIgnoreCase("center")) { + end(output, Center.class, new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER), true); + } + } + } + + /** + * Mark the opening tag by using private classes + * + * @param output + * @param mark + */ + private void start(Editable output, Object mark) { + int len = output.length(); + output.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK); + + if (HtmlTextView.DEBUG) { + Log.d(HtmlTextView.TAG, "len: " + len); + } + } + + private void end(Editable output, Class kind, Object repl, boolean paragraphStyle) { + Object obj = getLast(output, kind); + // start of the tag + int where = output.getSpanStart(obj); + // end of the tag + int len = output.length(); + + output.removeSpan(obj); + + if (where != len) { + // paragraph styles like AlignmentSpan need to end with a new line! + if (paragraphStyle) { + output.append("\n"); + len++; + } + output.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + if (HtmlTextView.DEBUG) { + Log.d(HtmlTextView.TAG, "where: " + where); + Log.d(HtmlTextView.TAG, "len: " + len); + } + } + + /** + * Get last marked position of a specific tag kind (private class) + * + * @param text + * @param kind + * @return + */ + private Object getLast(Editable text, Class kind) { + Object[] objs = text.getSpans(0, text.length(), kind); + if (objs.length == 0) { + return null; + } else { + for (int i = objs.length; i > 0; i--) { + if (text.getSpanFlags(objs[i - 1]) == Spannable.SPAN_MARK_MARK) { + return objs[i - 1]; + } + } + return null; + } + } + + private void handleListTag(Editable output) { + if (mListParents.lastElement().equals("ul")) { + output.append("\n"); + String[] split = output.toString().split("\n"); + + int lastIndex = split.length - 1; + int start = output.length() - split[lastIndex].length() - 1; + output.setSpan(new BulletSpan(15 * mListParents.size()), start, output.length(), 0); + } else if (mListParents.lastElement().equals("ol")) { + mListItemCount++; + + output.append("\n"); + String[] split = output.toString().split("\n"); + + int lastIndex = split.length - 1; + int start = output.length() - split[lastIndex].length() - 1; + output.insert(start, mListItemCount + ". "); + output.setSpan(new LeadingMarginSpan.Standard(15 * mListParents.size()), start, output.length(), 0); + } + } +} diff --git a/app/src/main/java/org/sufficientlysecure/htmltextview/HtmlTextView.java b/app/src/main/java/org/sufficientlysecure/htmltextview/HtmlTextView.java new file mode 100644 index 0000000..ba32dc5 --- /dev/null +++ b/app/src/main/java/org/sufficientlysecure/htmltextview/HtmlTextView.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2013-2014 Dominik Schürmann + * + * 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 org.sufficientlysecure.htmltextview; + +import android.content.Context; +import android.text.Html; +import android.text.method.LinkMovementMethod; +import android.util.AttributeSet; + +import java.io.InputStream; + +public class HtmlTextView extends JellyBeanSpanFixTextView { + + public static final String TAG = "HtmlTextView"; + public static final boolean DEBUG = false; + + public HtmlTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public HtmlTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public HtmlTextView(Context context) { + super(context); + } + + /** + * http://stackoverflow.com/questions/309424/read-convert-an-inputstream-to-a-string + * + * @param is + * @return + */ + static private String convertStreamToString(java.io.InputStream is) { + java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); + return s.hasNext() ? s.next() : ""; + } + + /** + * Loads HTML from a raw resource, i.e., a HTML file in res/raw/. + * This allows translatable resource (e.g., res/raw-de/ for german). + * The containing HTML is parsed to Android's Spannable format and then displayed. + * + * @param context + * @param id for example: R.raw.help + */ + public void setHtmlFromRawResource(Context context, int id, boolean useLocalDrawables) { + // load html from html file from /res/raw + InputStream inputStreamText = context.getResources().openRawResource(id); + + setHtmlFromString(convertStreamToString(inputStreamText), useLocalDrawables); + } + + /** + * Parses String containing HTML to Android's Spannable format and displays it in this TextView. + * + * @param html String containing HTML, for example: "Hello world!" + */ + public void setHtmlFromString(String html, boolean useLocalDrawables) { + Html.ImageGetter imgGetter; + if (useLocalDrawables) { + imgGetter = new LocalImageGetter(getContext()); + } else { + imgGetter = new UrlImageGetter(this, getContext()); + } + // this uses Android's Html class for basic parsing, and HtmlTagHandler + setText(Html.fromHtml(html, imgGetter, new HtmlTagHandler())); + + // make links work + setMovementMethod(LinkMovementMethod.getInstance()); + + // no flickering when clicking textview for Android < 4, but overriders color... +// text.setTextColor(getResources().getColor(android.R.color.secondary_text_dark_nodisable)); + } +} diff --git a/app/src/main/java/org/sufficientlysecure/htmltextview/JellyBeanSpanFixTextView.java b/app/src/main/java/org/sufficientlysecure/htmltextview/JellyBeanSpanFixTextView.java new file mode 100644 index 0000000..f3e7f66 --- /dev/null +++ b/app/src/main/java/org/sufficientlysecure/htmltextview/JellyBeanSpanFixTextView.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2013 Dominik Schürmann + * Copyright (C) 2012 Pierre-Yves Ricau + * + * 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 org.sufficientlysecure.htmltextview; + +import java.util.ArrayList; +import java.util.List; + +import android.content.Context; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.util.AttributeSet; +import android.util.Log; +import android.widget.TextView; + +/** + *

+ * A {@link android.widget.TextView} that insert spaces around its text spans where needed to prevent + * {@link IndexOutOfBoundsException} in {@link #onMeasure(int, int)} on Jelly Bean. + *

+ * When {@link #onMeasure(int, int)} throws an exception, we try to fix the text by adding spaces + * around spans, until it works again. We then try removing some of the added spans, to minimize the + * insertions. + *

+ * The fix is time consuming (a few ms, it depends on the size of your text), but it should only + * happen once per text change. + *

+ * See http://code.google.com/p/android/issues/detail?id=35466 + */ +public class JellyBeanSpanFixTextView extends TextView { + + private static class FixingResult { + public final boolean fixed; + public final List spansWithSpacesBefore; + public final List spansWithSpacesAfter; + + public static FixingResult fixed(List spansWithSpacesBefore, + List spansWithSpacesAfter) { + return new FixingResult(true, spansWithSpacesBefore, spansWithSpacesAfter); + } + + public static FixingResult notFixed() { + return new FixingResult(false, null, null); + } + + private FixingResult(boolean fixed, List spansWithSpacesBefore, + List spansWithSpacesAfter) { + this.fixed = fixed; + this.spansWithSpacesBefore = spansWithSpacesBefore; + this.spansWithSpacesAfter = spansWithSpacesAfter; + } + } + + public JellyBeanSpanFixTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public JellyBeanSpanFixTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public JellyBeanSpanFixTextView(Context context) { + super(context); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + try { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } catch (IndexOutOfBoundsException e) { + fixOnMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + /** + * If possible, fixes the Spanned text by adding spaces around spans when needed. + */ + private void fixOnMeasure(int widthMeasureSpec, int heightMeasureSpec) { + CharSequence text = getText(); + if (text instanceof Spanned) { + SpannableStringBuilder builder = new SpannableStringBuilder(text); + fixSpannedWithSpaces(builder, widthMeasureSpec, heightMeasureSpec); + } else { + if (HtmlTextView.DEBUG) { + Log.d(HtmlTextView.TAG, "The text isn't a Spanned"); + } + fallbackToString(widthMeasureSpec, heightMeasureSpec); + } + } + + /** + * Add spaces around spans until the text is fixed, and then removes the unneeded spaces + */ + private void fixSpannedWithSpaces(SpannableStringBuilder builder, int widthMeasureSpec, + int heightMeasureSpec) { + long startFix = System.currentTimeMillis(); + + FixingResult result = addSpacesAroundSpansUntilFixed(builder, widthMeasureSpec, + heightMeasureSpec); + + if (result.fixed) { + removeUnneededSpaces(widthMeasureSpec, heightMeasureSpec, builder, result); + } else { + fallbackToString(widthMeasureSpec, heightMeasureSpec); + } + + if (HtmlTextView.DEBUG) { + long fixDuration = System.currentTimeMillis() - startFix; + Log.d(HtmlTextView.TAG, "fixSpannedWithSpaces() duration in ms: " + fixDuration); + } + } + + private FixingResult addSpacesAroundSpansUntilFixed(SpannableStringBuilder builder, + int widthMeasureSpec, int heightMeasureSpec) { + + Object[] spans = builder.getSpans(0, builder.length(), Object.class); + List spansWithSpacesBefore = new ArrayList(spans.length); + List spansWithSpacesAfter = new ArrayList(spans.length); + + for (Object span : spans) { + int spanStart = builder.getSpanStart(span); + if (isNotSpace(builder, spanStart - 1)) { + builder.insert(spanStart, " "); + spansWithSpacesBefore.add(span); + } + + int spanEnd = builder.getSpanEnd(span); + if (isNotSpace(builder, spanEnd)) { + builder.insert(spanEnd, " "); + spansWithSpacesAfter.add(span); + } + + try { + setTextAndMeasure(builder, widthMeasureSpec, heightMeasureSpec); + return FixingResult.fixed(spansWithSpacesBefore, spansWithSpacesAfter); + } catch (IndexOutOfBoundsException notFixed) { + } + } + if (HtmlTextView.DEBUG) { + Log.d(HtmlTextView.TAG, "Could not fix the Spanned by adding spaces around spans"); + } + return FixingResult.notFixed(); + } + + private boolean isNotSpace(CharSequence text, int where) { + if (where < 0) { + return true; + } + return text.charAt(where) != ' '; + } + + private void setTextAndMeasure(CharSequence text, int widthMeasureSpec, int heightMeasureSpec) { + setText(text); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + private void removeUnneededSpaces(int widthMeasureSpec, int heightMeasureSpec, + SpannableStringBuilder builder, FixingResult result) { + + for (Object span : result.spansWithSpacesAfter) { + int spanEnd = builder.getSpanEnd(span); + builder.delete(spanEnd, spanEnd + 1); + try { + setTextAndMeasure(builder, widthMeasureSpec, heightMeasureSpec); + } catch (IndexOutOfBoundsException ignored) { + builder.insert(spanEnd, " "); + } + } + + boolean needReset = true; + for (Object span : result.spansWithSpacesBefore) { + int spanStart = builder.getSpanStart(span); + builder.delete(spanStart - 1, spanStart); + try { + setTextAndMeasure(builder, widthMeasureSpec, heightMeasureSpec); + needReset = false; + } catch (IndexOutOfBoundsException ignored) { + needReset = true; + int newSpanStart = spanStart - 1; + builder.insert(newSpanStart, " "); + } + } + + if (needReset) { + setText(builder); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + private void fallbackToString(int widthMeasureSpec, int heightMeasureSpec) { + if (HtmlTextView.DEBUG) { + Log.d(HtmlTextView.TAG, "Fallback to unspanned text"); + } + String fallbackText = getText().toString(); + setTextAndMeasure(fallbackText, widthMeasureSpec, heightMeasureSpec); + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/sufficientlysecure/htmltextview/LocalImageGetter.java b/app/src/main/java/org/sufficientlysecure/htmltextview/LocalImageGetter.java new file mode 100644 index 0000000..b8c527e --- /dev/null +++ b/app/src/main/java/org/sufficientlysecure/htmltextview/LocalImageGetter.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * Copyright (C) 2014 drawk + * + * 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 org.sufficientlysecure.htmltextview; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.text.Html; +import android.util.Log; + +/** + * Copied from http://stackoverflow.com/a/22298833 + */ +public class LocalImageGetter implements Html.ImageGetter { + Context c; + + public LocalImageGetter(Context c) { + this.c = c; + } + + public Drawable getDrawable(String source) { + int id = c.getResources().getIdentifier(source, "drawable", c.getPackageName()); + + if (id == 0) { + // the drawable resource wasn't found in our package, maybe it is a stock android drawable? + id = c.getResources().getIdentifier(source, "drawable", "android"); + } + + if (id == 0) { + // prevent a crash if the resource still can't be found + Log.e(HtmlTextView.TAG, "source could not be found: " + source); + return null; + } else { + Drawable d = c.getResources().getDrawable(id); + d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); + return d; + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/sufficientlysecure/htmltextview/UrlImageGetter.java b/app/src/main/java/org/sufficientlysecure/htmltextview/UrlImageGetter.java new file mode 100644 index 0000000..1e08b52 --- /dev/null +++ b/app/src/main/java/org/sufficientlysecure/htmltextview/UrlImageGetter.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2013 Antarix Tandon + * + * 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 org.sufficientlysecure.htmltextview; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.text.Html.ImageGetter; +import android.view.View; + +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.DefaultHttpClient; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; + +public class UrlImageGetter implements ImageGetter { + Context c; + View container; + + /** + * Construct the URLImageParser which will execute AsyncTask and refresh the container + * + * @param t + * @param c + */ + public UrlImageGetter(View t, Context c) { + this.c = c; + this.container = t; + } + + public Drawable getDrawable(String source) { + UrlDrawable urlDrawable = new UrlDrawable(); + + // get the actual source + ImageGetterAsyncTask asyncTask = new ImageGetterAsyncTask(urlDrawable); + + asyncTask.execute(source); + + // return reference to URLDrawable which will asynchronously load the image specified in the src tag + return urlDrawable; + } + + public class ImageGetterAsyncTask extends AsyncTask { + UrlDrawable urlDrawable; + + public ImageGetterAsyncTask(UrlDrawable d) { + this.urlDrawable = d; + } + + @Override + protected Drawable doInBackground(String... params) { + String source = params[0]; + return fetchDrawable(source); + } + + @Override + protected void onPostExecute(Drawable result) { + // set the correct bound according to the result from HTTP call + urlDrawable.setBounds(0, 0, 0 + result.getIntrinsicWidth(), 0 + result.getIntrinsicHeight()); + + // change the reference of the current drawable to the result from the HTTP call + urlDrawable.drawable = result; + + // redraw the image by invalidating the container + UrlImageGetter.this.container.invalidate(); + } + + /** + * Get the Drawable from URL + * + * @param urlString + * @return + */ + public Drawable fetchDrawable(String urlString) { + try { + InputStream is = fetch(urlString); + Drawable drawable = Drawable.createFromStream(is, "src"); + drawable.setBounds(0, 0, 0 + drawable.getIntrinsicWidth(), 0 + drawable.getIntrinsicHeight()); + return drawable; + } catch (Exception e) { + return null; + } + } + + private InputStream fetch(String urlString) throws MalformedURLException, IOException { + DefaultHttpClient httpClient = new DefaultHttpClient(); + HttpGet request = new HttpGet(urlString); + HttpResponse response = httpClient.execute(request); + return response.getEntity().getContent(); + } + } + + @SuppressWarnings("deprecation") + public class UrlDrawable extends BitmapDrawable { + protected Drawable drawable; + + @Override + public void draw(Canvas canvas) { + // override the draw to facilitate refresh function later + if (drawable != null) { + drawable.draw(canvas); + } + } + } +} diff --git a/app/src/main/res/layout/activity_help.xml b/app/src/main/res/layout/activity_help.xml new file mode 100644 index 0000000..eac6266 --- /dev/null +++ b/app/src/main/res/layout/activity_help.xml @@ -0,0 +1,32 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_help_about.xml b/app/src/main/res/layout/fragment_help_about.xml new file mode 100644 index 0000000..6d730c4 --- /dev/null +++ b/app/src/main/res/layout/fragment_help_about.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml index c0e3174..637a59b 100644 --- a/app/src/main/res/menu/main.xml +++ b/app/src/main/res/menu/main.xml @@ -19,4 +19,10 @@ android:title="@string/action_settings" i2pandroid:showAsAction="never"/> + + \ No newline at end of file diff --git a/app/src/main/res/raw/help_about.html b/app/src/main/res/raw/help_about.html new file mode 100644 index 0000000..090d967 --- /dev/null +++ b/app/src/main/res/raw/help_about.html @@ -0,0 +1,28 @@ + + + + + +

https://github.com/i2p/i2p.i2p-bote.android

+

Bote is the Android app for I2P-Bote.

+

License: GPLv3+

+ +

Libraries

+ + + diff --git a/app/src/main/res/raw/help_changelog.html b/app/src/main/res/raw/help_changelog.html new file mode 100644 index 0000000..c9b7a76 --- /dev/null +++ b/app/src/main/res/raw/help_changelog.html @@ -0,0 +1,62 @@ + + + + + + +

0.5

+
    +
  • Attachments!
  • +
  • Email content can be seleted/copied (API 11+)
  • +
  • Cc: and Bcc: fields
  • +
  • Much better support for legacy devices
  • +
  • UI improvements, bug fixes, translation updates
  • +
+ +

0.4

+
    +
  • New email notifications!
  • +
  • Fixed crash when clicking "Create new contact" button
  • +
  • Fixed crash when sending email
  • +
  • Fixed occasional crashes when Bote connects to / disconnects from the network
  • +
  • Added "Copy to clipboard" button to identities and contacts
  • +
  • Added labels to the address book actions menu
  • +
  • UI improvements, bug fixes, translation updates
  • +
+ +

0.3

+
    +
  • Migrated to new Material design from Android Lollipop
  • +
  • Overhauled password usability
  • +
  • Identicons for contacts without pictures
  • +
  • Check email improvements
  • +
  • Use new-style swipe
  • +
  • Users can now launch check from menu, or swipe on empty inbox
  • +
  • "Forward" and "Reply all" actions for emails
  • +
  • QR codes for sharing identities
  • +
  • Separated viewing and editing contacts
  • +
  • Improved network information page
  • +
  • Various bug fixes
  • +
  • Updated translations
  • +
+ +

0.3-rc3

+
    +
  • Test release to Norway on Google Play
  • +
+ +

0.2

+ +

0.1.1

+
    +
  • Fixed bugs in identity creation and password setting
  • +
+ +

0.1

+
    +
  • Initial release
  • +
+ + \ No newline at end of file diff --git a/app/src/main/res/raw/help_start.html b/app/src/main/res/raw/help_start.html new file mode 100644 index 0000000..51d658a --- /dev/null +++ b/app/src/main/res/raw/help_start.html @@ -0,0 +1,20 @@ + + + + + +

Getting started

+

First you need an identity. Create one via the "Settings" menu, or import an existing identity. Afterwards, you can add your friends' addresses by copy-paste, or exchange them via QR Codes or NFC.

+ +

On Android lower than 4.4, it is recommended that you install OI File Manager for enhanced file selection. To share via QR Codes install Barcode Scanner. Clicking on the links will open Google Play Store or F-Droid for installation.

+ +

I found a bug in Bote!

+

Please report the bug using the I2P bug tracker.

+ +

Translations

+

Help translating Bote! Everybody can participate at I2P on Transifex.

+ + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 127ac32..671e730 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -240,5 +240,10 @@ This field is required Bote address for %s Copied to clipboard + Help + Start + Changelog + About + Version: