Help and About pages
This commit is contained in:
2
TODO
2
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
|
||||
|
||||
@@ -138,6 +138,14 @@
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="i2p.bote.android.config.SettingsActivity"/>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".HelpActivity"
|
||||
android:label="@string/help"
|
||||
android:parentActivityName=".EmailListActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="i2p.bote.android.EmailListActivity"/>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name=".provider.AttachmentProvider"
|
||||
|
||||
@@ -305,6 +305,11 @@ public class EmailListActivity extends ActionBarActivity implements
|
||||
startActivity(si);
|
||||
return true;
|
||||
|
||||
case R.id.action_help:
|
||||
Intent hi = new Intent(this, HelpActivity.class);
|
||||
startActivity(hi);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
56
app/src/main/java/i2p/bote/android/HelpAboutFragment.java
Normal file
56
app/src/main/java/i2p/bote/android/HelpAboutFragment.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package i2p.bote.android;
|
||||
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||
|
||||
|
||||
public class HelpAboutFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_help_about, container, false);
|
||||
|
||||
TextView versionText = (TextView) view.findViewById(R.id.help_about_version);
|
||||
versionText.setText(getString(R.string.help_about_version) + " " + getVersion());
|
||||
|
||||
HtmlTextView aboutTextView = (HtmlTextView) view.findViewById(R.id.help_about_text);
|
||||
|
||||
// load html from raw resource (Parsing handled by HtmlTextView library)
|
||||
aboutTextView.setHtmlFromRawResource(getActivity(), R.raw.help_about, true);
|
||||
|
||||
// no flickering when clicking textview for Android < 4
|
||||
aboutTextView.setTextColor(getResources().getColor(android.R.color.black));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current package version.
|
||||
*
|
||||
* @return The current version.
|
||||
*/
|
||||
private String getVersion() {
|
||||
String result = "";
|
||||
try {
|
||||
PackageManager manager = getActivity().getPackageManager();
|
||||
PackageInfo info = manager.getPackageInfo(getActivity().getPackageName(), 0);
|
||||
|
||||
result = String.format("%s (%s)", info.versionName, info.versionCode);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.w(Constants.ANDROID_LOG_TAG, "Unable to get application version: " + e.getMessage());
|
||||
result = "Unable to get application version.";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
95
app/src/main/java/i2p/bote/android/HelpActivity.java
Normal file
95
app/src/main/java/i2p/bote/android/HelpActivity.java
Normal file
@@ -0,0 +1,95 @@
|
||||
package i2p.bote.android;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
|
||||
import com.viewpagerindicator.TitlePageIndicator;
|
||||
|
||||
public class HelpActivity extends ActionBarActivity {
|
||||
/**
|
||||
* The {@link android.support.v4.view.PagerAdapter} that will provide
|
||||
* fragments for each of the sections. We use a
|
||||
* {@link FragmentPagerAdapter} derivative, which will keep every
|
||||
* loaded fragment in memory. If this becomes too memory intensive, it
|
||||
* may be best to switch to a
|
||||
* {@link android.support.v4.app.FragmentStatePagerAdapter}.
|
||||
*/
|
||||
SectionsPagerAdapter mSectionsPagerAdapter;
|
||||
|
||||
/**
|
||||
* The {@link ViewPager} that will host the section contents.
|
||||
*/
|
||||
ViewPager mViewPager;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_help);
|
||||
|
||||
// Set the action bar
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
// Enable ActionBar app icon to behave as action to go back
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
// Create the sections adapter.
|
||||
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
|
||||
|
||||
// Set up the ViewPager with the sections adapter.
|
||||
mViewPager = (ViewPager) findViewById(R.id.pager);
|
||||
mViewPager.setAdapter(mSectionsPagerAdapter);
|
||||
|
||||
// Bind the page indicator to the pager.
|
||||
TitlePageIndicator pageIndicator = (TitlePageIndicator) findViewById(R.id.page_indicator);
|
||||
pageIndicator.setViewPager(mViewPager);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A {@link android.support.v4.app.FragmentPagerAdapter} that returns a fragment corresponding to
|
||||
* one of the help sections.
|
||||
*/
|
||||
public class SectionsPagerAdapter extends FragmentPagerAdapter {
|
||||
|
||||
public SectionsPagerAdapter(FragmentManager fm) {
|
||||
super(fm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
switch (position) {
|
||||
case 1:
|
||||
return getString(R.string.changelog);
|
||||
case 2:
|
||||
return getString(R.string.about);
|
||||
case 0:
|
||||
default:
|
||||
return getString(R.string.start);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
switch (position) {
|
||||
case 1:
|
||||
return HelpHtmlFragment.newInstance(R.raw.help_changelog);
|
||||
case 2:
|
||||
return new HelpAboutFragment();
|
||||
case 0:
|
||||
default:
|
||||
return HelpHtmlFragment.newInstance(R.raw.help_start);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
app/src/main/java/i2p/bote/android/HelpHtmlFragment.java
Normal file
35
app/src/main/java/i2p/bote/android/HelpHtmlFragment.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package i2p.bote.android;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||
|
||||
public class HelpHtmlFragment extends Fragment {
|
||||
public static final String ARG_HTML_FILE = "htmlFile";
|
||||
|
||||
static HelpHtmlFragment newInstance(int htmlFile) {
|
||||
HelpHtmlFragment f = new HelpHtmlFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(ARG_HTML_FILE, htmlFile);
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
ScrollView scroller = new ScrollView(getActivity());
|
||||
HtmlTextView text = new HtmlTextView(getActivity());
|
||||
scroller.addView(text);
|
||||
int padH = getResources().getDimensionPixelOffset(R.dimen.activity_horizontal_margin);
|
||||
int padV = getResources().getDimensionPixelOffset(R.dimen.activity_vertical_margin);
|
||||
text.setPadding(padH, padV, padH, padV);
|
||||
text.setHtmlFromRawResource(getActivity(), getArguments().getInt(ARG_HTML_FILE), true);
|
||||
text.setTextColor(getResources().getColor(R.color.primary_text_default_material_light));
|
||||
return scroller;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* 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<String> mListParents = new Vector<String>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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: "<b>Hello world!</b>"
|
||||
*/
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2012 Pierre-Yves Ricau <py.ricau@gmail.com>
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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<Object> spansWithSpacesBefore;
|
||||
public final List<Object> spansWithSpacesAfter;
|
||||
|
||||
public static FixingResult fixed(List<Object> spansWithSpacesBefore,
|
||||
List<Object> spansWithSpacesAfter) {
|
||||
return new FixingResult(true, spansWithSpacesBefore, spansWithSpacesAfter);
|
||||
}
|
||||
|
||||
public static FixingResult notFixed() {
|
||||
return new FixingResult(false, null, null);
|
||||
}
|
||||
|
||||
private FixingResult(boolean fixed, List<Object> spansWithSpacesBefore,
|
||||
List<Object> 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<Object> spansWithSpacesBefore = new ArrayList<Object>(spans.length);
|
||||
List<Object> spansWithSpacesAfter = new ArrayList<Object>(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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String, Void, Drawable> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
app/src/main/res/layout/activity_help.xml
Normal file
32
app/src/main/res/layout/activity_help.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/main_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
||||
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
|
||||
|
||||
<com.viewpagerindicator.TitlePageIndicator
|
||||
android:id="@+id/page_indicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/primary_text_disabled_material_light"
|
||||
app:footerColor="@color/accent"
|
||||
app:selectedBold="false"
|
||||
app:selectedColor="@color/primary_text_default_material_light"/>
|
||||
|
||||
<android.support.v4.view.ViewPager
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</LinearLayout>
|
||||
61
app/src/main/res/layout/fragment_help_about.xml
Normal file
61
app/src/main/res/layout/fragment_help_about.xml
Normal file
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:isScrollContainer="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:scrollbars="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top"
|
||||
android:layout_marginRight="10dp"
|
||||
android:src="@drawable/ic_launcher"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subject"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/help_about_version"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Secondary"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<org.sufficientlysecure.htmltextview.HtmlTextView
|
||||
android:id="@+id/help_about_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
@@ -19,4 +19,10 @@
|
||||
android:title="@string/action_settings"
|
||||
i2pandroid:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_help"
|
||||
android:orderInCategory="200"
|
||||
android:title="@string/help"
|
||||
i2pandroid:showAsAction="never"/>
|
||||
|
||||
</menu>
|
||||
28
app/src/main/res/raw/help_about.html
Normal file
28
app/src/main/res/raw/help_about.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!-- Maintain structure with headings with h2 tags and content with p tags.
|
||||
This makes it easy to translate the values with transifex!
|
||||
And don't add newlines before or after p tags because of transifex -->
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<p><a href="https://github.com/i2p/i2p.i2p-bote.android">https://github.com/i2p/i2p.i2p-bote.android</a></p>
|
||||
<p>Bote is the Android app for <a href="http://i2pbote.i2p.xyz">I2P-Bote</a>.</p>
|
||||
<p>License: GPLv3+</p>
|
||||
|
||||
<h2>Libraries</h2>
|
||||
<ul>
|
||||
<li><a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Apache License v2)</li>
|
||||
<li><a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Apache License v2)</li>
|
||||
<li><a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'recyclerview'</a> (Apache License v2)</li>
|
||||
<li><a href="http://androidplot.com">AndroidPlot</a> (Apache License v2)</li>
|
||||
<li><a href="https://github.com/str4d/android-floating-action-button">FloatingActionButton</a> (Apache License v2)</li>
|
||||
<li><a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache License v2)</li>
|
||||
<li><a href="https://github.com/i2p/i2p.android.base">I2P Client Library</a> (Apache License v2)</li>
|
||||
<li><a href="https://code.google.com/p/javamail-android/">JavaMail for Android</a> (GPLv2)</li>
|
||||
<li><a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 License)</li>
|
||||
<li><a href="https://github.com/splitwise/TokenAutoComplete">TokenAutoComplete</a> (Apache License v2)</li>
|
||||
<li><a href="https://github.com/PrestoCarrot/Android-ViewPagerIndicator">ViewPagerIndicator</a> (Apache License v2)</li>
|
||||
<li><a href="http://code.google.com/p/zxing/">ZXing</a> (Apache License v2)</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
62
app/src/main/res/raw/help_changelog.html
Normal file
62
app/src/main/res/raw/help_changelog.html
Normal file
@@ -0,0 +1,62 @@
|
||||
<!-- Maintain structure with headings with h2 tags and content with p tags.
|
||||
This makes it easy to translate the values with transifex!
|
||||
And don't add newlines before or after p tags because of transifex -->
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>0.5</h2>
|
||||
<ul>
|
||||
<li>Attachments!</li>
|
||||
<li>Email content can be seleted/copied (API 11+)</li>
|
||||
<li>Cc: and Bcc: fields</li>
|
||||
<li>Much better support for legacy devices</li>
|
||||
<li>UI improvements, bug fixes, translation updates</li>
|
||||
</ul>
|
||||
|
||||
<h2>0.4</h2>
|
||||
<ul>
|
||||
<li>New email notifications!</li>
|
||||
<li>Fixed crash when clicking "Create new contact" button</li>
|
||||
<li>Fixed crash when sending email</li>
|
||||
<li>Fixed occasional crashes when Bote connects to / disconnects from the network</li>
|
||||
<li>Added "Copy to clipboard" button to identities and contacts</li>
|
||||
<li>Added labels to the address book actions menu</li>
|
||||
<li>UI improvements, bug fixes, translation updates</li>
|
||||
</ul>
|
||||
|
||||
<h2>0.3</h2>
|
||||
<ul>
|
||||
<li>Migrated to new Material design from Android Lollipop</li>
|
||||
<li>Overhauled password usability</li>
|
||||
<li>Identicons for contacts without pictures</li>
|
||||
<li>Check email improvements</li>
|
||||
<li>Use new-style swipe</li>
|
||||
<li>Users can now launch check from menu, or swipe on empty inbox</li>
|
||||
<li>"Forward" and "Reply all" actions for emails</li>
|
||||
<li>QR codes for sharing identities</li>
|
||||
<li>Separated viewing and editing contacts</li>
|
||||
<li>Improved network information page</li>
|
||||
<li>Various bug fixes</li>
|
||||
<li>Updated translations</li>
|
||||
</ul>
|
||||
|
||||
<h2>0.3-rc3</h2>
|
||||
<ul>
|
||||
<li>Test release to Norway on Google Play</li>
|
||||
</ul>
|
||||
|
||||
<h2>0.2</h2>
|
||||
|
||||
<h2>0.1.1</h2>
|
||||
<ul>
|
||||
<li>Fixed bugs in identity creation and password setting</li>
|
||||
</ul>
|
||||
|
||||
<h2>0.1</h2>
|
||||
<ul>
|
||||
<li>Initial release</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
20
app/src/main/res/raw/help_start.html
Normal file
20
app/src/main/res/raw/help_start.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!-- Maintain structure with headings with h2 tags and content with p tags.
|
||||
This makes it easy to translate the values with transifex!
|
||||
And don't add newlines before or after p tags because of transifex -->
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Getting started</h2>
|
||||
<p>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.</p>
|
||||
|
||||
<p>On Android lower than 4.4, it is recommended that you install <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> for enhanced file selection. To share via QR Codes install <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a>. Clicking on the links will open Google Play Store or F-Droid for installation.</p>
|
||||
|
||||
<h2>I found a bug in Bote!</h2>
|
||||
<p>Please report the bug using the <a href="https://trac.i2p2.de">I2P bug tracker</a>.</p>
|
||||
|
||||
<h2>Translations</h2>
|
||||
<p>Help translating Bote! Everybody can participate at <a href="https://www.transifex.com/projects/p/I2P/">I2P on Transifex</a>.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -240,5 +240,10 @@
|
||||
<string name="this_field_is_required">This field is required</string>
|
||||
<string name="bote_dest_for">Bote address for %s</string>
|
||||
<string name="copied_to_clipboard">Copied to clipboard</string>
|
||||
<string name="help">Help</string>
|
||||
<string name="start">Start</string>
|
||||
<string name="changelog">Changelog</string>
|
||||
<string name="about">About</string>
|
||||
<string name="help_about_version">Version:</string>
|
||||
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user