1
0
Fork 0

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:
Dimo Karaivanov 2024-08-05 15:55:41 +03:00 committed by GitHub
parent 4c7c941e44
commit fa6e379b08
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 275 additions and 35 deletions

View file

@ -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) {}
}

View file

@ -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
View 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("/> <", "/><")
}

View file

@ -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=""

View file

@ -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 "";
}
}
}

View file

@ -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) {

View file

@ -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>

View file

@ -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