package com.ichi2.anki;
/****************************************************************************************
* Copyright (c) 2009 Casey Link <unnamedrambler@gmail.com> *
* Copyright (c) 2012 Norbert Nagold <norbert.nagold@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify it under *
* the terms of the GNU General Public License as published by the Free Software *
* Foundation; either version 3 of the License, or (at your option) any later *
* version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY *
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
* PARTICULAR PURPOSE. See the GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License along with *
* this program. If not, see <http://www.gnu.org/licenses/>. *
****************************************************************************************/
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.view.KeyEvent;
import android.view.MenuItem;
import com.ichi2.anim.ActivityTransitionAnimation;
import com.ichi2.anki.receiver.SdCardReceiver;
import com.ichi2.libanki.Collection;
import com.ichi2.preferences.StepsPreference;
import com.ichi2.themes.Themes;
import com.ichi2.ui.AppCompatPreferenceActivity;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import timber.log.Timber;
/**
* Preferences for the current deck.
*/
public class FilteredDeckOptions extends AppCompatPreferenceActivity implements OnSharedPreferenceChangeListener {
private JSONObject mDeck;
private Collection mCol;
private boolean mAllowCommit = true;
private boolean mPrefChanged = false;
private BroadcastReceiver mUnmountReceiver = null;
// TODO: not anymore used in libanki?
private String[] dynExamples = new String[] { null,
"{'search'=\"is:new\", 'resched'=False, 'steps'=\"1\", 'order'=5}",
"{'search'=\"added:1\", 'resched'=False, 'steps'=\"1\", 'order'=5}",
"{'search'=\"rated:1:1\", 'order'=4}",
"{'search'=\"prop:due<=2\", 'order'=6}",
"{'search'=\"is:due tag:TAG\", 'order'=6}",
"{'search'=\"is:due\", 'order'=3}",
"{'search'=\"\", 'steps'=\"1 10 20\", 'order'=0}" };
public class DeckPreferenceHack implements SharedPreferences {
private Map<String, String> mValues = new HashMap<>();
private Map<String, String> mSummaries = new HashMap<>();
public DeckPreferenceHack() {
this.cacheValues();
}
protected void cacheValues() {
Timber.d("cacheValues()");
try {
JSONArray ar = mDeck.getJSONArray("terms").getJSONArray(0);
mValues.put("search", ar.getString(0));
mValues.put("limit", ar.getString(1));
mValues.put("order", ar.getString(2));
JSONArray delays = mDeck.optJSONArray("delays");
if (delays != null) {
mValues.put("steps", StepsPreference.convertFromJSON(delays));
mValues.put("stepsOn", Boolean.toString(true));
} else {
mValues.put("steps", "1 10");
mValues.put("stepsOn", Boolean.toString(false));
}
mValues.put("resched", Boolean.toString(mDeck.getBoolean("resched")));
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
public class Editor implements SharedPreferences.Editor {
private ContentValues mUpdate = new ContentValues();
@Override
public SharedPreferences.Editor clear() {
Timber.d("clear()");
mUpdate = new ContentValues();
return this;
}
@Override
public boolean commit() {
Timber.d("commit() changes back to database");
try {
for (Entry<String, Object> entry : mUpdate.valueSet()) {
Timber.i("Change value for key '" + entry.getKey() + "': " + entry.getValue());
if (entry.getKey().equals("search")) {
JSONArray ar = mDeck.getJSONArray("terms");
ar.getJSONArray(0).put(0, entry.getValue());
mDeck.put("terms", ar);
} else if (entry.getKey().equals("limit")) {
JSONArray ar = mDeck.getJSONArray("terms");
ar.getJSONArray(0).put(1, entry.getValue());
mDeck.put("terms", ar);
} else if (entry.getKey().equals("order")) {
JSONArray ar = mDeck.getJSONArray("terms");
ar.getJSONArray(0).put(2, Integer.parseInt((String) entry.getValue()));
mDeck.put("terms", ar);
} else if (entry.getKey().equals("resched")) {
mDeck.put("resched", entry.getValue());
} else if (entry.getKey().equals("stepsOn")) {
boolean on = (Boolean) entry.getValue();
if (on) {
JSONArray steps = StepsPreference.convertToJSON(mValues.get("steps"));
if (steps.length() > 0) {
mDeck.put("delays", steps);
}
} else {
mDeck.put("delays", JSONObject.NULL);
}
} else if (entry.getKey().equals("steps")) {
mDeck.put("delays", StepsPreference.convertToJSON((String) entry.getValue()));
} else if (entry.getKey().equals("preset")) {
int i = Integer.parseInt((String) entry.getValue());
if (i > 0) {
JSONObject presetValues = new JSONObject(dynExamples[i]);
JSONArray ar = presetValues.names();
for (int j = 0; j < ar.length(); j++) {
String name = ar.getString(j);
if (name.equals("steps")) {
mUpdate.put("stepsOn", true);
}
if (name.equals("resched")) {
mUpdate.put(name, presetValues.getBoolean(name));
mValues.put(name, Boolean.toString(presetValues.getBoolean(name)));
} else {
mUpdate.put(name, presetValues.getString(name));
mValues.put(name, presetValues.getString(name));
}
}
mUpdate.put("preset", "0");
commit();
}
}
}
} catch (JSONException e) {
throw new RuntimeException(e);
}
// save deck
try {
mCol.getDecks().save(mDeck);
} catch (RuntimeException e) {
Timber.e(e, "RuntimeException on saving deck");
AnkiDroidApp.sendExceptionReport(e, "FilteredDeckOptionsSaveDeck");
setResult(DeckPicker.RESULT_DB_ERROR);
finish();
}
// make sure we refresh the parent cached values
cacheValues();
updateSummaries();
// and update any listeners
for (OnSharedPreferenceChangeListener listener : listeners) {
listener.onSharedPreferenceChanged(DeckPreferenceHack.this, null);
}
return true;
}
@Override
public SharedPreferences.Editor putBoolean(String key, boolean value) {
mUpdate.put(key, value);
return this;
}
@Override
public SharedPreferences.Editor putFloat(String key, float value) {
mUpdate.put(key, value);
return this;
}
@Override
public SharedPreferences.Editor putInt(String key, int value) {
mUpdate.put(key, value);
return this;
}
@Override
public SharedPreferences.Editor putLong(String key, long value) {
mUpdate.put(key, value);
return this;
}
@Override
public SharedPreferences.Editor putString(String key, String value) {
mUpdate.put(key, value);
return this;
}
@Override
public SharedPreferences.Editor remove(String key) {
Timber.d("Editor.remove(key=%s)", key);
mUpdate.remove(key);
return this;
}
public void apply() {
if (mAllowCommit) {
commit();
}
}
// @Override On Android 1.5 this is not Override
public android.content.SharedPreferences.Editor putStringSet(String arg0, Set<String> arg1) {
// TODO Auto-generated method stub
return null;
}
}
@Override
public boolean contains(String key) {
return mValues.containsKey(key);
}
@Override
public Editor edit() {
return new Editor();
}
@Override
public Map<String, ?> getAll() {
return mValues;
}
@Override
public boolean getBoolean(String key, boolean defValue) {
return Boolean.valueOf(this.getString(key, Boolean.toString(defValue)));
}
@Override
public float getFloat(String key, float defValue) {
return Float.valueOf(this.getString(key, Float.toString(defValue)));
}
@Override
public int getInt(String key, int defValue) {
return Integer.valueOf(this.getString(key, Integer.toString(defValue)));
}
@Override
public long getLong(String key, long defValue) {
return Long.valueOf(this.getString(key, Long.toString(defValue)));
}
@Override
public String getString(String key, String defValue) {
Timber.d("getString(key=%s, defValue=%s)", key, defValue);
if (!mValues.containsKey(key)) {
return defValue;
}
return mValues.get(key);
}
public List<OnSharedPreferenceChangeListener> listeners = new LinkedList<>();
@Override
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
listeners.add(listener);
}
@Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
listeners.remove(listener);
}
// @Override On Android 1.5 this is not Override
public Set<String> getStringSet(String arg0, Set<String> arg1) {
// TODO Auto-generated method stub
return null;
}
}
private DeckPreferenceHack mPref;
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
Timber.d("getSharedPreferences(name=%s)", name);
return mPref;
}
@Override
protected void onCreate(Bundle icicle) {
Themes.setThemeLegacy(this);
super.onCreate(icicle);
mCol = CollectionHelper.getInstance().getCol(this);
if (mCol == null) {
finish();
return;
}
Bundle extras = getIntent().getExtras();
if (extras != null && extras.containsKey("did")) {
mDeck = mCol.getDecks().get(extras.getLong("did"));
} else {
mDeck = mCol.getDecks().current();
}
registerExternalStorageListener();
try {
if (mCol == null || mDeck.getInt("dyn") != 1) {
Timber.w("No Collection loaded or deck is not a dyn deck");
finish();
return;
} else {
mPref = new DeckPreferenceHack();
mPref.registerOnSharedPreferenceChangeListener(this);
this.addPreferencesFromResource(R.xml.cram_deck_options);
this.buildLists();
this.updateSummaries();
}
} catch (JSONException e) {
throw new RuntimeException(e);
}
// Set the activity title to include the name of the deck
String title = getResources().getString(R.string.deckpreferences_title);
if (title.contains("XXX")) {
try {
title = title.replace("XXX", mDeck.getString("name"));
} catch (JSONException e) {
title = title.replace("XXX", "???");
}
}
this.setTitle(title);
// Add a home button to the actionbar
getSupportActionBar().setHomeButtonEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
closeDeckOptions();
return true;
}
return false;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// update values on changed preference
this.updateSummaries();
mPrefChanged = true;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
Timber.i("DeckOptions - onBackPressed()");
closeDeckOptions();
return true;
}
return super.onKeyDown(keyCode, event);
}
private void closeDeckOptions() {
if (mPrefChanged) {
// Rebuild the filtered deck if a setting has changed
mCol.getSched().rebuildDyn(mCol.getDecks().selected());
}
finish();
ActivityTransitionAnimation.slide(this, ActivityTransitionAnimation.FADE);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mUnmountReceiver != null) {
unregisterReceiver(mUnmountReceiver);
}
}
protected void updateSummaries() {
mAllowCommit = false;
// for all text preferences, set summary as current database value
for (String key : mPref.mValues.keySet()) {
Preference pref = this.findPreference(key);
String value = null;
if (pref == null) {
continue;
} else if (pref instanceof CheckBoxPreference) {
continue;
} else if (pref instanceof ListPreference) {
ListPreference lp = (ListPreference) pref;
CharSequence entry = lp.getEntry();
if (entry != null) {
value = entry.toString();
} else {
value = "";
}
} else {
value = this.mPref.getString(key, "");
}
// update value for EditTexts
if (pref instanceof EditTextPreference) {
((EditTextPreference) pref).setText(value);
}
// update summary
if (!mPref.mSummaries.containsKey(key)) {
CharSequence s = pref.getSummary();
mPref.mSummaries.put(key, s != null ? pref.getSummary().toString() : null);
}
String summ = mPref.mSummaries.get(key);
if (summ != null && summ.contains("XXX")) {
pref.setSummary(summ.replace("XXX", value));
} else {
pref.setSummary(value);
}
}
mAllowCommit = true;
}
protected void buildLists() {
ListPreference newOrderPref = (ListPreference) findPreference("order");
newOrderPref.setEntries(R.array.cram_deck_conf_order_labels);
newOrderPref.setEntryValues(R.array.cram_deck_conf_order_values);
newOrderPref.setValue(mPref.getString("order", "0"));
}
/**
* finish when sd card is ejected
*/
private void registerExternalStorageListener() {
if (mUnmountReceiver == null) {
mUnmountReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(SdCardReceiver.MEDIA_EJECT)) {
finish();
}
}
};
IntentFilter iFilter = new IntentFilter();
iFilter.addAction(SdCardReceiver.MEDIA_EJECT);
registerReceiver(mUnmountReceiver, iFilter);
}
}
}