Real help screen (#589)
* the manual is converted from markdown to HTML during build time * The user manual is now included in the application. The Help section will no longer attempt to open it from Github * fixed punctuation mistakes in the manual
This commit is contained in:
parent
4c7c941e44
commit
fa6e379b08
9 changed files with 275 additions and 35 deletions
|
|
@ -4,6 +4,7 @@ plugins {
|
|||
|
||||
apply from: 'constants.gradle'
|
||||
apply from: 'dictionary-tools.gradle'
|
||||
apply from: 'help-tools.gradle'
|
||||
apply from: 'validate-languages.gradle'
|
||||
apply from: 'version-tools.gradle'
|
||||
|
||||
|
|
@ -30,6 +31,15 @@ tasks.register('copyDictionaries', Copy) {
|
|||
into DICTIONARIES_OUTPUT_DIR
|
||||
}
|
||||
|
||||
tasks.register('convertHelp') {
|
||||
inputs.file HELP_MARKDOWN
|
||||
outputs.file HELP_HTML
|
||||
|
||||
doLast {
|
||||
markdownToHTML(HELP_MARKDOWN, HELP_HTML, getVersionName())
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('writeDictionaryProperties') {
|
||||
inputs.dir fileTree(dir: DICTIONARIES_INPUT_DIR)
|
||||
outputs.dir DICTIONARY_META_OUTPUT_DIR
|
||||
|
|
@ -107,7 +117,7 @@ android {
|
|||
].each { taskName ->
|
||||
try {
|
||||
tasks.named(taskName)?.configure {
|
||||
dependsOn(validateLanguages, copyDefinitions, copyDictionaries, writeDictionaryProperties)
|
||||
dependsOn(validateLanguages, copyDefinitions, copyDictionaries, writeDictionaryProperties, convertHelp)
|
||||
}
|
||||
} catch (UnknownTaskException ignored) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ def ROOT_DIR = "${project.rootDir}/app"
|
|||
def MAIN_ASSETS_DIR = "${ROOT_DIR}/src/main/assets"
|
||||
def FULL_VERSION_ASSETS_DIR = "${ROOT_DIR}/src/full/assets"
|
||||
|
||||
ext.HELP_MARKDOWN = "${project.rootDir}/docs/user-manual.md"
|
||||
ext.HELP_HTML = "${MAIN_ASSETS_DIR}/help.html"
|
||||
|
||||
ext.LANGUAGES_INPUT_DIR = "${ROOT_DIR}/${LANGUAGES_DIR_NAME}"
|
||||
ext.DEFINITIONS_INPUT_DIR = "${LANGUAGES_INPUT_DIR}/${DEFINITIONS_DIR_NAME}"
|
||||
ext.DICTIONARIES_INPUT_DIR = "${LANGUAGES_INPUT_DIR}/${DICTIONARIES_DIR_NAME}"
|
||||
|
|
|
|||
166
app/help-tools.gradle
Normal file
166
app/help-tools.gradle
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
ext.markdownToHTML = { markdownPath, htmlPath, version ->
|
||||
def text = new File(markdownPath).text
|
||||
|
||||
text = convertHeaders(text, version)
|
||||
text = convertOrderedLists(text)
|
||||
text = convertUnorderedLists(text)
|
||||
text = convertInlineTags(text)
|
||||
text = addStylesToTags(text)
|
||||
text = insertIndex(text, generateIndex(text))
|
||||
text = removeWhitespace(text)
|
||||
|
||||
new File(htmlPath).text = "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><style>${getStyles()}</style><title>Help</title></head><body>${text}</body></html>"
|
||||
}
|
||||
|
||||
|
||||
static getStyles() {
|
||||
return "body {padding: 0 6px; background-color: #f4f4f4; color: #000;}" +
|
||||
"a {color: #225682}" +
|
||||
"a:visited {color: #644280}" +
|
||||
"li {margin: 4px 0; padding: 1px;}" +
|
||||
"p {text-align: justify;}" +
|
||||
"p.wrap{word-wrap: break-word;}" +
|
||||
".toc {border: 1px solid; display: inline-block; padding: 12px 20px 12px 0; margin: 12px 0;}" +
|
||||
".toc > h3 {text-align: center; margin: 0;}" +
|
||||
"@media (prefers-color-scheme: dark) {" +
|
||||
"body { background-color: #333; color: #c8c8c8; }" +
|
||||
"a {color: #a0c1de}" +
|
||||
"a:visited {color: #d9bce1}" +
|
||||
"}"
|
||||
}
|
||||
|
||||
|
||||
static generateIndex(html) {
|
||||
def entries = html.split("\n").collect( { line ->
|
||||
def matches = line =~ "<h2 id=\"(\\S+)\">(.+)</h2>"
|
||||
if (matches.size() > 0 && matches[0].size() > 2) {
|
||||
return "<a href=\"#${matches[0][1]}\">${matches[0][2]}</a>"
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}).findAll { it != null }
|
||||
|
||||
return "<section class=\"toc\"><h3>Contents</h3>" +
|
||||
"<ol>${entries.collect { "<li>${it}</li>" }.join("\n")}</ol>" +
|
||||
"</section>"
|
||||
}
|
||||
|
||||
|
||||
static insertIndex(html, index) {
|
||||
return html.replaceFirst("<h2", "${index}<h2")
|
||||
}
|
||||
|
||||
|
||||
static convertHeaders(markdown, version) {
|
||||
def html = markdown.split("\n").collect { line ->
|
||||
if (line.startsWith("#")) {
|
||||
def headerNumber = 0
|
||||
for (int i = 0; i < line.length(); i++) {
|
||||
if (line[i] != '#') {
|
||||
headerNumber = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
def header = line.replaceAll("^#+", "").trim()
|
||||
def anchor = header.toLowerCase().replaceAll("[^a-z0-9]+", "-")
|
||||
|
||||
return "<h${headerNumber} id=\"${anchor}\">" +
|
||||
"${header}${headerNumber == 1 ? " v" + version : ''}" +
|
||||
"</h${headerNumber}>"
|
||||
} else {
|
||||
return line
|
||||
}
|
||||
}
|
||||
|
||||
return html.join("\n")
|
||||
}
|
||||
|
||||
|
||||
static convertOrderedLists(markdown) {
|
||||
def html = markdown.split("\n").collect { line ->
|
||||
if (line.matches("^\\d+\\..*")) {
|
||||
return "<li>${line.replaceAll("^\\d+\\.\\s*", "")}</li>"
|
||||
} else {
|
||||
return line
|
||||
}
|
||||
}
|
||||
|
||||
return html.join("\n").replaceAll("(?<!li>\n)<li>", "<ol><li>").replaceAll("</li>(?!\n<li)", "</li></ol>")
|
||||
}
|
||||
|
||||
|
||||
static convertUnorderedLists(markdown) {
|
||||
boolean inList = false
|
||||
boolean inNestedList = false
|
||||
|
||||
def html = ""
|
||||
|
||||
markdown.split("\n").each { line ->
|
||||
def convertedLine = ""
|
||||
|
||||
def innerLi = line.replaceAll("^\\s*-\\s*", "")
|
||||
|
||||
if (line.matches("^-.*")) {
|
||||
if (!inList) {
|
||||
convertedLine += "<ul>"
|
||||
inList = true
|
||||
}
|
||||
|
||||
if (inNestedList) {
|
||||
convertedLine += "</ul></li>"
|
||||
inNestedList = false
|
||||
}
|
||||
|
||||
convertedLine += "<li>${innerLi}</li>"
|
||||
} else if (line.matches("^\\s+-.*")) {
|
||||
if (!inNestedList) {
|
||||
if (html.endsWith("</li>")) {
|
||||
html = html.substring(0, html.length() - 5)
|
||||
} else if (html.endsWith("</li>\n")) {
|
||||
html = html.substring(0, html.length() - 6)
|
||||
}
|
||||
|
||||
convertedLine += "<ul>"
|
||||
inNestedList = true
|
||||
}
|
||||
|
||||
convertedLine += "<li>${innerLi}</li>"
|
||||
} else {
|
||||
if (inNestedList) {
|
||||
inNestedList = false
|
||||
convertedLine += "</ul></li>"
|
||||
}
|
||||
|
||||
if (inList) {
|
||||
inList = false
|
||||
convertedLine += "</ul>"
|
||||
}
|
||||
|
||||
convertedLine += line
|
||||
}
|
||||
|
||||
html += convertedLine + "\n"
|
||||
}
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
|
||||
static convertInlineTags(markdown) {
|
||||
return markdown
|
||||
.replaceAll("\n([^\n<]+?)(\n|\$)", "<p>\$1</p>")
|
||||
.replaceAll("_([^_]+)_", "<i>\$1</i>")
|
||||
.replaceAll("[*]{2}(.+?)[*]{2}", "<b>\$1</b>")
|
||||
.replaceAll("\\[([^]]+)\\]\\(([^)]+)\\)", "<a href=\"\$2\">\$1</a>")
|
||||
}
|
||||
|
||||
|
||||
static addStylesToTags(html) {
|
||||
return html.replaceAll("<p>([^<]+?googlequicksearchbox[^<]+?)</p>", "<p class=\"wrap\">\$1</p>")
|
||||
}
|
||||
|
||||
|
||||
static removeWhitespace(html) {
|
||||
return html.replaceAll("\\s+", " ").replaceAll("/> <", "/><")
|
||||
}
|
||||
|
|
@ -28,7 +28,9 @@
|
|||
<meta-data android:name="android.view.im" android:resource="@xml/method"/>
|
||||
</service>
|
||||
|
||||
<activity android:label="@string/app_name_short" android:name="io.github.sspanak.tt9.preferences.PreferencesActivity"
|
||||
<activity
|
||||
android:label="@string/app_name_short"
|
||||
android:name="io.github.sspanak.tt9.preferences.PreferencesActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
|
@ -42,6 +44,16 @@
|
|||
android:name="io.github.sspanak.tt9.ui.dialogs.PopupDialogActivity"
|
||||
android:theme="@style/alertDialog" />
|
||||
|
||||
<activity
|
||||
android:label="@string/pref_help"
|
||||
android:name="io.github.sspanak.tt9.preferences.HelpActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:excludeFromRecents="true"
|
||||
android:label=""
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
package io.github.sspanak.tt9.preferences;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Base64;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.util.Logger;
|
||||
|
||||
public class HelpActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
buildLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void buildLayout() {
|
||||
enableBackButton();
|
||||
setContent();
|
||||
}
|
||||
|
||||
private void enableBackButton() {
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayShowHomeEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void setContent() {
|
||||
WebView container = new WebView(this);
|
||||
|
||||
// On API > 30 the WebView does not load the entire String with .loadData(),
|
||||
// so we need to do this weird shit.
|
||||
// The "app:" prefix is mandatory, otherwise the anchor links do not work.
|
||||
// Reference: https://developer.android.com/develop/ui/views/layout/webapps/webview
|
||||
String html = getHelpHtml();
|
||||
String encodedHtml = "app:" + Base64.encodeToString(html.getBytes(), Base64.NO_PADDING);
|
||||
container.loadDataWithBaseURL(encodedHtml, html, "text/html", "UTF-8", null);
|
||||
|
||||
setContentView(container);
|
||||
}
|
||||
|
||||
private String getHelpHtml() {
|
||||
AssetManager assets = getAssets();
|
||||
try {
|
||||
InputStream stream = assets.open("help.html");
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
builder.append(line);
|
||||
}
|
||||
return builder.toString();
|
||||
} catch (Exception e) {
|
||||
Logger.e(getClass().getSimpleName(), "Failed opening the help.html file.");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,6 @@ public class MainSettingsScreen extends BaseScreenFragment {
|
|||
@Override
|
||||
public void onCreate() {
|
||||
createSettingsSection();
|
||||
addHelpLink();
|
||||
createAboutSection();
|
||||
resetFontSize(false);
|
||||
}
|
||||
|
|
@ -46,30 +45,6 @@ public class MainSettingsScreen extends BaseScreenFragment {
|
|||
}
|
||||
|
||||
|
||||
private void addHelpLink() {
|
||||
try {
|
||||
if (!releaseVersionRegex.matcher(BuildConfig.VERSION_NAME).find()) {
|
||||
throw new Exception("VERSION_NAME does not match: \\d+.\\d+");
|
||||
}
|
||||
|
||||
Preference helpSection = findPreference("help");
|
||||
if (helpSection == null) {
|
||||
throw new Exception("Could not find Help Preference");
|
||||
}
|
||||
|
||||
String majorVersion = BuildConfig.VERSION_NAME.substring(0, BuildConfig.VERSION_NAME.indexOf('.'));
|
||||
String versionedHelpUrl = getString(R.string.help_url).replace("blob/master", "blob/v" + majorVersion + ".0");
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setAction("android.intent.action.VIEW");
|
||||
intent.setData(Uri.parse(versionedHelpUrl));
|
||||
helpSection.setIntent(intent);
|
||||
} catch (Exception e) {
|
||||
Logger.w("MainSettingsScreen", "Could not set versioned help URL. Falling back to the default. " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void createAboutSection() {
|
||||
Preference donate = findPreference("donate_link");
|
||||
if (donate != null) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string translatable="false" name="dictionary_url">https://raw.githubusercontent.com/sspanak/tt9/%1$s/app/%2$s</string>
|
||||
<string translatable="false" name="help_url">https://github.com/sspanak/tt9/blob/master/docs/user-manual.md</string>
|
||||
<string name="app_name" translatable="false">Traditional T9</string>
|
||||
<string name="app_name_short" translatable="false">TT9</string>
|
||||
<string name="app_settings">TT9 Settings</string>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@
|
|||
app:orderingFromXml="true">
|
||||
|
||||
<Preference
|
||||
app:key="help"
|
||||
app:summary="github.com/sspanak/tt9"
|
||||
app:key="screen_help"
|
||||
app:summary="English only"
|
||||
app:title="@string/pref_help">
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:data="@string/help_url" />
|
||||
android:targetPackage="io.github.sspanak.tt9"
|
||||
android:targetClass="io.github.sspanak.tt9.preferences.HelpActivity" />
|
||||
</Preference>
|
||||
|
||||
<Preference
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ _If you don't see the icon right after installing, restart your phone, and it sh
|
|||
If your phone does not have a hardware keypad, check out the [On-screen Keypad section](#on-screen-keypad).
|
||||
|
||||
### Enabling Predictive Mode
|
||||
Predictive Mode requires a language dictionary to be loaded to provide word suggestions. You can toggle the enabled languages and load their dictionaries from Settings Screen → [Languages](#language-options). In case, you have forgotten to load some dictionary, Traditional T9 will do it for you automatically when you start typing.
|
||||
For more information, [see below](#language-options).
|
||||
Predictive Mode requires a language dictionary to be loaded to provide word suggestions. You can toggle the enabled languages and load their dictionaries from Settings Screen → [Languages](#language-options). In case, you have forgotten to load some dictionary, Traditional T9 will do it for you automatically when you start typing. For more information, [see below](#language-options).
|
||||
|
||||
#### Notes for low-end phones
|
||||
Dictionary loading may saturate low-end phones. When using the TT9 "lite" version, this will cause Android to abort the operation. If loading takes more than 30 seconds, plug in the charger or ensure the screen stays on during loading.
|
||||
|
|
@ -191,8 +190,7 @@ Click on the Traditional T9 launcher icon.
|
|||
- Press the 2-key.
|
||||
|
||||
#### Method 3
|
||||
- Go to Android Settings → System → Languages → Keyboards (or On-Screen Keyboards/Virtual Keyboards). This is where all installed keyboards
|
||||
are configured.
|
||||
- Go to Android Settings → System → Languages → Keyboards (or On-Screen Keyboards/Virtual Keyboards). This is where all installed keyboards are configured.
|
||||
- Select "Traditional T9".
|
||||
|
||||
_The actual menu names may vary depending on your phone, Android version, and language._
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue