package co.smartreceipts.android.settings; import android.content.Context; import android.content.SharedPreferences; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.TypedValue; import com.google.common.base.Preconditions; import java.util.Currency; import java.util.List; import java.util.Locale; import javax.inject.Inject; import co.smartreceipts.android.date.DateUtils; import co.smartreceipts.android.di.scopes.ApplicationScope; import co.smartreceipts.android.persistence.SharedPreferenceDefinitions; import co.smartreceipts.android.settings.catalog.UserPreference; import co.smartreceipts.android.utils.log.Logger; import io.reactivex.Observable; import io.reactivex.Scheduler; import io.reactivex.schedulers.Schedulers; @ApplicationScope public class UserPreferenceManager { public static final String PREFERENCES_FILE_NAME = SharedPreferenceDefinitions.SmartReceiptsCoreSettings.toString(); private final Context context; private final SharedPreferences preferences; private final Scheduler initializationScheduler; @Inject UserPreferenceManager(Context context) { this(context.getApplicationContext(), context.getSharedPreferences(PREFERENCES_FILE_NAME, 0), Schedulers.io()); } @VisibleForTesting UserPreferenceManager(Context context, SharedPreferences preferences, Scheduler initializationScheduler) { this.context = context; this.preferences = preferences; this.initializationScheduler = Preconditions.checkNotNull(initializationScheduler); initialize(); } public void initialize() { getUserPreferencesObservable() .subscribeOn(this.initializationScheduler) .subscribe(userPreferences -> { for (final UserPreference<?> userPreference : userPreferences) { final String preferenceName = getName(userPreference); if (!preferences.contains(preferenceName)) { // In here - we assign values that don't allow for preference_defaults.xml definitions (e.g. Locale Based Setings) // Additionally, we set all float fields, which don't don't allow for 'android:defaultValue' settings if (UserPreference.General.DateSeparator.equals(userPreference)) { final String assignedDateSeparator = context.getString(UserPreference.General.DateSeparator.getDefaultValue()); if (TextUtils.isEmpty(assignedDateSeparator)) { final String localeDefaultDateSeparator = DateUtils.getDateSeparator(context); preferences.edit().putString(preferenceName, localeDefaultDateSeparator).apply(); Logger.debug(UserPreferenceManager.this, "Assigned locale default date separator {}", localeDefaultDateSeparator); } } else if (UserPreference.General.DefaultCurrency.equals(userPreference)) { final String assignedCurrencyCode = context.getString(UserPreference.General.DefaultCurrency.getDefaultValue()); if (TextUtils.isEmpty(assignedCurrencyCode)) { try { final String currencyCode = Currency.getInstance(Locale.getDefault()).getCurrencyCode(); preferences.edit().putString(preferenceName, currencyCode).apply(); Logger.debug(UserPreferenceManager.this, "Assigned locale default currency code {}", currencyCode); } catch (IllegalArgumentException e) { preferences.edit().putString(preferenceName, "USD").apply(); Logger.warn(UserPreferenceManager.this, "Failed to find this Locale's currency code. Defaulting to USD", e); } } } else if (UserPreference.Receipts.MinimumReceiptPrice.equals(userPreference)) { final TypedValue typedValue = new TypedValue(); context.getResources().getValue(userPreference.getDefaultValue(), typedValue, true); if (typedValue.getFloat() < 0) { final float defaultMinimumReceiptPrice = -Float.MAX_VALUE; preferences.edit().putFloat(preferenceName, defaultMinimumReceiptPrice).apply(); Logger.debug(UserPreferenceManager.this, "Assigned default float value for {} as {}", preferenceName, defaultMinimumReceiptPrice); } } else if (Float.class.equals(userPreference.getType())) { final TypedValue typedValue = new TypedValue(); context.getResources().getValue(userPreference.getDefaultValue(), typedValue, true); preferences.edit().putFloat(preferenceName, typedValue.getFloat()).apply(); Logger.debug(UserPreferenceManager.this, "Assigned default float value for {} as {}", preferenceName, typedValue.getFloat()); } } } Logger.debug(UserPreferenceManager.this, "Completed user preference initialization"); }); } @NonNull public <T> Observable<T> getObservable(final UserPreference<T> preference) { return Observable.create(emitter -> { emitter.onNext(get(preference)); emitter.onComplete(); }); } @NonNull @SuppressWarnings("unchecked") public <T> T get(UserPreference<T> preference) { final String name = context.getString(preference.getName()); if (Boolean.class.equals(preference.getType())) { return (T) Boolean.valueOf(preferences.getBoolean(name, context.getResources().getBoolean(preference.getDefaultValue()))); } else if (String.class.equals(preference.getType())) { return (T) preferences.getString(name, context.getString(preference.getDefaultValue())); } else if (Float.class.equals(preference.getType())) { final TypedValue typedValue = new TypedValue(); context.getResources().getValue(preference.getDefaultValue(), typedValue, true); return (T) Float.valueOf(preferences.getFloat(name, typedValue.getFloat())); } else if (Integer.class.equals(preference.getType())) { return (T) Integer.valueOf(preferences.getInt(name, context.getResources().getInteger(preference.getDefaultValue()))); } else { throw new IllegalArgumentException("Unsupported preference type: " + preference.getType()); } } @NonNull public <T> Observable<T> setObservable(final UserPreference<T> preference, final T t) { return Observable.create(emitter -> { set(preference, t); emitter.onNext(t); emitter.onComplete(); }); } public <T> void set(UserPreference<T> preference, T t) { final String name = context.getString(preference.getName()); if (Boolean.class.equals(preference.getType())) { preferences.edit().putBoolean(name, (Boolean) t).apply(); } else if (String.class.equals(preference.getType())) { preferences.edit().putString(name, (String) t).apply(); } else if (Float.class.equals(preference.getType())) { preferences.edit().putFloat(name, (Float) t).apply(); } else if (Integer.class.equals(preference.getType())) { preferences.edit().putInt(name, (Integer) t).apply(); } else { throw new IllegalArgumentException("Unsupported preference type: " + preference.getType()); } } @NonNull public Observable<List<UserPreference<?>>> getUserPreferencesObservable() { return Observable.create(emitter -> { emitter.onNext(UserPreference.values()); emitter.onComplete(); }); } @NonNull public String getName(@NonNull UserPreference<?> preference) { return context.getString(preference.getName()); } /** * @return the current {@link SharedPreferences} implementation. This is now deprecated, and users * should prefer the {@link #set(UserPreference, Object)} method instead to interact with this component */ @NonNull @Deprecated public SharedPreferences getSharedPreferences() { return preferences; } }