/*
* Copyright (c) 2012 - 2015 Ngewi Fet <ngewif@gmail.com>
*
* 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 org.gnucash.android.ui.homescreen;
import android.app.Activity;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.support.v4.widget.SimpleCursorAdapter;
import android.support.v7.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ImageButton;
import android.widget.RemoteViews;
import android.widget.Spinner;
import android.widget.Toast;
import org.gnucash.android.R;
import org.gnucash.android.db.BookDbHelper;
import org.gnucash.android.db.DatabaseHelper;
import org.gnucash.android.db.DatabaseSchema;
import org.gnucash.android.db.adapter.AccountsDbAdapter;
import org.gnucash.android.db.adapter.BooksDbAdapter;
import org.gnucash.android.model.Account;
import org.gnucash.android.model.Book;
import org.gnucash.android.model.Money;
import org.gnucash.android.receivers.TransactionAppWidgetProvider;
import org.gnucash.android.ui.account.AccountsActivity;
import org.gnucash.android.ui.common.FormActivity;
import org.gnucash.android.ui.common.UxArgument;
import org.gnucash.android.ui.settings.PreferenceActivity;
import org.gnucash.android.ui.transaction.TransactionsActivity;
import org.gnucash.android.util.QualifiedAccountNameCursorAdapter;
import java.util.Locale;
import java.util.prefs.Preferences;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* Activity for configuration which account to display on a widget.
* The activity is opened each time a widget is added to the homescreen
* @author Ngewi Fet <ngewif@gmail.com>
*/
public class WidgetConfigurationActivity extends Activity {
private AccountsDbAdapter mAccountsDbAdapter;
private int mAppWidgetId;
@BindView(R.id.input_accounts_spinner) Spinner mAccountsSpinner;
@BindView(R.id.input_books_spinner) Spinner mBooksSpinner;
@BindView(R.id.input_hide_account_balance) CheckBox mHideAccountBalance;
@BindView(R.id.btn_save) Button mOkButton;
@BindView(R.id.btn_cancel) Button mCancelButton;
private SimpleCursorAdapter mAccountsCursorAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.widget_configuration);
setResult(RESULT_CANCELED);
ButterKnife.bind(this);
BooksDbAdapter booksDbAdapter = BooksDbAdapter.getInstance();
Cursor booksCursor = booksDbAdapter.fetchAllRecords();
String currentBookUID = booksDbAdapter.getActiveBookUID();
//determine the position of the currently active book in the cursor
int position = 0;
while (booksCursor.moveToNext()){
String bookUID = booksCursor.getString(booksCursor.getColumnIndexOrThrow(DatabaseSchema.BookEntry.COLUMN_UID));
if (bookUID.equals(currentBookUID))
break;
++position;
}
SimpleCursorAdapter booksCursorAdapter = new SimpleCursorAdapter(this,
android.R.layout.simple_spinner_item, booksCursor,
new String[]{DatabaseSchema.BookEntry.COLUMN_DISPLAY_NAME},
new int[]{android.R.id.text1}, 0);
booksCursorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mBooksSpinner.setAdapter(booksCursorAdapter);
mBooksSpinner.setSelection(position);
mAccountsDbAdapter = AccountsDbAdapter.getInstance();
Cursor cursor = mAccountsDbAdapter.fetchAllRecordsOrderedByFullName();
if (cursor.getCount() <= 0){
Toast.makeText(this, R.string.error_no_accounts, Toast.LENGTH_LONG).show();
finish();
}
mAccountsCursorAdapter = new QualifiedAccountNameCursorAdapter(this, cursor);
//without this line, the app crashes when a user tries to select an account
mAccountsCursorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mAccountsSpinner.setAdapter(mAccountsCursorAdapter);
boolean passcodeEnabled = PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
.getBoolean(UxArgument.ENABLED_PASSCODE, false);
mHideAccountBalance.setChecked(passcodeEnabled);
bindListeners();
}
/**
* Sets click listeners for the buttons in the dialog
*/
private void bindListeners() {
mBooksSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Book book = BooksDbAdapter.getInstance().getRecord(id);
SQLiteDatabase db = new DatabaseHelper(WidgetConfigurationActivity.this, book.getUID()).getWritableDatabase();
mAccountsDbAdapter = new AccountsDbAdapter(db);
Cursor cursor = mAccountsDbAdapter.fetchAllRecordsOrderedByFullName();
mAccountsCursorAdapter.swapCursor(cursor);
mAccountsCursorAdapter.notifyDataSetChanged();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
//nothing to see here, move along
}
});
mOkButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
mAppWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
}
if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID){
finish();
return;
}
String bookUID = BooksDbAdapter.getInstance().getUID(mBooksSpinner.getSelectedItemId());
String accountUID = mAccountsDbAdapter.getUID(mAccountsSpinner.getSelectedItemId());
boolean hideAccountBalance = mHideAccountBalance.isChecked();
configureWidget(WidgetConfigurationActivity.this, mAppWidgetId, bookUID, accountUID, hideAccountBalance);
updateWidget(WidgetConfigurationActivity.this, mAppWidgetId);
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
}
});
mCancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
/**
* Configure a given widget with the given parameters.
* @param context The current context
* @param appWidgetId ID of the widget to configure
* @param bookUID UID of the book for this widget
* @param accountUID UID of the account for this widget
* @param hideAccountBalance <code>true</code> if the account balance should be hidden,
* <code>false</code> otherwise
*/
public static void configureWidget(final Context context, int appWidgetId, String bookUID, String accountUID, boolean hideAccountBalance) {
context.getSharedPreferences("widget:" + appWidgetId, MODE_PRIVATE).edit()
.putString(UxArgument.BOOK_UID, bookUID)
.putString(UxArgument.SELECTED_ACCOUNT_UID, accountUID)
.putBoolean(UxArgument.HIDE_ACCOUNT_BALANCE_IN_WIDGET, hideAccountBalance)
.apply();
}
/**
* Remove the configuration for a widget. Primarily this should be called when a widget is
* destroyed.
* @param context The current context
* @param appWidgetId ID of the widget whose configuration should be removed
*/
public static void removeWidgetConfiguration(final Context context, int appWidgetId) {
context.getSharedPreferences("widget:" + appWidgetId, MODE_PRIVATE).edit()
.clear()
.apply();
}
/**
* Load obsolete preferences for a widget, if they exist, and save them using the new widget
* configuration format.
* @param context The current context
* @param appWidgetId ID of the widget whose configuration to load/save
*/
private static void loadOldPreferences(Context context, int appWidgetId) {
SharedPreferences preferences = PreferenceActivity.getActiveBookSharedPreferences();
String accountUID = preferences.getString(UxArgument.SELECTED_ACCOUNT_UID + appWidgetId, null);
if (accountUID != null) {
String bookUID = BooksDbAdapter.getInstance().getActiveBookUID();
boolean hideAccountBalance = preferences.getBoolean(UxArgument.HIDE_ACCOUNT_BALANCE_IN_WIDGET + appWidgetId, false);
configureWidget(context, appWidgetId, bookUID, accountUID, hideAccountBalance);
preferences.edit()
.remove(UxArgument.SELECTED_ACCOUNT_UID + appWidgetId)
.remove(UxArgument.HIDE_ACCOUNT_BALANCE_IN_WIDGET + appWidgetId)
.apply();
}
}
/**
* Updates the widget with id <code>appWidgetId</code> with information from the
* account with record ID <code>accountId</code>
* If the account has been deleted, then a notice is posted in the widget
* @param appWidgetId ID of the widget to be updated
*/
public static void updateWidget(final Context context, int appWidgetId) {
Log.i("WidgetConfiguration", "Updating widget: " + appWidgetId);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
loadOldPreferences(context, appWidgetId);
SharedPreferences preferences = context.getSharedPreferences("widget:" + appWidgetId, MODE_PRIVATE);
String bookUID = preferences.getString(UxArgument.BOOK_UID, null);
String accountUID = preferences.getString(UxArgument.SELECTED_ACCOUNT_UID, null);
boolean hideAccountBalance = preferences.getBoolean(UxArgument.HIDE_ACCOUNT_BALANCE_IN_WIDGET, false);
if (bookUID == null || accountUID == null) {
return;
}
AccountsDbAdapter accountsDbAdapter = new AccountsDbAdapter(BookDbHelper.getDatabase(bookUID));
final Account account;
try {
account = accountsDbAdapter.getRecord(accountUID);
} catch (IllegalArgumentException e) {
Log.i("WidgetConfiguration", "Account not found, resetting widget " + appWidgetId);
//if account has been deleted, let the user know
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.widget_4x1);
views.setTextViewText(R.id.account_name, context.getString(R.string.toast_account_deleted));
views.setTextViewText(R.id.transactions_summary, "");
//set it to simply open the app
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
new Intent(context, AccountsActivity.class), 0);
views.setOnClickPendingIntent(R.id.widget_layout, pendingIntent);
views.setOnClickPendingIntent(R.id.btn_new_transaction, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, views);
Editor editor = PreferenceActivity.getActiveBookSharedPreferences().edit(); //PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.remove(UxArgument.SELECTED_ACCOUNT_UID + appWidgetId);
editor.apply();
return;
}
final RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.widget_4x1);
views.setTextViewText(R.id.account_name, account.getName());
Money accountBalance = accountsDbAdapter.getAccountBalance(accountUID, -1, System.currentTimeMillis());
if (hideAccountBalance) {
views.setViewVisibility(R.id.transactions_summary, View.GONE);
} else {
views.setTextViewText(R.id.transactions_summary,
accountBalance.formattedString(Locale.getDefault()));
int color = accountBalance.isNegative() ? R.color.debit_red : R.color.credit_green;
views.setTextColor(R.id.transactions_summary, context.getResources().getColor(color));
}
Intent accountViewIntent = new Intent(context, TransactionsActivity.class);
accountViewIntent.setAction(Intent.ACTION_VIEW);
accountViewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
accountViewIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
accountViewIntent.putExtra(UxArgument.BOOK_UID, bookUID);
PendingIntent accountPendingIntent = PendingIntent
.getActivity(context, appWidgetId, accountViewIntent, 0);
views.setOnClickPendingIntent(R.id.widget_layout, accountPendingIntent);
if (accountsDbAdapter.isPlaceholderAccount(accountUID)) {
views.setOnClickPendingIntent(R.id.btn_view_account, accountPendingIntent);
views.setViewVisibility(R.id.btn_new_transaction, View.GONE);
} else {
Intent newTransactionIntent = new Intent(context, FormActivity.class);
newTransactionIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
newTransactionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
newTransactionIntent.putExtra(UxArgument.FORM_TYPE, FormActivity.FormType.TRANSACTION.name());
newTransactionIntent.putExtra(UxArgument.BOOK_UID, bookUID);
newTransactionIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
PendingIntent pendingIntent = PendingIntent
.getActivity(context, appWidgetId, newTransactionIntent, 0);
views.setOnClickPendingIntent(R.id.btn_new_transaction, pendingIntent);
views.setViewVisibility(R.id.btn_view_account, View.GONE);
}
appWidgetManager.updateAppWidget(appWidgetId, views);
}
/**
* Updates all widgets belonging to the application
* @param context Application context
*/
public static void updateAllWidgets(final Context context){
Log.i("WidgetConfiguration", "Updating all widgets");
AppWidgetManager widgetManager = AppWidgetManager.getInstance(context);
ComponentName componentName = new ComponentName(context, TransactionAppWidgetProvider.class);
final int[] appWidgetIds = widgetManager.getAppWidgetIds(componentName);
//update widgets asynchronously so as not to block method which called the update
//inside the computation of the account balance
new Thread(new Runnable() {
@Override
public void run() {
for (final int widgetId : appWidgetIds) {
updateWidget(context, widgetId);
}
}
}).start();
}
}