Java tutorial
/* * Dharmaseed Android app * Copyright (C) 2016 Brett Bethke * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package org.dharmaseed.android; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.Cursor; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.support.v4.content.LocalBroadcastManager; import android.support.v4.widget.CursorAdapter; import android.support.v4.widget.SwipeRefreshLayout; import android.text.Html; import android.text.method.LinkMovementMethod; import android.view.KeyEvent; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.util.Log; import android.view.View; import android.support.design.widget.NavigationView; 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.Toolbar; import android.view.Menu; import android.view.MenuItem; import android.widget.AdapterView; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ScrollView; import android.widget.Switch; import android.widget.TextView; import android.widget.Toast; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class NavigationActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, TextView.OnEditorActionListener, AdapterView.OnItemClickListener, View.OnFocusChangeListener { public final static String TALK_DETAIL_EXTRA = "org.dharmaseed.android.TALK_DETAIL"; NavigationView navigationView; ListView listView; int savedListPosition; EditText searchBox; String extraSearchTerms; LinearLayout searchCluster, header; TextView headerPrimary, headerDescription, websiteLink, donationLink; boolean starFilterOn; Menu menu; DBManager dbManager; CursorAdapter cursorAdapter; SwipeRefreshLayout refreshLayout; private int viewMode; private static final int VIEW_MODE_TALKS = 0; private static final int VIEW_MODE_TEACHERS = 1; private static final int VIEW_MODE_CENTERS = 2; private static final int VIEW_MODE_TEACHER_TALKS = 3; private static final int VIEW_MODE_CENTER_TALKS = 4; private static final int DETAIL_MODE_NONE = 0; private static final int DETAIL_MODE_TEACHER = 1; private static final int DETAIL_MODE_CENTER = 2; int detailMode; long detailId; TalkFetcherTask talkFetcherTask; TeacherFetcherTask teacherFetcherTask; CenterFetcherTask centerFetcherTask; private static final String LOG_TAG = "NavigationActivity"; private boolean downloadedOnly; private TalkRepository talkRepository; private TeacherRepository teacherRepository; private CenterRepository centerRepository; @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean("SearchClusterVisible", searchCluster.getVisibility() == View.VISIBLE); outState.putString("ExtraSearchTerms", extraSearchTerms); outState.putBoolean("HeaderVisible", header.getVisibility() == View.VISIBLE); outState.putBoolean("StarFilterOn", starFilterOn); outState.putInt("ViewMode", viewMode); outState.putInt("DetailMode", detailMode); outState.putLong("DetailId", detailId); // Save list position in the object as well to handle the case when the activity is // only being paused, not destroyed. This happens, for example, when navigating to // the play talk activity savedListPosition = listView.getFirstVisiblePosition(); outState.putInt("ListViewPosition", savedListPosition); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); starFilterOn = savedInstanceState.getBoolean("StarFilterOn"); searchCluster .setVisibility(savedInstanceState.getBoolean("SearchClusterVisible") ? View.VISIBLE : View.GONE); extraSearchTerms = savedInstanceState.getString("ExtraSearchTerms"); header.setVisibility(savedInstanceState.getBoolean("HeaderVisible") ? View.VISIBLE : View.GONE); setViewMode(savedInstanceState.getInt("ViewMode")); setDetailMode(savedInstanceState.getInt("DetailMode"), savedInstanceState.getLong("DetailId")); savedListPosition = savedInstanceState.getInt("ListViewPosition"); } @Override protected void onResume() { super.onResume(); // Get the latest data from dharmaseed.org if necessary if (dbManager.shouldSync()) { fetchNewDataFromServer(); } else { Log.i("onResume", "Don't need to fetch new data from server"); } updateDisplayedData(); // Restore list position listView.setSelectionFromTop(savedListPosition, 0); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_navigation); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); dbManager = DBManager.getInstance(this); // Configure search box searchBox = (EditText) findViewById(R.id.nav_search_text); searchBox.setOnEditorActionListener(this); searchBox.setOnFocusChangeListener(this); searchCluster = (LinearLayout) findViewById(R.id.nav_search_cluster); // Configure header header = (LinearLayout) findViewById(R.id.nav_sub_header); headerPrimary = (TextView) findViewById(R.id.nav_sub_header_primary); headerDescription = (TextView) findViewById(R.id.nav_sub_header_description); websiteLink = (TextView) findViewById(R.id.nav_links_website); donationLink = (TextView) findViewById(R.id.nav_links_donate); websiteLink.setMovementMethod(LinkMovementMethod.getInstance()); donationLink.setMovementMethod(LinkMovementMethod.getInstance()); // Configure navigation view navigationView = (NavigationView) findViewById(R.id.nav_view); navigationView.setNavigationItemSelectedListener(this); // Configure list view listView = (ListView) findViewById(R.id.talks_list_view); listView.setOnItemClickListener(this); // Initialize UI state starFilterOn = false; searchCluster.setVisibility(View.GONE); header.setVisibility(View.GONE); downloadedOnly = false; setViewMode(VIEW_MODE_TALKS); setDetailMode(DETAIL_MODE_NONE); extraSearchTerms = ""; talkRepository = new TalkRepository(dbManager); teacherRepository = new TeacherRepository(dbManager); centerRepository = new CenterRepository(dbManager); // Set swipe refresh listener refreshLayout = (SwipeRefreshLayout) findViewById(R.id.talks_list_view_swipe_refresh); refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { Log.i("refresh", "onrefresh"); fetchNewDataFromServer(); } }); DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.setDrawerListener(toggle); toggle.syncState(); LocalBroadcastManager.getInstance(this).registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Log.i("navigationActivity", "Received update broadcast"); updateDisplayedData(); } }, new IntentFilter("updateDisplayedData")); } void setViewMode(int viewMode) { setViewMode(viewMode, true); } void setViewMode(int viewMode, boolean setMenuCheck) { this.viewMode = viewMode; header.setVisibility(View.GONE); extraSearchTerms = ""; switch (viewMode) { case VIEW_MODE_TALKS: case VIEW_MODE_TEACHER_TALKS: case VIEW_MODE_CENTER_TALKS: getSupportActionBar().setTitle("Talks"); cursorAdapter = new TalkCursorAdapter(dbManager, this, R.layout.main_list_view_item, null); if (setMenuCheck) navigationView.getMenu().findItem(R.id.nav_talks).setChecked(true); break; case VIEW_MODE_TEACHERS: getSupportActionBar().setTitle("Teachers"); cursorAdapter = new TeacherCursorAdapter(dbManager, this, R.layout.main_list_view_item, null); if (setMenuCheck) navigationView.getMenu().findItem(R.id.nav_teachers).setChecked(true); break; case VIEW_MODE_CENTERS: getSupportActionBar().setTitle("Centers"); cursorAdapter = new CenterCursorAdapter(dbManager, this, R.layout.main_list_view_item, null); if (setMenuCheck) navigationView.getMenu().findItem(R.id.nav_centers).setChecked(true); break; } listView.setAdapter(cursorAdapter); } private void setDetailMode(int detailMode) { setDetailMode(detailMode, 0); } private void setDetailMode(int detailMode, long id) { this.detailMode = detailMode; this.detailId = id; if (detailMode == DETAIL_MODE_NONE) { header.setVisibility(View.GONE); } else { // Clear search and star filters starFilterOn = false; setStarButton(); clearSearch(searchCluster); switch (detailMode) { case DETAIL_MODE_TEACHER: setViewMode(VIEW_MODE_TEACHER_TALKS); setTeacherHeader(id); break; case DETAIL_MODE_CENTER: setViewMode(VIEW_MODE_CENTER_TALKS); setCenterHeader(id); break; } header.setVisibility(View.VISIBLE); updateDisplayedData(); } } private void setTeacherHeader(long id) { getSupportActionBar().setTitle("Teacher Detail"); Cursor cursor = teacherRepository.getTeacherById(id); Teacher teacher = Teacher.create(cursor); cursor.close(); headerPrimary.setText(teacher.getName()); if (!teacher.getWebsite().isEmpty()) { websiteLink.setText( Html.fromHtml(String.format("<a href=%s>Teacher's website</a>", teacher.getWebsite()))); } else { websiteLink.setText(""); } if (!teacher.getDonationUrl().isEmpty()) { donationLink.setText(Html .fromHtml(String.format("<a href=%s>Donate to this teacher</a>", teacher.getDonationUrl()))); } else { donationLink.setText(""); } headerDescription.setText(teacher.getBio()); } public void displayTalksByTeacher(long id) { Cursor cursor = talkRepository.getTalksByTeacher(getSearchTerms(), id, starFilterOn, downloadedOnly); if (cursor != null) { cursorAdapter.changeCursor(cursor); } else { showToast("There was a problem fetching talks by the teacher."); updateDisplayedTeachers(); } } private void setCenterHeader(long id) { getSupportActionBar().setTitle("Center Detail"); Cursor cursor = centerRepository.getCenterById(id); Center center = Center.create(cursor); cursor.close(); headerPrimary.setText(center.getName()); String descriptionHtml = center.getDescription().replace("\n", "\n<p>") + "\n<p>"; if (!center.getWebsite().isEmpty()) { websiteLink .setText(Html.fromHtml(String.format("<a href=%s>Center's website</a>", center.getWebsite()))); } else { websiteLink.setText(""); } donationLink.setText(""); headerDescription.setText(center.getDescription()); } private void displayTalksByCenter(long id) { Cursor cursor = talkRepository.getTalksByCenter(getSearchTerms(), id, starFilterOn, downloadedOnly); if (cursor != null) { cursorAdapter.changeCursor(cursor); } else { showToast("There was a problem fetching talks by the center."); updateDisplayedCenters(); } } public void fetchNewDataFromServer() { // Fetch new data from the server Log.i("navigationActivity", "fetchNewDataFromServer()"); if (teacherFetcherTask == null || teacherFetcherTask.getStatus() == AsyncTask.Status.FINISHED) { refreshLayout.setRefreshing(true); teacherFetcherTask = new TeacherFetcherTask(dbManager, this, this); teacherFetcherTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } if (centerFetcherTask == null || centerFetcherTask.getStatus() == AsyncTask.Status.FINISHED) { refreshLayout.setRefreshing(true); centerFetcherTask = new CenterFetcherTask(dbManager, this); centerFetcherTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } if (talkFetcherTask == null || talkFetcherTask.getStatus() == AsyncTask.Status.FINISHED) { refreshLayout.setRefreshing(true); talkFetcherTask = new TalkFetcherTask(dbManager, this); talkFetcherTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } } @Override public void onBackPressed() { DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); return; } switch (viewMode) { case VIEW_MODE_TALKS: // nowhere to go break; case VIEW_MODE_TEACHERS: setViewMode(VIEW_MODE_TALKS); break; case VIEW_MODE_TEACHER_TALKS: setViewMode(VIEW_MODE_TEACHERS); break; case VIEW_MODE_CENTERS: setViewMode(VIEW_MODE_TALKS); break; case VIEW_MODE_CENTER_TALKS: setViewMode(VIEW_MODE_CENTERS); break; default: super.onBackPressed(); return; } updateDisplayedData(); } @Override public boolean onCreateOptionsMenu(final Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.navigation, menu); this.menu = menu; setStarButton(); return true; } public void clearSearch(View v) { searchCluster.setVisibility(View.GONE); searchBox.setText(""); updateDisplayedData(); resetListToTop(); } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); switch (id) { case R.id.action_refresh_server_data: fetchNewDataFromServer(); return true; case R.id.action_search: Log.i("nav", "Search!"); EditText searchBox = (EditText) findViewById(R.id.nav_search_text); if (searchCluster.getVisibility() == View.GONE) { searchCluster.setVisibility(View.VISIBLE); searchBox.requestFocus(); searchBox.setCursorVisible(true); InputMethodManager keyboard = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); keyboard.showSoftInput(searchBox, 0); } else { clearSearch(searchCluster); } return true; case R.id.action_toggle_starred: starFilterOn = !starFilterOn; setStarButton(); updateDisplayedData(); resetListToTop(); return true; } return super.onOptionsItemSelected(item); } // Called when an item in the main list view is clicked @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Log.d("onItemClick", "selected " + position + ", " + id); Context ctx = parent.getContext(); switch (viewMode) { case VIEW_MODE_TALKS: case VIEW_MODE_TEACHER_TALKS: case VIEW_MODE_CENTER_TALKS: Intent intent = new Intent(ctx, PlayTalkActivity.class); intent.putExtra(TALK_DETAIL_EXTRA, id); ctx.startActivity(intent); break; case VIEW_MODE_TEACHERS: setDetailMode(DETAIL_MODE_TEACHER, id); break; case VIEW_MODE_CENTERS: setDetailMode(DETAIL_MODE_CENTER, id); break; } } // Reset the list to show the first item. Still not entirely sure why it's necessary to do it // this way, but see https://groups.google.com/forum/#!topic/android-developers/EnyldBQDUwE // and http://stackoverflow.com/questions/1446373/android-listview-setselection-does-not-seem-to-work private void resetListToTop() { listView.clearFocus(); listView.post(new Runnable() { @Override public void run() { listView.setSelection(0); } }); } public void setStarButton() { int icon; if (starFilterOn) { icon = getResources().getIdentifier("btn_star_big_on", "drawable", "android"); } else { icon = getResources().getIdentifier("btn_star_big_off", "drawable", "android"); } if (menu != null) { MenuItem starButton = menu.findItem(R.id.action_toggle_starred); starButton.setIcon(ContextCompat.getDrawable(this, icon)); } } @SuppressWarnings("StatementWithEmptyBody") @Override public boolean onNavigationItemSelected(MenuItem item) { // Handle navigation view item clicks here. int id = item.getItemId(); if (id == R.id.nav_talks) { setViewMode(VIEW_MODE_TALKS); } else if (id == R.id.nav_teachers) { setViewMode(VIEW_MODE_TEACHERS); } else if (id == R.id.nav_centers) { setViewMode(VIEW_MODE_CENTERS); } // else if (id == R.id.nav_retreats) { // Intent intent = new Intent(this, RetreatSearchActivity.class); // this.startActivity(intent); // } setDetailMode(DETAIL_MODE_NONE); updateDisplayedData(); DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); drawer.closeDrawer(GravityCompat.START); return true; } /** * Called when the "Downloaded only" switch in the nav drawer is pressed * @param view */ public void downloadOnlySwitchClicked(View view) { Switch downloadSwitch = (Switch) view; downloadedOnly = downloadSwitch.isChecked(); updateDisplayedData(); } public void headingDetailCollapseExpandButtonClicked(View view) { ScrollView scrollView = (ScrollView) findViewById(R.id.nav_sub_header_description_scroll); ImageButton button = (ImageButton) findViewById(R.id.heading_detail_collapse_expand_button); if (scrollView.getVisibility() == View.VISIBLE) { scrollView.setVisibility(View.GONE); button.setImageDrawable(ContextCompat.getDrawable(this, getResources().getIdentifier("arrow_down_float", "drawable", "android"))); } else { scrollView.setVisibility(View.VISIBLE); button.setImageDrawable(ContextCompat.getDrawable(this, getResources().getIdentifier("arrow_up_float", "drawable", "android"))); } } @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { Log.i("onEditorAction", v.getText().toString()); // Close keyboard InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(v.getWindowToken(), 0); // Give up focus (which will invoke onFocusChange below to hide the cursor and keyboard) v.clearFocus(); // Search for talks meeting the new criteria updateDisplayedData(); // Scroll to the top of the list resetListToTop(); return false; } // Search box edit text focus listener @Override public void onFocusChange(View v, boolean hasFocus) { Log.i("focusChange", hasFocus + ""); if (hasFocus) { ((EditText) v).setCursorVisible(true); } else { ((EditText) v).setCursorVisible(false); InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(v.getWindowToken(), 0); } } public void updateDisplayedData() { switch (viewMode) { case VIEW_MODE_TALKS: updateDisplayedTalks(); break; case VIEW_MODE_TEACHERS: updateDisplayedTeachers(); break; case VIEW_MODE_CENTERS: updateDisplayedCenters(); break; case VIEW_MODE_TEACHER_TALKS: displayTalksByTeacher(detailId); break; case VIEW_MODE_CENTER_TALKS: displayTalksByCenter(detailId); break; } } void updateDisplayedTeachers() { Cursor cursor = teacherRepository.getTeachers(getSearchTerms(), starFilterOn); if (cursor != null) { cursorAdapter.changeCursor(cursor); } else { showToast("There was a problem displaying teachers."); setViewMode(VIEW_MODE_TALKS); updateDisplayedData(); } } void updateDisplayedCenters() { Cursor cursor = centerRepository.getCenters(getSearchTerms(), starFilterOn); if (cursor != null) { cursorAdapter.changeCursor(cursor); } else { showToast("There was a problem with the query"); setViewMode(VIEW_MODE_TALKS); updateDisplayedData(); } } /** * Updates the view with talks by search term, and whether talks are downloaded/starred */ private void updateDisplayedTalks() { Cursor cursor = talkRepository.getTalkAdapterData(getSearchTerms(), starFilterOn, downloadedOnly); if (cursor != null) { cursorAdapter.changeCursor(cursor); } else { showToast("There was a problem with the query"); } } /** * @return the list of search terms in the search box or null if empty */ private List<String> getSearchTerms() { ArrayList<String> searchTerms = null; if (searchBox.getText().length() > 0) { searchTerms = new ArrayList<>(Arrays.asList(searchBox.getText().toString().trim().split("\\s+"))); if (!extraSearchTerms.equals("")) { searchTerms.add(extraSearchTerms); } } return searchTerms; } /** * Shows a short toast with text=message * @param message */ public void showToast(String message) { Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); } }