I'm learning how to use Preferences in Android. I have a small a application on which I want to set a series of preferences which includes a RingTonePreference. My activity implements OnSharedPreferenceChangeListener and override the onSharedPreferenceChanged(SharedPreferences sharedPreferences,String key) method to handle all the different preferences.
Until the ringtone one everything was working fine. My problem seems to be that the key of the ringTonePreference is not found and my code never enters the if statement of the ringTonePreference.
Here is my onSharedPreferenceChanged method:
public class preferenceActivity extends PreferenceActivity implements
OnSharedPreferenceChangeListener {
// code here
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
if (key.equals("list_subject_pref")) {
// do something
} else if (key.equals("et_subject_pref")) {
// do something
} else if (key.equals("list_times_pref")) {
// do something
// here is problem, it never enters the if statement why?
} else if (key.equals("ringtonePref")) {
RingtonePreference preference = (RingtonePreference) findPreference("ringtonePref");
preference.setSummary("ringtone selected");
}
}
}
And here is my preferences.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceCategory android:title="#string/subjectpreferences" >
<ListPreference
android:key="list_subject_pref"
// other stuff here/>
<EditTextPreference
android:defaultValue=""
android:key="et_subject_pref"
// other stuff here />
<MultiSelectListPreference
android:key="list_times_pref"
// other stuff here />
</PreferenceCategory>
<PreferenceCategory android:title="#string/reminderpreferences" >
<RingtonePreference
android:key="ringtonePref"
android:showDefault="true"
android:showSilent="true"
android:summary="Select a ringtone"
android:title="Ringtones" />
<CheckBoxPreference
android:defaultValue="true"
android:key="preference_ringtonereminder"
android:summary="summary"
android:summaryOff="#string/remindme_off"
android:summaryOn="#string/remindme_on"
android:title="#string/reminderringtone_title" />
</PreferenceCategory>
I have seen this question but I'm still not able to fix my problem.
Related
I am running into an issue that I can't figure out. I wrote a simple custom IME keyboard based on this sample.
It basically has two custom keyboards, one for letters and one for numbers. They use different layouts.
However, when I add two EditText controls one for text and one for numbers, the keyboard does not refresh to the type it belongs. What I mean is that if I select the EditText with inputType="text" first, the QWERTY keyboard layout comes up. But then when I select the second EditText with inputType="number" the QWERTY keyboard shows up again. However it is supposed to load a different layout for numbers that is wired into the code.
In other words, here's the test activity layout:
Now if I select the "Text" field, the QWERTY keyboard comes up as below:
However, if I select the "Number" filed, the QWERTY keyboard still shows up which is wrong.
The expected behavior would be this keyboard to show up.
Here's the code for the CustomIME and I tried to use postInvalidate() on the view, pre-load all layouts during onInitializeInterface() but nothing worked. It never switches to the number's layout properly
public class CustomIME extends InputMethodService
implements KeyboardView.OnKeyboardActionListener {
public static final String CUSTOM_IME = "CUSTOM_IME";
private KeyboardView mKeyboardView;
private Keyboard mKeyboardCurrent;
private KeyboardType mKeyboardTypeCurrent = KeyboardType.QWERTY_LETTERS;
private boolean mCAPs = false;
enum KeyboardType {
QWERTY_LETTERS,
NUMBERS
}
#Override
public View onCreateInputView() {
loadCurrentKeyboard();
mKeyboardView = (KeyboardView) getLayoutInflater().inflate(R.layout.custom_ime_keyboard, null);
mKeyboardView.setBackgroundResource(R.drawable.btn_gradient);
mKeyboardView.setOnKeyboardActionListener(this);
if (mKeyboardCurrent != null) {
mKeyboardView.setKeyboard(mKeyboardCurrent);
}
return mKeyboardView;
}
#Override
public void onInitializeInterface() {
// tried loading everything here but did not make a difference
}
private void loadCurrentKeyboard() {
if (mKeyboardTypeCurrent == KeyboardType.QWERTY_LETTERS) {
mKeyboardCurrent = new Keyboard(getApplicationContext(), R.xml.custom_ime_qwerty);
} else if (mKeyboardTypeCurrent == KeyboardType.NUMBERS) {
mKeyboardCurrent = new Keyboard(getApplicationContext(), R.xml.custom_ime_number);
} else {
Log.e(CUSTOM_IME, "Invalid keyboard type");
}
}
#Override
public void onStartInput(EditorInfo attribute, boolean restarting) {
super.onStartInput(attribute, restarting);
switch (attribute.inputType & InputType.TYPE_MASK_CLASS) {
case InputType.TYPE_CLASS_NUMBER:
boolean signed = (attribute.inputType & InputType.TYPE_NUMBER_FLAG_SIGNED) != 0;
boolean decimal = (attribute.inputType & InputType.TYPE_NUMBER_FLAG_DECIMAL) != 0;
// set default
mKeyboardTypeCurrent = KeyboardType.QWERTY_LETTERS;
if (!signed && !decimal) {
mKeyboardTypeCurrent = KeyboardType.NUMBERS;
}
break;
case InputType.TYPE_CLASS_TEXT:
default:
mKeyboardTypeCurrent = KeyboardType.QWERTY_LETTERS;
}
// This did not make a difference
if (mKeyboardView != null) {
mKeyboardView.postInvalidate();
}
}
#Override
public void onKey(int primaryCode, int[] keyCodes) {
InputConnection inputConnection = getCurrentInputConnection();
switch (primaryCode) {
default:
char asciiCode = (char) primaryCode;
if (Character.isLetter(asciiCode) && mCAPs) {
asciiCode = Character.toUpperCase(asciiCode);
}
inputConnection.commitText(String.valueOf(asciiCode), 1);
}
}
}
And the layouts:
custom_ime_keyboard.xml:
<?xml version="1.0" encoding="UTF-8"?>
<android.inputmethodservice.KeyboardView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/custom_ime_keyboard_id1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:keyPreviewLayout="#layout/custom_ime_preview" />
activity_main.xml
<LinearLayout
android:id="#+id/layout1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_margin="10dp"
android:orientation="horizontal">
<EditText
android:id="#+id/edit1"
android:layout_width="100dp"
android:layout_height="60dp"
android:inputType="text"
android:hint="Text"
android:padding="10dp"
android:textSize="12sp" />
<EditText
android:id="#+id/edit2"
android:layout_width="100dp"
android:layout_height="60dp"
android:hint="Number"
android:inputType="number"
android:padding="10dp"
android:textSize="12sp" />
</LinearLayout>
Finally the keyboard layouts (custom_ime_qwerty.xml, and custom_ime_number.xml).
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyHeight="64dp"
android:keyWidth="9%p">
<!--1st row-->
<Row android:rowEdgeFlags="top">
<Key
android:codes="113"
android:keyEdgeFlags="left"
android:keyLabel="q" />
<Key
android:codes="119"
android:keyLabel="w" />
<Key
android:codes="101"
android:keyLabel="e" />
<Key
android:codes="114"
android:keyLabel="r" />
etc...
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyHeight="64dp"
android:keyWidth="20%p"
android:label="number"
android:verticalGap="0px">
<!--1st row-->
<Row android:rowEdgeFlags="top">
<Key
android:codes="49"
android:keyEdgeFlags="left"
android:keyLabel="1" />
<Key
android:codes="50"
android:keyLabel="2" />
<Key
android:codes="51"
android:keyLabel="3" />
I think onStartInputView() is the callback you need to obtain:
Called when the input view is being shown and input has started on a new editor. This will always be called after onStartInput(EditorInfo, boolean), allowing you to do your general setup there and just view-specific setup here. You are guaranteed that onCreateInputView() will have been called some time before this function is called.
So, you get to know what exact input type to show in onStartInput(), but the actual place to perform switching to this new keyboard type should be onStartInputView().
See how the sample SoftKeyboard application handles that functionality.
#Override public void onStartInput(EditorInfo attribute, boolean restarting) {
super.onStartInput(attribute, restarting);
...
// We are now going to initialize our state based on the type of
// text being edited.
switch (attribute.inputType & InputType.TYPE_MASK_CLASS) {
case InputType.TYPE_CLASS_NUMBER:
case InputType.TYPE_CLASS_DATETIME:
mCurKeyboard = mSymbolsKeyboard;
break;
case InputType.TYPE_CLASS_PHONE:
mCurKeyboard = mSymbolsKeyboard;
break;
case InputType.TYPE_CLASS_TEXT:
mCurKeyboard = mQwertyKeyboard;
...
break;
default:
// For all unknown input types, default to the alphabetic
// keyboard with no special features.
mCurKeyboard = mQwertyKeyboard;
}
}
#Override public void onStartInputView(EditorInfo attribute, boolean restarting) {
super.onStartInputView(attribute, restarting);
// Apply the selected keyboard to the input view.
setLatinKeyboard(mCurKeyboard);
...
}
private void setLatinKeyboard(LatinKeyboard nextKeyboard) {
final boolean shouldSupportLanguageSwitchKey =
mInputMethodManager.shouldOfferSwitchingToNextInputMethod(getToken());
nextKeyboard.setLanguageSwitchKeyVisibility(shouldSupportLanguageSwitchKey);
mInputView.setKeyboard(nextKeyboard);
}
My preference values aren't saving and everytime I read the SharedPreferences file it returns default values; changes are supposed to be automatically handled by the system when a user makes a choice (like selecting an option in a ListPreference) so I don't know why this isn't happening in my app.
According to google
"When the user changes a setting, the system updates the corresponding value in the SharedPreferences file for you. The only time you should directly interact with the associated SharedPreferences file is when you need to read the value in order to determine your app's behavior based on the user's setting."
Should I be manipulating a SharedPreference.Editor instance in my OnSharedPreferenceChangeListener or is there something else I need to do to make values of user settings persist?
The Problem in Code: As a result of this misunderstanding, my code doesn't persist user settings values (the default value is always chosen when I read the SharedPreference file in my MainActivity's OnCreate). As it is now, the buttons chosen in my Preferences menu View persist upon app restarts, but it seems that choosing an option in this menu doesn't save key values to the SharedPreferences file.
What do I need to do to make the values set by the user in my ListPreference persist?
MainActivity
public class MainActivity extends FragmentActivity {
private static int prefWoodColor; //saved pref variable for OpenGL neck texture
private SharedPreferences settings;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
//Restore preferences
settings = PreferenceManager.getDefaultSharedPreferences(this);
prefWoodColor = Integer.parseInt(settings.getString(this.KEY_PREF_WOOD_TYPE, "Maple"));
...
}
}
Preferences Activity
public class FragmentSettingsMenu extends com.takisoft.fix.support.v7.preference.PreferenceFragmentCompat {
private SharedPreferences.OnSharedPreferenceChangeListener listener;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from the XML resource
addPreferencesFromResource(R.xml.preferences);
listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
if (key.equals("pref_wood")) {
Preference woodPref = findPreference(key);
String color = woodPref.getSharedPreferences().getString(key, "Maple");
//Should I be calling edit.apply() logic here?
}
}
};
}
...
}
Preferences.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
android:title="Settings"
<ListPreference
android:key="pref_wood"
android:title="#string/pref_wood"
android:dialogTitle="#string/pref_wood"
android:entries="#array/pref_wood_entries"
android:entryValues="#array/pref_wood_values"
android:defaultValue="#string/pref_wood_default" />
</PreferenceScreen>
Strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">MyApp</string>
<string name="dummy_button">Dummy Button</string>
<string name="dummy_content">DUMMY\nCONTENT</string>
<!--Preference Menu Strings-->
<string name="pref_wood">Wood Style</string>
<string-array name="pref_wood_entries">
<item>"Maple"</item>
<item>"Cedar"</item>
<item>"Oak"</item>
</string-array>
<string-array name="pref_wood_values">
<item >0</item>
<item >1</item>
<item >2</item>
</string-array>
<string name="pref_wood_default">Maple</string>
</resources>
The system saves changes automatically!
I have a preference activity in which I have a PreferenceCheckBox.
My preference activity:
package com.tjs.balr;
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class SettingsActivity extends PreferenceActivity{
#SuppressWarnings("deprecation")
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
addPreferencesFromResource( R.xml.preferences);
}
}
My checkbox:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="Audio">
<CheckBoxPreference
android:id="#+id/checkSound"
android:summary="Turn sounds on or off"
android:defaultValue="true"
android:title="Sounds"
android:key="soundPref"
/>
</PreferenceCategory>
</PreferenceScreen>
In my main activity I try to get the value of my checkbox using:
private void showUserSettings() {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
musicPlaying = sharedPrefs.getBoolean("checkSound", true);
if(musicPlaying){
Toast.makeText(this, "music is turned on", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(this, "music is turned off ", Toast.LENGTH_SHORT).show();
}
}
A LOT of people here on stackoverflow describe musicPlaying = sharedPrefs.getBoolean("checkSound", true); as the way to get the value of the checkbox, but in my case it stays true. Mainly because I say the default value of checkSound is true. Am I forgetting something in order to change the value of my PreferenceCheckBox? To my understanding of an PreferenceActivity all data is saved automatically, is this correct?
In getBoolean(key, defaultValue) , you need to pass the Key not the id.
You get always true because he dont find the CheckBox with checkSound as key so it return the default value (true in your case).
To fix that, just change
musicPlaying = sharedPrefs.getBoolean("checkSound", true);
to
musicPlaying = sharedPrefs.getBoolean("soundPref", true);
I'm in the middle of making an app and I'm running into a road block.
Basically I have a checkbox... If the checkbox is checked, it is supposed enable the entry of text in an edit text preference, and use that value. But here's where the problem lies... If the box is unchecked it uses the value in the EditTextPreference. If the box is checked, it uses the hard coded value.
Part of my activity:
private void loadURI()
{
PreferenceManager.setDefaultValues(this, R.xml.settings, true);
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
if (settings.getBoolean("default_uri", false)){
String defaultURI = getString(R.string.dream_uri);
this.mWebView.loadUrl(defaultURI);
}
else {
String customURI = settings.getString("custom_uri", "");
this.mWebView.loadUrl(customURI);
}
}
Settings.xml:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="Backdrop Settings" >
<CheckBoxPreference
android:title="Use Custom Backdrop URL"
android:id="#id/checkBox1"
android:key="default_uri"
android:defaultValue="true"/>
<EditTextPreference
android:title="Custom Backdrop URL"
android:summary="Tap here to enter custom URL"
android:id="#id/editText1"
android:defaultValue="Enter Custom URL here"
android:key="custom_uri"
android:dependency="default_uri" />
<CheckBoxPreference
android:title="Use a Custom Slideshow Period?"
android:key="slide_duration"
android:defaultValue="false"/>
<ListPreference
android:title="Slideshow Duration"
android:summary="How long should each slide be displayed?"
android:key="list_duration"
android:dependency="slide_duration"/>
</PreferenceCategory>
<PreferenceCategory
android:title="Other">
<Preference
android:title="Test Dream"
android:summary="Tap here to test the dream"
android:key="testDream" />
<Preference
android:title="About"
android:key="AboutApp"
android:summary="About this app" />
</PreferenceCategory>
</PreferenceScreen>
And my Preferences activity:
public class mollySettings extends PreferenceActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.settings);
Preference button = findPreference("testDream");
button.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
#Override
public boolean onPreferenceClick(Preference arg0) {
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName("com.android.systemui", "com.android.systemui.Somnambulator");
startActivity(intent);
return true;
}
});
Preference about = findPreference("AboutApp");
about.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
#Override
public boolean onPreferenceClick(Preference arg1) {
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName("com.death2all110.flogging.molly", "com.death2all110.flogging.molly.About");
startActivity(intent);
return true;
}
});
}
}
Thanks in advance, and please let me know if there is anything else you need
Got it figured out.
I added an exclamation point to the if statement. So the loadURI method is now:
private void loadURI()
{
PreferenceManager.setDefaultValues(this, R.xml.settings, true);
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
if (!settings.getBoolean("default_uri", false)){
String defaultURI = getString(R.string.dream_uri);
this.mWebView.loadUrl(defaultURI);
}
else {
String customURI = settings.getString("custom_uri", "");
this.mWebView.loadUrl(customURI);
}
}
Instead of:
private void loadURI()
{
PreferenceManager.setDefaultValues(this, R.xml.settings, true);
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
if (settings.getBoolean("default_uri", false)){
String defaultURI = getString(R.string.dream_uri);
this.mWebView.loadUrl(defaultURI);
}
else {
String customURI = settings.getString("custom_uri", "");
this.mWebView.loadUrl(customURI);
}
}
I'm new in the "Android-App-Dev"-Scene and got one question:
How do I easily make a good clean looking settings page for my app?
There are some kind of headlines and some kind of big buttons on you can tab to go to a new page.
I'm using Android Studio and know how to create a new page, class etc..
As of 2019 the recommended way to do this is to use the AndroidX Preference Library.
PreferenceActivity is actually deprecated in API Level 29 (source):
This class was deprecated in API level 29.
Use the AndroidX Preference Library for consistent behavior across all devices. For more information on using the AndroidX Preference Library see Settings.
For a working minimal example please refer to this example in the official docs.
Use PreferenceActivity
sample code from the developer site:
public class PreferenceWithHeaders extends PreferenceActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Add a button to the header list.
if (hasHeaders()) {
Button button = new Button(this);
button.setText("Some action");
setListFooter(button);
}
}
/**
* Populate the activity with the top-level headers.
*/
#Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.preference_headers, target);
}
/**
* This fragment shows the preferences for the first header.
*/
public static class Prefs1Fragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Make sure default values are applied. In a real app, you would
// want this in a shared function that is used to retrieve the
// SharedPreferences wherever they are needed.
PreferenceManager.setDefaultValues(getActivity(),
R.xml.advanced_preferences, false);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.fragmented_preferences);
}
}
/**
* This fragment contains a second-level set of preference that you
* can get to by tapping an item in the first preferences fragment.
*/
public static class Prefs1FragmentInner extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Can retrieve arguments from preference XML.
Log.i("args", "Arguments: " + getArguments());
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.fragmented_preferences_inner);
}
}
/**
* This fragment shows the preferences for the second header.
*/
public static class Prefs2Fragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Can retrieve arguments from headers XML.
Log.i("args", "Arguments: " + getArguments());
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preference_dependencies);
}
}
}
The preference_headers resource describes the headers to be displayed and the fragments associated with them. It is:
<header android:fragment="com.example.android.apis.preference.PreferenceWithHeaders$Prefs1Fragment"
android:icon="#drawable/ic_settings_applications"
android:title="Prefs 1"
android:summary="An example of some preferences." />
<header android:fragment="com.example.android.apis.preference.PreferenceWithHeaders$Prefs2Fragment"
android:icon="#drawable/ic_settings_display"
android:title="Prefs 2"
android:summary="Some other preferences you can see.">
<!-- Arbitrary key/value pairs can be included with a header as
arguments to its fragment. -->
<extra android:name="someKey" android:value="someHeaderValue" />
</header>
<header android:icon="#drawable/ic_settings_display"
android:title="Intent"
android:summary="Launches an Intent.">
<intent android:action="android.intent.action.VIEW"
android:data="http://www.android.com" />
</header>
The first header is shown by Prefs1Fragment, which populates itself from the following XML resource:
<PreferenceCategory
android:title="#string/inline_preferences">
<CheckBoxPreference
android:key="checkbox_preference"
android:title="#string/title_checkbox_preference"
android:summary="#string/summary_checkbox_preference" />
</PreferenceCategory>
<PreferenceCategory
android:title="#string/dialog_based_preferences">
<EditTextPreference
android:key="edittext_preference"
android:title="#string/title_edittext_preference"
android:summary="#string/summary_edittext_preference"
android:dialogTitle="#string/dialog_title_edittext_preference" />
<ListPreference
android:key="list_preference"
android:title="#string/title_list_preference"
android:summary="#string/summary_list_preference"
android:entries="#array/entries_list_preference"
android:entryValues="#array/entryvalues_list_preference"
android:dialogTitle="#string/dialog_title_list_preference" />
</PreferenceCategory>
<PreferenceCategory
android:title="#string/launch_preferences">
<!-- This PreferenceScreen tag sends the user to a new fragment of
preferences. If running in a large screen, they can be embedded
inside of the overall preferences UI. -->
<PreferenceScreen
android:fragment="com.example.android.apis.preference.PreferenceWithHeaders$Prefs1FragmentInner"
android:title="#string/title_fragment_preference"
android:summary="#string/summary_fragment_preference">
<!-- Arbitrary key/value pairs can be included for fragment arguments -->
<extra android:name="someKey" android:value="somePrefValue" />
</PreferenceScreen>
<!-- This PreferenceScreen tag sends the user to a completely different
activity, switching out of the current preferences UI. -->
<PreferenceScreen
android:title="#string/title_intent_preference"
android:summary="#string/summary_intent_preference">
<intent android:action="android.intent.action.VIEW"
android:data="http://www.android.com" />
</PreferenceScreen>
</PreferenceCategory>
<PreferenceCategory
android:title="#string/preference_attributes">
<CheckBoxPreference
android:key="parent_checkbox_preference"
android:title="#string/title_parent_preference"
android:summary="#string/summary_parent_preference" />
<!-- The visual style of a child is defined by this styled theme attribute. -->
<CheckBoxPreference
android:key="child_checkbox_preference"
android:dependency="parent_checkbox_preference"
android:layout="?android:attr/preferenceLayoutChild"
android:title="#string/title_child_preference"
android:summary="#string/summary_child_preference" />
</PreferenceCategory>
Note that this XML resource contains a preference screen holding another fragment, the Prefs1FragmentInner implemented here. This allows the user to traverse down a hierarchy of preferences; pressing back will pop each fragment off the stack to return to the previous preferences.
See PreferenceFragment for information on implementing the fragments themselves.