Loading app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java +226 −26 Original line number Diff line number Diff line Loading @@ -4,8 +4,11 @@ import android.content.Intent; import android.content.SharedPreferences; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.support.design.widget.Snackbar; import android.support.design.widget.TextInputLayout; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; Loading @@ -14,11 +17,20 @@ import android.text.TextWatcher; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Button; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import java.net.URLDecoder; import java.util.HashMap; import java.util.Locale; import java.util.Map; import at.bitfire.cert4android.CustomCertManager; import butterknife.BindView; import butterknife.ButterKnife; Loading @@ -42,10 +54,15 @@ public class SettingsActivity extends AppCompatActivity { public static final String DEFAULT_SETTINGS = ""; public static final int CREDENTIALS_CHANGED = 3; public static final String LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":"; public static final String WEBDAV_PATH_4_0_AND_LATER = "/remote.php/webdav"; private SharedPreferences preferences = null; @BindView(R.id.settings_url) EditText field_url; @BindView(R.id.settings_username_wrapper) TextInputLayout username_wrapper; @BindView(R.id.settings_username) EditText field_username; @BindView(R.id.settings_password) Loading @@ -58,7 +75,10 @@ public class SettingsActivity extends AppCompatActivity { View urlWarnHttp; private String old_password = ""; private WebView webView; private boolean first_run = false; private boolean useWebLogin = true; @Override public void onCreate(Bundle savedInstanceState) { Loading @@ -76,6 +96,32 @@ public class SettingsActivity extends AppCompatActivity { } } setupListener(); // Load current Preferences field_url.setText(preferences.getString(SETTINGS_URL, DEFAULT_SETTINGS)); field_username.setText(preferences.getString(SETTINGS_USERNAME, DEFAULT_SETTINGS)); old_password = preferences.getString(SETTINGS_PASSWORD, DEFAULT_SETTINGS); field_password.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { login(); return true; } }); field_password.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { setPasswordHint(hasFocus); } }); setPasswordHint(false); handleSubmitButtonEnabled(); } private void setupListener() { field_url.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { Loading @@ -99,7 +145,7 @@ public class SettingsActivity extends AppCompatActivity { urlWarnHttp.setVisibility(View.GONE); } handleSubmitButtonEnabled(field_url.getText(), field_username.getText()); handleSubmitButtonEnabled(); } @Override Loading @@ -115,7 +161,7 @@ public class SettingsActivity extends AppCompatActivity { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { handleSubmitButtonEnabled(field_url.getText(), field_username.getText()); handleSubmitButtonEnabled(); } @Override Loading @@ -124,27 +170,6 @@ public class SettingsActivity extends AppCompatActivity { } }); // Load current Preferences field_url.setText(preferences.getString(SETTINGS_URL, DEFAULT_SETTINGS)); field_username.setText(preferences.getString(SETTINGS_USERNAME, DEFAULT_SETTINGS)); old_password = preferences.getString(SETTINGS_PASSWORD, DEFAULT_SETTINGS); field_password.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { login(); return true; } }); field_password.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { setPasswordHint(hasFocus); } }); setPasswordHint(false); btn_submit.setEnabled(false); btn_submit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Loading Loading @@ -180,7 +205,7 @@ public class SettingsActivity extends AppCompatActivity { } } private void login() { private void legacyLogin() { String url = field_url.getText().toString().trim(); String username = field_username.getText().toString(); String password = field_password.getText().toString(); Loading @@ -194,8 +219,174 @@ public class SettingsActivity extends AppCompatActivity { new LoginValidatorAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url, username, password); } private void handleSubmitButtonEnabled(Editable url, Editable username) { if (field_username.getText().length() > 0 && field_url.getText().length() > 0) { private void login() { if (useWebLogin) { webLogin(); } else { legacyLogin(); } } private void webLogin() { setContentView(R.layout.activity_settings_webview); webView = findViewById(R.id.login_webview); webView.setVisibility(View.GONE); final ProgressBar progressBar = findViewById(R.id.login_webview_progress_bar); WebSettings settings = webView.getSettings(); settings.setAllowFileAccess(false); settings.setJavaScriptEnabled(true); settings.setDomStorageEnabled(true); settings.setUserAgentString(getWebLoginUserAgent()); settings.setSaveFormData(false); settings.setSavePassword(false); Map<String, String> headers = new HashMap<>(); headers.put("OCS-APIREQUEST", "true"); webView.loadUrl(normalizeUrlSuffix(field_url.getText().toString()) + "index.php/login/flow", headers); webView.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (url.startsWith("nc://login/")) { parseAndLoginFromWebView(url); return true; } return false; } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); progressBar.setVisibility(View.GONE); webView.setVisibility(View.VISIBLE); } // @Override // public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { // X509Certificate cert = SsoWebViewClient.getX509CertificateFromError(error); // // try { // if (cert != null && NetworkUtils.isCertInKnownServersStore(cert, getApplicationContext())) { // handler.proceed(); // } else { // showUntrustedCertDialog(cert, error, handler); // } // } catch (Exception e) { // Log.e("Note", "Cert could not be verified"); // } // } }); // show snackbar after 60s to switch back to old login method new Handler().postDelayed(new Runnable() { @Override public void run() { Snackbar.make(webView, R.string.fallback_weblogin_text, Snackbar.LENGTH_INDEFINITE) .setAction(R.string.fallback_weblogin_back, new View.OnClickListener() { @Override public void onClick(View v) { initLegacyLogin(field_url.getText().toString()); } }).show(); } }, 60 * 1000); } private String getWebLoginUserAgent() { return Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) + Build.MANUFACTURER.substring(1).toLowerCase(Locale.getDefault()) + " " + Build.MODEL; } private void parseAndLoginFromWebView(String dataString) { String prefix = "nc://login/"; LoginUrlInfo loginUrlInfo = parseLoginDataUrl(prefix, dataString); if (loginUrlInfo != null) { String url = normalizeUrlSuffix(loginUrlInfo.serverAddress); new LoginValidatorAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url, loginUrlInfo.username, loginUrlInfo.password); } } /** * parses a URI string and returns a login data object with the information from the URI string. * * @param prefix URI beginning, e.g. cloud://login/ * @param dataString the complete URI * @return login data * @throws IllegalArgumentException when */ private LoginUrlInfo parseLoginDataUrl(String prefix, String dataString) throws IllegalArgumentException { if (dataString.length() < prefix.length()) { throw new IllegalArgumentException("Invalid login URL detected"); } LoginUrlInfo loginUrlInfo = new LoginUrlInfo(); // format is basically xxx://login/server:xxx&user:xxx&password while all variables are optional String data = dataString.substring(prefix.length()); // parse data String[] values = data.split("&"); if (values.length < 1 || values.length > 3) { // error illegal number of URL elements detected throw new IllegalArgumentException("Illegal number of login URL elements detected: " + values.length); } for (String value : values) { if (value.startsWith("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { loginUrlInfo.username = URLDecoder.decode( value.substring(("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length())); } else if (value.startsWith("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { loginUrlInfo.password = URLDecoder.decode( value.substring(("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length())); } else if (value.startsWith("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { loginUrlInfo.serverAddress = URLDecoder.decode( value.substring(("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length())); } else { // error illegal URL element detected throw new IllegalArgumentException("Illegal magic login URL element detected: " + value); } } return loginUrlInfo; } private String normalizeUrlSuffix(String url) { if (url.toLowerCase(Locale.ROOT).endsWith(WEBDAV_PATH_4_0_AND_LATER)) { return url.substring(0, url.length() - WEBDAV_PATH_4_0_AND_LATER.length()); } if (!url.endsWith("/")) { return url + "/"; } return url; } private void initLegacyLogin(String oldUrl) { useWebLogin = false; webView.setVisibility(View.INVISIBLE); setContentView(R.layout.activity_settings); ButterKnife.bind(this); setupListener(); field_url.setText(oldUrl); username_wrapper.setVisibility(View.VISIBLE); password_wrapper.setVisibility(View.VISIBLE); } private void handleSubmitButtonEnabled() { if (field_url.getText().length() > 0 && (username_wrapper.getVisibility() == View.GONE || (username_wrapper.getVisibility() == View.VISIBLE && field_username.getText().length() > 0))) { btn_submit.setEnabled(true); } else { btn_submit.setEnabled(false); Loading Loading @@ -293,4 +484,13 @@ public class SettingsActivity extends AppCompatActivity { field_password.setEnabled(enabled); } } /** * Data object holding the login url fields. */ public class LoginUrlInfo { String serverAddress; String username; String password; } } app/src/main/res/layout/activity_settings.xml +4 −2 Original line number Diff line number Diff line Loading @@ -38,7 +38,8 @@ <android.support.design.widget.TextInputLayout android:id="@+id/settings_username_wrapper" android:layout_width="match_parent" android:layout_height="wrap_content"> android:layout_height="wrap_content" android:visibility="gone"> <EditText android:id="@+id/settings_username" Loading @@ -53,7 +54,8 @@ android:id="@+id/settings_password_wrapper" android:layout_width="match_parent" android:layout_height="wrap_content" app:passwordToggleEnabled="true"> app:passwordToggleEnabled="true" android:visibility="gone"> <EditText android:id="@+id/settings_password" Loading app/src/main/res/layout/activity_settings_webview.xml 0 → 100644 +20 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <WebView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/login_webview"> </WebView> <ProgressBar android:id="@+id/login_webview_progress_bar" style="?android:attr/progressBarStyle" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:indeterminate="true"/> </FrameLayout> app/src/main/res/values/strings.xml +2 −0 Original line number Diff line number Diff line Loading @@ -143,6 +143,8 @@ <string name="pref_value_mode_note" translatable="false">note</string> <string name="pref_value_theme_light">Light</string> <string name="pref_value_theme_dark">Dark</string> <string name="fallback_weblogin_text">Revert to old login method</string> <string name="fallback_weblogin_back">Back</string> <!-- Array: note modes --> <string-array name="noteMode_entries"> Loading Loading
app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java +226 −26 Original line number Diff line number Diff line Loading @@ -4,8 +4,11 @@ import android.content.Intent; import android.content.SharedPreferences; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.support.design.widget.Snackbar; import android.support.design.widget.TextInputLayout; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; Loading @@ -14,11 +17,20 @@ import android.text.TextWatcher; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Button; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import java.net.URLDecoder; import java.util.HashMap; import java.util.Locale; import java.util.Map; import at.bitfire.cert4android.CustomCertManager; import butterknife.BindView; import butterknife.ButterKnife; Loading @@ -42,10 +54,15 @@ public class SettingsActivity extends AppCompatActivity { public static final String DEFAULT_SETTINGS = ""; public static final int CREDENTIALS_CHANGED = 3; public static final String LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":"; public static final String WEBDAV_PATH_4_0_AND_LATER = "/remote.php/webdav"; private SharedPreferences preferences = null; @BindView(R.id.settings_url) EditText field_url; @BindView(R.id.settings_username_wrapper) TextInputLayout username_wrapper; @BindView(R.id.settings_username) EditText field_username; @BindView(R.id.settings_password) Loading @@ -58,7 +75,10 @@ public class SettingsActivity extends AppCompatActivity { View urlWarnHttp; private String old_password = ""; private WebView webView; private boolean first_run = false; private boolean useWebLogin = true; @Override public void onCreate(Bundle savedInstanceState) { Loading @@ -76,6 +96,32 @@ public class SettingsActivity extends AppCompatActivity { } } setupListener(); // Load current Preferences field_url.setText(preferences.getString(SETTINGS_URL, DEFAULT_SETTINGS)); field_username.setText(preferences.getString(SETTINGS_USERNAME, DEFAULT_SETTINGS)); old_password = preferences.getString(SETTINGS_PASSWORD, DEFAULT_SETTINGS); field_password.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { login(); return true; } }); field_password.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { setPasswordHint(hasFocus); } }); setPasswordHint(false); handleSubmitButtonEnabled(); } private void setupListener() { field_url.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { Loading @@ -99,7 +145,7 @@ public class SettingsActivity extends AppCompatActivity { urlWarnHttp.setVisibility(View.GONE); } handleSubmitButtonEnabled(field_url.getText(), field_username.getText()); handleSubmitButtonEnabled(); } @Override Loading @@ -115,7 +161,7 @@ public class SettingsActivity extends AppCompatActivity { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { handleSubmitButtonEnabled(field_url.getText(), field_username.getText()); handleSubmitButtonEnabled(); } @Override Loading @@ -124,27 +170,6 @@ public class SettingsActivity extends AppCompatActivity { } }); // Load current Preferences field_url.setText(preferences.getString(SETTINGS_URL, DEFAULT_SETTINGS)); field_username.setText(preferences.getString(SETTINGS_USERNAME, DEFAULT_SETTINGS)); old_password = preferences.getString(SETTINGS_PASSWORD, DEFAULT_SETTINGS); field_password.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { login(); return true; } }); field_password.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { setPasswordHint(hasFocus); } }); setPasswordHint(false); btn_submit.setEnabled(false); btn_submit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Loading Loading @@ -180,7 +205,7 @@ public class SettingsActivity extends AppCompatActivity { } } private void login() { private void legacyLogin() { String url = field_url.getText().toString().trim(); String username = field_username.getText().toString(); String password = field_password.getText().toString(); Loading @@ -194,8 +219,174 @@ public class SettingsActivity extends AppCompatActivity { new LoginValidatorAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url, username, password); } private void handleSubmitButtonEnabled(Editable url, Editable username) { if (field_username.getText().length() > 0 && field_url.getText().length() > 0) { private void login() { if (useWebLogin) { webLogin(); } else { legacyLogin(); } } private void webLogin() { setContentView(R.layout.activity_settings_webview); webView = findViewById(R.id.login_webview); webView.setVisibility(View.GONE); final ProgressBar progressBar = findViewById(R.id.login_webview_progress_bar); WebSettings settings = webView.getSettings(); settings.setAllowFileAccess(false); settings.setJavaScriptEnabled(true); settings.setDomStorageEnabled(true); settings.setUserAgentString(getWebLoginUserAgent()); settings.setSaveFormData(false); settings.setSavePassword(false); Map<String, String> headers = new HashMap<>(); headers.put("OCS-APIREQUEST", "true"); webView.loadUrl(normalizeUrlSuffix(field_url.getText().toString()) + "index.php/login/flow", headers); webView.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (url.startsWith("nc://login/")) { parseAndLoginFromWebView(url); return true; } return false; } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); progressBar.setVisibility(View.GONE); webView.setVisibility(View.VISIBLE); } // @Override // public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { // X509Certificate cert = SsoWebViewClient.getX509CertificateFromError(error); // // try { // if (cert != null && NetworkUtils.isCertInKnownServersStore(cert, getApplicationContext())) { // handler.proceed(); // } else { // showUntrustedCertDialog(cert, error, handler); // } // } catch (Exception e) { // Log.e("Note", "Cert could not be verified"); // } // } }); // show snackbar after 60s to switch back to old login method new Handler().postDelayed(new Runnable() { @Override public void run() { Snackbar.make(webView, R.string.fallback_weblogin_text, Snackbar.LENGTH_INDEFINITE) .setAction(R.string.fallback_weblogin_back, new View.OnClickListener() { @Override public void onClick(View v) { initLegacyLogin(field_url.getText().toString()); } }).show(); } }, 60 * 1000); } private String getWebLoginUserAgent() { return Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) + Build.MANUFACTURER.substring(1).toLowerCase(Locale.getDefault()) + " " + Build.MODEL; } private void parseAndLoginFromWebView(String dataString) { String prefix = "nc://login/"; LoginUrlInfo loginUrlInfo = parseLoginDataUrl(prefix, dataString); if (loginUrlInfo != null) { String url = normalizeUrlSuffix(loginUrlInfo.serverAddress); new LoginValidatorAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url, loginUrlInfo.username, loginUrlInfo.password); } } /** * parses a URI string and returns a login data object with the information from the URI string. * * @param prefix URI beginning, e.g. cloud://login/ * @param dataString the complete URI * @return login data * @throws IllegalArgumentException when */ private LoginUrlInfo parseLoginDataUrl(String prefix, String dataString) throws IllegalArgumentException { if (dataString.length() < prefix.length()) { throw new IllegalArgumentException("Invalid login URL detected"); } LoginUrlInfo loginUrlInfo = new LoginUrlInfo(); // format is basically xxx://login/server:xxx&user:xxx&password while all variables are optional String data = dataString.substring(prefix.length()); // parse data String[] values = data.split("&"); if (values.length < 1 || values.length > 3) { // error illegal number of URL elements detected throw new IllegalArgumentException("Illegal number of login URL elements detected: " + values.length); } for (String value : values) { if (value.startsWith("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { loginUrlInfo.username = URLDecoder.decode( value.substring(("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length())); } else if (value.startsWith("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { loginUrlInfo.password = URLDecoder.decode( value.substring(("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length())); } else if (value.startsWith("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { loginUrlInfo.serverAddress = URLDecoder.decode( value.substring(("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length())); } else { // error illegal URL element detected throw new IllegalArgumentException("Illegal magic login URL element detected: " + value); } } return loginUrlInfo; } private String normalizeUrlSuffix(String url) { if (url.toLowerCase(Locale.ROOT).endsWith(WEBDAV_PATH_4_0_AND_LATER)) { return url.substring(0, url.length() - WEBDAV_PATH_4_0_AND_LATER.length()); } if (!url.endsWith("/")) { return url + "/"; } return url; } private void initLegacyLogin(String oldUrl) { useWebLogin = false; webView.setVisibility(View.INVISIBLE); setContentView(R.layout.activity_settings); ButterKnife.bind(this); setupListener(); field_url.setText(oldUrl); username_wrapper.setVisibility(View.VISIBLE); password_wrapper.setVisibility(View.VISIBLE); } private void handleSubmitButtonEnabled() { if (field_url.getText().length() > 0 && (username_wrapper.getVisibility() == View.GONE || (username_wrapper.getVisibility() == View.VISIBLE && field_username.getText().length() > 0))) { btn_submit.setEnabled(true); } else { btn_submit.setEnabled(false); Loading Loading @@ -293,4 +484,13 @@ public class SettingsActivity extends AppCompatActivity { field_password.setEnabled(enabled); } } /** * Data object holding the login url fields. */ public class LoginUrlInfo { String serverAddress; String username; String password; } }
app/src/main/res/layout/activity_settings.xml +4 −2 Original line number Diff line number Diff line Loading @@ -38,7 +38,8 @@ <android.support.design.widget.TextInputLayout android:id="@+id/settings_username_wrapper" android:layout_width="match_parent" android:layout_height="wrap_content"> android:layout_height="wrap_content" android:visibility="gone"> <EditText android:id="@+id/settings_username" Loading @@ -53,7 +54,8 @@ android:id="@+id/settings_password_wrapper" android:layout_width="match_parent" android:layout_height="wrap_content" app:passwordToggleEnabled="true"> app:passwordToggleEnabled="true" android:visibility="gone"> <EditText android:id="@+id/settings_password" Loading
app/src/main/res/layout/activity_settings_webview.xml 0 → 100644 +20 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <WebView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/login_webview"> </WebView> <ProgressBar android:id="@+id/login_webview_progress_bar" style="?android:attr/progressBarStyle" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:indeterminate="true"/> </FrameLayout>
app/src/main/res/values/strings.xml +2 −0 Original line number Diff line number Diff line Loading @@ -143,6 +143,8 @@ <string name="pref_value_mode_note" translatable="false">note</string> <string name="pref_value_theme_light">Light</string> <string name="pref_value_theme_dark">Dark</string> <string name="fallback_weblogin_text">Revert to old login method</string> <string name="fallback_weblogin_back">Back</string> <!-- Array: note modes --> <string-array name="noteMode_entries"> Loading