package com.orgzly.android.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.Fragment;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.SearchView;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import com.orgzly.BuildConfig;
import com.orgzly.R;
import com.orgzly.android.Book;
import com.orgzly.android.BookName;
import com.orgzly.android.AppIntent;
import com.orgzly.android.Filter;
import com.orgzly.android.Note;
import com.orgzly.android.NotesBatch;
import com.orgzly.android.Notifications;
import com.orgzly.android.SearchQuery;
import com.orgzly.android.Shelf;
import com.orgzly.android.prefs.AppPreferences;
import com.orgzly.android.provider.clients.BooksClient;
import com.orgzly.android.provider.clients.ReposClient;
import com.orgzly.android.repos.ContentRepo;
import com.orgzly.android.repos.Repo;
import com.orgzly.android.ui.dialogs.SimpleOneLinerDialog;
import com.orgzly.android.ui.dialogs.WhatsNewDialog;
import com.orgzly.android.ui.fragments.BookFragment;
import com.orgzly.android.ui.fragments.BookPrefaceFragment;
import com.orgzly.android.ui.fragments.BooksFragment;
import com.orgzly.android.ui.fragments.DrawerFragment;
import com.orgzly.android.ui.fragments.FilterFragment;
import com.orgzly.android.ui.fragments.FiltersFragment;
import com.orgzly.android.ui.fragments.NoteFragment;
import com.orgzly.android.ui.fragments.NoteListFragment;
import com.orgzly.android.ui.fragments.SettingsFragment;
import com.orgzly.android.ui.fragments.SyncFragment;
import com.orgzly.android.ui.util.ActivityUtils;
import com.orgzly.android.util.AppPermissions;
import com.orgzly.android.util.LogUtils;
import com.orgzly.android.util.MiscUtils;
import com.orgzly.org.datetime.OrgDateTime;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
public class MainActivity extends CommonActivity
implements
ActionModeListener,
FilterFragment.FilterFragmentListener,
FiltersFragment.FiltersFragmentListener,
BooksFragment.BooksFragmentListener,
BookFragment.BookFragmentListener,
NoteFragment.NoteFragmentListener,
SyncFragment.SyncFragmentListener,
SimpleOneLinerDialog.SimpleOneLinerDialogListener,
BookPrefaceFragment.EditorListener,
NoteListFragment.NoteListFragmentListener,
SettingsFragment.SettingsFragmentListener,
DrawerFragment.DrawerFragmentListener {
public static final String TAG = MainActivity.class.getName();
public static final int ACTIVITY_REQUEST_CODE_FOR_FILE_CHOOSER = 0;
private static final String GETTING_STARTED_NOTEBOOK_NAME = "Getting Started with Orgzly";
private static final int GETTING_STARTED_NOTEBOOK_RESOURCE_ID = R.raw.orgzly_getting_started;
private static final int DIALOG_NEW_BOOK = 1;
private static final int DIALOG_IMPORT_BOOK = 5;
public static final String EXTRA_BOOK_ID = "book_id";
public static final String EXTRA_NOTE_ID = "note_id";
public SyncFragment mSyncFragment;
private DrawerLayout mDrawerLayout;
private ActionBarDrawerToggle mDrawerToggle;
/** isDrawerOpen() is not reliable - there was a race condition - closing drawer VS fragment resume. */
private boolean mIsDrawerOpen = false;
private CharSequence mSavedTitle;
private CharSequence mSavedSubtitle;
/** Original title used when book is not being displayed. */
private CharSequence mDefaultTitle;
private DisplayManager mDisplayManager;
private ActionMode mActionMode;
// private Dialog mTooltip;
private AlertDialog mWhatsNewDialog;
private BroadcastReceiver dbUpgradeStartedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mWhatsNewDialog != null) {
mWhatsNewDialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.running_database_update);
mWhatsNewDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
mWhatsNewDialog.setCancelable(false);
}
}
};
private BroadcastReceiver dbUpgradeEndedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mWhatsNewDialog != null) {
mWhatsNewDialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.ok);
mWhatsNewDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
mWhatsNewDialog.setCancelable(true);
}
}
};
private Bundle mImportChosenBook = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, savedInstanceState);
super.onCreate(savedInstanceState);
LocalBroadcastManager.getInstance(this)
.registerReceiver(dbUpgradeStartedReceiver, new IntentFilter(AppIntent.ACTION_DB_UPGRADE_STARTED));
LocalBroadcastManager.getInstance(this)
.registerReceiver(dbUpgradeEndedReceiver, new IntentFilter(AppIntent.ACTION_DB_UPGRADE_ENDED));
/*
* Set defaults.
* Attempting to set defaults every time (true parameter below), in case some preference's
* key has been changed. If false is used, opening Settings fragment can trigger preference
* change listener, even though nothing has been changed by user.
*/
android.preference.PreferenceManager.setDefaultValues(this, R.xml.preferences, true);
setContentView(R.layout.activity_main);
mSavedTitle = mDefaultTitle = getTitle();
mSavedSubtitle = null;
setupDrawer();
setupDisplay(savedInstanceState);
if (AppPreferences.newNoteNotification(this)) {
Notifications.createNewNoteNotification(this);
}
}
/**
* Adds initial set of fragments, depending on intent extras
*/
private void setupDisplay(Bundle savedInstanceState) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, getIntent().getExtras());
mDisplayManager = new DisplayManager(getSupportFragmentManager());
if (savedInstanceState == null) { // Not a configuration change.
long bookId = getIntent().getLongExtra(EXTRA_BOOK_ID, 0L);
long noteId = getIntent().getLongExtra(EXTRA_NOTE_ID, 0L);
mDisplayManager.displayBooks(false);
/* Display requested book and note. */
if (bookId > 0) {
mDisplayManager.displayBook(bookId, noteId);
if (noteId > 0) {
mDisplayManager.displayNote(bookId, noteId);
}
}
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
private void setupDrawer() {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
if (mDrawerLayout != null) {
// Set the drawer toggle as the DrawerListener
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.string.drawer_open, R.string.drawer_close) {
private int state = -1;
/*
* onDrawerOpened and onDrawerClosed are not called fast enough.
* So state is determined using onDrawerSlide callback and checking the slide offset.
*/
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
super.onDrawerSlide(drawerView, slideOffset);
switch (state) {
case -1: // Unknown
if (slideOffset == 0) {
state = 0;
drawerClosed();
} else if (slideOffset > 0) {
state = 1;
drawerOpened();
}
break;
case 0: // Closed
if (slideOffset > 0) {
state = 1;
drawerOpened();
}
break;
case 1: // Opened
if (slideOffset == 0) {
state = 0;
drawerClosed();
}
break;
}
}
};
mDrawerLayout.addDrawerListener(mDrawerToggle);
}
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
mSyncFragment = addSyncFragment();
addDrawerFragment();
}
private void drawerOpened() {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
/* Causes onPrepareOptionsMenu to be called. */
supportInvalidateOptionsMenu();
getSupportActionBar().setTitle(mDefaultTitle);
getSupportActionBar().setSubtitle(null);
mIsDrawerOpen = true;
ActivityUtils.closeSoftKeyboard(this);
}
private void drawerClosed() {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
/* Causes onPrepareOptionsMenu to be called. */
supportInvalidateOptionsMenu();
getSupportActionBar().setTitle(mSavedTitle);
getSupportActionBar().setSubtitle(mSavedSubtitle);
mIsDrawerOpen = false;
}
private SyncFragment addSyncFragment() {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
SyncFragment fragment = (SyncFragment) getSupportFragmentManager()
.findFragmentByTag(SyncFragment.FRAGMENT_TAG);
/* If the Fragment is non-null, then it is currently being
* retained across a configuration change.
*/
if (fragment == null) {
fragment = SyncFragment.getInstance();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.drawer_sync_container, fragment, SyncFragment.FRAGMENT_TAG)
.commit();
}
return fragment;
}
private void addDrawerFragment() {
if (getSupportFragmentManager().findFragmentByTag(DrawerFragment.FRAGMENT_TAG) == null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.drawer_list_container, DrawerFragment.getInstance(), DrawerFragment.FRAGMENT_TAG)
.commit();
}
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, savedInstanceState);
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
if (mDrawerToggle != null) {
mDrawerToggle.syncState();
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, newConfig);
super.onConfigurationChanged(newConfig);
if (mDrawerToggle != null) {
mDrawerToggle.onConfigurationChanged(newConfig);
}
}
@Override
protected void onResume() {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
super.onResume();
performIntros();
}
private void performIntros() {
int currentVersion = AppPreferences.lastUsedVersionCode(this);
boolean isNewVersion = checkIfNewAndUpdateVersion();
if (isNewVersion) {
/* Import Getting Started notebook. */
if (! AppPreferences.isGettingStartedNotebookLoaded(this)) {
importGettingStartedNotebook();
/* This will be marked as done after book has been loaded in onBookLoaded(). */
}
/* Open drawer for the first time user. */
if (currentVersion == 0 && mDrawerLayout != null) {
mDrawerLayout.openDrawer(GravityCompat.START);
mIsDrawerOpen = true;
}
onWhatsNewDisplayRequest();
}
}
private void importGettingStartedNotebook() {
mSyncFragment.loadBook(
GETTING_STARTED_NOTEBOOK_NAME,
getResources(),
GETTING_STARTED_NOTEBOOK_RESOURCE_ID);
}
private boolean checkIfNewAndUpdateVersion() {
boolean isNewVersion = false;
if (BuildConfig.VERSION_CODE > AppPreferences.lastUsedVersionCode(this)) {
isNewVersion = true;
}
AppPreferences.lastUsedVersionCode(this, BuildConfig.VERSION_CODE);
return isNewVersion;
}
@Override
protected void onPause() {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
super.onPause();
/* Dismiss What's new dialog. */
if (mWhatsNewDialog != null) {
mWhatsNewDialog.dismiss();
mWhatsNewDialog = null;
}
}
@Override
protected void onDestroy() {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
super.onDestroy();
LocalBroadcastManager.getInstance(this).unregisterReceiver(dbUpgradeStartedReceiver);
LocalBroadcastManager.getInstance(this).unregisterReceiver(dbUpgradeEndedReceiver);
if (mDrawerLayout != null && mDrawerToggle != null) {
mDrawerLayout.removeDrawerListener(mDrawerToggle);
}
}
@Override
public void onBackPressed() {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
/* Close drawer if opened. */
if (mDrawerLayout != null) {
if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
mDrawerLayout.closeDrawer(GravityCompat.START);
mIsDrawerOpen = false;
return;
}
}
/* Handle back press when editing note - check for changes */
Fragment fragment = getSupportFragmentManager().findFragmentByTag(NoteFragment.FRAGMENT_TAG);
if (fragment != null && fragment instanceof NoteFragment && fragment.isVisible()) {
final NoteFragment noteFragment = (NoteFragment) fragment;
if (noteFragment.isAskingForConfirmationForModifiedNote()) {
return;
}
}
super.onBackPressed();
}
@Override
public void onAttachFragment(Fragment fragment) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, fragment);
super.onAttachFragment(fragment);
}
@Override
protected void onResumeFragments() {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
super.onResumeFragments();
/* Showing dialog in onResume() fails with:
* Can not perform this action after onSaveInstanceState
*/
if (mImportChosenBook != null) {
importChosenBook(mImportChosenBook);
mImportChosenBook = null;
}
}
/**
* Callback for options menu.
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, menu);
super.onCreateOptionsMenu(menu);
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_actions, menu);
setupSearchView(menu);
// new Handler().postDelayed(new Runnable() {
// @Override
// public void run() {
// tryDisplayingTooltip();
// }
// }, 200);
return true;
}
/**
* SearchView setup and query text listeners.
* TODO: http://developer.android.com/training/search/setup.html
*/
private void setupSearchView(Menu menu) {
final MenuItem searchItem = menu.findItem(R.id.activity_action_search);
final SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
searchView.setQueryHint(getString(R.string.search_hint));
/* When user starts the search, fill the search box with text depending on current fragment. */
searchView.setOnSearchClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/* Make search as wide as possible. */
ViewGroup.LayoutParams layoutParams = searchView.getLayoutParams();
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
/* For Query fragment, fill the box with full query. */
SearchQuery q = mDisplayManager.getDisplayedQuery();
if (q != null) {
searchView.setQuery(q.toString() + " ", false);
} else {
/* If searching from book, add book name to query. */
Book book = getActiveFragmentBook();
if (book != null) {
SearchQuery query = new SearchQuery();
query.setBookName(book.getName());
searchView.setQuery(query.toString() + " ", false);
}
}
}
});
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextChange(String str) {
return false;
}
@Override
public boolean onQueryTextSubmit(String str) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, str);
/* Close search. */
MenuItemCompat.collapseActionView(searchItem);
mDisplayManager.displayQuery(str);
return true;
}
});
}
/**
* Callback for options menu.
* Called after each invalidateOptionsMenu().
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
/* Hide all menu items if drawer is visible. */
if (mDrawerLayout != null) {
menuItemsSetVisible(menu, !mDrawerLayout.isDrawerVisible(GravityCompat.START));
}
return true;
}
private void menuItemsSetVisible(Menu menu, boolean visible) {
for(int i = 0; i < menu.size(); i++){
menu.getItem(i).setVisible(visible);
}
}
/**
* Callback for options menu.
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Pass the event to ActionBarDrawerToggle, if it returns
// true, then it has handled the app icon touch event
if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
switch (item.getItemId()) {
case R.id.activity_action_sync:
mSyncFragment.onSyncButton();
return true;
case R.id.activity_action_settings:
mDisplayManager.displaySettings();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
/* Open selected file. */
case ACTIVITY_REQUEST_CODE_FOR_FILE_CHOOSER:
if (resultCode == RESULT_OK) {
Uri uri = data.getData();
Bundle bundle = new Bundle();
bundle.putString("uri", uri.toString());
/* This will get picked up in onResume(). */
mImportChosenBook = bundle;
}
break;
}
}
/**
* Display a dialog for user to enter notebook's name.
*/
private void importChosenBook(final Bundle bundle) {
Uri uri = Uri.parse(bundle.getString("uri"));
String guessedBookName = guessBookNameFromUri(uri);
SimpleOneLinerDialog
.getInstance(DIALOG_IMPORT_BOOK, R.string.import_as, R.string.name, R.string.import_, R.string.cancel, guessedBookName, bundle)
.show(getSupportFragmentManager(), SimpleOneLinerDialog.FRAGMENT_TAG);
}
/**
* @return Guessed book name or {@code null} if it couldn't be guessed
*/
private String guessBookNameFromUri(Uri uri) {
String fileName = BookName.getFileName(this, uri);
if (BookName.isSupportedFormatFileName(fileName)) {
BookName bookName = BookName.fromFileName(fileName);
return bookName.getName();
} else {
return null;
}
}
/**
* Import notebook from URI saving it under specified name.
*/
private void importBookFromUri(Uri uri, String bookName, BookName.Format format) {
/* Check if book name already exists in database. */
if (BooksClient.doesExist(this, bookName)) {
showSimpleSnackbarLong(getString(R.string.book_name_already_exists, bookName));
return;
}
mSyncFragment.importBookFromUri(bookName, format, uri);
}
/**
* Note has been clicked in list view.
*
* @param fragment Fragment from which the action came.
* @param view view
* @param position note position in list
* @param noteId note ID
*/
@Override
public void onNoteClick(NoteListFragment fragment, View view, int position, long noteId) {
if (AppPreferences.isReverseNoteClickAction(this)) {
toggleNoteSelection(fragment, view, noteId);
} else {
/* If there are already selected note, toggle the selection of this one.
* If there are no notes selected, open note
*/
if (fragment.getSelection().getCount() > 0) {
toggleNoteSelection(fragment, view, noteId);
} else {
openNote(fragment.getFragmentTag(), noteId);
}
}
}
/**
* Note has been long-clicked in list view.
*
* @param fragment Fragment from which the action came.
* @param view view
* @param position note position in list
* @param noteId note ID
*/
@Override
public void onNoteLongClick(NoteListFragment fragment, View view, int position, long noteId) {
if (AppPreferences.isReverseNoteClickAction(this)) {
openNote(fragment.getFragmentTag(), noteId);
} else {
toggleNoteSelection(fragment, view, noteId);
}
}
private void openNote(String fragmentTag, long noteId) {
finishActionMode();
/* Get book ID from note ID. */
// TODO: Avoid using Shelf from activity directly
Shelf shelf = new Shelf(this);
Note note = shelf.getNote(noteId);
long bookId = note.getPosition().getBookId();
mDisplayManager.displayNote(bookId, noteId);
}
@Override
public void onNoteScrollToRequest(long noteId) {
/* Get book ID from note ID. */
// TODO: Avoid using Shelf from activity directly
Shelf shelf = new Shelf(this);
Note note = shelf.getNote(noteId);
long bookId = note.getPosition().getBookId();
mSyncFragment.sparseTree(bookId, noteId);
mDisplayManager.displayBook(bookId, noteId);
}
/**
* Toggle selection of a note (or notes, if selected note is folded).
*/
private void toggleNoteSelection(NoteListFragment fragment, View view, long noteId) {
Selection selection = fragment.getSelection();
selection.toggle(view, noteId);
updateActionModeForSelection(fragment.getSelection(), fragment.getNewActionMode());
}
/* Open note fragment to create a new note. */
@Override
public void onNoteNewRequest(NotePlace target) {
finishActionMode();
mDisplayManager.displayNewNote(target);
}
/* Save note. */
@Override
public void onNoteCreateRequest(Note note, NotePlace notePlace) {
finishActionMode();
popBackStackAndCloseKeyboard();
mSyncFragment.createNote(note, notePlace);
}
@Override
public void onNoteCreated(final Note note) {
/*
* Display Snackbar with an action - create new note below just created one.
*/
View view = findViewById(R.id.main_content);
if (view != null) {
showSnackbar(Snackbar
.make(view, R.string.message_note_created, MiscUtils.SNACKBAR_WITH_ACTION_DURATION)
.setAction(R.string.new_below, new View.OnClickListener() {
@Override
public void onClick(View view) {
NotePlace notePlace = new NotePlace(
note.getPosition().getBookId(),
note.getId(),
Place.BELOW);
mDisplayManager.displayNewNote(notePlace);
}
}));
}
/* Animate updated note. */
// Set<Long> set = new HashSet<>();
// set.add(note.getId());
// animateNotesAfterEdit(set);
}
@Override
public void onNoteCreatingFailed() {
showSimpleSnackbarLong(R.string.message_failed_creating_note);
}
@Override
public void onNoteUpdateRequest(Note note) {
popBackStackAndCloseKeyboard();
mSyncFragment.updateNote(note);
}
@Override
public void onNoteUpdated(Note note) {
}
@Override
public void onNoteUpdatingFailed(Note note) {
showSimpleSnackbarLong(R.string.message_failed_updating_note);
}
@Override
public void onNoteCancelRequest(Note note) {
popBackStackAndCloseKeyboard();
}
@Override
public void onNoteDeleteRequest(Note note) {
popBackStackAndCloseKeyboard();
mSyncFragment.deleteNotes(note.getPosition().getBookId(), note.getId());
}
@Override
public void onStateChangeRequest(Set<Long> noteIds, String state) {
mSyncFragment.updateNoteState(noteIds, state);
}
@Override
public void onStateCycleRequest(long id, int direction) {
mSyncFragment.shiftNoteState(id, direction);
}
@Override
public void onStateToDoneRequest(long noteId) {
mSyncFragment.setStateToDone(noteId);
}
@Override
public void onStateChanged(Set<Long> noteIds, String state) {
// animateNotesAfterEdit(noteIds);
}
@Override
public void onScheduledTimeUpdateRequest(Set<Long> noteIds, OrgDateTime time) {
mSyncFragment.updateScheduledTime(noteIds, time);
}
@Override
public void onCycleVisibilityRequest(Book book) {
mSyncFragment.cycleVisibility(book);
}
@Override
public void onScheduledTimeUpdated(Set<Long> noteIds, OrgDateTime time) {
// animateNotesAfterEdit(noteIds);
}
@Override /* BookFragment */
public void onBookPrefaceEditRequest(Book book) {
finishActionMode();
mDisplayManager.displayEditor(book);
}
@Override
public void onNotesDeleteRequest(final long bookId, final TreeSet<Long> noteIds) {
mSyncFragment.deleteNotes(bookId, noteIds);
}
@Override
public void onNotesDeleted(int count) {
String message;
if (count == 0) {
message = getResources().getString(R.string.no_notes_deleted);
} else {
message = getResources().getQuantityString(R.plurals.notes_deleted, count, count);
}
showSimpleSnackbarLong(message);
}
@Override
public void onNotesCutRequest(long bookId, TreeSet<Long> noteIds) {
mSyncFragment.cutNotes(bookId, noteIds);
}
@Override
public void onNotesCut(int count) {
String message;
if (count == 0) {
message = getResources().getString(R.string.no_notes_cut);
} else {
message = getResources().getQuantityString(R.plurals.notes_cut, count, count);
}
showSimpleSnackbarLong(message);
}
@Override
public void onStateKeywordsPreferenceChanged() {
new AlertDialog.Builder(this)
.setTitle(R.string.todo_keywords_configuration_changed_dialog_title)
.setMessage(R.string.todo_keywords_configuration_changed_dialog_message)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mSyncFragment.reParseNotes();
}
})
.setNegativeButton(R.string.not_now, null)
.show();
}
/**
* Ask user to confirm, then delete book.
*/
@Override
public void onBookDeleteRequest(final long bookId) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, "Get name for the book " + bookId + "...");
final Book book = BooksClient.get(this, bookId);
View view = View.inflate(this, R.layout.dialog_book_delete, null);
final CheckBox checkBox = (CheckBox) view.findViewById(R.id.checkbox);
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which){
case DialogInterface.BUTTON_POSITIVE:
boolean deleteLinked = checkBox.isChecked();
/* Delete book. */
mSyncFragment.deleteBook(book, deleteLinked);
break;
case DialogInterface.BUTTON_NEGATIVE:
break;
}
}
};
AlertDialog.Builder builder =
new AlertDialog.Builder(this)
.setTitle("Delete " + MiscUtils.quotedString(book.getName()))
.setMessage("Delete this notebook?")
.setPositiveButton(R.string.ok, dialogClickListener)
.setNegativeButton(R.string.cancel, dialogClickListener);
if (book.getLink() != null) {
builder.setView(view);
}
builder.show();
}
@Override
public void onBookRenameRequest(final long bookId) {
final Book book = BooksClient.get(this, bookId);
if (book == null) {
return;
}
final View dialogView = View.inflate(this, R.layout.dialog_book_rename, null);
final TextInputLayout nameInputLayout = (TextInputLayout) dialogView.findViewById(R.id.name_input_layout);
final EditText name = (EditText) dialogView.findViewById(R.id.name);
Uri originalLinkUri = book.getLink() != null ? book.getLink().getUri() : null;
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
doRenameBook(book, name.getText().toString(), nameInputLayout);
break;
case DialogInterface.BUTTON_NEGATIVE:
break;
}
}
};
AlertDialog.Builder builder =
new AlertDialog.Builder(this)
.setTitle(getString(R.string.rename_book, MiscUtils.quotedString(book.getName())))
.setPositiveButton(R.string.rename, dialogClickListener)
.setNegativeButton(R.string.cancel, dialogClickListener)
.setView(dialogView);
name.setText(book.getName());
final AlertDialog dialog = builder.create();
/* Finish on keyboard action press. */
name.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
return true;
}
});
final Activity activity = this;
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
ActivityUtils.openSoftKeyboard(activity, name);
}
});
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
ActivityUtils.closeSoftKeyboard(activity);
}
});
if (originalLinkUri != null) {
name.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable str) {
/* Disable the button is nothing is entered. */
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(!TextUtils.isEmpty(str));
}
});
}
dialog.show();
}
private void doRenameBook(Book book, String name, TextInputLayout inputLayout) {
if (! TextUtils.isEmpty(name)) {
inputLayout.setError(null);
mSyncFragment.renameBook(book, name);
} else {
inputLayout.setError(getString(R.string.can_not_be_empty));
}
}
@Override
public void onBookLinkSetRequest(final long bookId) {
final Book book = BooksClient.get(this, bookId);
Map<String, Repo> repos = ReposClient.getAll(this);
if (repos.size() == 0) {
showSnackbarWithReposLink(getString(R.string.no_repos));
return;
}
LinkedHashMap<String, Integer> items = new LinkedHashMap<>();
int itemIndex = 0;
/* Add "no link" item. */
items.put(getString(R.string.no_link), itemIndex++);
/* Add repositories.
* FIXME: Skipping ContentRepo for now, as we can't construct Uri for a non-existent document.
* Repo might need changing to be repo uri + path
*/
for (String repoUri: repos.keySet()) {
Repo repo = repos.get(repoUri);
if (! (repo instanceof ContentRepo)) {
items.put(repoUri, itemIndex++);
}
}
View view = getLayoutInflater().inflate(R.layout.dialog_spinner, null, false);
final Spinner spinner = (Spinner) view.findViewById(R.id.dialog_spinner);
ArrayAdapter<String> adapter = new ArrayAdapter<>(spinner.getContext(), R.layout.spinner_item, new ArrayList<>(items.keySet()));
adapter.setDropDownViewResource(R.layout.dropdown_item);
spinner.setAdapter(adapter);
/* Set spinner to current book's link. */
if (book.getLink() != null) {
Integer pos = items.get(book.getLink().getRepoUri().toString());
if (pos != null) {
spinner.setSelection(pos);
}
}
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
Shelf shelf = new Shelf(MainActivity.this);
String repoUrl = (String) spinner.getSelectedItem();
if (getString(R.string.no_link).equals(repoUrl)) {
shelf.setLink(book, null);
} else {
shelf.setLink(book, repoUrl);
}
break;
case DialogInterface.BUTTON_NEGATIVE:
break;
}
}
};
new AlertDialog.Builder(this)
.setTitle("Link " + MiscUtils.quotedString(book.getName()) + " to repository")
.setView(view)
.setPositiveButton(R.string.set, dialogClickListener)
.setNegativeButton(R.string.cancel, dialogClickListener)
.show();
}
@Override
public void onForceSaveRequest(long bookId) {
mSyncFragment.forceSaveBook(bookId);
}
@Override
public void onBookSaved(Book book) {
}
@Override
public void onBookForceSavingFailed(Exception exception) {
}
@Override
public void onForceLoadRequest(long bookId) {
mSyncFragment.loadBook(bookId);
}
/**
* Callback from {@link com.orgzly.android.ui.fragments.BooksFragment}.
*/
@Override
public void onBookExportRequest(final long bookId) {
actionAfterPermissionGrant = new Runnable() {
@Override
public void run() {
mSyncFragment.exportBook(bookId);
}
};
/* Check for permissions. */
boolean isGranted = AppPermissions.isGrantedOrRequest(this, AppPermissions.FOR_BOOK_EXPORT);
if (isGranted) {
actionAfterPermissionGrant.run();
actionAfterPermissionGrant = null;
}
}
@Override
public void onBookLoadRequest() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
/* Allow choosing multiple files. */
// intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
startActivityForResult(
Intent.createChooser(intent, getString(R.string.import_org_file)),
ACTIVITY_REQUEST_CODE_FOR_FILE_CHOOSER);
}
@Override
public void onGettingStartedNotebookReloadRequest() {
importGettingStartedNotebook();
}
@Override
public void onWhatsNewDisplayRequest() {
if (mWhatsNewDialog != null) {
mWhatsNewDialog.dismiss();
}
mWhatsNewDialog = WhatsNewDialog.create(this);
mWhatsNewDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
mWhatsNewDialog = null;
}
});
mWhatsNewDialog.show();
}
/**
* Wipe database, after prompting user to confirm.
*/
@Override
public void onDatabaseClearRequest() {
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which){
case DialogInterface.BUTTON_POSITIVE:
mDisplayManager.reset();
mSyncFragment.clearDatabase();
break;
case DialogInterface.BUTTON_NEGATIVE:
break;
}
}
};
new AlertDialog.Builder(this)
.setTitle("Database")
.setMessage(R.string.clear_database_dialog_message)
.setPositiveButton(R.string.ok, dialogClickListener)
.setNegativeButton(R.string.cancel, dialogClickListener)
.show();
}
// @Override
// public void onRepoSettingsRequest() {
// mDisplayManager.reposSettingsRequest();
// }
/**
* Prompt user for book name and then create it.
*/
@Override
public void onBookCreateRequest() {
SimpleOneLinerDialog
.getInstance(DIALOG_NEW_BOOK, R.string.new_notebook, R.string.name, R.string.create, R.string.cancel, null, null)
.show(getSupportFragmentManager(), SimpleOneLinerDialog.FRAGMENT_TAG);
}
@Override /* SyncFragment */
public void onBookCreated(Book book) {
}
@Override /* SyncFragment */
public void onBookCreationFailed(Exception exception) {
showSimpleSnackbarLong(exception.getMessage());
}
@Override /* SyncFragment */
public void onBookLoaded(Book book) {
/* If it's Getting Started notebook, mark that it has been loaded in preferences,
* so we don't try to load it again.
*/
if (book.getName().equals(GETTING_STARTED_NOTEBOOK_NAME)) {
/* If notebook was already previously loaded, user probably requested reload.
* Display a message in that case.
*/
if (AppPreferences.isGettingStartedNotebookLoaded(this)) {
showSimpleSnackbarLong(R.string.getting_started_loaded);
} else {
AppPreferences.isGettingStartedNotebookLoaded(this, true);
}
}
}
@Override
public void onBookLoadFailed(Exception exception) {
}
/**
* Sync finished.
*
* Display snackbar with a message. If it makes sense also set action to open a repository..
*
* @param msg Error message if syncing failed, null if it was successful
*/
@Override
public void onSyncFinished(String msg) {
if (msg != null) {
showSnackbarWithReposLink(getString(R.string.syncing_failed, msg));
}
}
@Override /* SyncFragment */
public void onBookExported(File file) {
showSimpleSnackbarLong(getString(R.string.book_exported, file.getAbsolutePath()));
}
/**
* Display snackbar and include link to repositories.
*/
private void showSnackbarWithReposLink(String msg) {
View view = findViewById(R.id.main_content);
if (view != null) {
showSnackbar(Snackbar.make(view, msg, MiscUtils.SNACKBAR_WITH_ACTION_DURATION)
.setAction(R.string.repositories, new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setClass(MainActivity.this, ReposActivity.class);
startActivity(intent);
}
}));
}
}
@Override /* SyncFragment */
public void onBookExportFailed(Exception e) {
showSimpleSnackbarLong(getString(R.string.failed_exporting_book, e.getLocalizedMessage()));
}
@Override
public void onNotesPasteRequest(long bookId, long noteId, Place place) {
mSyncFragment.pasteNotes(bookId, noteId, place);
}
@Override
public void onNotesPromoteRequest(long bookId, Set<Long> noteIds) {
mSyncFragment.promoteNotes(bookId, noteIds);
}
@Override
public void onNotesDemoteRequest(long bookId, Set<Long> noteIds) {
mSyncFragment.demoteNotes(bookId, noteIds);
}
@Override
public void onNotesPasted(NotesBatch batch) {
String message = getResources().getQuantityString(
R.plurals.notes_pasted,
batch.getCount(),
batch.getCount());
showSimpleSnackbarLong(message);
}
@Override
public void onNotesNotPasted() {
showSimpleSnackbarLong(getResources().getString(R.string.no_notes_pasted));
}
@Override /* SyncFragment */
public void onBookDeleted(Book book) {
showSimpleSnackbarLong(R.string.message_book_deleted);
}
@Override
public void onBookDeletingFailed(Book book, IOException exception) {
String message = getResources().getString(
R.string.message_deleting_book_failed,
exception.toString());
showSimpleSnackbarLong(message);
}
@Override /* SyncFragment */
public void onDatabaseCleared() {
}
@Override
public void onBookClicked(long bookId) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, bookId);
mDisplayManager.displayBook(bookId, 0);
}
// private void animateNotesAfterEdit(Set<Long> noteIds) {
// Fragment f;
//
// f = getSupportFragmentManager().findFragmentByTag(BookFragment.FRAGMENT_TAG);
// if (f != null && f.isVisible()) {
// BookFragment heads = (BookFragment) f;
// heads.animateNotes(noteIds, HeadAnimator.ANIMATE_FOR_HEAD_MODIFIED);
// }
//
// f = getSupportFragmentManager().findFragmentByTag(QueryFragment.FRAGMENT_TAG);
// if (f != null && f.isVisible()) {
// QueryFragment heads = (QueryFragment) f;
// heads.animateNotes(noteIds, HeadAnimator.ANIMATE_FOR_HEAD_MODIFIED);
// }
// }
@Override /* EditorFragment */
public void onBookPrefaceEditSaveRequest(Book book) {
popBackStackAndCloseKeyboard();
mSyncFragment.updateBookSettings(book);
}
@Override
public void onBookPrefaceEditCancelRequest() {
popBackStackAndCloseKeyboard();
}
// TODO: Implement handlers when dialog is created
@Override
public void onSimpleOneLinerDialogValue(int id, String value, Bundle userData) {
switch (id) {
case DIALOG_NEW_BOOK:
mSyncFragment.createNewBook(value);
break;
case DIALOG_IMPORT_BOOK:
/* We are assuming it's an Org file. */
Uri uri = Uri.parse(userData.getString("uri"));
importBookFromUri(uri, value, BookName.Format.ORG);
break;
}
}
private Book getActiveFragmentBook() {
Fragment f = getSupportFragmentManager().findFragmentByTag(BookFragment.FRAGMENT_TAG);
if (f != null && f.isVisible()) {
BookFragment bookFragment = (BookFragment) f;
return bookFragment.getBook();
}
return null;
}
/*
* Action mode
*/
@Override
public void updateActionModeForSelection(Selection selection, ActionMode.Callback actionMode) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, selection, actionMode);
if (mActionMode != null) { /* Action menu is already activated. */
/* Finish action mode if there are no more selected items. */
if (selection.getCount() == 0) {
mActionMode.finish();
} else {
mActionMode.invalidate();
}
} else { /* No action menu activated - started it. */
if (selection.getCount() > 0) {
/* Start new action mode. */
mActionMode = startSupportActionMode(actionMode);
/* onPrepareActionMode is not being called (any more?) when action mode
* is first created. This causes title to be left empty. This forces
* onPrepareActionMode to be called and title updated.
*
* TODO: Update: Might not be the case any more
* We now get two calls to onPrepareActionMode when action mode is first created.
*/
mActionMode.invalidate();
}
}
}
@Override
public ActionMode getActionMode() {
return mActionMode;
}
@Override
public void actionModeDestroyed() {
mActionMode = null;
}
private void finishActionMode() {
if (mActionMode != null) {
mActionMode.finish();
}
}
/**
* Updates colors, FAB, title and subtitle, all depending on displayed fragment.
*/
@Override
public void announceChanges(String fragmentTag, CharSequence title, CharSequence subTitle, int selectionCount) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, fragmentTag, title, subTitle, selectionCount);
/* Save titles so they can be restored after drawer open/close. */
mSavedTitle = title != null ? title : mDefaultTitle;
mSavedSubtitle = subTitle;
/* Do not set titles if drawer is opened.
* This check is only needed for when drawer is opened for the first time
* programmatically, before fragment got to its onResume().
*/
if (! mIsDrawerOpen) {
/* Change titles. */
getSupportActionBar().setTitle(mSavedTitle);
getSupportActionBar().setSubtitle(mSavedSubtitle);
}
/* Set status and action bar colors depending on the fragment. */
ActivityUtils.setColorsForFragment(this, fragmentTag);
/* Notify drawer about currently active fragment. */
Fragment drawerFragment = getSupportFragmentManager()
.findFragmentByTag(DrawerFragment.FRAGMENT_TAG);
if (drawerFragment != null) {
((DrawerFragment) drawerFragment).setActiveFragment(fragmentTag);
}
/* Update floating action button. */
MainFab.updateFab(this, fragmentTag, selectionCount);
}
@Override
public void onFilterNewRequest() {
mDisplayManager.onFilterNewRequest();
}
@Override
public void onFilterDeleteRequest(Set<Long> ids) {
mSyncFragment.deleteFilters(ids);
}
@Override
public void onFilterEditRequest(long id) {
mDisplayManager.onFilterEditRequest(id);
}
@Override
public void onFilterMoveUpRequest(long id) {
mSyncFragment.moveFilterUp(id);
}
@Override
public void onFilterMoveDownRequest(long id) {
mSyncFragment.moveFilterDown(id);
}
@Override
public void onFilterCreateRequest(Filter filter) {
popBackStackAndCloseKeyboard();
mSyncFragment.createFilter(filter);
}
@Override
public void onFilterUpdateRequest(long id, Filter filter) {
popBackStackAndCloseKeyboard();
mSyncFragment.updateFilter(id, filter);
}
@Override
public void onFilterCancelRequest() {
popBackStackAndCloseKeyboard();
}
// private void tryDisplayingTooltip() {
// if (mTooltip == null) {
// mTooltip = Tooltips.display(this, mIsDrawerOpen);
//
// if (mTooltip != null) {
// mTooltip.setOnDismissListener(new DialogInterface.OnDismissListener() {
// @Override
// public void onDismiss(DialogInterface dialog) {
// mTooltip = null;
// }
// });
// }
// }
// }
@Override
public void onDrawerItemClicked(final DrawerFragment.DrawerItem item) {
/* Don't end action mode if the click was on book - it could be the same book. */
if (! (item instanceof DrawerFragment.BookItem)) {
finishActionMode();
}
/* Close drawer. */
if (mDrawerLayout != null) {
mDrawerLayout.closeDrawer(GravityCompat.START);
}
runDelayedAfterDrawerClose(new Runnable() {
@Override
public void run() {
if (item instanceof DrawerFragment.BooksItem) {
mDisplayManager.displayBooks(true);
} else if (item instanceof DrawerFragment.FiltersItem) {
mDisplayManager.displayFilters();
} else if (item instanceof DrawerFragment.SettingsItem) {
mDisplayManager.displaySettings();
} else if (item instanceof DrawerFragment.BookItem) {
long bookId = ((DrawerFragment.BookItem) item).id;
mDisplayManager.displayBook(bookId, 0);
} else if (item instanceof DrawerFragment.FilterItem) {
String query = ((DrawerFragment.FilterItem) item).query;
mDisplayManager.displayQuery(query);
}
}
});
}
/* Avoid jerky drawer close by displaying new fragment with a delay.
* Previous title might be displayed if loading of the new fragment takes too long and it
* might look ugly, showing for only a fraction of a second before being replaced with new one.
* FIXME: Maybe move drawer handling here and add a flag for *not* changing the title.
*/
private void runDelayedAfterDrawerClose(Runnable runnable) {
new Handler().postDelayed(runnable, 300);
}
}