Java tutorial
/* * Copyright 2015 Kostiantyn Aloshychev * * 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 ua.boberproduction.bbr; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.app.SearchManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.SearchView; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.widget.GridView; import android.widget.Toast; import com.google.android.gms.ads.AdView; import com.google.firebase.analytics.FirebaseAnalytics; import java.util.LinkedList; import java.util.List; import rx.Observable; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; import ua.boberproduction.bbr.billing.Billing; import ua.boberproduction.bbr.feedback.FeedbackFragment; import ua.boberproduction.bbr.navigation.AnimatedExpandableListAdapter; import ua.boberproduction.bbr.navigation.AnimatedExpandableListView; import ua.boberproduction.bbr.navigation.AnimatedExpandableListView.ChildItem; import ua.boberproduction.bbr.navigation.AnimatedExpandableListView.GroupItem; import ua.boberproduction.bbr.navigation.ListMenuFragment; import ua.boberproduction.bbr.navigation.MainMenuFragment; import ua.boberproduction.bbr.navigation.RootMenuManager; import ua.boberproduction.bbr.notes.NoteViewerFragment; import ua.boberproduction.bbr.sqlite.DBInstallTaskFragment; import ua.boberproduction.bbr.sqlite.DBInstaller; import ua.boberproduction.bbr.sqlite.SQLiteStructureRepository; import ua.boberproduction.bbr.textcontent.TextContentFragment; import ua.boberproduction.bbr.util.Utils; /** * Base Activity class implementing common UI elements and behaviour. */ public abstract class BaseActivity extends AppCompatActivity { // load NDK SQLite library containing ICU to support cyrillic FTS search. static { System.loadLibrary("android_sqlite"); } private final int NAV_MENU_HIDE_DELAY = 200; //private IabHelper iabHelper; private FirebaseAnalytics firebaseAnalytics; private SearchView searchView; private ActionBarDrawerToggle drawerToggle; private DrawerLayout drawerLayout; private Ad adBanner; private Toolbar toolbar; private Billing billing; @Override protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main_bbr); super.onCreate(savedInstanceState); toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); // check if external storage is available for read and write. If not, quit application and show toast message. if (!Utils.isExternalStorageWritable()) { try { throw new Error("External memory not available. Quitting app."); } catch (Error error) { Toast.makeText(getApplicationContext(), getString(R.string.error_storage_read_unavailable), Toast.LENGTH_LONG).show(); finish(); System.exit(0); } } // if there is no savedInstanceState (initial activity load), // create a dummy retainable fragment to hold AsyncTask which is copying database from assets to the system storage. // It is made in order to pass the task to a recreated activity after configuration change. // When completed, it loads content. if (!DBInstaller.isLatestContentDBInstalled(this)) { Fragment mTaskFragment = getFragmentManager() .findFragmentByTag(DBInstallTaskFragment.TAG_TASK_FRAGMENT); // If the Fragment is non-null, then it is currently being retained across a configuration change. if (mTaskFragment == null) { mTaskFragment = new DBInstallTaskFragment(); getFragmentManager().beginTransaction().add(mTaskFragment, DBInstallTaskFragment.TAG_TASK_FRAGMENT) .commit(); } } else { setupUI(); } firebaseAnalytics = FirebaseAnalytics.getInstance(this); // initialize in-app billing, if premium mode is not active and Google services are available if (!Ad.getGodModeStatus(this)) { // Showing status if (isGoogleServicesAvailable()) { billing = new Billing(this); billing.onCreate(); } } } private boolean isGoogleServicesAvailable() { /* GoogleApiAvailability googleAPI = GoogleApiAvailability.getInstance(); return googleAPI.isGooglePlayServicesAvailable(this) == SUCCESS;*/ return true; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (billing != null) billing.onActivityResult(requestCode, resultCode, data); } public FirebaseAnalytics getFirebaseAnalytics() { if (firebaseAnalytics == null) firebaseAnalytics = FirebaseAnalytics.getInstance(this); return firebaseAnalytics; } public Ad getAdBanner() { return adBanner; } public void setupUI() { Observable.fromCallable(() -> { if (RootMenuManager.listIsNull()) RootMenuManager.getList(); return null; }).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).doOnError((e) -> { Log.e(BBRApplication.TAG_BBR, "Error getting root menu list", e); Utils.toastError(this, getString(R.string.error_loading_menu_list), Toast.LENGTH_SHORT); }).subscribe((r) -> { setupToolbar(); setupNavigationDrawer(); showContent(); }); } public void setupToolbar() { // Customize toolbar String apptitle = getString(R.string.app_name); toolbar.setTitle(apptitle); setSupportActionBar(toolbar); if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); } } /** * Show the activity content. Called after all preparation (db checking and installing etc) is finished. */ public abstract void showContent(); @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); MenuItem searchItem = menu.findItem(R.id.action_search); SearchManager searchManager = (SearchManager) this.getSystemService(Context.SEARCH_SERVICE); searchView = null; if (searchItem != null) { searchView = (SearchView) searchItem.getActionView(); } if (searchView != null) { searchView.setSearchableInfo(searchManager.getSearchableInfo(this.getComponentName())); searchView.setIconified(true); searchView.clearFocus(); searchView.onActionViewCollapsed(); } // if user activated God Mode, hide the 'remove ads' menu option.. if (Ad.getGodModeStatus(this)) { MenuItem item = menu.findItem(R.id.action_remove_ads); item.setVisible(false); } return super.onCreateOptionsMenu(menu); } @Override public void onAttachFragment(Fragment fragment) { super.onAttachFragment(fragment); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); boolean godMode = prefs.getBoolean(getString(R.string.pref_god_mode), false); if (!godMode) { AdView adView = (AdView) findViewById(R.id.adView); if (adBanner == null) adBanner = new Ad(adView); if (!(fragment instanceof MainMenuFragment)) { adBanner.showBanner(); } else adBanner.hideBanner(); } } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_settings) { startActivity(new Intent(this, EditPreferencesActivity.class)); return true; } else if (id == R.id.action_rate_app) { final String appPackageName = getPackageName(); // getPackageName() from Context or Activity object try { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + appPackageName))); } catch (android.content.ActivityNotFoundException anfe) { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + appPackageName))); } } else if (id == R.id.action_feedback) { FragmentTransaction ft = this.getFragmentManager().beginTransaction(); FeedbackFragment fragment = new FeedbackFragment(); ft.setCustomAnimations(R.animator.slide_in_right, R.animator.slide_out_left, R.animator.slide_in_left, R.animator.slide_out_right); ft.replace(R.id.main_frame, fragment).addToBackStack(null).commit(); } else if (id == R.id.action_remove_ads) { // if Google Services are available, launch the billing. If not, show error message. // Showing status if (isGoogleServicesAvailable()) billing.purchaseRemoveAds(); else Toast.makeText(this, getString(R.string.toast_error_google_services_unavailable), Toast.LENGTH_SHORT).show(); } return drawerToggle.onOptionsItemSelected(item) || super.onOptionsItemSelected(item); } @Override protected void onResume() { super.onResume(); // collapse the search line (if it's activated) if (searchView != null && !searchView.isIconified()) { searchView.clearFocus(); searchView.setIconified(true); searchView.onActionViewCollapsed(); } // Load fullscreen Ads into memory Ad.requestNewInterstitial(this); } @Override protected void onStop() { super.onStop(); if (adBanner != null) adBanner.close(); } private List<GroupItem> getGroupItems() { List<Chapter> rootCategories = RootMenuManager.getList(); List<GroupItem> groupItems = new LinkedList<>(); groupItems.add(new GroupItem(getString(R.string.menu_homepage))); GroupItem group; // Populate our GroupItem List by parent and child items for (Chapter rootCategory : rootCategories) { group = new GroupItem(rootCategory.getTitle(), rootCategory.getId()); List<Chapter> children = SQLiteStructureRepository.getInstance().getChapters(rootCategory.getId()); if (children.size() > 0) { for (Chapter child : children) { ChildItem childItem = new ChildItem(child.getTitle(), child.getId()); group.getItems().add(childItem); } } groupItems.add(group); } // add 'Favorites', 'Notebook', etc. menu entries groupItems.add(new GroupItem(getString(R.string.menu_favorites), 1000)); groupItems.add(new GroupItem(getString(R.string.menu_notepad), 1001)); return groupItems; } // TODO: Delegate to drawer (AnimatedExpandableListView) class public void setupNavigationDrawer() { drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.drawer_open, R.string.drawer_close); drawerToggle.setDrawerIndicatorEnabled(true); // next, add the categories from the DB Observable.fromCallable(this::getGroupItems).observeOn(AndroidSchedulers.mainThread()) .doOnError((e) -> Log.e(BBRApplication.TAG_BBR, "Error generating navigation drawer menu.", e)) .subscribeOn(Schedulers.newThread()).subscribe((groupItems) -> { AnimatedExpandableListAdapter adapter = new AnimatedExpandableListAdapter(this, groupItems); AnimatedExpandableListView listView = (AnimatedExpandableListView) findViewById( R.id.nav_drawer_animated_listview); listView.setAdapter(adapter); listView.setOnGroupClickListener((parent, v, groupPosition, id) -> { // If the selected group has any children, // we call collapseGroupWithAnimation(int) and // expandGroupWithAnimation(int) to animate group // expansion/collapse. if (groupItems.get(groupPosition).getItems().size() > 0) { if (listView.isGroupExpanded(groupPosition)) { listView.collapseGroupWithAnimation(groupPosition); } else { for (int i = 0; i < listView.getCount(); i++) { if (listView.isGroupExpanded(i) && groupPosition != i) { listView.collapseGroupWithAnimation(i); } } listView.expandGroupWithAnimation(groupPosition); } // if there are no children, make appropriate action } else { Chapter selectedChapter = new Chapter(groupItems.get(groupPosition).getId(), groupItems.get(groupPosition).getTitle(), 0); closeNavigationDrawer(); new Handler().postDelayed( () -> RootMenuManager.performOnClickAction(selectedChapter, this), NAV_MENU_HIDE_DELAY); } return true; }); listView.setOnChildClickListener( (expandableListView, view, groupPosition, childPosition, id) -> { drawerLayout.closeDrawer(GravityCompat.START); listView.collapseGroup(groupPosition); ChildItem childItem = groupItems.get(groupPosition).getItems().get(childPosition); Chapter selectedChapter = new Chapter(childItem.getId(), childItem.getTitle(), groupItems.get(groupPosition).getId()); closeNavigationDrawer(); new Handler().postDelayed(() -> RootMenuManager .performOnClickAction(selectedChapter, BaseActivity.this), NAV_MENU_HIDE_DELAY); return false; }); }); drawerLayout.addDrawerListener(drawerToggle); } // close navigation drawer, if it's open. Return true if drawer was open on method launch. private boolean closeNavigationDrawer() { if (drawerLayout.isDrawerOpen(GravityCompat.START)) { drawerLayout.closeDrawer(GravityCompat.START); Fragment fragment = getFragmentManager().findFragmentById(R.id.main_frame); if (fragment instanceof MainMenuFragment) { GridView gridLayout = (GridView) findViewById(R.id.grid_menu); if (null != gridLayout) { gridLayout.invalidate(); gridLayout.requestLayout(); } } return true; } return false; } @Override public void onBackPressed() { // close navigation drawer, if it's open if (closeNavigationDrawer()) return; Fragment fragment = getFragmentManager().findFragmentById(R.id.main_frame); if (fragment instanceof TextContentFragment && ((TextContentFragment) fragment).isShowingPopup()) { ((TextContentFragment) fragment).hidePopupImage(); return; } else if (fragment instanceof MainMenuFragment) { this.finishAffinity(); // from NoteViewer, ensure that back press returns directly to NotebookFragment, avoiding NoteEditor even if it was saved in backstack. } else if (fragment instanceof NoteViewerFragment) { if (!getFragmentManager().popBackStackImmediate(getString(R.string.tag_fragment_notebook), FragmentManager.POP_BACK_STACK_INCLUSIVE)) { super.onBackPressed(); } } // Navigate through the fragments using the "back" button. // Check if there are fragments in the backstack, and pop them out. if (getFragmentManager().getBackStackEntryCount() > 0) { getFragmentManager().popBackStack(); return; } super.onBackPressed(); } public void openCategory(int id, String category) { // Check if there are any subcategories for the chosen category. Observable.fromCallable(() -> SQLiteStructureRepository.getInstance().getChapters(id)) .subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).doOnError((e) -> { Log.e(BBRApplication.TAG_BBR, "Error getting category list.", e); Utils.toastError(this, getString(R.string.error_loading_categories), Toast.LENGTH_SHORT); }).subscribe((categories) -> { Fragment fragment; Bundle bundle = new Bundle(); // If there are, load another ListMenuFragment. // Also, it will need the 'parent' category to put it into the actionBar's subtitle, so we're putting it into the bundle. if (categories.size() > 0) { fragment = new ListMenuFragment(); } else // If not, load content. fragment = new TextContentFragment(); // put the clicked category name into a bundle, and put the bundle into the fragment bundle.putInt(getString(R.string.tag_category_id), id); bundle.putString(getString(R.string.tag_category), category); FragmentTransaction ft = this.getFragmentManager().beginTransaction(); fragment.setArguments(bundle); //set sliding animations ft.setCustomAnimations(R.animator.slide_in_right, R.animator.slide_out_left, R.animator.slide_in_left, R.animator.slide_out_right); ft.replace(R.id.main_frame, fragment).addToBackStack(null).commit(); this.getFragmentManager().executePendingTransactions(); }); } @Override protected void onDestroy() { AdView adView = (AdView) findViewById(R.id.adView); if (adView != null) { adView.destroy(); } if (billing != null) billing.onDestroy(); super.onDestroy(); } public void setActiveChapterId(int activeChapterId) { AnimatedExpandableListView listView = (AnimatedExpandableListView) findViewById( R.id.nav_drawer_animated_listview); AnimatedExpandableListAdapter adapter = (AnimatedExpandableListAdapter) listView.getExpandableListAdapter(); if (adapter != null) { adapter.setCurrentChapterId(activeChapterId); adapter.notifyDataSetChanged(); } } }