/**
* XeniaStoryList.java
*
* Ver 1.0, 2012-12-1, alex_yh, Create file.
*/
package com.flounder.fishDiary;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.content.AsyncQueryHandler;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnMultiChoiceClickListener;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Toast;
import com.flounder.fishDiary.data.Constants;
import com.flounder.fishDiary.data.FishDiary;
import com.flounder.fishDiary.data.FishDiaryProvider.DatebaseHelper;
import com.flounder.fishDiary.image.ImageUtil;
import com.flounder.fishDiary.util.FileUtils;
import com.flounder.fishDiary.util.Utils;
import com.flounder.fishDiary.view.BaseStyleDialog;
import com.flounder.fishDiary.view.PasswordDialog;
import com.flounder.fishDiary.view.BaseStyleDialog.ICallBack;
import com.flounder.fishDiary.view.PasswordDialog.IPasswdCallBack;
public class FishStoryListActivity extends ListActivity {
private Button mBtnAdd;
private Button mBtnSetting;
private ImageView mImage;
private MyCursorAdapter mAdapter;
private MyQueryHandler mHandler;
private Context mContext;
private String mExportedDir;
private String mTagLists;
private String[] mTagItems;
public static final int QUERY_NOTES_TOKEN = 1;
public static final int QUERY_SINGLE_TOKEN = 99;
private static final String TIME_STAMP_NAME = "yyyy-MM-dd";
private static final String TIME_STAMP_DEATAIL = "yyyy-MM-dd HH:mm";
private static final String TIME_STAMP_RENAME = "MM-dd_HH-mm";
/** The columns needed by the cursor adapter */
private static final String[] PROJECTION = new String[] {
FishDiary.Notes._ID,
FishDiary.Notes.COLUMN_NAME_TITLE,
FishDiary.Notes.COLUMN_NAME_TAG,
FishDiary.Notes.COLUMN_NAME_AUTHOR,
FishDiary.Notes.COLUMN_NAME_ENCRYTED,
FishDiary.Notes.COLUMN_NAME_CREATE_DATE,
FishDiary.Notes.COLUMN_NAME_MODIFICATION_DATE,
FishDiary.Notes.COLUMN_NAME_NOTE };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_story_list);
initView();
// If no data is given in the Intent that started this Activity,
// use the default provider URI.
Intent intent = getIntent();
if (intent.getData() == null) {
intent.setData(FishDiary.Notes.CONTENT_URI);
}
mContext = this;
mAdapter = new MyCursorAdapter(this);
mHandler = new MyQueryHandler(getContentResolver(), mAdapter);
boolean firstTime = FishPreferences.isFirstTime(mContext);
if (firstTime) {
ContentValues value = new ContentValues();
value.put(FishDiary.Notes.COLUMN_NAME_TITLE,
getString(R.string.text_welcome));
value.put(FishDiary.Notes.COLUMN_NAME_NOTE,
FileUtils.readTextFromAssets(mContext,
Constants.FILENAME_WELCOME));
value.put(FishDiary.Notes.COLUMN_NAME_TAG, "<Welcome>");
mHandler.startInsert(0, null, getIntent().getData(), value);
updateFirstTimePref(false);
}
}
@Override
protected void onResume() {
super.onResume();
overridePendingTransition(0, 0);
initHeadPhoto();
checkEncryptState(); // check if password has been removed
mHandler.startQuery(QUERY_NOTES_TOKEN, null, getIntent().getData(),
PROJECTION, null, null, FishDiary.Notes.DEFAULT_SORT_ORDER);
setListAdapter(mAdapter);
}
private void initView() {
mBtnAdd = (Button) findViewById(R.id.btn_add);
mBtnAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
insertNote();
}
});
mBtnSetting = (Button) findViewById(R.id.btn_setting);
mBtnSetting.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FishStoryListActivity.this.openOptionsMenu();
}
});
/* Sets the callback for context menu activation for the ListView */
getListView().setOnCreateContextMenuListener(this);
}
/** Set head icon */
private void initHeadPhoto() {
mImage = (ImageView) findViewById(R.id.image);
mImage.setImageBitmap(ImageUtil.getHeadPhoto(this));
}
/** If password has been removed, update all entries first */
private void checkEncryptState() {
if (FishPreferences.getPassword(mContext) == null) {
ContentValues diffValues = new ContentValues();
diffValues.put(FishDiary.Notes.COLUMN_NAME_ENCRYTED, 0);
mHandler.startUpdate(QUERY_NOTES_TOKEN, null, getIntent().getData(),
diffValues, null, null);
}
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Uri uri = ContentUris.withAppendedId(getIntent().getData(), id);
if (viewEncryptedNote(uri))
viewNote(uri);
}
private void viewNote(Uri uri) {
Intent intent = new Intent(this, FishDiaryActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.setData(uri);
startActivity(intent);
}
private boolean viewEncryptedNote(final Uri uri) {
mHandler.startQuery(QUERY_SINGLE_TOKEN, null, uri, PROJECTION, null, null,
null);
Cursor cursor = mAdapter.getCursor();
if (cursor.getInt(cursor
.getColumnIndex(FishDiary.Notes.COLUMN_NAME_ENCRYTED)) == 0) {
return true;
}
if (FishPreferences.getPassword(mContext) != null) {
PasswordDialog.IPasswdCallBack callback = new IPasswdCallBack() {
String _passwd = "";
@Override
public void posButtonClicked() {
if (_passwd.equals(FishPreferences.getPassword(mContext))) {
viewNote(uri);
} else {
PasswordDialog.prompPasswdErrDialog(mContext);
}
}
@Override
public void negButtonClicked() {
}
@Override
public void getPassword(String password) {
_passwd = password;
}
};
new PasswordDialog(mContext, callback);
}
return false;
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.list_activity_context_menu, menu);
// If no password has been set, disable the "encrypt" options
if (FishPreferences.getPassword(mContext) == null) {
menu.removeItem(R.id.context_menu_encrypt);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.list_activity_options_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.options_menu_insert:
insertNote();
break;
case R.id.options_menu_settings:
Intent intent = new Intent(this, FishSettingsActivity.class);
startActivity(intent);
break;
case R.id.options_menu_export:
initChecking(); // check environment first
if (FishPreferences.getPassword(mContext) != null) {
PasswordDialog.IPasswdCallBack callback = new IPasswdCallBack() {
String _passwd = "";
@Override
public void posButtonClicked() {
if (_passwd.equals(FishPreferences.getPassword(mContext))) {
exportNote();
} else {
PasswordDialog.prompPasswdErrDialog(mContext);
}
}
@Override
public void negButtonClicked() {
}
@Override
public void getPassword(String password) {
_passwd = password;
}
};
new PasswordDialog(mContext, callback);
} else {
exportNote();
}
break;
case R.id.options_menu_quit:
confirmQuit();
break;
}
return true;
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
Uri noteUri = ContentUris.withAppendedId(getIntent().getData(), info.id);
switch (item.getItemId()) {
case R.id.context_menu_tag:
setTag(noteUri);
return true;
case R.id.context_menu_encrypt:
mHandler.startQuery(QUERY_SINGLE_TOKEN, null, noteUri, PROJECTION, null,
null, null);
Cursor cursor = mAdapter.getCursor();
int flag = cursor.getInt(cursor
.getColumnIndex(FishDiary.Notes.COLUMN_NAME_ENCRYTED));
setNoteEncrypted(noteUri, flag != 1);
return true;
case R.id.context_menu_delete:
confirmDelete(noteUri);
return true;
case R.id.context_menu_copy:
copyNote(noteUri);
return true;
default:
return super.onContextItemSelected(item);
}
}
private void setNoteEncrypted(final Uri uri, final boolean flag) {
if (!flag) { // unencrypt
PasswordDialog.IPasswdCallBack callback = new IPasswdCallBack() {
String _passwd = "";
@Override
public void posButtonClicked() {
if (_passwd.equals(FishPreferences.getPassword(mContext))) {
updateEncryptState(uri, false);
} else {
PasswordDialog.prompPasswdErrDialog(mContext);
}
}
@Override
public void negButtonClicked() {
}
@Override
public void getPassword(String password) {
_passwd = password;
}
};
new PasswordDialog(mContext, callback);
}
// encrypt note
updateEncryptState(uri, true);
}
private void updateEncryptState(Uri uri, boolean flag) {
ContentValues values = new ContentValues();
values.put(FishDiary.Notes.COLUMN_NAME_ENCRYTED, (flag ? 1 : 0));
mHandler.startUpdate(0, null, uri, values, null, null);
// For refresh
mHandler.startQuery(QUERY_NOTES_TOKEN, null, getIntent().getData(),
PROJECTION, null, null, FishDiary.Notes.DEFAULT_SORT_ORDER);
}
/**
* TODO: Two operation "Manage Tag(list)" and "Set (single Note's)Tag" are combined
* here into a single scenario.
* It's NOT good... They are two completely different use-cases.
* However there would be too much repeated code if separating the use's entrance.
*/
private void setTag(final Uri uri) {
String[] noteTagSelected;
final Set<Integer> selectedIndexSet = new HashSet<Integer>();
// get note's Tag, and split into string array
mHandler.startQuery(QUERY_SINGLE_TOKEN, null, uri, PROJECTION,
null, null, null);
Cursor cursor = mAdapter.getCursor();
String noteTagStr = cursor.getString(cursor
.getColumnIndex(FishDiary.Notes.COLUMN_NAME_TAG));
noteTagSelected = noteTagStr.split(Constants.TAG_SEPERATOR);
// get global Tag lists (from SharedPref)
mTagLists = FishPreferences.getNoteTag(this);
// checkedItems for MultiChoiceDialog
List<Boolean> _selected = new ArrayList<Boolean>();
boolean[] selected = null;
if (mTagLists != null && !mTagLists.equals("")) {
// compare noteTagSelected to TagList
mTagItems = mTagLists.split(Constants.TAG_SEPERATOR);
for (int i = 0; i < mTagItems.length; i++) {
String str = mTagItems[i];
_selected.add(i, false);
for (String tag : noteTagSelected) {
if (str.equals(tag)) {
selectedIndexSet.add(i);
_selected.add(i, true);
}
}
}
// and set result to array selected(boolean[])
selected = Utils.toPrimitiveArray(_selected);
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
if (mTagLists == null || mTagLists.equals(""))
// for a better looking dialog
builder.setMessage(R.string.dialog_create_tag);
builder.setMultiChoiceItems(mTagItems, selected,
new OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which,
boolean isChecked) {
if (isChecked) {
if (!selectedIndexSet.contains(which))
selectedIndexSet.add(which);
}
if (!isChecked) {
if (selectedIndexSet.contains(which))
selectedIndexSet.remove(which);
}
}
});
builder.setPositiveButton(R.string.button_ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (mTagItems == null) // Fix NPE problems
return;
StringBuilder strBuilder = new StringBuilder();
for (int i = 0; i < mTagItems.length; i++) {
if (selectedIndexSet.contains(i))
strBuilder
.append(mTagItems[i] + Constants.TAG_SEPERATOR);
}
saveTag(strBuilder.toString(), uri);
}
});
if (mTagLists != null && !mTagLists.equals("")) {
builder.setNegativeButton(R.string.button_delete, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
StringBuilder strBuilder = new StringBuilder();
// mTagItems can't be null here, needless do the null check
for (int i = 0; i < mTagItems.length; i++) {
if (selectedIndexSet.contains(i)) {
String str = mTagItems[i] + Constants.TAG_SEPERATOR;
String sql = "UPDATE " + FishDiary.Notes.TABLE_NAME
+ " SET "
+ FishDiary.Notes.COLUMN_NAME_TAG + "="
+ "REPLACE(" + FishDiary.Notes.COLUMN_NAME_TAG
+ ",'" + str + "','')";
Utils.logd(sql);
DatebaseHelper dbHelper = new DatebaseHelper(mContext);
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.execSQL(sql);
dbHelper.close(); // everything closed?
}
if (!selectedIndexSet.contains(i)) {
strBuilder
.append(mTagItems[i] + Constants.TAG_SEPERATOR);
}
}
// For refresh
mHandler.startQuery(QUERY_NOTES_TOKEN, null, getIntent()
.getData(), PROJECTION, null, null,
FishDiary.Notes.DEFAULT_SORT_ORDER);
SharedPreferences.Editor editor = FishPreferences
.getEditor(mContext);
editor.putString(FishPreferences.KEY_NOTE_TAG,
strBuilder.toString().trim());
editor.commit();
}
});
}
builder.setNeutralButton(R.string.button_add, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
promptTagDialog();
}
});
builder.show();
}
private void promptTagDialog() {
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.dialog_input_text, null);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setCancelable(false);
builder.setView(view);
builder.setTitle(R.string.dialog_create_tag);
final EditText etTag = (EditText) view.findViewById(R.id.et_dialog);
builder.setNegativeButton(R.string.button_cancel, null);
builder.setPositiveButton(R.string.button_ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String tagText = etTag.getText().toString().trim();
// check for duplication
if (mTagItems != null)
for (String str : mTagItems) {
if (tagText.equals(str))
return;
}
if (!tagText.equals("")) {
mTagLists = (mTagLists == null || mTagLists.equals("")) ?
tagText : mTagLists + Constants.TAG_SEPERATOR + tagText;
SharedPreferences.Editor editor = FishPreferences
.getEditor(mContext);
editor.putString(FishPreferences.KEY_NOTE_TAG, mTagLists);
editor.commit();
Toast.makeText(mContext, R.string.text_tag_added,
Toast.LENGTH_SHORT).show();
}
}
});
builder.show();
}
private void saveTag(String str, Uri uri) {
ContentValues values = new ContentValues();
values.put(FishDiary.Notes.COLUMN_NAME_TAG, str);
mHandler.startUpdate(0, null, uri, values, null, null);
// For refresh
mHandler.startQuery(QUERY_NOTES_TOKEN, null, getIntent().getData(),
PROJECTION, null, null, FishDiary.Notes.DEFAULT_SORT_ORDER);
}
private void insertNote() {
Intent intent = new Intent(FishStoryListActivity.this,
FishDiaryActivity.class);
intent.setAction(Intent.ACTION_INSERT);
intent.setData(getIntent().getData());
startActivity(intent);
}
private void initChecking() {
if (!FileUtils.isSDCardMounted()) {
Utils.showErrorAndFinish(this, R.string.error_sdcard_unmount);
}
if (!FileUtils.isRootFolderCreated()) {
Utils.showErrorAndFinish(this, R.string.error_failed_create_folder);
}
}
private String makeExportFolder() {
String date = new SimpleDateFormat(TIME_STAMP_NAME).format(new Date());
File dir = new File(FileUtils.getRootFolder(), date);
if (!dir.exists() || !dir.isDirectory())
dir.mkdirs();
return date;
}
private void exportNote() {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
saveNoteToText();
return null;
}
@Override
protected void onPostExecute(Void result) {
String msg = getString(R.string.text_exported)
+ FileUtils.getRootFolder() + File.separator + mExportedDir;
BaseStyleDialog.buildMsgDialog(mContext, msg);
}
}.execute();
}
private void saveNoteToText() {
mExportedDir = makeExportFolder();
Cursor cursor = mAdapter.getCursor();
int encryptStateFlag;
long lCreateDate, lModifyDate;
String authorName, noteName, noteContent, createDate, modifyDate, encryptState, noteTag;
String createDateStr = getString(R.string.export_text_createAt);
String modifyDateStr = getString(R.string.export_text_modifyAt);
String tagStr = getString(R.string.export_text_tag);
String authorStr = getString(R.string.export_text_author);
String encryptStr = getString(R.string.export_text_encrypt);
if (cursor != null) {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
// Get note createDate
lCreateDate = cursor.getLong(cursor
.getColumnIndex(FishDiary.Notes.COLUMN_NAME_CREATE_DATE));
createDate = new SimpleDateFormat(TIME_STAMP_DEATAIL)
.format(new Date(lCreateDate));
// Get noteName, rename name if invalid for fileName
noteName = cursor.getString(cursor
.getColumnIndex(FishDiary.Notes.COLUMN_NAME_TITLE));
if (!FileUtils.isFileNameValid(noteName))
// noteName should be different [fix]
noteName = "note_" + new SimpleDateFormat(TIME_STAMP_RENAME)
.format(new Date(lCreateDate));
// Get noteContent
noteContent = cursor.getString(cursor
.getColumnIndex(FishDiary.Notes.COLUMN_NAME_NOTE));
// Get note modifyDate
lModifyDate = cursor
.getLong(cursor
.getColumnIndex(FishDiary.Notes.COLUMN_NAME_MODIFICATION_DATE));
modifyDate = new SimpleDateFormat(TIME_STAMP_DEATAIL)
.format(new Date(lModifyDate));
// Get author name
authorName = cursor.getString(cursor
.getColumnIndex(FishDiary.Notes.COLUMN_NAME_AUTHOR));
// Get encrypt state
encryptStateFlag = cursor.getInt(cursor
.getColumnIndex(FishDiary.Notes.COLUMN_NAME_ENCRYTED));
encryptState = (encryptStateFlag == 1) ? getString(R.string.yes)
: getString(R.string.no);
// Get Note tag
noteTag = "";
String _noteTag = cursor.getString(cursor
.getColumnIndex(FishDiary.Notes.COLUMN_NAME_TAG));
for (String s : _noteTag.split(Constants.TAG_SEPERATOR))
noteTag += (s + " ");
StringBuilder builder = new StringBuilder(noteContent);
builder.append("\n\n");
builder.append("[" + authorStr + authorName + "]\n");
builder.append("[" + createDateStr + createDate + "]\n");
builder.append("[" + modifyDateStr + modifyDate + "]\n");
if (!noteTag.equals("")) // remove this line if no tag
builder.append("[" + tagStr + noteTag + "]\n");
builder.append("[" + encryptStr + encryptState + "]");
FileUtils.saveTextToFile(noteName, mExportedDir, builder.toString());
cursor.moveToNext();
}
}
}
private void copyNote(Uri uri) {
mHandler.startQuery(QUERY_SINGLE_TOKEN, null, uri, PROJECTION,
null, null, null);
Cursor _cursor = mAdapter.getCursor();
if (_cursor != null) {
_cursor.moveToFirst();
String note = _cursor.getString(_cursor
.getColumnIndex(FishDiary.Notes.COLUMN_NAME_NOTE));
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setPrimaryClip(ClipData.newPlainText("Note", note));
Toast.makeText(mContext, R.string.text_copied, Toast.LENGTH_SHORT)
.show();
}
}
private void deleteNote(Uri uri) {
mHandler.startDelete(0, null, uri, null, null);
mHandler.startQuery(QUERY_NOTES_TOKEN, null, getIntent().getData(),
PROJECTION, null, null, FishDiary.Notes.DEFAULT_SORT_ORDER);
}
private void confirmDelete(final Uri uri) {
BaseStyleDialog.ICallBack callback = new ICallBack() {
@Override
public void posButtonClicked() {
deleteNote(uri);
}
@Override
public void negButtonClicked() {
}
};
BaseStyleDialog.buildInfoDialog(this, R.string.dialog_confirm_delete,
callback);
}
private void confirmQuit() {
BaseStyleDialog.ICallBack callback = new ICallBack() {
@Override
public void posButtonClicked() {
finish();
}
@Override
public void negButtonClicked() {
}
};
BaseStyleDialog
.buildInfoDialog(this, R.string.dialog_confirm_quit, callback);
}
@Override
public void onBackPressed() {
confirmQuit();
}
private void updateFirstTimePref(boolean value) {
SharedPreferences.Editor editor = FishPreferences.getEditor(mContext);
editor.putBoolean(FishPreferences.KEY_FIRST_TIME, value);
editor.commit();
}
/** Perform provider operations asynchronously on a handler thread */
private class MyQueryHandler extends AsyncQueryHandler {
private MyCursorAdapter mAdapter;
public MyQueryHandler(ContentResolver cr, MyCursorAdapter adapter) {
super(cr);
this.mAdapter = adapter;
}
@Override
protected void onDeleteComplete(int token, Object cookie, int result) {
super.onDeleteComplete(token, cookie, result);
mAdapter.notifyDataSetChanged();
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
super.onQueryComplete(token, cookie, cursor);
if (token == QUERY_NOTES_TOKEN) {
mAdapter.changeCursor(cursor);
}
}
}
}