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

View file

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