package co.smartreceipts.android.settings.widget; import android.annotation.TargetApi; import android.content.Intent; import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.preference.ListPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceCategory; import android.support.annotation.NonNull; import android.support.v4.app.NavUtils; import android.support.v4.app.TaskStackBuilder; import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.widget.LinearLayout; import android.widget.Toast; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.inject.Inject; import co.smartreceipts.android.R; import co.smartreceipts.android.activities.AppCompatPreferenceActivity; import co.smartreceipts.android.activities.SmartReceiptsActivity; import co.smartreceipts.android.analytics.Analytics; import co.smartreceipts.android.analytics.events.DataPoint; import co.smartreceipts.android.analytics.events.DefaultDataPointEvent; import co.smartreceipts.android.analytics.events.Events; import co.smartreceipts.android.persistence.PersistenceManager; import co.smartreceipts.android.purchases.PurchaseEventsListener; import co.smartreceipts.android.purchases.PurchaseManager; import co.smartreceipts.android.purchases.model.InAppPurchase; import co.smartreceipts.android.purchases.source.PurchaseSource; import co.smartreceipts.android.purchases.wallet.PurchaseWallet; import co.smartreceipts.android.settings.UserPreferenceManager; import co.smartreceipts.android.settings.catalog.UserPreference; import co.smartreceipts.android.utils.IntentUtils; import co.smartreceipts.android.utils.log.LogConstants; import co.smartreceipts.android.utils.log.Logger; import co.smartreceipts.android.workers.EmailAssistant; import dagger.android.AndroidInjection; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import wb.android.flex.Flex; import wb.android.preferences.SummaryEditTextPreference; public class SettingsActivity extends AppCompatPreferenceActivity implements OnPreferenceClickListener, UniversalPreferences, PurchaseEventsListener { public static final String EXTRA_GO_TO_CATEGORY = "GO_TO_CATEGORY"; @Inject Flex flex; @Inject PersistenceManager persistenceManager; @Inject PurchaseWallet purchaseWallet; @Inject Analytics analytics; @Inject PurchaseManager purchaseManager; private volatile Set<InAppPurchase> availablePurchases; private CompositeDisposable compositeDisposable; private boolean isUsingHeaders; /** * Ugly hack to determine if a fragment header is currently showing or not. See if I can replace by counting the * fragment manager entries */ private boolean isFragmentHeaderShowing = false; @Override @SuppressWarnings("deprecation") protected void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); isUsingHeaders = getResources().getBoolean(R.bool.isTablet); if (!isUsingHeaders) { // Load the legacy preferences headers getPreferenceManager().setSharedPreferencesName(UserPreferenceManager.PREFERENCES_FILE_NAME); addPreferencesFromResource(R.xml.preference_legacy); configurePreferencesGeneral(this); configurePreferencesReceipts(this); configurePreferencesOutput(this); configurePreferencesEmail(this); configurePreferencesCamera(this); configurePreferencesLayoutCustomizations(this); configurePreferencesDistance(this); configureProPreferences(this); configurePreferencesHelp(this); configurePreferencesAbout(this); } purchaseManager.addEventListener(this); // Scroll to a predefined preference category (if provided). Only for when not using headers - // when we are using headers, selecting the appropriate header is handled by the EXTRA_SHOW_FRAGMENT if (!isUsingHeaders) { // For some reason (http://stackoverflow.com/a/8167755) // getListView().setSelection() won't work in onCreate, onResume or even onPostResume // Only way I got it to work was by postDelaying it new Handler().postDelayed(new Runnable() { @Override public void run() { int sectionHeader = getIntent().getIntExtra(EXTRA_GO_TO_CATEGORY, 0); if (sectionHeader > 0) { scrollToCategory(SettingsActivity.this, getString(sectionHeader)); } } }, 10); } } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); final LinearLayout root = (LinearLayout) findViewById(android.R.id.list).getParent().getParent().getParent(); if (root != null) { final Toolbar toolbar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.toolbar, root, false); root.addView(toolbar, 0); // insert at top setSupportActionBar(toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); } } @Override protected void onStart() { super.onStart(); } @Override protected void onResume() { super.onResume(); final ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setTitle(R.string.menu_main_settings); actionBar.setDisplayHomeAsUpEnabled(true); } compositeDisposable = new CompositeDisposable(); compositeDisposable.add(purchaseManager.getAllAvailablePurchaseSkus() .observeOn(AndroidSchedulers.mainThread()) .subscribe(inAppPurchases -> { Logger.info(SettingsActivity.this, "The following purchases are available: {}", availablePurchases); availablePurchases = inAppPurchases; }, throwable -> Logger.warn(SettingsActivity.this, "Failed to retrieve purchases for this session.", throwable))); } // Called only on Honeycomb and later @Override @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void onBuildHeaders(List<Header> target) { // Called before onCreate it seems isUsingHeaders = getResources().getBoolean(R.bool.isTablet); if (isUsingHeaders) { loadHeadersFromResource(R.xml.preference_headers, target); } } @Override protected boolean isValidFragment(String fragmentName) { try { return AbstractPreferenceHeaderFragment.class.isAssignableFrom( Class.forName(fragmentName)); } catch (ClassNotFoundException e) { return false; } } @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override public boolean onOptionsItemSelected(MenuItem item) { if (isFragmentHeaderShowing) { // If we're actively showing a fragment, let it handle the call return super.onOptionsItemSelected(item); } if (item.getItemId() == android.R.id.home) { final Intent upIntent = new Intent(this, SmartReceiptsActivity.class); if (NavUtils.shouldUpRecreateTask(this, upIntent)) { // This activity is NOT part of this app's task, so create a new task // when navigating up, with a synthesized back stack. TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent) // Add all of this activity's // parents to the back stack .startActivities(); // Navigate up to the closest parent } else { NavUtils.navigateUpTo(this, upIntent); } return true; } else { return super.onOptionsItemSelected(item); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (!purchaseManager.onActivityResult(requestCode, resultCode, data)) { super.onActivityResult(requestCode, resultCode, data); } } @Override protected void onPause() { compositeDisposable.clear(); super.onPause(); } @Override protected void onDestroy() { purchaseManager.removeEventListener(this); super.onDestroy(); } public boolean isUsingHeaders() { return isUsingHeaders; } public void setFragmentHeaderIsShowing(boolean isShowing) { isFragmentHeaderShowing = isShowing; } @Override @SuppressWarnings("deprecation") public Preference findPreference(int stringId) { return findPreference(getString(stringId)); } public void configurePreferencesGeneral(UniversalPreferences universal) { // Get the currency list // PersistenceManager persistenceManager = ((SmartReceiptsApplication) getApplication()).getPersistenceManager(); ArrayList<CharSequence> currencyList = persistenceManager.getDatabase().getCurrenciesList(); CharSequence[] currencyArray = new CharSequence[currencyList.size()]; currencyList.toArray(currencyArray); // Get the date separator list final String defaultSepartor = persistenceManager.getPreferenceManager().get(UserPreference.General.DateSeparator); final CharSequence[] dateSeparators = getDateSeparatorOptions(persistenceManager.getPreferenceManager()); // Set up the ListPreference data ListPreference currencyPreference = (ListPreference) universal.findPreference(R.string.pref_general_default_currency_key); currencyPreference.setEntries(currencyArray); currencyPreference.setEntryValues(currencyArray); currencyPreference.setValue(persistenceManager.getPreferenceManager().get(UserPreference.General.DefaultCurrency)); ListPreference dateSeparatorPreference = (ListPreference) universal.findPreference(R.string.pref_general_default_date_separator_key); dateSeparatorPreference.setEntries(dateSeparators); dateSeparatorPreference.setEntryValues(dateSeparators); dateSeparatorPreference.setValue(defaultSepartor); } private CharSequence[] getDateSeparatorOptions(UserPreferenceManager preferences) { final int definedDateSeparatorCount = 3; CharSequence[] dateSeparators; final String defaultSepartor = preferences.get(UserPreference.General.DateSeparator); if (!defaultSepartor.equals("-") && !defaultSepartor.equals("/")) { dateSeparators = new CharSequence[definedDateSeparatorCount + 1]; dateSeparators[definedDateSeparatorCount] = defaultSepartor; } else { dateSeparators = new CharSequence[definedDateSeparatorCount]; } dateSeparators[0] = "/"; dateSeparators[1] = "-"; dateSeparators[2] = "."; return dateSeparators; } public void configurePreferencesReceipts(UniversalPreferences universal) { // Set on Preference Click Listeners for all that require it universal.findPreference(R.string.pref_receipt_customize_categories_key).setOnPreferenceClickListener(this); universal.findPreference(R.string.pref_receipt_payment_methods_key).setOnPreferenceClickListener(this); // Here we restore our current values (easier than getting the FloatEditText stuff to work) UserPreferenceManager preferences = persistenceManager.getPreferenceManager(); DefaultTaxPercentagePreference taxPercentagePreference = (DefaultTaxPercentagePreference) universal.findPreference(R.string.pref_receipt_tax_percent_key); taxPercentagePreference.setText(Float.toString(preferences.get(UserPreference.Receipts.DefaultTaxPercentage))); MinimumPriceEditTextPreference minimumPriceEditTextPreference = (MinimumPriceEditTextPreference) universal.findPreference(R.string.pref_receipt_minimum_receipts_price_key); minimumPriceEditTextPreference.setText(Float.toString(preferences.get(UserPreference.Receipts.MinimumReceiptPrice))); } public void configurePreferencesOutput(UniversalPreferences universal) { // Set on Preference Click Listeners for all that require it universal.findPreference(R.string.pref_output_custom_csv_key).setOnPreferenceClickListener(this); universal.findPreference(R.string.pref_output_custom_pdf_key).setOnPreferenceClickListener(this); } private void scrollToCategory(UniversalPreferences universal, String sectionHeader) { PreferenceCategory category = (PreferenceCategory) universal.findPreference(sectionHeader); if (category == null) { return; } for (int i = 0; i < getPreferenceScreen().getRootAdapter().getCount(); i++) { Object o = getPreferenceScreen().getRootAdapter().getItem(i); if (o.equals(category)) { getListView().setSelection(i); break; } } } public void configurePreferencesEmail(UniversalPreferences universal) { Preference subjectPreference = universal.findPreference(R.string.pref_email_default_email_subject_key); subjectPreference.setDefaultValue(flex.getString(this, R.string.EMAIL_DATA_SUBJECT)); } public void configurePreferencesCamera(UniversalPreferences universal) { } public void configurePreferencesLayoutCustomizations(UniversalPreferences universal) { } public void configurePreferencesDistance(UniversalPreferences universal) { } public void configureProPreferences(UniversalPreferences universal) { final boolean hasProSubscription = purchaseWallet.hasActivePurchase(InAppPurchase.SmartReceiptsPlus); final SummaryEditTextPreference pdfFooterPreference = (SummaryEditTextPreference) universal.findPreference(R.string.pref_pro_pdf_footer_key); pdfFooterPreference.setAppearsEnabled(hasProSubscription); pdfFooterPreference.setOnPreferenceClickListener(this); } public void configurePreferencesHelp(UniversalPreferences universal) { // Set on Preference Click Listeners for all that require it universal.findPreference(R.string.pref_help_send_feedback_key).setOnPreferenceClickListener(this); universal.findPreference(R.string.pref_help_send_love_key).setOnPreferenceClickListener(this); universal.findPreference(R.string.pref_help_support_email_key).setOnPreferenceClickListener(this); } public void configurePreferencesAbout(UniversalPreferences universal) { // Set up Version Summary Preference versionPreference = universal.findPreference(R.string.pref_about_version_key); versionPreference.setSummary(getAppVersion()); universal.findPreference(R.string.pref_about_privacy_policy_key).setOnPreferenceClickListener(this); } @Override public boolean onPreferenceClick(Preference preference) { final String key = preference.getKey(); if (key.equals(getString(R.string.pref_receipt_customize_categories_key)) || key.equals(getString(R.string.pref_output_custom_csv_key)) || key.equals(getString(R.string.pref_output_custom_pdf_key)) || key.equals(getString(R.string.pref_receipt_payment_methods_key))) { final Intent intent = new Intent(this, SettingsViewerActivity.class); intent.putExtra(SettingsViewerActivity.KEY_FLAG, key); startActivity(intent); return true; } else if (key.equals(getString(R.string.pref_help_send_love_key))) { // Dark Pattern... Send Love => AppStore. // Others for email startActivity(IntentUtils.getRatingIntent(this)); return true; } else if (key.equals(getString(R.string.pref_help_send_feedback_key)) || key.equals(getString(R.string.pref_help_support_email_key))) { final String emailSubject; if (key.equals(getString(R.string.pref_help_send_feedback_key))) { emailSubject = getString(R.string.feedback, getString(R.string.sr_app_name)); } else { emailSubject = getString(R.string.support, getString(R.string.sr_app_name)); } final List<File> files = new ArrayList<>(); final File file1 = new File(getFilesDir(), LogConstants.LOG_FILE_NAME_1); final File file2 = new File(getFilesDir(), LogConstants.LOG_FILE_NAME_2); if (file1.exists()) { files.add(file1); } if (file2.exists()) { files.add(file2); } final Intent intent = EmailAssistant.getEmailDeveloperIntent(this, emailSubject, getDebugScreen(), files); startActivity(Intent.createChooser(intent, getResources().getString(R.string.send_email))); return true; } else if (key.equals(getString(R.string.pref_pro_pdf_footer_key))) { // Let's check if we should prompt the user to upgrade for this preference final boolean haveProSubscription = purchaseWallet.hasActivePurchase(InAppPurchase.SmartReceiptsPlus); final boolean proSubscriptionIsAvailable = availablePurchases != null && availablePurchases.contains(InAppPurchase.SmartReceiptsPlus); // If we don't already have the pro subscription and it's available, let's buy it if (!haveProSubscription) { if (proSubscriptionIsAvailable) { purchaseManager.initiatePurchase(InAppPurchase.SmartReceiptsPlus, PurchaseSource.PdfFooterSetting); } else { Toast.makeText(SettingsActivity.this, R.string.purchase_unavailable, Toast.LENGTH_SHORT).show(); } } return true; } else if (key.equals(getString(R.string.pref_about_privacy_policy_key))) { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.smartreceipts.co/privacy"))); return true; } else { return false; } } @Override public void onPurchaseSuccess(@NonNull InAppPurchase inAppPurchase, @NonNull PurchaseSource purchaseSource) { analytics.record(new DefaultDataPointEvent(Events.Purchases.PurchaseSuccess).addDataPoint(new DataPoint("sku", inAppPurchase.getSku())).addDataPoint(new DataPoint("source", purchaseSource))); runOnUiThread(new Runnable() { @Override public void run() { invalidateOptionsMenu(); // To hide the subscription option Toast.makeText(SettingsActivity.this, R.string.purchase_succeeded, Toast.LENGTH_LONG).show(); } }); } @Override public void onPurchaseFailed(@NonNull PurchaseSource purchaseSource) { analytics.record(new DefaultDataPointEvent(Events.Purchases.PurchaseFailed).addDataPoint(new DataPoint("source", purchaseSource))); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(SettingsActivity.this, R.string.purchase_failed, Toast.LENGTH_LONG).show(); } }); } private String getAppVersion() { try { return getPackageManager().getPackageInfo(getString(R.string.package_name), 0).versionName; } catch (NameNotFoundException e) { return null; } } private String getDebugScreen() { final boolean hasProSubscription = purchaseWallet.hasActivePurchase(InAppPurchase.SmartReceiptsPlus); return "Debug-information: \n" + "Smart Receipts Version: " + getAppVersion() + "\n" + "Package: " + getPackageName() + "\n" + "Plus: " + hasProSubscription + "\n" + "Brand: " + Build.BRAND + "\n" + "OS API Level: " + Build.VERSION.SDK_INT + "\n" + "Device: " + Build.DEVICE + "\n" + "Manufacturer: " + Build.MANUFACTURER + "\n" + "Model (and Product): " + Build.MODEL + " (" + Build.PRODUCT + ")\n" + "Two-Paned: " + isUsingHeaders; } }