Java tutorial
/* * Copyright (c) 2012-2013 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.export; import android.Manifest; import android.app.Activity; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.app.Fragment; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.SwitchCompat; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.LinearLayout; import android.widget.RadioButton; import android.widget.Spinner; import android.widget.TextView; import com.codetroopers.betterpickers.calendardatepicker.CalendarDatePickerDialogFragment; import com.codetroopers.betterpickers.radialtimepicker.RadialTimePickerDialogFragment; import com.codetroopers.betterpickers.recurrencepicker.EventRecurrence; import com.codetroopers.betterpickers.recurrencepicker.EventRecurrenceFormatter; import com.codetroopers.betterpickers.recurrencepicker.RecurrencePickerDialogFragment; import com.dropbox.core.android.Auth; import org.gnucash.android.R; import org.gnucash.android.app.GnuCashApplication; import org.gnucash.android.db.adapter.DatabaseAdapter; import org.gnucash.android.db.adapter.ScheduledActionDbAdapter; import org.gnucash.android.export.DropboxHelper; import org.gnucash.android.export.ExportAsyncTask; import org.gnucash.android.export.ExportFormat; import org.gnucash.android.export.ExportParams; import org.gnucash.android.model.BaseModel; import org.gnucash.android.model.ScheduledAction; import org.gnucash.android.ui.account.AccountsActivity; import org.gnucash.android.ui.common.UxArgument; import org.gnucash.android.ui.settings.BackupPreferenceFragment; import org.gnucash.android.ui.settings.dialog.OwnCloudDialogFragment; import org.gnucash.android.ui.transaction.TransactionFormFragment; import org.gnucash.android.ui.util.RecurrenceParser; import org.gnucash.android.ui.util.RecurrenceViewClickListener; import org.gnucash.android.util.PreferencesHelper; import org.gnucash.android.util.TimestampHelper; import java.sql.Timestamp; import java.text.ParseException; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import butterknife.BindView; import butterknife.ButterKnife; /** * Dialog fragment for exporting accounts and transactions in various formats * <p>The dialog is used for collecting information on the export options and then passing them * to the {@link org.gnucash.android.export.Exporter} responsible for exporting</p> * @author Ngewi Fet <ngewif@gmail.com> */ public class ExportFormFragment extends Fragment implements RecurrencePickerDialogFragment.OnRecurrenceSetListener, CalendarDatePickerDialogFragment.OnDateSetListener, RadialTimePickerDialogFragment.OnTimeSetListener { /** * Spinner for selecting destination for the exported file. * The destination could either be SD card, or another application which * accepts files, like Google Drive. */ @BindView(R.id.spinner_export_destination) Spinner mDestinationSpinner; /** * Checkbox for deleting all transactions after exporting them */ @BindView(R.id.checkbox_post_export_delete) CheckBox mDeleteAllCheckBox; /** * Text view for showing warnings based on chosen export format */ @BindView(R.id.export_warning) TextView mExportWarningTextView; /** * Recurrence text view */ @BindView(R.id.input_recurrence) TextView mRecurrenceTextView; /** * Text view displaying start date to export from */ @BindView(R.id.export_start_date) TextView mExportStartDate; @BindView(R.id.export_start_time) TextView mExportStartTime; /** * Switch toggling whether to export all transactions or not */ @BindView(R.id.switch_export_all) SwitchCompat mExportAllSwitch; @BindView(R.id.export_date_layout) LinearLayout mExportDateLayout; @BindView(R.id.radio_ofx_format) RadioButton mOfxRadioButton; @BindView(R.id.radio_qif_format) RadioButton mQifRadioButton; @BindView(R.id.radio_xml_format) RadioButton mXmlRadioButton; /** * Event recurrence options */ private EventRecurrence mEventRecurrence = new EventRecurrence(); /** * Recurrence rule */ private String mRecurrenceRule; private Calendar mExportStartCalendar = Calendar.getInstance(); /** * Tag for logging */ private static final String TAG = "ExportFormFragment"; /** * Export format */ private ExportFormat mExportFormat = ExportFormat.QIF; private ExportParams.ExportTarget mExportTarget = ExportParams.ExportTarget.SD_CARD; private void onRadioButtonClicked(View view) { switch (view.getId()) { case R.id.radio_ofx_format: mExportFormat = ExportFormat.OFX; if (GnuCashApplication.isDoubleEntryEnabled()) { mExportWarningTextView.setText(getActivity().getString(R.string.export_warning_ofx)); mExportWarningTextView.setVisibility(View.VISIBLE); } else { mExportWarningTextView.setVisibility(View.GONE); } mExportDateLayout.setVisibility(View.VISIBLE); break; case R.id.radio_qif_format: mExportFormat = ExportFormat.QIF; //TODO: Also check that there exist transactions with multiple currencies before displaying warning if (GnuCashApplication.isDoubleEntryEnabled()) { mExportWarningTextView.setText(getActivity().getString(R.string.export_warning_qif)); mExportWarningTextView.setVisibility(View.VISIBLE); } else { mExportWarningTextView.setVisibility(View.GONE); } mExportDateLayout.setVisibility(View.VISIBLE); break; case R.id.radio_xml_format: mExportFormat = ExportFormat.XML; mExportWarningTextView.setText(R.string.export_warning_xml); mExportDateLayout.setVisibility(View.GONE); break; } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_export_form, container, false); ButterKnife.bind(this, view); bindViewListeners(); return view; } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.default_save_actions, menu); MenuItem menuItem = menu.findItem(R.id.menu_save); menuItem.setTitle(R.string.btn_export); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_save: startExport(); return true; case android.R.id.home: getActivity().finish(); return true; default: return super.onOptionsItemSelected(item); } } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); ActionBar supportActionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); assert supportActionBar != null; supportActionBar.setTitle(R.string.title_export_dialog); setHasOptionsMenu(true); getSDWritePermission(); } @Override public void onResume() { super.onResume(); DropboxHelper.retrieveAndSaveToken(); } @Override public void onPause() { super.onPause(); // When the user try to export sharing to 3rd party service like DropBox // then pausing all activities. That cause passcode screen appearing happened. // We use a disposable flag to skip this unnecessary passcode screen. SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); prefs.edit().putBoolean(UxArgument.SKIP_PASSCODE_SCREEN, true).apply(); } /** * Get permission for WRITING SD card for Android Marshmallow and above */ private void getSDWritePermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (getActivity().checkSelfPermission( Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { getActivity().requestPermissions( new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE }, AccountsActivity.REQUEST_PERMISSION_WRITE_SD_CARD); } } } /** * Starts the export of transactions with the specified parameters */ private void startExport() { ExportParams exportParameters = new ExportParams(mExportFormat); if (mExportAllSwitch.isChecked()) { exportParameters.setExportStartTime(TimestampHelper.getTimestampFromEpochZero()); } else { exportParameters.setExportStartTime(new Timestamp(mExportStartCalendar.getTimeInMillis())); } exportParameters.setExportTarget(mExportTarget); exportParameters.setDeleteTransactionsAfterExport(mDeleteAllCheckBox.isChecked()); Log.i(TAG, "Commencing async export of transactions"); new ExportAsyncTask(getActivity(), GnuCashApplication.getActiveDb()).execute(exportParameters); if (mRecurrenceRule != null) { ScheduledAction scheduledAction = new ScheduledAction(ScheduledAction.ActionType.BACKUP); scheduledAction.setRecurrence(RecurrenceParser.parse(mEventRecurrence)); scheduledAction.setTag(exportParameters.toCsv()); scheduledAction.setActionUID(BaseModel.generateUID()); ScheduledActionDbAdapter.getInstance().addRecord(scheduledAction, DatabaseAdapter.UpdateMethod.insert); } int position = mDestinationSpinner.getSelectedItemPosition(); PreferenceManager.getDefaultSharedPreferences(getActivity()).edit() .putInt(getString(R.string.key_last_export_destination), position).apply(); // finish the activity will cause the progress dialog to be leaked // which would throw an exception //getActivity().finish(); } /** * Bind views to actions when initializing the export form */ private void bindViewListeners() { // export destination bindings ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(getActivity(), R.array.export_destinations, android.R.layout.simple_spinner_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mDestinationSpinner.setAdapter(adapter); mDestinationSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { View recurrenceOptionsView = getView().findViewById(R.id.recurrence_options); switch (position) { case 0: mExportTarget = ExportParams.ExportTarget.SD_CARD; recurrenceOptionsView.setVisibility(View.VISIBLE); break; case 1: recurrenceOptionsView.setVisibility(View.VISIBLE); mExportTarget = ExportParams.ExportTarget.DROPBOX; String dropboxAppKey = getString(R.string.dropbox_app_key, BackupPreferenceFragment.DROPBOX_APP_KEY); String dropboxAppSecret = getString(R.string.dropbox_app_secret, BackupPreferenceFragment.DROPBOX_APP_SECRET); if (!DropboxHelper.hasToken()) { Auth.startOAuth2Authentication(getActivity(), dropboxAppKey); } break; case 2: recurrenceOptionsView.setVisibility(View.VISIBLE); mExportTarget = ExportParams.ExportTarget.GOOGLE_DRIVE; BackupPreferenceFragment.mGoogleApiClient = BackupPreferenceFragment .getGoogleApiClient(getActivity()); BackupPreferenceFragment.mGoogleApiClient.connect(); break; case 3: recurrenceOptionsView.setVisibility(View.VISIBLE); mExportTarget = ExportParams.ExportTarget.OWNCLOUD; if (!(PreferenceManager.getDefaultSharedPreferences(getActivity()) .getBoolean(getString(R.string.key_owncloud_sync), false))) { OwnCloudDialogFragment ocDialog = OwnCloudDialogFragment.newInstance(null); ocDialog.show(getActivity().getSupportFragmentManager(), "ownCloud dialog"); } break; case 4: mExportTarget = ExportParams.ExportTarget.SHARING; recurrenceOptionsView.setVisibility(View.GONE); break; default: mExportTarget = ExportParams.ExportTarget.SD_CARD; break; } } @Override public void onNothingSelected(AdapterView<?> parent) { } }); int position = PreferenceManager.getDefaultSharedPreferences(getActivity()) .getInt(getString(R.string.key_last_export_destination), 0); mDestinationSpinner.setSelection(position); //**************** export start time bindings ****************** Timestamp timestamp = PreferencesHelper.getLastExportTime(); mExportStartCalendar.setTimeInMillis(timestamp.getTime()); final Date date = new Date(timestamp.getTime()); mExportStartDate.setText(TransactionFormFragment.DATE_FORMATTER.format(date)); mExportStartTime.setText(TransactionFormFragment.TIME_FORMATTER.format(date)); mExportStartDate.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { long dateMillis = 0; try { Date date = TransactionFormFragment.DATE_FORMATTER.parse(mExportStartDate.getText().toString()); dateMillis = date.getTime(); } catch (ParseException e) { Log.e(getTag(), "Error converting input time to Date object"); } Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(dateMillis); int year = calendar.get(Calendar.YEAR); int monthOfYear = calendar.get(Calendar.MONTH); int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); CalendarDatePickerDialogFragment datePickerDialog = new CalendarDatePickerDialogFragment(); datePickerDialog.setOnDateSetListener(ExportFormFragment.this); datePickerDialog.setPreselectedDate(year, monthOfYear, dayOfMonth); datePickerDialog.show(getFragmentManager(), "date_picker_fragment"); } }); mExportStartTime.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { long timeMillis = 0; try { Date date = TransactionFormFragment.TIME_FORMATTER.parse(mExportStartTime.getText().toString()); timeMillis = date.getTime(); } catch (ParseException e) { Log.e(getTag(), "Error converting input time to Date object"); } Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(timeMillis); RadialTimePickerDialogFragment timePickerDialog = new RadialTimePickerDialogFragment(); timePickerDialog.setOnTimeSetListener(ExportFormFragment.this); timePickerDialog.setStartTime(calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE)); timePickerDialog.show(getFragmentManager(), "time_picker_dialog_fragment"); } }); SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); mExportAllSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { mExportStartDate.setEnabled(!isChecked); mExportStartTime.setEnabled(!isChecked); int color = isChecked ? android.R.color.darker_gray : android.R.color.black; mExportStartDate.setTextColor(getResources().getColor(color)); mExportStartTime.setTextColor(getResources().getColor(color)); } }); mExportAllSwitch.setChecked(sharedPrefs.getBoolean(getString(R.string.key_export_all_transactions), false)); mDeleteAllCheckBox.setChecked( sharedPrefs.getBoolean(getString(R.string.key_delete_transactions_after_export), false)); mRecurrenceTextView.setOnClickListener( new RecurrenceViewClickListener((AppCompatActivity) getActivity(), mRecurrenceRule, this)); //this part (setting the export format) must come after the recurrence view bindings above String defaultExportFormat = sharedPrefs.getString(getString(R.string.key_default_export_format), ExportFormat.QIF.name()); mExportFormat = ExportFormat.valueOf(defaultExportFormat); View.OnClickListener radioClickListener = new View.OnClickListener() { @Override public void onClick(View view) { onRadioButtonClicked(view); } }; View v = getView(); assert v != null; mOfxRadioButton.setOnClickListener(radioClickListener); mQifRadioButton.setOnClickListener(radioClickListener); mXmlRadioButton.setOnClickListener(radioClickListener); ExportFormat defaultFormat = ExportFormat.valueOf(defaultExportFormat.toUpperCase()); switch (defaultFormat) { case QIF: mQifRadioButton.performClick(); break; case OFX: mOfxRadioButton.performClick(); break; case XML: mXmlRadioButton.performClick(); break; } if (GnuCashApplication.isDoubleEntryEnabled()) { mOfxRadioButton.setVisibility(View.GONE); } else { mXmlRadioButton.setVisibility(View.GONE); } } @Override public void onRecurrenceSet(String rrule) { mRecurrenceRule = rrule; String repeatString = getString(R.string.label_tap_to_create_schedule); if (mRecurrenceRule != null) { mEventRecurrence.parse(mRecurrenceRule); repeatString = EventRecurrenceFormatter.getRepeatString(getActivity(), getResources(), mEventRecurrence, true); } mRecurrenceTextView.setText(repeatString); } /** * Callback for when the activity chooser dialog is completed */ @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == BackupPreferenceFragment.REQUEST_RESOLVE_CONNECTION && resultCode == Activity.RESULT_OK) { BackupPreferenceFragment.mGoogleApiClient.connect(); } } @Override public void onDateSet(CalendarDatePickerDialogFragment dialog, int year, int monthOfYear, int dayOfMonth) { Calendar cal = new GregorianCalendar(year, monthOfYear, dayOfMonth); mExportStartDate.setText(TransactionFormFragment.DATE_FORMATTER.format(cal.getTime())); mExportStartCalendar.set(Calendar.YEAR, year); mExportStartCalendar.set(Calendar.MONTH, monthOfYear); mExportStartCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth); } @Override public void onTimeSet(RadialTimePickerDialogFragment dialog, int hourOfDay, int minute) { Calendar cal = new GregorianCalendar(0, 0, 0, hourOfDay, minute); mExportStartTime.setText(TransactionFormFragment.TIME_FORMATTER.format(cal.getTime())); mExportStartCalendar.set(Calendar.HOUR_OF_DAY, hourOfDay); mExportStartCalendar.set(Calendar.MINUTE, minute); } }