mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-17 15:32:48 +00:00
add backup / restore option
and convert AdvancedSettingsFragment to kotlin fixes #200
This commit is contained in:
parent
62dd2d954f
commit
b8f4f589e8
8 changed files with 283 additions and 173 deletions
|
@ -405,7 +405,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
|||
final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
|
||||
for (final String subDictType : subDictTypesToUse) {
|
||||
final ExpandableBinaryDictionary subDict;
|
||||
if (noExistingDictsForThisLocale
|
||||
if (noExistingDictsForThisLocale || forceReloadMainDictionary
|
||||
|| !oldDictionaryGroupForLocale.hasDict(subDictType, account)) {
|
||||
// Create a new dictionary.
|
||||
subDict = getSubDict(subDictType, context, locale, null /* dictFile */, dictNamePrefix, account);
|
||||
|
|
|
@ -1,158 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.dslul.openboard.inputmethod.latin.settings;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import org.dslul.openboard.inputmethod.keyboard.KeyboardLayoutSet;
|
||||
import org.dslul.openboard.inputmethod.latin.AudioAndHapticFeedbackManager;
|
||||
import org.dslul.openboard.inputmethod.latin.BuildConfig;
|
||||
import org.dslul.openboard.inputmethod.latin.R;
|
||||
import org.dslul.openboard.inputmethod.latin.SystemBroadcastReceiver;
|
||||
import org.dslul.openboard.inputmethod.latin.common.FileUtils;
|
||||
import org.dslul.openboard.inputmethod.latin.define.JniLibName;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* "Advanced" settings sub screen.
|
||||
*
|
||||
* This settings sub screen handles the following advanced preferences.
|
||||
* - Key popup dismiss delay
|
||||
* - Keypress vibration duration
|
||||
* - Keypress sound volume
|
||||
* - Show app icon
|
||||
* - Improve keyboard
|
||||
* - Debug settings
|
||||
*/
|
||||
public final class AdvancedSettingsFragment extends SubScreenFragment {
|
||||
private final int REQUEST_CODE_GESTURE_LIBRARY = 570289;
|
||||
File libfile = null;
|
||||
@Override
|
||||
public void onCreate(final Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
addPreferencesFromResource(R.xml.prefs_screen_advanced);
|
||||
|
||||
final Context context = requireContext();
|
||||
|
||||
// When we are called from the Settings application but we are not already running, some
|
||||
// singleton and utility classes may not have been initialized. We have to call
|
||||
// initialization method of these classes here. See {@link LatinIME#onCreate()}.
|
||||
AudioAndHapticFeedbackManager.init(context);
|
||||
|
||||
if (!BuildConfig.DEBUG) {
|
||||
removePreference(Settings.SCREEN_DEBUG);
|
||||
}
|
||||
|
||||
setupKeyLongpressTimeoutSettings();
|
||||
final Preference loadGestureLibrary = findPreference("load_gesture_library");
|
||||
if (loadGestureLibrary != null) {
|
||||
loadGestureLibrary.setOnPreferenceClickListener(preference -> {
|
||||
// get architecture for telling user which file to use
|
||||
String abi;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
abi = Build.SUPPORTED_ABIS[0];
|
||||
} else {
|
||||
abi = Build.CPU_ABI;
|
||||
}
|
||||
// show delete / add dialog
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.load_gesture_library)
|
||||
.setMessage(context.getString(R.string.load_gesture_library_message, abi))
|
||||
.setPositiveButton(R.string.load_gesture_library_button_load, (dialogInterface, i) -> {
|
||||
final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.setType("application/octet-stream");
|
||||
startActivityForResult(intent, REQUEST_CODE_GESTURE_LIBRARY);
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null);
|
||||
libfile = new File(context.getFilesDir().getAbsolutePath() + File.separator + JniLibName.JNI_LIB_IMPORT_FILE_NAME);
|
||||
if (libfile.exists())
|
||||
builder.setNeutralButton(R.string.load_gesture_library_button_delete, (dialogInterface, i) -> {
|
||||
libfile.delete();
|
||||
Runtime.getRuntime().exit(0);
|
||||
});
|
||||
builder.show();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
|
||||
if (requestCode != REQUEST_CODE_GESTURE_LIBRARY || resultCode != Activity.RESULT_OK || resultData == null) return;
|
||||
if (resultData.getData() != null && libfile != null) {
|
||||
try {
|
||||
final InputStream in = requireContext().getContentResolver().openInputStream(resultData.getData());
|
||||
FileUtils.copyStreamToNewFile(in, libfile);
|
||||
Runtime.getRuntime().exit(0); // exit will restart the app, so library will be loaded
|
||||
} catch (IOException e) {
|
||||
// should inform user, but probably the issues will only come when reading the library
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void setupKeyLongpressTimeoutSettings() {
|
||||
final SharedPreferences prefs = getSharedPreferences();
|
||||
final Resources res = getResources();
|
||||
final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(
|
||||
Settings.PREF_KEY_LONGPRESS_TIMEOUT);
|
||||
if (pref == null) {
|
||||
return;
|
||||
}
|
||||
pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
|
||||
@Override
|
||||
public void writeValue(final int value, final String key) {
|
||||
prefs.edit().putInt(key, value).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDefaultValue(final String key) {
|
||||
prefs.edit().remove(key).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readValue(final String key) {
|
||||
return Settings.readKeyLongpressTimeout(prefs, res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readDefaultValue(final String key) {
|
||||
return Settings.readDefaultKeyLongpressTimeout(res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueText(final int value) {
|
||||
return res.getString(R.string.abbreviation_unit_milliseconds, Integer.toString(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void feedbackValue(final int value) {}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
|
||||
if (key.equals(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) {
|
||||
SystemBroadcastReceiver.toggleAppIcon(requireContext());
|
||||
} else if (key.equals(Settings.PREF_SHOW_ALL_MORE_KEYS)) {
|
||||
KeyboardLayoutSet.onKeyboardThemeChanged();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
* modified
|
||||
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||
*/
|
||||
package org.dslul.openboard.inputmethod.latin.settings
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.preference.Preference
|
||||
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants
|
||||
import org.dslul.openboard.inputmethod.keyboard.KeyboardLayoutSet
|
||||
import org.dslul.openboard.inputmethod.latin.AudioAndHapticFeedbackManager
|
||||
import org.dslul.openboard.inputmethod.latin.BuildConfig
|
||||
import org.dslul.openboard.inputmethod.latin.R
|
||||
import org.dslul.openboard.inputmethod.latin.RichInputMethodManager
|
||||
import org.dslul.openboard.inputmethod.latin.SystemBroadcastReceiver
|
||||
import org.dslul.openboard.inputmethod.latin.common.FileUtils
|
||||
import org.dslul.openboard.inputmethod.latin.define.JniLibName
|
||||
import org.dslul.openboard.inputmethod.latin.settings.SeekBarDialogPreference.ValueProxy
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.IOException
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
|
||||
/**
|
||||
* "Advanced" settings sub screen.
|
||||
*
|
||||
* This settings sub screen handles the following advanced preferences.
|
||||
* - Key popup dismiss delay
|
||||
* - Keypress vibration duration
|
||||
* - Keypress sound volume
|
||||
* - Show app icon
|
||||
* - Improve keyboard
|
||||
* - Debug settings
|
||||
*/
|
||||
class AdvancedSettingsFragment : SubScreenFragment() {
|
||||
private var libfile: File? = null
|
||||
private val backupFilePatterns by lazy { listOf(
|
||||
"blacklists/.*\\.txt".toRegex(),
|
||||
"dicts/.*/.*user\\.dict".toRegex(),
|
||||
"userunigram.*/userunigram.*\\.(body|header)".toRegex(),
|
||||
"UserHistoryDictionary.*/UserHistoryDictionary.*\\.(body|header)".toRegex(),
|
||||
"spellcheck_userunigram.*/spellcheck_userunigram.*\\.(body|header)".toRegex(),
|
||||
// todo: found "b.<locale>.dict" folder, where does it come from?
|
||||
// possibly some obfuscation thing that occurred after upgrading to gradle 8?
|
||||
) }
|
||||
|
||||
override fun onCreate(icicle: Bundle?) {
|
||||
super.onCreate(icicle)
|
||||
addPreferencesFromResource(R.xml.prefs_screen_advanced)
|
||||
val context = requireContext()
|
||||
|
||||
// When we are called from the Settings application but we are not already running, some
|
||||
// singleton and utility classes may not have been initialized. We have to call
|
||||
// initialization method of these classes here. See {@link LatinIME#onCreate()}.
|
||||
AudioAndHapticFeedbackManager.init(context)
|
||||
if (!BuildConfig.DEBUG) {
|
||||
removePreference(Settings.SCREEN_DEBUG)
|
||||
}
|
||||
setupKeyLongpressTimeoutSettings()
|
||||
findPreference<Preference>("load_gesture_library")?.setOnPreferenceClickListener { onClickLoadLibrary() }
|
||||
findPreference<Preference>("pref_backup_restore")?.setOnPreferenceClickListener { showBackupRestoreDialog() }
|
||||
}
|
||||
|
||||
private fun onClickLoadLibrary(): Boolean {
|
||||
// get architecture for telling user which file to use
|
||||
val abi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
Build.SUPPORTED_ABIS[0]
|
||||
} else {
|
||||
Build.CPU_ABI
|
||||
}
|
||||
// show delete / add dialog
|
||||
val builder = AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.load_gesture_library)
|
||||
.setMessage(requireContext().getString(R.string.load_gesture_library_message, abi))
|
||||
.setPositiveButton(R.string.load_gesture_library_button_load) { _, _ ->
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.setType("application/octet-stream")
|
||||
startActivityForResult(intent, REQUEST_CODE_GESTURE_LIBRARY)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
libfile = File(requireContext().filesDir.absolutePath + File.separator + JniLibName.JNI_LIB_IMPORT_FILE_NAME)
|
||||
if (libfile?.exists() == true) {
|
||||
builder.setNeutralButton(R.string.load_gesture_library_button_delete) { _, _ ->
|
||||
libfile?.delete()
|
||||
Runtime.getRuntime().exit(0)
|
||||
}
|
||||
}
|
||||
builder.show()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, result: Intent?) {
|
||||
val uri = result?.data
|
||||
if (resultCode != Activity.RESULT_OK || uri == null) return
|
||||
when (requestCode) {
|
||||
REQUEST_CODE_GESTURE_LIBRARY -> copyLibrary(uri)
|
||||
REQUEST_CODE_BACKUP -> backup(uri)
|
||||
REQUEST_CODE_RESTORE -> restore(uri)
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyLibrary(uri: Uri) {
|
||||
if (libfile == null) return
|
||||
try {
|
||||
val inputStream = requireContext().contentResolver.openInputStream(uri)
|
||||
FileUtils.copyStreamToNewFile(inputStream, libfile)
|
||||
Runtime.getRuntime().exit(0) // exit will restart the app, so library will be loaded
|
||||
} catch (e: IOException) {
|
||||
// should inform user, but probably the issues will only come when reading the library
|
||||
}
|
||||
}
|
||||
|
||||
private fun showBackupRestoreDialog(): Boolean {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.backup_restore_title)
|
||||
.setMessage(R.string.backup_restore_message)
|
||||
.setNegativeButton(R.string.button_backup) { _, _ ->
|
||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.putExtra(
|
||||
Intent.EXTRA_TITLE,
|
||||
requireContext().getString(R.string.english_ime_name)
|
||||
.replace(" ", "_") + "_backup.zip"
|
||||
)
|
||||
.setType("application/zip")
|
||||
startActivityForResult(intent, REQUEST_CODE_BACKUP)
|
||||
}
|
||||
.setPositiveButton(android.R.string.cancel, null)
|
||||
.setNeutralButton(R.string.button_restore) { _, _ ->
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.setType("application/zip")
|
||||
startActivityForResult(intent, REQUEST_CODE_RESTORE)
|
||||
}
|
||||
.show()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun backup(uri: Uri) {
|
||||
// zip all files matching the backup patterns
|
||||
// essentially this is the typed words information, and user-added dictionaries
|
||||
val filesDir = requireContext().filesDir ?: return
|
||||
val filesPath = filesDir.path + File.separator
|
||||
val files = mutableListOf<File>()
|
||||
filesDir.walk().forEach { file ->
|
||||
val path = file.path.replace(filesPath, "")
|
||||
if (backupFilePatterns.any { path.matches(it) })
|
||||
files.add(file)
|
||||
}
|
||||
if (files.isEmpty()) {
|
||||
infoDialog(requireContext(), R.string.backup_error)
|
||||
return
|
||||
}
|
||||
try {
|
||||
activity?.contentResolver?.openOutputStream(uri)?.use { os ->
|
||||
// write files to zip
|
||||
val zipStream = ZipOutputStream(os)
|
||||
files.forEach {
|
||||
val fileStream = FileInputStream(it).buffered()
|
||||
zipStream.putNextEntry(ZipEntry(it.path.replace(filesPath, "")))
|
||||
fileStream.copyTo(zipStream, 1024)
|
||||
fileStream.close()
|
||||
zipStream.closeEntry()
|
||||
}
|
||||
zipStream.close()
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
// inform about every error
|
||||
infoDialog(requireContext(), R.string.backup_error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun restore(uri: Uri) {
|
||||
try {
|
||||
activity?.contentResolver?.openInputStream(uri)?.use { inputStream ->
|
||||
ZipInputStream(inputStream).use { zip ->
|
||||
var entry: ZipEntry? = zip.nextEntry
|
||||
val filesDir = requireContext().filesDir?.path ?: return
|
||||
while (entry != null) {
|
||||
if (backupFilePatterns.any { entry!!.name.matches(it) }) {
|
||||
val file = File(filesDir, entry.name)
|
||||
FileUtils.copyStreamToNewFile(zip, file)
|
||||
}
|
||||
zip.closeEntry()
|
||||
entry = zip.nextEntry
|
||||
}
|
||||
}
|
||||
}
|
||||
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
|
||||
activity?.sendBroadcast(newDictBroadcast)
|
||||
} catch (t: Throwable) {
|
||||
// inform about every error
|
||||
infoDialog(requireContext(), requireContext().getString(R.string.restore_error, t.message))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupKeyLongpressTimeoutSettings() {
|
||||
val prefs = sharedPreferences
|
||||
findPreference<SeekBarDialogPreference>(Settings.PREF_KEY_LONGPRESS_TIMEOUT)?.setInterface(object : ValueProxy {
|
||||
override fun writeValue(value: Int, key: String) = prefs.edit().putInt(key, value).apply()
|
||||
|
||||
override fun writeDefaultValue(key: String) = prefs.edit().remove(key).apply()
|
||||
|
||||
override fun readValue(key: String) = Settings.readKeyLongpressTimeout(prefs, resources)
|
||||
|
||||
override fun readDefaultValue(key: String) = Settings.readDefaultKeyLongpressTimeout(resources)
|
||||
|
||||
override fun getValueText(value: Int) =
|
||||
resources.getString(R.string.abbreviation_unit_milliseconds, value.toString())
|
||||
|
||||
override fun feedbackValue(value: Int) {}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String?) {
|
||||
if (Settings.PREF_SHOW_SETUP_WIZARD_ICON == key) {
|
||||
SystemBroadcastReceiver.toggleAppIcon(requireContext())
|
||||
} else if (Settings.PREF_SHOW_ALL_MORE_KEYS == key) {
|
||||
KeyboardLayoutSet.onKeyboardThemeChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val REQUEST_CODE_GESTURE_LIBRARY = 570289
|
||||
private const val REQUEST_CODE_BACKUP = 98665973
|
||||
private const val REQUEST_CODE_RESTORE = 98665974
|
|
@ -280,14 +280,6 @@ class LanguageSettingsDialog(
|
|||
}
|
||||
}
|
||||
|
||||
fun confirmDialog(context: Context, message: String, confirmButton: String, onConfirmed: (() -> Unit)) {
|
||||
AlertDialog.Builder(context)
|
||||
.setMessage(message)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(confirmButton) { _, _ -> onConfirmed() }
|
||||
.show()
|
||||
}
|
||||
|
||||
/** @return list of user dictionary files and whether an internal dictionary exists */
|
||||
fun getUserAndInternalDictionaries(context: Context, locale: String): Pair<List<File>, Boolean> {
|
||||
val localeString = locale.lowercase() // internal files and folders always use lowercase
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package org.dslul.openboard.inputmethod.latin.settings
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
|
||||
// maybe rather put to DialogUtils (and convert that to kotlin)
|
||||
|
||||
fun confirmDialog(context: Context, message: String, confirmButton: String, onConfirmed: (() -> Unit)) {
|
||||
AlertDialog.Builder(context)
|
||||
.setMessage(message)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(confirmButton) { _, _ -> onConfirmed() }
|
||||
.show()
|
||||
}
|
||||
|
||||
fun infoDialog(context: Context, messageId: Int) {
|
||||
AlertDialog.Builder(context)
|
||||
.setMessage(messageId)
|
||||
.setNegativeButton(android.R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
fun infoDialog(context: Context, message: String) {
|
||||
AlertDialog.Builder(context)
|
||||
.setMessage(message)
|
||||
.setNegativeButton(android.R.string.ok, null)
|
||||
.show()
|
||||
}
|
|
@ -5,7 +5,6 @@ package org.dslul.openboard.inputmethod.latin.utils
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants
|
||||
import org.dslul.openboard.inputmethod.latin.R
|
||||
|
@ -15,7 +14,6 @@ import org.dslul.openboard.inputmethod.latin.makedict.DictionaryHeader
|
|||
import org.dslul.openboard.inputmethod.latin.settings.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.text.DateFormat
|
||||
import java.util.*
|
||||
|
||||
class NewDictionaryAdder(private val context: Context, private val onAdded: ((Boolean, File) -> Unit)?) {
|
||||
|
@ -146,10 +144,7 @@ class NewDictionaryAdder(private val context: Context, private val onAdded: ((Bo
|
|||
// Toast.makeText(context, messageId, Toast.LENGTH_LONG).show()
|
||||
// show a dialog because toasts are not showing up on some Android versions
|
||||
// possibly Android 13 because of notification permission
|
||||
AlertDialog.Builder(context)
|
||||
.setMessage(messageId)
|
||||
.setNegativeButton(R.string.dialog_close, null)
|
||||
.show()
|
||||
infoDialog(context, messageId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -158,6 +158,18 @@
|
|||
<string name="space_language_slide">Space bar language slide</string>
|
||||
<!-- Description for "space_language_slide" option. -->
|
||||
<string name="space_language_slide_summary">Swipe upwards on the spacebar to change the language</string>
|
||||
<!-- Preferences item and dialog title for backup and restore -->
|
||||
<string name="backup_restore_title">Backup and restore</string>
|
||||
<!-- Message for backup and restore dialog -->
|
||||
<string name="backup_restore_message">Save or load from file. Warning: restore will overwrite existing data</string>
|
||||
<!-- Error message for backup -->
|
||||
<string name="backup_error">Backup error</string>
|
||||
<!-- Error message for restore, %s is replaced by the error message -->
|
||||
<string name="restore_error">Error restoring the backup: %s</string>
|
||||
<!-- backup button -->
|
||||
<string name="button_backup">Backup</string>
|
||||
<!-- restore button -->
|
||||
<string name="button_restore">Restore</string>
|
||||
<!-- Preferences item for choosing secondary language -->
|
||||
<string name="secondary_locale">Multilingual typing</string>
|
||||
<!-- Preferences item for loading an external gesture typing library -->
|
||||
|
|
|
@ -56,6 +56,10 @@
|
|||
android:defaultValue="true"
|
||||
android:persistent="true" />
|
||||
|
||||
<Preference
|
||||
android:key="pref_backup_restore"
|
||||
android:title="@string/backup_restore_title" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.dslul.openboard.inputmethod.latin.settings.DebugSettingsFragment"
|
||||
android:key="screen_debug"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue