/*
Copyright (C) 2012 Haowen Ning
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 2
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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.liberty.android.fantastischmemo.ui;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.Toast;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.liberty.android.fantastischmemo.common.AMEnv;
import org.liberty.android.fantastischmemo.common.AMPrefKeys;
import org.liberty.android.fantastischmemo.common.AnyMemoDBOpenHelper;
import org.liberty.android.fantastischmemo.common.AnyMemoDBOpenHelperManager;
import org.liberty.android.fantastischmemo.R;
import org.liberty.android.fantastischmemo.common.BaseActivity;
import org.liberty.android.fantastischmemo.dao.CardDao;
import org.liberty.android.fantastischmemo.dao.CategoryDao;
import org.liberty.android.fantastischmemo.dao.LearningDataDao;
import org.liberty.android.fantastischmemo.entity.Card;
import org.liberty.android.fantastischmemo.entity.Category;
import org.liberty.android.fantastischmemo.entity.LearningData;
import org.liberty.android.fantastischmemo.ui.AudioRecorderFragment.AudioRecorderResultListener;
import org.liberty.android.fantastischmemo.ui.CategoryEditorFragment.CategoryEditorResultListener;
import java.io.File;
public class CardEditor extends BaseActivity {
private final int ACTIVITY_IMAGE_FILE = 1;
private final int ACTIVITY_AUDIO_FILE = 2;
private static final int PERMISSION_REQUEST_RECORD_AUDIO = 1;
Card currentCard = null;
Card prevCard = null;
private Integer prevOrdinal = null;
private Integer currentCardId;
private EditText questionEdit;
private EditText answerEdit;
private Button categoryButton;
private EditText noteEdit;
private RadioGroup addRadio;
private boolean addBack = true;
private boolean isEditNew = false;
private String dbName = null;
String dbPath = null;
CardDao cardDao;
CategoryDao categoryDao;
LearningDataDao learningDataDao;
private InitTask initTask;
private AnyMemoDBOpenHelper helper;
private String originalQuestion;
private String originalAnswer;
private String originalNote;
public static String EXTRA_DBPATH = "dbpath";
public static String EXTRA_CARD_ID = "id";
public static String EXTRA_RESULT_CARD_ID= "result_card_id";
public static String EXTRA_IS_EDIT_NEW = "is_edit_new";
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.card_editor_layout);
initTask = new InitTask();
initTask.execute((Void)null);
}
@Override
public void onDestroy() {
super.onDestroy();
AnyMemoDBOpenHelperManager.releaseHelper(helper);
}
@Override
public void restartActivity() {
assert currentCard != null : "Null card is used when restarting activity";
assert dbPath != null : "Use null dbPath to restartAcitivity";
Intent myIntent = new Intent(this, CardEditor.class);
myIntent.putExtra(EXTRA_CARD_ID, currentCard.getId());
myIntent.putExtra(EXTRA_DBPATH, dbPath);
finish();
startActivity(myIntent);
}
@Override
public void onBackPressed() {
String qText = questionEdit.getText().toString();
String aText = answerEdit.getText().toString();
String nText = noteEdit.getText().toString();
if (!isEditNew && (!qText.equals(originalQuestion) || !aText.equals(originalAnswer) || !nText.equals(originalNote))) {
new AlertDialog.Builder(this)
.setTitle(R.string.warning_text)
.setMessage(R.string.edit_dialog_unsave_warning)
.setPositiveButton(R.string.yes_text, new DialogInterface.OnClickListener(){
public void onClick(DialogInterface d, int which){
SaveCardTask task = new SaveCardTask();
task.execute((Void)null);
}
})
.setNeutralButton(R.string.no_text, new DialogInterface.OnClickListener(){
public void onClick(DialogInterface d, int which){
Intent resultIntent = new Intent();
setResult(Activity.RESULT_CANCELED, resultIntent);
finish();
}
})
.setNegativeButton(R.string.cancel_text, null)
.create()
.show();
}
else{
Intent resultIntent = new Intent();
setResult(Activity.RESULT_CANCELED, resultIntent);
finish();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu){
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.card_editor_menu, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
View focusView = getCurrentFocus();
switch (item.getItemId()) {
case R.id.save:
SaveCardTask task = new SaveCardTask();
task.execute((Void)null);
return true;
case R.id.editor_menu_br:
if(focusView == questionEdit || focusView ==answerEdit || focusView == noteEdit){
addTextToView((EditText)focusView, "<br />");
}
return true;
case R.id.editor_menu_image:
if(focusView == questionEdit || focusView ==answerEdit || focusView == noteEdit){
Intent myIntent = new Intent(this, FileBrowserActivity.class);
myIntent.putExtra(FileBrowserActivity.EXTRA_FILE_EXTENSIONS, ".png,.jpg,.tif,.bmp");
startActivityForResult(myIntent, ACTIVITY_IMAGE_FILE);
}
return true;
case R.id.add_existing_audio:
if (isViewEligibleToEditAudio()) {
addExistingAudio();
}
return true;
case R.id.add_new_audio:
if (isViewEligibleToEditAudio()) {
addNewAudio();
}
return true;
case R.id.remove_audio:
if (isViewEligibleToEditAudio()) {
removeAudio();
}
return true;
}
return false;
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_RECORD_AUDIO: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startAudioRecorder();
} else {
Toast.makeText(this, R.string.record_audio_permission_denied_message, Toast.LENGTH_LONG)
.show();
}
return;
}
}
}
private boolean isViewEligibleToEditAudio(){
View focusView = getCurrentFocus();
if(focusView == questionEdit || focusView == answerEdit){
return true;
} else {
return false;
}
}
private void showConfirmDialog(String msg, DialogInterface.OnClickListener positiveClickListener){
new AlertDialog.Builder(this)
.setMessage(msg)
.setPositiveButton(getString(R.string.yes_text), positiveClickListener)
.setNegativeButton(getString(R.string.no_text), null)
.create()
.show();
}
private boolean audioPreviouslyExists(){
View focusView = getCurrentFocus();
String curContent = ((EditText)focusView).getText().toString();
return curContent.contains("src=");
}
private void addExistingAudio(){
if(audioPreviouslyExists()){
//if there is audio previously defined,show alert
DialogInterface.OnClickListener positiveClickListener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
startAudioBrowser();
}
};
showConfirmDialog(getString(R.string.override_audio_warning_text), positiveClickListener);
} else {
startAudioBrowser();
}
}
private void addNewAudio(){
if(audioPreviouslyExists()){
//if there is audio previously defined,show alert
DialogInterface.OnClickListener positiveClickListener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
startAudioRecorderWithPermissionCheck();
}
};
showConfirmDialog(getString(R.string.override_audio_warning_text), positiveClickListener);
} else {
startAudioRecorderWithPermissionCheck();
}
}
private void removeAudio(){
View focusView = getCurrentFocus();
if(focusView == questionEdit){
currentCard.setQuestion(currentCard.getQuestion().replaceAll("<audio src=.*/>", ""));
((EditText)focusView).setText(currentCard.getQuestion());
} else if (focusView == answerEdit) {
currentCard.setAnswer(currentCard.getAnswer().replaceAll("<audio src=.*/>", ""));
((EditText)focusView).setText(currentCard.getAnswer());
} else {
return;
}
}
private void startAudioBrowser(){
removeAudio();
Intent myIntent = new Intent(this, FileBrowserActivity.class);
myIntent.putExtra(FileBrowserActivity.EXTRA_FILE_EXTENSIONS, ".3gp,.ogg,.mp3,.wav,.amr");
startActivityForResult(myIntent, ACTIVITY_AUDIO_FILE);
}
private void startAudioRecorderWithPermissionCheck() {
// Request record permission
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.RECORD_AUDIO},
PERMISSION_REQUEST_RECORD_AUDIO);
} else {
startAudioRecorder();
}
}
private void startAudioRecorder(){
removeAudio();
View focusView = getCurrentFocus();
String audioFilename = AMEnv.DEFAULT_AUDIO_PATH + dbName;
new File(audioFilename).mkdirs();
AudioRecorderFragment recorder = new AudioRecorderFragment();
if (focusView == questionEdit) {
audioFilename += "/"+ currentCardId + "_q.3gp";
} else if (focusView == answerEdit) {
audioFilename += "/"+ currentCardId + "_a.3gp";
} else {
return;
}
Bundle b = new Bundle();
b.putString(AudioRecorderFragment.EXTRA_AUDIO_FILENAME, audioFilename);
recorder.setAudioRecorderResultListener(new AudioRecorderResultListener() {
public void onReceiveAudio() {
View focusView = getCurrentFocus();
if(focusView == questionEdit){
addTextToView((EditText) focusView, "<audio src=\"" + currentCardId + "_q.3gp\" />");
} else if (focusView == answerEdit){
addTextToView((EditText) focusView, "<audio src=\"" + currentCardId + "_a.3gp\" />");
}
}
});
recorder.setArguments(b);
getSupportFragmentManager().beginTransaction()
.add(recorder, "AudioRecorderDialog")
.commitAllowingStateLoss();
}
private void addTextToView(EditText v, String text){
String origText = v.getText().toString();
/*
* keep track of the cursor location and restore it
* after pasting because the default location is the
* begining of the EditText
*/
int cursorPos = v.getSelectionStart();
try{
String newText = origText.substring(0, cursorPos) + text + origText.substring(cursorPos, origText.length());
v.setText(newText);
v.setSelection(cursorPos + text.length());
}
catch(Exception e){
Log.e(TAG, "cursor position is wrong", e);
}
}
public void onActivityResult(int requestCode, int resultCode, Intent data){
super.onActivityResult(requestCode, resultCode, data);
String name, path;
switch(requestCode){
case ACTIVITY_IMAGE_FILE:
if(resultCode == Activity.RESULT_OK){
View focusView = getCurrentFocus();
if(focusView == questionEdit || focusView ==answerEdit || focusView == noteEdit){
path = data.getStringExtra(FileBrowserActivity.EXTRA_RESULT_PATH);
name = FilenameUtils.getName(path);
addTextToView((EditText)focusView, "<img src=\"" + name + "\" />");
/* Copy the image to correct location */
String imageRoot = AMEnv.DEFAULT_IMAGE_PATH;
String imagePath = imageRoot + dbName + "/";
new File(imageRoot).mkdir();
new File(imagePath).mkdir();
try{
String target = imagePath + name;
if(!(new File(target)).exists()){
FileUtils.copyFile(new File(path), new File(target));
}
}
catch(Exception e){
Log.e(TAG, "Error copying image", e);
}
}
}
break;
case ACTIVITY_AUDIO_FILE:
if(resultCode == Activity.RESULT_OK){
View focusView = getCurrentFocus();
if(focusView == questionEdit || focusView ==answerEdit || focusView == noteEdit){
path = data.getStringExtra(FileBrowserActivity.EXTRA_RESULT_PATH);
name = FilenameUtils.getName(path);
addTextToView((EditText)focusView, "<audio src=\"" + name + "\" />");
/* Copy the image to correct location */
String audioRoot = AMEnv.DEFAULT_AUDIO_PATH;
String audioPath = audioRoot + dbName + "/";
new File(audioRoot).mkdir();
new File(audioPath).mkdir();
try{
String target = audioPath + name;
if(!(new File(target)).exists()){
FileUtils.copyFile(new File(path), new File(audioPath + name));
}
}
catch(Exception e){
Log.e(TAG, "Error copying audio", e);
}
}
}
break;
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
private void setInitRadioButton(){
if(!isEditNew){
addRadio.setVisibility(View.GONE);
addBack = false;
}
else{
/*
* The radio button is only valid when the user is creating
* new items. If the user is editng, it has no effect at all
*/
addRadio.setVisibility(View.VISIBLE);
final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
// Only for new card we need to add back.
// For existing cards, we just edit the current card.
addBack = settings.getBoolean(AMPrefKeys.ADD_BACK_KEY, true);
if(addBack){
addRadio.check(R.id.add_back_radio);
}
else{
addRadio.check(R.id.add_here_radio);
}
RadioGroup.OnCheckedChangeListener changeListener = new RadioGroup.OnCheckedChangeListener(){
public void onCheckedChanged(RadioGroup group, int checkedId){
SharedPreferences.Editor editor = settings.edit();
if(checkedId == R.id.add_here_radio){
addBack = false;
editor.putBoolean(AMPrefKeys.ADD_BACK_KEY, false);
editor.commit();
}
else{
addBack = true;
editor.putBoolean(AMPrefKeys.ADD_BACK_KEY, true);
editor.commit();
}
}
};
addRadio.setOnCheckedChangeListener(changeListener);
}
}
private void updateViews() {
updateCategoryView();
/* Prefill the note if it is empty */
if(isEditNew){
/* Use this one or the one below ?*/
noteEdit.setText(currentCard.getNote());
}
if(!isEditNew){
originalQuestion = currentCard.getQuestion();
originalAnswer = currentCard.getAnswer();
originalNote = currentCard.getNote();
questionEdit.setText(originalQuestion);
answerEdit.setText(originalAnswer);
noteEdit.setText(originalNote);
}
}
private void updateCategoryView() {
/* Retain the last category when editing new */
String categoryName = currentCard.getCategory().getName();
if (categoryName.equals("")) {
categoryButton.setText(R.string.uncategorized_text);
} else {
categoryButton.setText(categoryName);
}
}
private class InitTask extends AsyncTask<Void, Void, Void> {
private ProgressDialog progressDialog;
@Override
public void onPreExecute() {
setTitle(R.string.memo_edit_dialog_title);
Bundle extras = getIntent().getExtras();
if (extras != null) {
currentCardId = extras.getInt(EXTRA_CARD_ID);
dbPath = extras.getString(EXTRA_DBPATH);
dbName = FilenameUtils.getName(dbPath);
isEditNew = extras.getBoolean(EXTRA_IS_EDIT_NEW);
}
progressDialog = new ProgressDialog(CardEditor.this);
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.setTitle(getString(R.string.loading_please_wait));
progressDialog.setMessage(getString(R.string.loading_database));
progressDialog.setCancelable(false);
progressDialog.show();
assert dbPath != null : "dbPath shouldn't be null";
}
@Override
public Void doInBackground(Void... params) {
helper =
AnyMemoDBOpenHelperManager.getHelper(CardEditor.this, dbPath);
cardDao = helper.getCardDao();
categoryDao = helper.getCategoryDao();
learningDataDao = helper.getLearningDataDao();
Card prevCard = cardDao.queryForId(currentCardId);
if (prevCard != null) {
prevOrdinal = prevCard.getOrdinal();
}
if (isEditNew) {
currentCard = new Card();
// Search for "Uncategorized".
Category c = categoryDao.queryForId(1);
currentCard.setCategory(c);
// Save the ordinal to be used when saving.
LearningData ld = new LearningData();
learningDataDao.create(ld);
currentCard.setLearningData(ld);
} else {
currentCard = prevCard;
}
assert currentCard != null : "Try to edit null card!";
categoryDao.refresh(currentCard.getCategory());
return null;
}
@Override
public void onPostExecute(Void result){
// It means empty set
questionEdit = (EditText)findViewById(R.id.edit_dialog_question_entry);
answerEdit = (EditText)findViewById(R.id.edit_dialog_answer_entry);
categoryButton = (Button)findViewById(R.id.edit_dialog_category_button);
noteEdit = (EditText)findViewById(R.id.edit_dialog_note_entry);
addRadio = (RadioGroup)findViewById(R.id.add_radio);
categoryButton.setOnClickListener(categoryButtonClickListener);
updateViews();
/* Should be called after the private fields are inited */
setInitRadioButton();
progressDialog.dismiss();
}
}
private View.OnClickListener categoryButtonClickListener =
new View.OnClickListener() {
public void onClick(View v) {
CategoryEditorFragment df = new CategoryEditorFragment();
df.setResultListener(categoryResultListener);
Bundle b = new Bundle();
b.putString(CategoryEditorFragment.EXTRA_DBPATH, dbPath);
b.putInt(CategoryEditorFragment.EXTRA_CATEGORY_ID, currentCard.getCategory().getId());
df.setArguments(b);
df.show(getSupportFragmentManager(), "CategoryEditDialog");
}
};
private class SaveCardTask extends AsyncTask<Void, Void, Void> {
private ProgressDialog progressDialog;
@Override
public void onPreExecute() {
progressDialog = new ProgressDialog(CardEditor.this);
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.setTitle(getString(R.string.loading_please_wait));
progressDialog.setMessage(getString(R.string.loading_database));
progressDialog.setCancelable(false);
progressDialog.show();
String qText = questionEdit.getText().toString();
String aText = answerEdit.getText().toString();
String nText = noteEdit.getText().toString();
currentCard.setQuestion(qText);
currentCard.setAnswer(aText);
currentCard.setNote(nText);
assert currentCard != null : "Current card shouldn't be null";
}
@Override
public Void doInBackground(Void... params) {
if (prevOrdinal != null && !addBack) {
currentCard.setOrdinal(prevOrdinal);
} else {
Card lastCard = cardDao.queryLastOrdinal();
// last card = null means this is the first card to add
// We should set ordinal to 1.
if (lastCard == null) {
currentCard.setOrdinal(1);
} else {
int lastOrd = lastCard.getOrdinal();
currentCard.setOrdinal(lastOrd + 1);
}
}
if (isEditNew) {
cardDao.create(currentCard);
} else {
cardDao.update(currentCard);
}
return null;
}
@Override
public void onPostExecute(Void result){
progressDialog.dismiss();
Intent resultIntent = new Intent();
resultIntent.putExtra(EXTRA_RESULT_CARD_ID, currentCard.getId());
setResult(Activity.RESULT_OK, resultIntent);
finish();
}
}
// When a category is selected in category fragment.
private CategoryEditorResultListener categoryResultListener =
new CategoryEditorResultListener() {
public void onReceiveCategory(Category c) {
currentCard.setCategory(c);
updateCategoryView();
}
};
}