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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue