add dictionary screen and dialogs

This commit is contained in:
Helium314 2025-02-22 22:42:36 +01:00
parent 7494d85aea
commit 52c887e941
12 changed files with 406 additions and 8 deletions

View file

@ -389,7 +389,7 @@ class LanguageSettingsDialog(
}
/** @return list of user dictionary files and whether an internal dictionary exists */
fun getUserAndInternalDictionaries(context: Context, locale: Locale): Pair<List<File>, Boolean> {
private fun getUserAndInternalDictionaries(context: Context, locale: Locale): Pair<List<File>, Boolean> {
val userDicts = mutableListOf<File>()
var hasInternalDict = false
val userLocaleDir = File(DictionaryInfoUtils.getCacheDirectoryForLocale(locale, context))

View file

@ -15,8 +15,7 @@ import helium314.keyboard.latin.common.LocaleUtils.constructLocale
import helium314.keyboard.latin.settings.Defaults
import helium314.keyboard.latin.settings.Settings
import java.io.File
import java.util.*
import kotlin.collections.HashSet
import java.util.Locale
fun getDictionaryLocales(context: Context): MutableSet<Locale> {
val locales = HashSet<Locale>()

View file

@ -16,9 +16,13 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import helium314.keyboard.latin.R
import helium314.keyboard.latin.common.FileUtils
import helium314.keyboard.latin.utils.LayoutUtilsCustom
import helium314.keyboard.latin.utils.getActivity
import helium314.keyboard.settings.dialogs.InfoDialog
import helium314.keyboard.settings.dialogs.NewDictionaryDialog
import java.io.File
import java.util.Locale
val layoutIntent = Intent(Intent.ACTION_OPEN_DOCUMENT)
.addCategory(Intent.CATEGORY_OPENABLE)
@ -59,3 +63,22 @@ fun layoutFilePicker(
return loadFilePicker
}
@Composable
fun dictionaryFilePicker(mainLocale: Locale?): ManagedActivityResultLauncher<Intent, ActivityResult> {
val ctx = LocalContext.current
val cachedDictionaryFile = File(ctx.cacheDir.path + File.separator + "temp_dict")
var done by remember { mutableStateOf(false) }
val picker = filePicker { uri ->
cachedDictionaryFile.delete()
FileUtils.copyContentUriToNewFile(uri, ctx, cachedDictionaryFile)
done = true
}
if (done)
NewDictionaryDialog(
onDismissRequest = { done = false },
cachedDictionaryFile,
mainLocale
)
return picker
}

View file

@ -13,6 +13,8 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Surface
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.ComposeView
import androidx.core.view.ViewCompat
import androidx.core.view.isGone
@ -26,6 +28,7 @@ import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.utils.ExecutorUtils
import helium314.keyboard.latin.utils.cleanUnusedMainDicts
import helium314.keyboard.latin.utils.prefs
import helium314.keyboard.settings.dialogs.NewDictionaryDialog
import kotlinx.coroutines.flow.MutableStateFlow
import java.io.BufferedOutputStream
import java.io.File
@ -43,6 +46,8 @@ import java.util.zip.ZipOutputStream
class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
private val prefs by lazy { this.prefs() }
val prefChanged = MutableStateFlow(0) // simple counter, as the only relevant information is that something changed
private val dictUriFlow = MutableStateFlow<Uri?>(null)
private val cachedDictionaryFile by lazy { File(this.cacheDir.path + File.separator + "temp_dict") }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -55,7 +60,7 @@ class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferen
askAboutCrashReports()
// with this the layout edit dialog is not covered by the keyboard
// alterative of Modifier.imePadding() and properties = DialogProperties(decorFitsSystemWindows = false) has other weird side effects
// alternative of Modifier.imePadding() and properties = DialogProperties(decorFitsSystemWindows = false) has other weird side effects
ViewCompat.setOnApplyWindowInsetsListener(window.decorView.rootView) { _, insets -> insets }
settingsContainer = SettingsContainer(this)
@ -73,6 +78,7 @@ class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferen
findViewById<ComposeView>(R.id.navHost).setContent {
Theme {
Surface {
val dictUri by dictUriFlow.collectAsState()
if (spellchecker)
Column { // lazy way of implementing spell checker settings
settingsContainer[Settings.PREF_USE_CONTACTS]!!.Preference()
@ -87,11 +93,27 @@ class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferen
else supportFragmentManager.popBackStack()
}
)
if (dictUri != null) {
NewDictionaryDialog(
onDismissRequest = { dictUriFlow.value = null },
cachedFile = cachedDictionaryFile,
mainLocale = null
)
}
}
}
}
if (intent?.action == Intent.ACTION_VIEW) {
intent?.data?.let {
cachedDictionaryFile.delete()
FileUtils.copyContentUriToNewFile(it, this, cachedDictionaryFile)
dictUriFlow.value = it
}
intent = null
}
}
private fun updateContainerVisibility() { // todo: remove when removing old settings
findViewById<RelativeLayout>(R.id.settingsFragmentContainer).isGone = supportFragmentManager.findFragmentById(R.id.settingsFragmentContainer) == null
}

View file

@ -15,6 +15,7 @@ import helium314.keyboard.settings.screens.AdvancedSettingsScreen
import helium314.keyboard.settings.screens.AppearanceScreen
import helium314.keyboard.settings.screens.ColorsScreen
import helium314.keyboard.settings.screens.DebugScreen
import helium314.keyboard.settings.screens.DictionaryScreen
import helium314.keyboard.settings.screens.GestureTypingScreen
import helium314.keyboard.settings.screens.LanguageScreen
import helium314.keyboard.settings.screens.MainSettingsScreen
@ -60,6 +61,7 @@ fun SettingsNavHost(
onClickAppearance = { navController.navigate(SettingsDestination.Appearance) },
onClickLanguage = { navController.navigate(SettingsDestination.Languages) },
onClickLayouts = { navController.navigate(SettingsDestination.Layouts) },
onClickDictionaries = { navController.navigate(SettingsDestination.Dictionaries) },
onClickBack = ::goBack,
)
}
@ -95,6 +97,9 @@ fun SettingsNavHost(
composable(SettingsDestination.Languages) {
LanguageScreen(onClickBack = ::goBack)
}
composable(SettingsDestination.Dictionaries) {
DictionaryScreen(onClickBack = ::goBack)
}
composable(SettingsDestination.Layouts) {
SecondaryLayoutScreen(onClickBack = ::goBack)
}
@ -124,6 +129,7 @@ object SettingsDestination {
const val PersonalDictionary = "personal_dictionary"
const val Languages = "languages"
const val Layouts = "layouts"
const val Dictionaries = "dictionaries"
val navTarget = MutableStateFlow(Settings)
private val navScope = CoroutineScope(Dispatchers.Default)

View file

@ -0,0 +1,91 @@
package helium314.keyboard.settings.dialogs
import android.content.Intent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import helium314.keyboard.compat.locale
import helium314.keyboard.latin.Dictionary
import helium314.keyboard.latin.R
import helium314.keyboard.latin.common.LocaleUtils
import helium314.keyboard.latin.utils.DictionaryInfoUtils
import helium314.keyboard.settings.dictionaryFilePicker
import helium314.keyboard.settings.screens.getUserAndInternalDictionaries
import java.util.Locale
@Composable
fun DictionaryDialog(
onDismissRequest: () -> Unit,
locale: Locale,
) {
val ctx = LocalContext.current
val (dictionaries, hasInternal) = getUserAndInternalDictionaries(ctx, locale)
val picker = dictionaryFilePicker(locale)
ThreeButtonAlertDialog(
onDismissRequest = onDismissRequest,
onConfirmed = {},
confirmButtonText = null,
cancelButtonText = stringResource(R.string.dialog_close),
title = { Text(LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, ctx)) },
text = {
Column {
if (hasInternal) {
val color = if (dictionaries.none { it.startsWith(Dictionary.TYPE_MAIN + ":") }) MaterialTheme.colorScheme.onSurface
else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) // for disabled look
Text(stringResource(R.string.internal_dictionary_summary), color = color, modifier = Modifier.fillMaxWidth())
}
dictionaries.forEach {
val header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(it)
val type = header?.mIdString?.substringBefore(":")
var showDeleteDialog by remember { mutableStateOf(false) }
if (header != null) {
HorizontalDivider()
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)
) {
Column {
Text(header.info(LocalContext.current.resources.configuration.locale()), style = MaterialTheme.typography.bodyMedium)
}
IconButton(
onClick = { showDeleteDialog = true }
) { Icon(painterResource(R.drawable.ic_bin), stringResource(R.string.delete)) }
}
}
if (showDeleteDialog)
ConfirmationDialog(
onDismissRequest = { showDeleteDialog = false },
onConfirmed = { it.delete() },
text = { Text(stringResource(R.string.remove_dictionary_message, type ?: ""))}
)
}
}
},
neutralButtonText = stringResource(R.string.add_new_dictionary_title),
onNeutral = {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.setType("application/octet-stream")
picker.launch(intent)
}
)
}

View file

@ -0,0 +1,93 @@
package helium314.keyboard.settings.dialogs
import android.content.Intent
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import helium314.keyboard.compat.locale
import helium314.keyboard.dictionarypack.DictionaryPackConstants
import helium314.keyboard.latin.Dictionary
import helium314.keyboard.latin.R
import helium314.keyboard.latin.ReadOnlyBinaryDictionary
import helium314.keyboard.latin.common.LocaleUtils
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
import helium314.keyboard.latin.makedict.DictionaryHeader
import helium314.keyboard.latin.settings.USER_DICTIONARY_SUFFIX
import helium314.keyboard.latin.utils.DictionaryInfoUtils
import helium314.keyboard.latin.utils.ScriptUtils.script
import helium314.keyboard.latin.utils.SubtypeSettings
import java.io.File
import java.util.Locale
@Composable
fun NewDictionaryDialog(
onDismissRequest: () -> Unit,
cachedFile: File,
mainLocale: Locale?
) {
val (error, header) = checkDict(cachedFile)
if (error != null) {
InfoDialog(stringResource(error), onDismissRequest)
cachedFile.delete()
} else if (header != null) {
val ctx = LocalContext.current
val dictLocale = header.mLocaleString.constructLocale()
var locale by remember { mutableStateOf(mainLocale ?: dictLocale) }
val comparer = compareBy<Locale>({ it != mainLocale}, { it != dictLocale }, { it.script() != dictLocale.script() })
val locales = SubtypeSettings.getAvailableSubtypeLocales().sortedWith(comparer)
val cacheDir = DictionaryInfoUtils.getCacheDirectoryForLocale(locale, ctx)
val dictFile = File(cacheDir, header.mIdString.substringBefore(":") + "_" + USER_DICTIONARY_SUFFIX)
ThreeButtonAlertDialog(
onDismissRequest = { onDismissRequest(); cachedFile.delete() },
onConfirmed = {
dictFile.parentFile?.mkdirs()
dictFile.delete()
cachedFile.renameTo(dictFile)
if (header.mIdString.substringBefore(":") == Dictionary.TYPE_MAIN) {
// replaced main dict, remove the one created from internal data
val internalMainDictFile = File(cacheDir, DictionaryInfoUtils.getExtractedMainDictFilename())
internalMainDictFile.delete()
}
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
ctx.sendBroadcast(newDictBroadcast)
},
text = {
Column {
Text(header.info(LocalContext.current.resources.configuration.locale()))
// todo: dropdown takes very long to load, should be lazy!
// but can't be lazy because of measurements (has width of widest element)
// -> what do?
DropDownField(
selectedItem = locale,
onSelected = { locale = it },
items = locales
) { Text(LocaleUtils.getLocaleDisplayNameInSystemLocale(it, ctx)) }
if (locale.script() != dictLocale.script())
Text("wrong script", color = MaterialTheme.colorScheme.error) // todo: string resource
if (dictFile.exists())
Text("will overwrite existing dictionary", color = MaterialTheme.colorScheme.error) // todo: string resource
}
}
)
}
}
private fun checkDict(file: File): Pair<Int?, DictionaryHeader?> {
val newHeader = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(file, 0, file.length())
?: return R.string.dictionary_file_error to null
val locale = newHeader.mLocaleString.constructLocale()
val dict = ReadOnlyBinaryDictionary(file.absolutePath, 0, file.length(), false, locale, "test")
if (!dict.isValidDictionary) {
dict.close()
return R.string.dictionary_load_error to null
}
return null to newHeader
}

View file

@ -295,6 +295,7 @@ fun SubtypeDialog(
currentSubtype = if (newValue.isEmpty()) currentSubtype.without(ExtraValue.SECONDARY_LOCALES)
else currentSubtype.with(ExtraValue.SECONDARY_LOCALES, newValue)
},
title = { Text("languages with dictionaries") }, // todo: string resource
items = availableLocalesForScript,
initialSelection = currentSubtype.getExtraValueOf(ExtraValue.SECONDARY_LOCALES)
?.split(Separators.KV)?.map { it.constructLocale() }.orEmpty(),
@ -392,7 +393,7 @@ private fun WithSmallTitle(
}
@Composable
private fun <T>DropDownField(
fun <T>DropDownField(
items: List<T>,
selectedItem: T,
onSelected: (T) -> Unit,

View file

@ -86,7 +86,7 @@ fun ThreeButtonAlertDialog(
if (confirmButtonText != null)
TextButton(
enabled = checkOk(),
onClick = { onDismissRequest(); onConfirmed() },
onClick = { onConfirmed(); onDismissRequest() },
) { Text(confirmButtonText) }
}
}

View file

@ -0,0 +1,131 @@
package helium314.keyboard.settings.screens
import android.content.Context
import android.content.Intent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import helium314.keyboard.latin.Dictionary
import helium314.keyboard.latin.R
import helium314.keyboard.latin.common.LocaleUtils
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
import helium314.keyboard.latin.common.splitOnWhitespace
import helium314.keyboard.latin.settings.USER_DICTIONARY_SUFFIX
import helium314.keyboard.latin.utils.DICTIONARY_URL
import helium314.keyboard.latin.utils.DictionaryInfoUtils
import helium314.keyboard.latin.utils.SubtypeLocaleUtils
import helium314.keyboard.latin.utils.SubtypeSettings
import helium314.keyboard.latin.utils.getDictionaryLocales
import helium314.keyboard.latin.utils.locale
import helium314.keyboard.latin.utils.prefs
import helium314.keyboard.settings.SearchScreen
import helium314.keyboard.settings.dialogs.ConfirmationDialog
import helium314.keyboard.settings.dialogs.DictionaryDialog
import helium314.keyboard.settings.dictionaryFilePicker
import java.io.File
import java.util.Locale
@Composable
fun DictionaryScreen(
onClickBack: () -> Unit,
) {
val ctx = LocalContext.current
val enabledLanguages = SubtypeSettings.getEnabledSubtypes(ctx.prefs(), true).map { it.locale().language }
val comparer = compareBy<Locale>({ it.language !in enabledLanguages }, { it.displayName }) // todo: could also prefer if there is a user-added dict
val dictionaryLocales = getDictionaryLocales(ctx).sortedWith(comparer).toMutableList()
dictionaryLocales.add(0, Locale(SubtypeLocaleUtils.NO_LANGUAGE))
var selectedLocale: Locale? by remember { mutableStateOf(null) }
var showAddDictDialog by remember { mutableStateOf(false) }
val dictPicker = dictionaryFilePicker(selectedLocale)
SearchScreen(
onClickBack = onClickBack,
title = { Text(stringResource(R.string.dictionary_settings_category)) },
filteredItems = { term ->
if (term.isBlank()) dictionaryLocales
else dictionaryLocales.filter {
it.language != SubtypeLocaleUtils.NO_LANGUAGE &&
LocaleUtils.getLocaleDisplayNameInSystemLocale(it, ctx).replace("(", "")
.splitOnWhitespace().any { it.startsWith(term, true) }
}
},
itemContent = {
if (it.language == SubtypeLocaleUtils.NO_LANGUAGE) {
Text(stringResource(R.string.add_new_dictionary_title), Modifier.clickable { showAddDictDialog = true })
} else {
Column(
Modifier.clickable { selectedLocale = it }
.padding(vertical = 6.dp, horizontal = 16.dp)
.fillMaxWidth()
) {
val (dicts, hasInternal) = getUserAndInternalDictionaries(ctx, it)
val types = dicts.mapTo(mutableListOf()) { it.name.substringBefore("_${USER_DICTIONARY_SUFFIX}") }
if (hasInternal && !types.contains(Dictionary.TYPE_MAIN))
types.add(0, stringResource(R.string.internal_dictionary_summary))
Text(LocaleUtils.getLocaleDisplayNameInSystemLocale(it, ctx))
Text(
types.joinToString(", "),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
)
if (showAddDictDialog) {
ConfirmationDialog(
onDismissRequest = { showAddDictDialog = false },
onConfirmed = {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.setType("application/octet-stream")
dictPicker.launch(intent)
},
title = { Text(stringResource(R.string.add_new_dictionary_title)) },
text = {
// todo: no html in compose
val dictLink = "<a href='$DICTIONARY_URL'>" + ctx.getString(R.string.dictionary_link_text) + "</a>"
Text(stringResource(R.string.add_dictionary, dictLink))
}
)
}
if (selectedLocale != null) {
DictionaryDialog(
onDismissRequest = { selectedLocale = null },
locale = selectedLocale!!
)
}
}
/** @return list of user dictionary files and whether an internal dictionary exists */
fun getUserAndInternalDictionaries(context: Context, locale: Locale): Pair<List<File>, Boolean> {
val userDicts = mutableListOf<File>()
var hasInternalDict = false
val userLocaleDir = File(DictionaryInfoUtils.getCacheDirectoryForLocale(locale, context))
if (userLocaleDir.exists() && userLocaleDir.isDirectory) {
userLocaleDir.listFiles()?.forEach {
if (it.name.endsWith(USER_DICTIONARY_SUFFIX))
userDicts.add(it)
else if (it.name.startsWith(DictionaryInfoUtils.MAIN_DICT_PREFIX))
hasInternalDict = true
}
}
if (hasInternalDict)
return userDicts to true
val internalDicts = DictionaryInfoUtils.getAssetsDictionaryList(context) ?: return userDicts to false
val best = LocaleUtils.getBestMatch(locale, internalDicts.toList()) {
DictionaryInfoUtils.extractLocaleFromAssetsDictionaryFile(it)?.constructLocale() ?: SubtypeLocaleUtils.NO_LANGUAGE.constructLocale()
}
return userDicts to (best != null)
}

View file

@ -44,6 +44,7 @@ fun MainSettingsScreen(
onClickAppearance: () -> Unit,
onClickLanguage: () -> Unit,
onClickLayouts: () -> Unit,
onClickDictionaries: () -> Unit,
onClickBack: () -> Unit,
) {
val ctx = LocalContext.current
@ -133,6 +134,17 @@ fun MainSettingsScreen(
contentDescription = null
)
}
Preference(
name = stringResource(R.string.dictionary_settings_category),
onClick = onClickDictionaries,
icon = R.drawable.ic_dictionary
) {
Icon(
painter = painterResource(R.drawable.ic_arrow_left),
modifier = Modifier.scale(-1f, 1f),
contentDescription = null
)
}
Preference(
name = stringResource(R.string.settings_screen_advanced),
onClick = onClickAdvanced,
@ -198,7 +210,7 @@ fun MainSettingsScreen(
private fun PreviewScreen() {
Theme(true) {
Surface {
MainSettingsScreen({}, {}, {}, {}, {}, {}, {}, {}, {}, {})
MainSettingsScreen({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {})
}
}
}

View file

@ -0,0 +1,20 @@
<!--
icon available in Android Studio
SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp">
<path android:fillColor="#FFF" android:pathData="M21,5c-1.11,-0.35 -2.33,-0.5 -3.5,-0.5c-1.95,0 -4.05,0.4 -5.5,1.5c-1.45,-1.1 -3.55,-1.5 -5.5,-1.5S2.45,4.9 1,6v14.65c0,0.25 0.25,0.5 0.5,0.5c0.1,0 0.15,-0.05 0.25,-0.05C3.1,20.45 5.05,20 6.5,20c1.95,0 4.05,0.4 5.5,1.5c1.35,-0.85 3.8,-1.5 5.5,-1.5c1.65,0 3.35,0.3 4.75,1.05c0.1,0.05 0.15,0.05 0.25,0.05c0.25,0 0.5,-0.25 0.5,-0.5V6C22.4,5.55 21.75,5.25 21,5zM21,18.5c-1.1,-0.35 -2.3,-0.5 -3.5,-0.5c-1.7,0 -4.15,0.65 -5.5,1.5V8c1.35,-0.85 3.8,-1.5 5.5,-1.5c1.2,0 2.4,0.15 3.5,0.5V18.5z"/>
<path android:fillColor="#FFF" android:pathData="M17.5,10.5c0.88,0 1.73,0.09 2.5,0.26V9.24C19.21,9.09 18.36,9 17.5,9c-1.7,0 -3.24,0.29 -4.5,0.83v1.66C14.13,10.85 15.7,10.5 17.5,10.5z"/>
<path android:fillColor="#FFF" android:pathData="M13,12.49v1.66c1.13,-0.64 2.7,-0.99 4.5,-0.99c0.88,0 1.73,0.09 2.5,0.26V11.9c-0.79,-0.15 -1.64,-0.24 -2.5,-0.24C15.8,11.66 14.26,11.96 13,12.49z"/>
<path android:fillColor="#FFF" android:pathData="M17.5,14.33c-1.7,0 -3.24,0.29 -4.5,0.83v1.66c1.13,-0.64 2.7,-0.99 4.5,-0.99c0.88,0 1.73,0.09 2.5,0.26v-1.52C19.21,14.41 18.36,14.33 17.5,14.33z"/>
</vector>