/* * Copyright (c) 2013 Menny Even-Danan * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.anysoftkeyboard.ui.settings.wordseditor; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.database.Cursor; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v7.app.ActionBar; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.GridView; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.Spinner; import com.anysoftkeyboard.dictionaries.EditableDictionary; import com.anysoftkeyboard.dictionaries.UserDictionary; import com.anysoftkeyboard.dictionaries.WordsCursor; import com.anysoftkeyboard.keyboards.KeyboardAddOnAndBuilder; import com.anysoftkeyboard.keyboards.KeyboardFactory; import com.anysoftkeyboard.utils.Log; import com.menny.android.anysoftkeyboard.AnyApplication; import com.menny.android.anysoftkeyboard.R; import net.evendanan.pushingpixels.AsyncTaskWithProgressWindow; import net.evendanan.pushingpixels.FragmentChauffeurActivity; import net.evendanan.pushingpixels.PassengerFragmentSupport; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; public class UserDictionaryEditorFragment extends Fragment implements AsyncTaskWithProgressWindow.AsyncTaskOwner, AdapterView.OnItemClickListener, UserWordsListAdapter.AdapterCallbacks { private Dialog mDialog; private static final String ASK_USER_WORDS_SDCARD_FILENAME = "UserWords.xml"; static final int DIALOG_SAVE_SUCCESS = 10; static final int DIALOG_SAVE_FAILED = 11; static final int DIALOG_LOAD_SUCCESS = 20; static final int DIALOG_LOAD_FAILED = 21; static final String TAG = "ASK_UDE"; Spinner mLanguagesSpinner; WordsCursor mCursor; private String mSelectedLocale = null; EditableDictionary mCurrentDictionary; AbsListView mWordsListView;//this may be either ListView or GridView (in tablets) private static final Comparator<UserWordsListAdapter.Word> msWordsComparator = new Comparator<UserWordsListAdapter.Word>() { @Override public int compare(UserWordsListAdapter.Word lhs, UserWordsListAdapter.Word rhs) { return lhs.word.compareTo(rhs.word); } }; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { setHasOptionsMenu(true); FragmentChauffeurActivity activity = (FragmentChauffeurActivity) getActivity(); ActionBar actionBar = activity.getSupportActionBar(); actionBar.setDisplayShowCustomEnabled(true); actionBar.setDisplayShowTitleEnabled(false); View v = inflater.inflate(R.layout.words_editor_actionbar_view, null); mLanguagesSpinner = (Spinner) v.findViewById(R.id.user_dictionay_langs); actionBar.setCustomView(v); return inflater.inflate(R.layout.user_dictionary_editor, container, false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mLanguagesSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) { mSelectedLocale = ((DictionaryLocale) arg0.getItemAtPosition(arg2)).getLocale(); fillWordsList(); } public void onNothingSelected(AdapterView<?> arg0) { Log.d(TAG, "No locale selected"); mSelectedLocale = null; } }); View emptyView = view.findViewById(R.id.empty_user_dictionary); emptyView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { createEmptyItemForAdd(); } }); mWordsListView = (AbsListView) view.findViewById(android.R.id.list); mWordsListView.setFastScrollEnabled(true); //this is for the "empty state" - it will allow the user to quickly add the first word. mWordsListView.setEmptyView(emptyView); mWordsListView.setOnItemClickListener(this); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // Inflate the menu items for use in the action bar inflater.inflate(R.menu.words_editor_menu_actions, menu); super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.add_user_word: createEmptyItemForAdd(); return true; case R.id.backup_words: new BackupUserWordsAsyncTask(UserDictionaryEditorFragment.this, ASK_USER_WORDS_SDCARD_FILENAME).execute(); return true; case R.id.restore_words: new RestoreUserWordsAsyncTask(UserDictionaryEditorFragment.this, ASK_USER_WORDS_SDCARD_FILENAME).execute(); return true; default: return super.onOptionsItemSelected(item); } } private void createEmptyItemForAdd() { UserWordsListAdapter adapter = (UserWordsListAdapter) mWordsListView.getAdapter(); final int addWordItemIndex = adapter.getCount() == 0 ? 0 : adapter.getCount() - 1; //will use smooth scrolling on API8+ AnyApplication.getDeviceSpecific().performListScrollToPosition(mWordsListView, addWordItemIndex); onItemClick(mWordsListView, null, addWordItemIndex, 0l); } @Override public void onStart() { super.onStart(); PassengerFragmentSupport.setActivityTitle(this, getString(R.string.user_dict_settings_titlebar)); fillLanguagesSpinner(); } @Override public void onDestroy() { FragmentChauffeurActivity activity = (FragmentChauffeurActivity) getActivity(); ActionBar actionBar = activity.getSupportActionBar(); actionBar.setDisplayShowCustomEnabled(false); actionBar.setDisplayShowTitleEnabled(true); actionBar.setCustomView(null); if (mDialog != null && mDialog.isShowing()) mDialog.dismiss(); mDialog = null; super.onDestroy(); if (mCursor != null) mCursor.close(); if (mCurrentDictionary != null) mCurrentDictionary.close(); mCursor = null; mCurrentDictionary = null; } void fillLanguagesSpinner() { new UserWordsEditorAsyncTask(this) { private ArrayAdapter<DictionaryLocale> mAdapter; @Override protected void onPreExecute() { super.onPreExecute(); //creating in the UI thread mAdapter = new ArrayAdapter<>( getActivity(), android.R.layout.simple_spinner_item); } @Override protected Void doAsyncTask(Void[] params) throws Exception { ArrayList<DictionaryLocale> languagesList = new ArrayList<>(); ArrayList<KeyboardAddOnAndBuilder> keyboards = KeyboardFactory.getEnabledKeyboards(getActivity().getApplicationContext()); for (KeyboardAddOnAndBuilder kbd : keyboards) { String locale = kbd.getKeyboardLocale(); if (TextUtils.isEmpty(locale)) continue; DictionaryLocale dictionaryLocale = new DictionaryLocale(locale, kbd.getName()); //Don't worry, DictionaryLocale equals any DictionaryLocale with the same locale (no matter what its name is) if (languagesList.contains(dictionaryLocale)) continue; Log.d(TAG, "Adding locale " + locale + " to editor."); languagesList.add(dictionaryLocale); } mAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); for (DictionaryLocale lang : languagesList) mAdapter.add(lang); return null; } @Override protected void applyResults(Void result, Exception backgroundException) { mLanguagesSpinner.setAdapter(mAdapter); } }.execute(); } public void showDialog(int id) { mDialog = onCreateDialog(id); mDialog.show(); } private Dialog onCreateDialog(int id) { switch (id) { case DIALOG_SAVE_SUCCESS: return createDialogAlert(R.string.user_dict_backup_success_title, R.string.user_dict_backup_success_text); case DIALOG_SAVE_FAILED: return createDialogAlert(R.string.user_dict_backup_fail_title, R.string.user_dict_backup_fail_text); case DIALOG_LOAD_SUCCESS: return createDialogAlert(R.string.user_dict_restore_success_title, R.string.user_dict_restore_success_text); case DIALOG_LOAD_FAILED: return createDialogAlert(R.string.user_dict_restore_fail_title, R.string.user_dict_restore_fail_text); } return null; } private Dialog createDialogAlert(int title, int text) { return new AlertDialog.Builder(getActivity()) .setTitle(title) .setMessage(text) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }).create(); } private void deleteWord(String word) { mCurrentDictionary.deleteWord(word); } private void fillWordsList() { Log.d(TAG, "Selected locale is " + mSelectedLocale); new UserWordsEditorAsyncTask(this) { private EditableDictionary mNewDictionary; private List<UserWordsListAdapter.Word> mWordsList; @Override protected void onPreExecute() { super.onPreExecute(); // all the code below can be safely (and must) be called in the // UI thread. mNewDictionary = getEditableDictionary(mSelectedLocale); if (mNewDictionary != mCurrentDictionary && mCurrentDictionary != null && mCursor != null) { mCurrentDictionary.close(); } } @Override protected Void doAsyncTask(Void[] params) throws Exception { mCurrentDictionary = mNewDictionary; mCurrentDictionary.loadDictionary(); mCursor = mCurrentDictionary.getWordsCursor(); Cursor cursor = mCursor.getCursor(); mWordsList = new ArrayList<>(mCursor.getCursor().getCount()); cursor.moveToFirst(); while (!cursor.isAfterLast()) { UserWordsListAdapter.Word word = new UserWordsListAdapter.Word( mCursor.getCurrentWord(), mCursor.getCurrentWordFrequency()); mWordsList.add(word); cursor.moveToNext(); } //now, sorting the word list alphabetically Collections.sort(mWordsList, msWordsComparator); return null; } protected void applyResults(Void result, Exception backgroundException) { ListAdapter adapter = getWordsListAdapter(mWordsList); //AbsListView introduced the setAdapter method in API11, so I'm required to check the instance type if (mWordsListView instanceof ListView) { //this is NOT a redundant cast! ((ListView)mWordsListView).setAdapter(adapter); } else if (mWordsListView instanceof GridView) { //this is NOT a redundant cast! ((GridView)mWordsListView).setAdapter(adapter); } else { throw new ClassCastException("Unknown mWordsListView type "+mWordsListView.getClass()); } } }.execute(); } protected ListAdapter getWordsListAdapter(List<UserWordsListAdapter.Word> wordsList) { return new UserWordsListAdapter( getActivity(), wordsList, this); } protected EditableDictionary getEditableDictionary(String locale) { return new UserDictionary(getActivity().getApplicationContext(), locale); } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { ((UserWordsListAdapter) mWordsListView.getAdapter()).onItemClicked(parent, position); } @Override public void onWordDeleted(final UserWordsListAdapter.Word word) { new UserWordsEditorAsyncTask(this) { @Override protected Void doAsyncTask(Void[] params) throws Exception { deleteWord(word.word); return null; } @Override protected void applyResults(Void aVoid, Exception backgroundException) { fillWordsList(); } }.execute(); } @Override public void onWordUpdated(final String oldWord, final UserWordsListAdapter.Word newWord) { new UserWordsEditorAsyncTask(this) { @Override protected Void doAsyncTask(Void[] params) throws Exception { if (!TextUtils.isEmpty(oldWord))//it can be empty in case it's a new word. deleteWord(oldWord); deleteWord(newWord.word); mCurrentDictionary.addWord(newWord.word, newWord.frequency); return null; } @Override protected void applyResults(Void aVoid, Exception backgroundException) { fillWordsList(); } }.execute(); } @Override public void performDiscardEdit() { ((UserWordsListAdapter) mWordsListView.getAdapter()).onItemClicked(mWordsListView, -1/*doesn't really matter what position it is*/); } }