/*******************************************************************************
* Copyright (c) 2010 Denis Solonenko.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v2.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* Denis Solonenko - initial API and implementation
******************************************************************************/
package ru.orangesoftware.financisto2.activity;
import android.app.DatePickerDialog;
import android.app.DatePickerDialog.OnDateSetListener;
import android.app.TimePickerDialog;
import android.app.TimePickerDialog.OnTimeSetListener;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.text.InputType;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import android.widget.TimePicker;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.Extra;
import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.OptionsMenuItem;
import java.io.File;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import ru.orangesoftware.financisto2.R;
import ru.orangesoftware.financisto2.datetime.DateUtils;
import ru.orangesoftware.financisto2.db.DatabaseHelper.AccountColumns;
import ru.orangesoftware.financisto2.db.DatabaseHelper.TransactionColumns;
import ru.orangesoftware.financisto2.model.Account;
import ru.orangesoftware.financisto2.model.Attribute;
import ru.orangesoftware.financisto2.model.Category;
import ru.orangesoftware.financisto2.model.Payee;
import ru.orangesoftware.financisto2.model.SystemAttribute;
import ru.orangesoftware.financisto2.model.Transaction;
import ru.orangesoftware.financisto2.model.TransactionAttribute;
import ru.orangesoftware.financisto2.model.TransactionStatus;
import ru.orangesoftware.financisto2.recur.NotificationOptions;
import ru.orangesoftware.financisto2.recur.Recurrence;
import ru.orangesoftware.financisto2.utils.EnumUtils;
import ru.orangesoftware.financisto2.utils.MyPreferences;
import ru.orangesoftware.financisto2.utils.TransactionUtils;
import ru.orangesoftware.financisto2.view.AttributeView;
import ru.orangesoftware.financisto2.view.AttributeViewFactory;
import ru.orangesoftware.financisto2.widget.RateLayoutView;
import static ru.orangesoftware.financisto2.utils.ThumbnailUtil.PICTURES_DIR;
import static ru.orangesoftware.financisto2.utils.ThumbnailUtil.PICTURES_THUMB_DIR;
import static ru.orangesoftware.financisto2.utils.ThumbnailUtil.PICTURE_FILE_NAME_FORMAT;
import static ru.orangesoftware.financisto2.utils.ThumbnailUtil.createAndStoreImageThumbnail;
import static ru.orangesoftware.financisto2.utils.Utils.text;
@EActivity
public abstract class AbstractTransactionActivity extends AbstractActivity implements CategorySelector.CategorySelectorListener {
private static final int RECURRENCE_REQUEST = 4003;
private static final int NOTIFICATION_REQUEST = 4004;
private static final int PICTURE_REQUEST = 4005;
private static final TransactionStatus[] statuses = TransactionStatus.values();
@Extra
protected long accountId = -1;
@Extra
protected long transactionId = -1;
@Extra
protected long transactionDateTime = System.currentTimeMillis();
@Extra
protected boolean isDuplicate = false;
@Extra
boolean isNewFromTemplate = false;
@Extra
int template = 0;
@OptionsMenuItem(R.id.menu_save_and_new)
protected MenuItem saveAndNewMenuItem;
protected RateLayoutView rateView;
protected EditText templateName;
protected TextView accountText;
protected Cursor accountCursor;
protected ListAdapter accountAdapter;
protected Calendar dateTime;
protected ImageButton status;
protected Button dateText;
protected Button timeText;
protected EditText noteText;
protected TextView recurText;
protected TextView notificationText;
private ImageView pictureView;
private String pictureFileName;
private CheckBox ccardPayment;
protected Account selectedAccount;
protected String recurrence;
protected String notificationOptions;
protected ProjectSelector projectSelector;
protected CategorySelector categorySelector;
protected boolean isRememberLastAccount;
protected boolean isRememberLastCategory;
protected boolean isRememberLastProject;
protected boolean isShowNote;
protected boolean isShowTakePicture;
protected boolean isShowIsCCardPayment;
protected boolean isOpenCalculatorForTemplates;
protected boolean isShowPayee = true;
protected AutoCompleteTextView payeeText;
protected SimpleCursorAdapter payeeAdapter;
protected AttributeView deleteAfterExpired;
protected DateFormat df;
protected DateFormat tf;
protected Transaction transaction = new Transaction();
public AbstractTransactionActivity() {}
protected abstract int getLayoutId();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
df = DateUtils.getLongDateFormat(this);
tf = DateUtils.getTimeFormat(this);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(getLayoutId());
}
@AfterViews
public void initViews() {
long t0 = System.currentTimeMillis();
isRememberLastAccount = MyPreferences.isRememberAccount(this);
isRememberLastCategory = isRememberLastAccount && MyPreferences.isRememberCategory(this);
isRememberLastProject = isRememberLastCategory && MyPreferences.isRememberProject(this);
isShowNote = MyPreferences.isShowNote(this);
isShowTakePicture = MyPreferences.isShowTakePicture(this);
isShowIsCCardPayment = MyPreferences.isShowIsCCardPayment(this);
isOpenCalculatorForTemplates = MyPreferences.isOpenCalculatorForTemplates(this);
fetchCategories();
projectSelector = new ProjectSelector(this, x);
projectSelector.fetchProjects();
transaction.dateTime = transactionDateTime;
if (transactionId != -1) {
transaction = db.getTransaction(transactionId);
transaction.categoryAttributes = db.getAllAttributesForTransaction(transactionId);
if (isDuplicate) {
transaction.id = -1;
transaction.dateTime = System.currentTimeMillis();
}
}
if (template > 0) {
transaction.isTemplate = template;
}
if (transaction.id == -1) {
accountCursor = db.getAllActiveAccounts();
} else {
accountCursor = db.getAccountsForTransaction(transaction);
}
startManagingCursor(accountCursor);
accountAdapter = TransactionUtils.createAccountAdapter(this, accountCursor);
dateTime = Calendar.getInstance();
Date date = dateTime.getTime();
status = (ImageButton)findViewById(R.id.status);
status.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
ArrayAdapter<String> adapter = EnumUtils.createDropDownAdapter(AbstractTransactionActivity.this, statuses);
x.selectPosition(AbstractTransactionActivity.this, R.id.status, R.string.transaction_status, adapter, transaction.status.ordinal());
}
});
dateText = (Button)findViewById(R.id.date);
dateText.setText(df.format(date));
dateText.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View arg0) {
DatePickerDialog d = new DatePickerDialog(AbstractTransactionActivity.this, new OnDateSetListener(){
@Override
public void onDateSet(DatePicker arg0, int y, int m, int d) {
dateTime.set(y, m, d);
dateText.setText(df.format(dateTime.getTime()));
}
}, dateTime.get(Calendar.YEAR), dateTime.get(Calendar.MONTH), dateTime.get(Calendar.DAY_OF_MONTH));
d.show();
}
});
timeText = (Button)findViewById(R.id.time);
timeText.setText(tf.format(date));
timeText.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View arg0) {
boolean is24Format = DateUtils.is24HourFormat(AbstractTransactionActivity.this);
TimePickerDialog d = new TimePickerDialog(AbstractTransactionActivity.this, new OnTimeSetListener(){
@Override
public void onTimeSet(TimePicker picker, int h, int m) {
dateTime.set(Calendar.HOUR_OF_DAY, picker.getCurrentHour());
dateTime.set(Calendar.MINUTE, picker.getCurrentMinute());
timeText.setText(tf.format(dateTime.getTime()));
}
}, dateTime.get(Calendar.HOUR_OF_DAY), dateTime.get(Calendar.MINUTE), is24Format);
d.show();
}
});
internalOnCreate();
LinearLayout layout = (LinearLayout)findViewById(R.id.list);
this.templateName = new EditText(this);
if (transaction.isTemplate()) {
x.addEditNode(layout, R.string.template_name, templateName);
}
rateView = new RateLayoutView(this, x, layout);
createListNodes(layout);
categorySelector.createAttributesLayout(layout);
createCommonNodes(layout);
if (transaction.isScheduled()) {
recurText = x.addListNode(layout, R.id.recurrence_pattern, R.string.recur, R.string.recur_interval_no_recur);
notificationText = x.addListNode(layout, R.id.notification, R.string.notification, R.string.notification_options_default);
Attribute sa = db.getSystemAttribute(SystemAttribute.DELETE_AFTER_EXPIRED);
deleteAfterExpired = AttributeViewFactory.createViewForAttribute(this, sa);
String value = transaction.getSystemAttribute(SystemAttribute.DELETE_AFTER_EXPIRED);
deleteAfterExpired.inflateView(layout, value != null ? value : sa.defaultValue);
}
if (transactionId != -1) {
isOpenCalculatorForTemplates &= isNewFromTemplate;
editTransaction(transaction);
} else {
setDateTime(transaction.dateTime);
categorySelector.selectCategory(0);
if (accountId != -1) {
selectAccount(accountId);
} else {
long lastAccountId = MyPreferences.getLastAccount(this);
if (isRememberLastAccount && lastAccountId != -1) {
selectAccount(lastAccountId);
}
}
if (!isRememberLastProject) {
projectSelector.selectProject(0);
}
if (transaction.isScheduled()) {
selectStatus(TransactionStatus.PN);
}
}
long t1 = System.currentTimeMillis();
Log.i("TransactionActivity", "onCreate "+(t1-t0)+"ms");
}
protected void internalOnCreate() {
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
boolean result = super.onCreateOptionsMenu(menu);
if (transaction.id > 0) {
saveAndNewMenuItem.setTitle(R.string.cancel);
}
return result;
}
protected void createPayeeNode(LinearLayout layout) {
payeeAdapter = TransactionUtils.createPayeeAdapter(this, db);
payeeText = new AutoCompleteTextView(this);
payeeText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS |
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS |
InputType.TYPE_TEXT_VARIATION_FILTER);
payeeText.setThreshold(1);
payeeText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean hasFocus) {
if (hasFocus) {
payeeText.setAdapter(payeeAdapter);
payeeText.selectAll();
}
}
});
x.addEditNode(layout, R.string.payee, payeeText);
}
protected abstract void fetchCategories();
@OptionsItem(R.id.menu_save)
protected boolean saveAndFinish() {
long id = save();
if (id > 0) {
Intent data = new Intent();
data.putExtra(TransactionColumns._id.name(), id);
setResult(RESULT_OK, data);
finish();
return true;
}
return false;
}
@OptionsItem(R.id.menu_save_and_new)
protected void saveAndNew() {
if (transaction.id > 0) {
setResult(RESULT_CANCELED);
finish();
} else {
if (saveAndFinish()) {
//intent.putExtra(DATETIME_EXTRA, transaction.dateTime);
//startActivityForResult(intent, -1);
}
}
}
private long save() {
if (onOKClicked()) {
boolean isNew = transaction.id == -1;
long id = db.insertOrUpdate(transaction, getAttributes());
if (isNew) {
MyPreferences.setLastAccount(this, transaction.fromAccountId);
}
AccountWidget.updateWidgets(this);
return id;
}
return -1;
}
private List<TransactionAttribute> getAttributes() {
List<TransactionAttribute> attributes = categorySelector.getAttributes();
if (deleteAfterExpired != null) {
TransactionAttribute ta = deleteAfterExpired.newTransactionAttribute();
attributes.add(ta);
}
return attributes;
}
@Override
protected boolean shouldLock() {
return MyPreferences.isPinProtectedNewTransaction(this);
}
protected void createCommonNodes(LinearLayout layout) {
int noteOrder = MyPreferences.getNoteOrder(this);
int projectOrder = MyPreferences.getProjectOrder(this);
for (int i=0; i<6; i++) {
if (i == noteOrder) {
if (isShowNote) {
//note
noteText = new EditText(this);
x.addEditNode(layout, R.string.note, noteText);
}
}
if (i == projectOrder) {
projectSelector.createNode(layout);
}
}
if (isShowTakePicture && transaction.isNotTemplateLike()) {
pictureView = x.addPictureNodeMinus(this, layout, R.id.attach_picture, R.id.delete_picture, R.string.attach_picture, R.string.new_picture);
}
if (isShowIsCCardPayment) {
// checkbox to register if the transaction is a credit card payment.
// this will be used to exclude from totals in bill preview
ccardPayment = x.addCheckboxNode(layout, R.id.is_ccard_payment,
R.string.is_ccard_payment, R.string.is_ccard_payment_summary, false);
}
}
protected abstract void createListNodes(LinearLayout layout);
protected abstract boolean onOKClicked();
@Override
protected void onClick(View v, int id) {
projectSelector.onClick(id);
categorySelector.onClick(id);
switch(id) {
case R.id.account:
x.select(this, R.id.account, R.string.account, accountCursor, accountAdapter,
AccountColumns.ID, getSelectedAccountId());
break;
case R.id.recurrence_pattern: {
Intent intent = new Intent(this, RecurrenceActivity.class);
intent.putExtra(RecurrenceActivity.RECURRENCE_PATTERN, recurrence);
startActivityForResult(intent, RECURRENCE_REQUEST);
break;
}
case R.id.notification: {
Intent intent = new Intent(this, NotificationOptionsActivity.class);
intent.putExtra(NotificationOptionsActivity.NOTIFICATION_OPTIONS, notificationOptions);
startActivityForResult(intent, NOTIFICATION_REQUEST);
break;
}
case R.id.attach_picture: {
PICTURES_DIR.mkdirs();
PICTURES_THUMB_DIR.mkdirs();
pictureFileName = PICTURE_FILE_NAME_FORMAT.format(new Date())+".jpg";
transaction.blobKey=null;
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT,
Uri.fromFile(new File(PICTURES_DIR, pictureFileName)));
startActivityForResult(intent, PICTURE_REQUEST);
break;
}
case R.id.delete_picture: {
removePicture();
break;
}
case R.id.is_ccard_payment: {
ccardPayment.setChecked(!ccardPayment.isChecked());
transaction.isCCardPayment = ccardPayment.isChecked()?1:0;
}
}
}
@Override
public void onSelectedPos(int id, int selectedPos) {
projectSelector.onSelectedPos(id, selectedPos);
switch(id) {
case R.id.status:
selectStatus(statuses[selectedPos]);
break;
}
}
@Override
public void onSelectedId(int id, long selectedId) {
categorySelector.onSelectedId(id, selectedId);
switch(id) {
case R.id.account:
selectAccount(selectedId);
break;
}
}
private void selectStatus(TransactionStatus transactionStatus) {
transaction.status = transactionStatus;
status.setImageResource(transactionStatus.iconId);
}
protected Account selectAccount(long accountId) {
return selectAccount(accountId, true);
}
protected Account selectAccount(long accountId, boolean selectLast) {
Account a = db.getAccount(accountId);
if (a != null) {
accountText.setText(a.title);
rateView.selectCurrencyFrom(a.currency);
selectedAccount = a;
}
return a;
}
protected long getSelectedAccountId() {
return selectedAccount != null ? selectedAccount.id : -1;
}
@Override
public void onCategorySelected(Category category, boolean selectLast) {
addOrRemoveSplits();
categorySelector.addAttributes(transaction);
switchIncomeExpenseButton(category);
if (selectLast && isRememberLastProject) {
projectSelector.selectProject(category.lastProjectId);
}
projectSelector.setProjectNodeVisible(!category.isSplit());
}
protected void addOrRemoveSplits() {
}
protected void switchIncomeExpenseButton(Category category) {
}
private void setRecurrence(String recurrence) {
this.recurrence = recurrence;
if (recurrence == null) {
recurText.setText(R.string.recur_interval_no_recur);
dateText.setEnabled(true);
timeText.setEnabled(true);
} else {
dateText.setEnabled(false);
timeText.setEnabled(false);
Recurrence r = Recurrence.parse(recurrence);
recurText.setText(r.toInfoString(this));
}
}
private void setNotification(String notificationOptions) {
this.notificationOptions = notificationOptions;
if (notificationOptions == null) {
notificationText.setText(R.string.notification_options_default);
} else {
NotificationOptions o = NotificationOptions.parse(notificationOptions);
notificationText.setText(o.toInfoString(this));
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
projectSelector.onActivityResult(requestCode, resultCode, data);
categorySelector.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
rateView.onActivityResult(requestCode, data);
switch (requestCode) {
case RECURRENCE_REQUEST:
String recurrence = data.getStringExtra(RecurrenceActivity.RECURRENCE_PATTERN);
setRecurrence(recurrence);
break;
case NOTIFICATION_REQUEST:
String notificationOptions = data.getStringExtra(NotificationOptionsActivity.NOTIFICATION_OPTIONS);
setNotification(notificationOptions);
break;
case PICTURE_REQUEST:
selectPicture(pictureFileName);
break;
default:
break;
}
} else {
if (requestCode == PICTURE_REQUEST) {
removePicture();
}
}
}
private void selectPicture(String pictureFileName) {
if (pictureView == null) {
return;
}
if (pictureFileName == null) {
return;
}
File pictureFile = new File(PICTURES_DIR, pictureFileName);
if (pictureFile.exists()) {
Bitmap thumb = createThumbnail(pictureFile);
pictureView.setImageBitmap(thumb);
pictureView.setTag(pictureFileName);
transaction.attachedPicture = pictureFileName;
}
}
private void removePicture() {
if (pictureView == null) {
return;
}
if (pictureFileName != null) {
new File(PICTURES_DIR, pictureFileName).delete();
new File(PICTURES_THUMB_DIR, pictureFileName).delete();
}
pictureFileName = null;
transaction.attachedPicture = null;
transaction.blobKey=null;
pictureView.setImageBitmap(null);
pictureView.setTag(null);
}
private Bitmap createThumbnail(File pictureFile) {
return createAndStoreImageThumbnail(getContentResolver(), pictureFile);
}
protected void setDateTime(long date) {
Date d = new Date(date);
dateTime.setTime(d);
dateText.setText(df.format(d));
timeText.setText(tf.format(d));
}
protected abstract void editTransaction(Transaction transaction);
protected void commonEditTransaction(Transaction transaction) {
selectStatus(transaction.status);
categorySelector.selectCategory(transaction.categoryId, false);
projectSelector.selectProject(transaction.projectId);
setDateTime(transaction.dateTime);
if (isShowNote) {
noteText.setText(transaction.note);
}
if (transaction.isTemplate()) {
templateName.setText(transaction.templateName);
}
if (transaction.isScheduled()) {
setRecurrence(transaction.recurrence);
setNotification(transaction.notificationOptions);
}
if (isShowTakePicture) {
selectPicture(transaction.attachedPicture);
}
if (isShowIsCCardPayment) {
setIsCCardPayment(transaction.isCCardPayment);
}
if (transaction.isCreatedFromTemlate()&& isOpenCalculatorForTemplates ){
rateView.openFromAmountCalculator();
}
}
private void setIsCCardPayment(int isCCardPaymentValue) {
transaction.isCCardPayment = isCCardPaymentValue;
ccardPayment.setChecked(isCCardPaymentValue==1);
}
protected void updateTransactionFromUI(Transaction transaction) {
transaction.categoryId = categorySelector.getSelectedCategoryId();
transaction.projectId = projectSelector.getSelectedProjectId();
if (transaction.isScheduled()) {
DateUtils.zeroSeconds(dateTime);
}
transaction.dateTime = dateTime.getTime().getTime();
if (isShowPayee) {
transaction.payeeId = db.insertPayee(text(payeeText)).id;
}
if (isShowNote) {
transaction.note = text(noteText);
}
if (transaction.isTemplate()) {
transaction.templateName = text(templateName);
}
if (transaction.isScheduled()) {
transaction.recurrence = recurrence;
transaction.notificationOptions = notificationOptions;
}
}
protected void selectPayee(long payeeId) {
if (isShowPayee) {
Payee p = db.get(Payee.class, payeeId);
selectPayee(p);
}
}
protected void selectPayee(Payee p) {
if (p != null) {
payeeText.setText(p.title);
transaction.payeeId = p.id;
}
}
}