/* * Copyright (C) 2015 Federico Iosue (federico.iosue@gmail.com) * * 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 it.feio.android.omninotes; import android.Manifest; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; import android.app.DatePickerDialog.OnDateSetListener; import android.app.TimePickerDialog.OnTimeSetListener; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.location.Location; import android.media.MediaPlayer; import android.media.MediaRecorder; import android.media.ThumbnailUtils; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.provider.MediaStore; import android.support.annotation.Nullable; import android.support.v4.app.FragmentTransaction; import android.support.v4.util.Pair; import android.support.v4.view.MenuItemCompat; import android.support.v4.widget.DrawerLayout; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.util.DisplayMetrics; import android.util.Log; import android.view.*; import android.view.View.OnClickListener; import android.view.View.OnTouchListener; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; import android.view.animation.AnimationUtils; import android.widget.*; import butterknife.Bind; import butterknife.ButterKnife; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.bumptech.glide.load.resource.bitmap.GlideBitmapDrawable; import com.neopixl.pixlui.components.edittext.EditText; import com.neopixl.pixlui.components.textview.TextView; import com.pushbullet.android.extension.MessagingExtension; import de.greenrobot.event.EventBus; import de.keyboardsurfer.android.widget.crouton.Style; import it.feio.android.checklistview.exceptions.ViewNotSupportedException; import it.feio.android.checklistview.interfaces.CheckListChangedListener; import it.feio.android.checklistview.models.CheckListViewItem; import it.feio.android.checklistview.models.ChecklistManager; import it.feio.android.checklistview.utils.DensityUtil; import it.feio.android.omninotes.async.AttachmentTask; import it.feio.android.omninotes.async.bus.NotesUpdatedEvent; import it.feio.android.omninotes.async.bus.PushbulletReplyEvent; import it.feio.android.omninotes.async.bus.SwitchFragmentEvent; import it.feio.android.omninotes.async.notes.NoteProcessorDelete; import it.feio.android.omninotes.async.notes.SaveNoteTask; import it.feio.android.omninotes.db.DbHelper; import it.feio.android.omninotes.helpers.PermissionsHelper; import it.feio.android.omninotes.helpers.date.DateHelper; import it.feio.android.omninotes.models.*; import it.feio.android.omninotes.models.adapters.AttachmentAdapter; import it.feio.android.omninotes.models.adapters.NavDrawerCategoryAdapter; import it.feio.android.omninotes.models.adapters.PlacesAutoCompleteAdapter; import it.feio.android.omninotes.models.listeners.OnAttachingFileListener; import it.feio.android.omninotes.models.listeners.OnGeoUtilResultListener; import it.feio.android.omninotes.models.listeners.OnNoteSaved; import it.feio.android.omninotes.models.listeners.OnReminderPickedListener; import it.feio.android.omninotes.models.views.ExpandableHeightGridView; import it.feio.android.omninotes.utils.*; import it.feio.android.omninotes.utils.Display; import it.feio.android.omninotes.utils.date.DateUtils; import it.feio.android.omninotes.utils.date.ReminderPickers; import it.feio.android.pixlui.links.TextLinkClickListener; import org.apache.commons.lang.StringUtils; import java.io.File; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.List; import static com.nineoldandroids.view.ViewPropertyAnimator.animate; public class DetailFragment extends BaseFragment implements OnReminderPickedListener, OnTouchListener, OnGlobalLayoutListener, OnAttachingFileListener, TextWatcher, CheckListChangedListener, OnNoteSaved, OnGeoUtilResultListener { private static final int TAKE_PHOTO = 1; private static final int TAKE_VIDEO = 2; private static final int SET_PASSWORD = 3; private static final int SKETCH = 4; private static final int CATEGORY = 5; private static final int DETAIL = 6; private static final int FILES = 7; @Bind(R.id.detail_root) ViewGroup root; @Bind(R.id.detail_title) EditText title; @Bind(R.id.detail_content) EditText content; @Bind(R.id.detail_attachments_above) ViewStub attachmentsAbove; @Bind(R.id.detail_attachments_below) ViewStub attachmentsBelow; @Nullable @Bind(R.id.gridview) ExpandableHeightGridView mGridView; @Bind(R.id.location) TextView locationTextView; @Bind(R.id.detail_timestamps) View timestampsView; @Bind(R.id.reminder_layout) LinearLayout reminder_layout; @Bind(R.id.reminder_icon) ImageView reminderIcon; @Bind(R.id.datetime) TextView datetime; @Bind(R.id.detail_tile_card) View titleCardView; @Bind(R.id.content_wrapper) ScrollView scrollView; @Bind(R.id.creation) TextView creationTextView; @Bind(R.id.last_modification) TextView lastModificationTextView; @Bind(R.id.title_wrapper) View titleWrapperView; @Bind(R.id.tag_marker) View tagMarkerView; @Bind(R.id.detail_wrapper) ViewManager detailWrapperView; @Bind(R.id.snackbar_placeholder) View snackBarPlaceholder; public OnDateSetListener onDateSetListener; public OnTimeSetListener onTimeSetListener; public boolean goBack = false; View toggleChecklistView; private Uri attachmentUri; private AttachmentAdapter mAttachmentAdapter; private PopupWindow attachmentDialog; private Note note; private Note noteTmp; private Note noteOriginal; // Audio recording private String recordName; private MediaRecorder mRecorder = null; private MediaPlayer mPlayer = null; private boolean isRecording = false; private View isPlayingView = null; private Bitmap recordingBitmap; private ChecklistManager mChecklistManager; // Values to print result private String exitMessage; private Style exitCroutonStyle = ONStyle.CONFIRM; // Flag to check if after editing it will return to ListActivity or not // and in the last case a Toast will be shown instead than Crouton private boolean afterSavedReturnsToList = true; private boolean showKeyboard = false; private boolean swiping; private int startSwipeX; private SharedPreferences prefs; private View keyboardPlaceholder; private boolean orientationChanged; private long audioRecordingTimeStart; private long audioRecordingTime; private DetailFragment mFragment; private Attachment sketchEdited; private int contentLineCounter = 1; private int contentCursorPosition; private ArrayList<String> mergedNotesIds; private MainActivity mainActivity; private boolean activityPausing; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mFragment = this; } @Override public void onStart() { super.onStart(); EventBus.getDefault().post(new SwitchFragmentEvent(SwitchFragmentEvent.Direction.CHILDREN)); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); GeocodeHelper.stop(); } @Override public void onResume() { super.onResume(); // Adding a layout observer to perform calculus when showing keyboard if (root != null) { root.getViewTreeObserver().addOnGlobalLayoutListener(this); } activityPausing = false; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_detail, container, false); ButterKnife.bind(this, view); return view; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mainActivity = (MainActivity) getActivity(); prefs = mainActivity.prefs; mainActivity.getSupportActionBar().setDisplayShowTitleEnabled(false); mainActivity.getToolbar().setNavigationOnClickListener(v -> navigateUp()); // Force the navigation drawer to stay opened if tablet mode is on, otherwise has to stay closed if (NavigationDrawerFragment.isDoublePanelActive()) { mainActivity.getDrawerLayout().setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN); } else { mainActivity.getDrawerLayout().setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); } // Restored temp note after orientation change if (savedInstanceState != null) { noteTmp = savedInstanceState.getParcelable("noteTmp"); note = savedInstanceState.getParcelable("note"); noteOriginal = savedInstanceState.getParcelable("noteOriginal"); attachmentUri = savedInstanceState.getParcelable("attachmentUri"); orientationChanged = savedInstanceState.getBoolean("orientationChanged"); } // Added the sketched image if present returning from SketchFragment if (mainActivity.sketchUri != null) { Attachment mAttachment = new Attachment(mainActivity.sketchUri, Constants.MIME_TYPE_SKETCH); addAttachment(mAttachment); mainActivity.sketchUri = null; // Removes previous version of edited image if (sketchEdited != null) { noteTmp.getAttachmentsList().remove(sketchEdited); sketchEdited = null; } } init(); setHasOptionsMenu(true); setRetainInstance(false); } @Override public void onSaveInstanceState(Bundle outState) { if (noteTmp != null) { noteTmp.setTitle(getNoteTitle()); noteTmp.setContent(getNoteContent()); outState.putParcelable("noteTmp", noteTmp); outState.putParcelable("note", note); outState.putParcelable("noteOriginal", noteOriginal); outState.putParcelable("attachmentUri", attachmentUri); outState.putBoolean("orientationChanged", orientationChanged); } super.onSaveInstanceState(outState); } @SuppressLint("NewApi") @SuppressWarnings("deprecation") @Override public void onPause() { super.onPause(); activityPausing = true; // Checks "goBack" value to avoid performing a double saving if (!goBack) { saveNote(this); } if (mRecorder != null) { mRecorder.release(); mRecorder = null; } // Unregistering layout observer if (root != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { root.getViewTreeObserver().removeOnGlobalLayoutListener(this); } else { root.getViewTreeObserver().removeGlobalOnLayoutListener(this); } } // Closes keyboard on exit if (toggleChecklistView != null) { KeyboardUtils.hideKeyboard(toggleChecklistView); content.clearFocus(); } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (getResources().getConfiguration().orientation != newConfig.orientation) { orientationChanged = true; } } private void init() { // Handling of Intent actions handleIntents(); if (noteOriginal == null) { noteOriginal = getArguments().getParcelable(Constants.INTENT_NOTE); } if (note == null) { note = new Note(noteOriginal); } if (noteTmp == null) { noteTmp = new Note(note); } if (noteTmp.isLocked() && !noteTmp.isPasswordChecked()) { checkNoteLock(noteTmp); return; } initViews(); } /** * Checks note lock and password before showing note content */ private void checkNoteLock(Note note) { // If note is locked security password will be requested if (note.isLocked() && prefs.getString(Constants.PREF_PASSWORD, null) != null && !prefs.getBoolean("settings_password_access", false)) { PasswordHelper.requestPassword(mainActivity, passwordConfirmed -> { if (passwordConfirmed) { noteTmp.setPasswordChecked(true); init(); } else { goBack = true; goHome(); } }); } else { noteTmp.setPasswordChecked(true); init(); } } private void handleIntents() { Intent i = mainActivity.getIntent(); if (IntentChecker.checkAction(i, Constants.ACTION_MERGE)) { noteOriginal = new Note(); note = new Note(noteOriginal); noteTmp = getArguments().getParcelable(Constants.INTENT_NOTE); if (i.getStringArrayListExtra("merged_notes") != null) { mergedNotesIds = i.getStringArrayListExtra("merged_notes"); } } // Action called from home shortcut if (IntentChecker.checkAction(i, Constants.ACTION_SHORTCUT, Constants.ACTION_NOTIFICATION_CLICK)) { afterSavedReturnsToList = false; noteOriginal = DbHelper.getInstance().getNote(i.getLongExtra(Constants.INTENT_KEY, 0)); // Checks if the note pointed from the shortcut has been deleted try { note = new Note(noteOriginal); noteTmp = new Note(noteOriginal); } catch (NullPointerException e) { mainActivity.showToast(getText(R.string.shortcut_note_deleted), Toast.LENGTH_LONG); mainActivity.finish(); } } // Check if is launched from a widget if (IntentChecker.checkAction(i, Constants.ACTION_WIDGET, Constants.ACTION_TAKE_PHOTO)) { afterSavedReturnsToList = false; showKeyboard = true; // with tags to set tag if (i.hasExtra(Constants.INTENT_WIDGET)) { String widgetId = i.getExtras().get(Constants.INTENT_WIDGET).toString(); if (widgetId != null) { String sqlCondition = prefs.getString(Constants.PREF_WIDGET_PREFIX + widgetId, ""); String categoryId = TextHelper.checkIntentCategory(sqlCondition); if (categoryId != null) { Category category; try { category = DbHelper.getInstance().getCategory(Long.parseLong(categoryId)); noteTmp = new Note(); noteTmp.setCategory(category); } catch (NumberFormatException e) { Log.e(Constants.TAG, "Category with not-numeric value!", e); } } } } // Sub-action is to take a photo if (IntentChecker.checkAction(i, Constants.ACTION_TAKE_PHOTO)) { takePhoto(); } } /** * Handles third party apps requests of sharing */ if (IntentChecker.checkAction(i, Intent.ACTION_SEND, Intent.ACTION_SEND_MULTIPLE, Constants.INTENT_GOOGLE_NOW) && i.getType() != null) { afterSavedReturnsToList = false; if (noteTmp == null) noteTmp = new Note(); // Text title String title = i.getStringExtra(Intent.EXTRA_SUBJECT); if (title != null) { noteTmp.setTitle(title); } // Text content String content = i.getStringExtra(Intent.EXTRA_TEXT); if (content != null) { noteTmp.setContent(content); } // Single attachment data Uri uri = i.getParcelableExtra(Intent.EXTRA_STREAM); // Due to the fact that Google Now passes intent as text but with // audio recording attached the case must be handled in specific way if (uri != null && !Constants.INTENT_GOOGLE_NOW.equals(i.getAction())) { String name = FileHelper.getNameFromUri(mainActivity, uri); new AttachmentTask(this, uri, name, this).execute(); } // Multiple attachment data ArrayList<Uri> uris = i.getParcelableArrayListExtra(Intent.EXTRA_STREAM); if (uris != null) { for (Uri uriSingle : uris) { String name = FileHelper.getNameFromUri(mainActivity, uriSingle); new AttachmentTask(this, uriSingle, name, this).execute(); } } } if (IntentChecker.checkAction(i, Intent.ACTION_MAIN, Constants.ACTION_WIDGET_SHOW_LIST, Constants .ACTION_SHORTCUT_WIDGET, Constants.ACTION_WIDGET)) { showKeyboard = true; } i.setAction(null); } @SuppressLint("NewApi") private void initViews() { // Sets onTouchListener to the whole activity to swipe notes root.setOnTouchListener(this); // Overrides font sizes with the one selected from user Fonts.overrideTextSize(mainActivity, prefs, root); // Color of tag marker if note is tagged a function is active in preferences setTagMarkerColor(noteTmp.getCategory()); initViewTitle(); initViewContent(); initViewLocation(); initViewAttachments(); initViewReminder(); initViewFooter(); } private void initViewFooter() { // Footer dates of creation... String creation = DateHelper.getFormattedDate(noteTmp.getCreation(), prefs.getBoolean(Constants .PREF_PRETTIFIED_DATES, true)); creationTextView.append(creation.length() > 0 ? getString(R.string.creation) + " " + creation : ""); if (creationTextView.getText().length() == 0) creationTextView.setVisibility(View.GONE); // ... and last modification String lastModification = DateHelper.getFormattedDate(noteTmp.getLastModification(), prefs.getBoolean(Constants .PREF_PRETTIFIED_DATES, true)); lastModificationTextView.append(lastModification.length() > 0 ? getString(R.string.last_update) + " " + lastModification : ""); if (lastModificationTextView.getText().length() == 0) lastModificationTextView.setVisibility(View.GONE); } private void initViewReminder() { // Preparation for reminder icon reminder_layout.setOnClickListener(v -> { int pickerType = prefs.getBoolean("settings_simple_calendar", false) ? ReminderPickers.TYPE_AOSP : ReminderPickers.TYPE_GOOGLE; ReminderPickers reminderPicker = new ReminderPickers(mainActivity, mFragment, pickerType); Long presetDateTime = noteTmp.getAlarm() != null ? Long.parseLong(noteTmp.getAlarm()) : DateUtils .getNextMinute(); reminderPicker.pick(presetDateTime, noteTmp.getRecurrenceRule()); onDateSetListener = reminderPicker; onTimeSetListener = reminderPicker; }); reminder_layout.setOnLongClickListener(v -> { MaterialDialog dialog = new MaterialDialog.Builder(mainActivity) .content(R.string.remove_reminder) .positiveText(R.string.ok) .callback(new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog materialDialog) { noteTmp.setAlarm(null); reminderIcon.setImageResource(R.drawable.ic_alarm_black_18dp); datetime.setText(""); } }).build(); dialog.show(); return true; }); // Reminder String reminderString = initReminder(noteTmp); if (!StringUtils.isEmpty(reminderString)) { reminderIcon.setImageResource(R.drawable.ic_alarm_add_black_18dp); datetime.setText(reminderString); } // Timestamps view // Bottom padding set for translucent navbar in Kitkat if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { int navBarHeight = Display.getNavigationBarHeightKitkat(mainActivity); int negativePadding = navBarHeight >= 27 * 3 ? -27 : 0; int timestampsViewPaddingBottom = navBarHeight > 0 ? navBarHeight + negativePadding : timestampsView .getPaddingBottom(); timestampsView.setPadding(timestampsView.getPaddingStart(), timestampsView.getPaddingTop(), timestampsView.getPaddingEnd(), timestampsViewPaddingBottom); } } private void initViewLocation() { DetailFragment detailFragment = this; if (isNoteLocationValid()) { if (TextUtils.isEmpty(noteTmp.getAddress())) { //FIXME: What's this "sasd"? GeocodeHelper.getAddressFromCoordinates(new Location("sasd"), detailFragment); } else { locationTextView.setText(noteTmp.getAddress()); locationTextView.setVisibility(View.VISIBLE); } } // Automatic location insertion if (prefs.getBoolean(Constants.PREF_AUTO_LOCATION, false) && noteTmp.get_id() == null) { getLocation(detailFragment); } locationTextView.setOnClickListener(v -> { String uriString = "geo:" + noteTmp.getLatitude() + ',' + noteTmp.getLongitude() + "?q=" + noteTmp.getLatitude() + ',' + noteTmp.getLongitude(); Intent locationIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uriString)); if (!IntentChecker.isAvailable(mainActivity, locationIntent, null)) { uriString = "http://maps.google.com/maps?q=" + noteTmp.getLatitude() + ',' + noteTmp .getLongitude(); locationIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uriString)); } startActivity(locationIntent); }); locationTextView.setOnLongClickListener(v -> { MaterialDialog.Builder builder = new MaterialDialog.Builder(mainActivity); builder.content(R.string.remove_location); builder.positiveText(R.string.ok); builder.callback(new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog materialDialog) { noteTmp.setLatitude(""); noteTmp.setLongitude(""); fade(locationTextView, false); } }); MaterialDialog dialog = builder.build(); dialog.show(); return true; }); } private void getLocation(OnGeoUtilResultListener onGeoUtilResultListener) { PermissionsHelper.requestPermission(getActivity(), Manifest.permission.ACCESS_COARSE_LOCATION, R.string .permission_coarse_location, snackBarPlaceholder, () -> { GeocodeHelper.getLocation(onGeoUtilResultListener); }); } private void initViewAttachments() { // Attachments position based on preferences if (prefs.getBoolean(Constants.PREF_ATTANCHEMENTS_ON_BOTTOM, false)) { attachmentsBelow.inflate(); } else { attachmentsAbove.inflate(); } mGridView = (ExpandableHeightGridView) root.findViewById(R.id.gridview); // Some fields can be filled by third party application and are always shown mAttachmentAdapter = new AttachmentAdapter(mainActivity, noteTmp.getAttachmentsList(), mGridView); // Initialzation of gridview for images mGridView.setAdapter(mAttachmentAdapter); mGridView.autoresize(); // Click events for images in gridview (zooms image) mGridView.setOnItemClickListener((parent, v, position, id) -> { Attachment attachment = (Attachment) parent.getAdapter().getItem(position); Uri uri = attachment.getUri(); Intent attachmentIntent; if (Constants.MIME_TYPE_FILES.equals(attachment.getMime_type())) { attachmentIntent = new Intent(Intent.ACTION_VIEW); attachmentIntent.setDataAndType(uri, StorageHelper.getMimeType(mainActivity, attachment.getUri())); attachmentIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent .FLAG_GRANT_WRITE_URI_PERMISSION); if (IntentChecker.isAvailable(mainActivity.getApplicationContext(), attachmentIntent, null)) { startActivity(attachmentIntent); } else { mainActivity.showMessage(R.string.feature_not_available_on_this_device, ONStyle.WARN); } // Media files will be opened in internal gallery } else if (Constants.MIME_TYPE_IMAGE.equals(attachment.getMime_type()) || Constants.MIME_TYPE_SKETCH.equals(attachment.getMime_type()) || Constants.MIME_TYPE_VIDEO.equals(attachment.getMime_type())) { // Title noteTmp.setTitle(getNoteTitle()); noteTmp.setContent(getNoteContent()); String title1 = TextHelper.parseTitleAndContent(mainActivity, noteTmp)[0].toString(); // Images int clickedImage = 0; ArrayList<Attachment> images = new ArrayList<>(); for (Attachment mAttachment : noteTmp.getAttachmentsList()) { if (Constants.MIME_TYPE_IMAGE.equals(mAttachment.getMime_type()) || Constants.MIME_TYPE_SKETCH.equals(mAttachment.getMime_type()) || Constants.MIME_TYPE_VIDEO.equals(mAttachment.getMime_type())) { images.add(mAttachment); if (mAttachment.equals(attachment)) { clickedImage = images.size() - 1; } } } // Intent attachmentIntent = new Intent(mainActivity, GalleryActivity.class); attachmentIntent.putExtra(Constants.GALLERY_TITLE, title1); attachmentIntent.putParcelableArrayListExtra(Constants.GALLERY_IMAGES, images); attachmentIntent.putExtra(Constants.GALLERY_CLICKED_IMAGE, clickedImage); startActivity(attachmentIntent); } else if (Constants.MIME_TYPE_AUDIO.equals(attachment.getMime_type())) { playback(v, attachment.getUri()); } }); mGridView.setOnItemLongClickListener((parent, v, position, id) -> { // To avoid deleting audio attachment during playback if (mPlayer != null) return false; List<String> items = Arrays.asList(getResources().getStringArray(R.array.attachments_actions)); if (!Constants.MIME_TYPE_SKETCH.equals(mAttachmentAdapter.getItem(position).getMime_type())) { items = items.subList(0, items.size() - 1); } new MaterialDialog.Builder(mainActivity) .title(mAttachmentAdapter.getItem(position).getName()) .items(items.toArray(new String[items.size()])) .itemsCallback((materialDialog, view, i, charSequence) -> performAttachmentAction(position, i)) .build() .show(); return true; }); } /** * Performs an action when long-click option is selected * * @param attachmentPosition * @param i item index */ private void performAttachmentAction(int attachmentPosition, int i) { switch (getResources().getStringArray(R.array.attachments_actions_values)[i]) { case "share": Intent shareIntent = new Intent(Intent.ACTION_SEND); Attachment attachment = mAttachmentAdapter.getItem(attachmentPosition); shareIntent.setType(StorageHelper.getMimeType(OmniNotes.getAppContext(), attachment.getUri())); shareIntent.putExtra(Intent.EXTRA_STREAM, attachment.getUri()); if (IntentChecker.isAvailable(OmniNotes.getAppContext(), shareIntent, null)) { startActivity(shareIntent); } else { mainActivity.showMessage(R.string.feature_not_available_on_this_device, ONStyle.WARN); } break; case "delete": removeAttachment(attachmentPosition); mAttachmentAdapter.notifyDataSetChanged(); mGridView.autoresize(); break; case "delete all": new MaterialDialog.Builder(mainActivity) .title(R.string.delete_all_attachments) .positiveText(R.string.confirm) .onPositive((materialDialog, dialogAction) -> removeAllAttachments()) .build() .show(); break; case "edit": takeSketch(mAttachmentAdapter.getItem(attachmentPosition)); break; default: Log.w(Constants.TAG, "No action available"); } } private void initViewTitle() { title.setText(noteTmp.getTitle()); title.gatherLinksForText(); title.setOnTextLinkClickListener(textLinkClickListener); // To avoid dropping here the dragged checklist items if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { title.setOnDragListener((v, event) -> { // ((View)event.getLocalState()).setVisibility(View.VISIBLE); return true; }); } //When editor action is pressed focus is moved to last character in content field title.setOnEditorActionListener((v, actionId, event) -> { content.requestFocus(); content.setSelection(content.getText().length()); return false; }); requestFocus(title); } private void initViewContent() { content.setText(noteTmp.getContent()); content.gatherLinksForText(); content.setOnTextLinkClickListener(textLinkClickListener); // Avoids focused line goes under the keyboard content.addTextChangedListener(this); // Restore checklist toggleChecklistView = content; if (noteTmp.isChecklist()) { noteTmp.setChecklist(false); AlphaManager.setAlpha(toggleChecklistView, 0); toggleChecklist2(); } } /** * Force focus and shows soft keyboard. Only happens if it's a new note, without shared content. * {@link showKeyboard} is used to check if the note is created from shared content. */ private void requestFocus(final EditText view) { if (note.get_id() == null && !noteTmp.isChanged(note) && showKeyboard) { KeyboardUtils.showKeyboard(view); } } /** * Colors tag marker in note's title and content elements */ private void setTagMarkerColor(Category tag) { String colorsPref = prefs.getString("settings_colors_app", Constants.PREF_COLORS_APP_DEFAULT); // Checking preference if (!"disabled".equals(colorsPref)) { // Choosing target view depending on another preference ArrayList<View> target = new ArrayList<>(); if ("complete".equals(colorsPref)) { target.add(titleWrapperView); target.add(scrollView); } else { target.add(tagMarkerView); } // Coloring the target if (tag != null && tag.getColor() != null) { for (View view : target) { view.setBackgroundColor(Integer.parseInt(tag.getColor())); } } else { for (View view : target) { view.setBackgroundColor(Color.parseColor("#00000000")); } } } } private void displayLocationDialog() { getLocation(new OnGeoUtilResultListenerImpl(mainActivity, mFragment, noteTmp)); } private static class OnGeoUtilResultListenerImpl implements OnGeoUtilResultListener { private final WeakReference<MainActivity> mainActivityWeakReference; private final WeakReference<DetailFragment> detailFragmentWeakReference; private final WeakReference<Note> noteTmpWeakReference; OnGeoUtilResultListenerImpl(MainActivity activity, DetailFragment mFragment, Note noteTmp) { this.mainActivityWeakReference = new WeakReference<>(activity); this.detailFragmentWeakReference = new WeakReference<>(mFragment); this.noteTmpWeakReference = new WeakReference<>(noteTmp); } @Override public void onAddressResolved(String address) { } @Override public void onCoordinatesResolved(Location location, String address) { } @Override public void onLocationUnavailable() { mainActivityWeakReference.get().showMessage(R.string.location_not_found, ONStyle.ALERT); } @Override public void onLocationRetrieved(Location location) { if (!checkWeakReferences()) { return; } if (location == null) { return; } if (!ConnectionManager.internetAvailable(mainActivityWeakReference.get())) { noteTmpWeakReference.get().setLatitude(location.getLatitude()); noteTmpWeakReference.get().setLongitude(location.getLongitude()); onAddressResolved(""); return; } LayoutInflater inflater = mainActivityWeakReference.get().getLayoutInflater(); View v = inflater.inflate(R.layout.dialog_location, null); final AutoCompleteTextView autoCompView = (AutoCompleteTextView) v.findViewById(R.id .auto_complete_location); autoCompView.setHint(mainActivityWeakReference.get().getString(R.string.search_location)); autoCompView.setAdapter(new PlacesAutoCompleteAdapter(mainActivityWeakReference.get(), R.layout .simple_text_layout)); final MaterialDialog dialog = new MaterialDialog.Builder(mainActivityWeakReference.get()) .customView(autoCompView, false) .positiveText(R.string.use_current_location) .callback(new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog materialDialog) { if (TextUtils.isEmpty(autoCompView.getText().toString())) { noteTmpWeakReference.get().setLatitude(location.getLatitude()); noteTmpWeakReference.get().setLongitude(location.getLongitude()); GeocodeHelper.getAddressFromCoordinates(location, detailFragmentWeakReference.get()); } else { GeocodeHelper.getCoordinatesFromAddress(autoCompView.getText().toString(), detailFragmentWeakReference.get()); } } }) .build(); autoCompView.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (s.length() != 0) { dialog.setActionButton(DialogAction.POSITIVE, mainActivityWeakReference.get().getString(R.string.confirm)); } else { dialog.setActionButton(DialogAction.POSITIVE, mainActivityWeakReference.get().getString(R.string .use_current_location)); } } @Override public void afterTextChanged(Editable s) { } }); dialog.show(); } private boolean checkWeakReferences() { return mainActivityWeakReference.get() != null && !mainActivityWeakReference.get().isFinishing() && detailFragmentWeakReference.get() != null && noteTmpWeakReference.get() != null; } } @Override public void onLocationRetrieved(Location location) { if (location == null) { mainActivity.showMessage(R.string.location_not_found, ONStyle.ALERT); } if (location != null) { noteTmp.setLatitude(location.getLatitude()); noteTmp.setLongitude(location.getLongitude()); if (!TextUtils.isEmpty(noteTmp.getAddress())) { locationTextView.setVisibility(View.VISIBLE); locationTextView.setText(noteTmp.getAddress()); } else { GeocodeHelper.getAddressFromCoordinates(location, mFragment); } } } @Override public void onLocationUnavailable() { mainActivity.showMessage(R.string.location_not_found, ONStyle.ALERT); } @Override public void onAddressResolved(String address) { if (TextUtils.isEmpty(address)) { if (!isNoteLocationValid()) { mainActivity.showMessage(R.string.location_not_found, ONStyle.ALERT); return; } address = noteTmp.getLatitude() + ", " + noteTmp.getLongitude(); } if (!GeocodeHelper.areCoordinates(address)) { noteTmp.setAddress(address); } locationTextView.setVisibility(View.VISIBLE); locationTextView.setText(address); fade(locationTextView, true); } @Override public void onCoordinatesResolved(Location location, String address) { if (location != null) { noteTmp.setLatitude(location.getLatitude()); noteTmp.setLongitude(location.getLongitude()); noteTmp.setAddress(address); locationTextView.setVisibility(View.VISIBLE); locationTextView.setText(address); fade(locationTextView, true); } else { mainActivity.showMessage(R.string.location_not_found, ONStyle.ALERT); } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.menu_detail, menu); super.onCreateOptionsMenu(menu, inflater); } @Override public void onPrepareOptionsMenu(Menu menu) { // Closes search view if left open in List fragment MenuItem searchMenuItem = menu.findItem(R.id.menu_search); if (searchMenuItem != null) { MenuItemCompat.collapseActionView(searchMenuItem); } boolean newNote = noteTmp.get_id() == null; menu.findItem(R.id.menu_checklist_on).setVisible(!noteTmp.isChecklist()); menu.findItem(R.id.menu_checklist_off).setVisible(noteTmp.isChecklist()); menu.findItem(R.id.menu_lock).setVisible(!noteTmp.isLocked()); menu.findItem(R.id.menu_unlock).setVisible(noteTmp.isLocked()); // If note is trashed only this options will be available from menu if (noteTmp.isTrashed()) { menu.findItem(R.id.menu_untrash).setVisible(true); menu.findItem(R.id.menu_delete).setVisible(true); // Otherwise all other actions will be available } else { menu.findItem(R.id.menu_add_shortcut).setVisible(!newNote); menu.findItem(R.id.menu_archive).setVisible(!newNote && !noteTmp.isArchived()); menu.findItem(R.id.menu_unarchive).setVisible(!newNote && noteTmp.isArchived()); menu.findItem(R.id.menu_trash).setVisible(!newNote); } } @SuppressLint("NewApi") public boolean goHome() { stopPlaying(); // The activity has managed a shared intent from third party app and // performs a normal onBackPressed instead of returning back to ListActivity if (!afterSavedReturnsToList) { if (!TextUtils.isEmpty(exitMessage)) { mainActivity.showToast(exitMessage, Toast.LENGTH_SHORT); } mainActivity.finish(); } else { if (!TextUtils.isEmpty(exitMessage) && exitCroutonStyle != null) { mainActivity.showMessage(exitMessage, exitCroutonStyle); } // Otherwise the result is passed to ListActivity if (mainActivity != null && mainActivity.getSupportFragmentManager() != null) { mainActivity.getSupportFragmentManager().popBackStack(); if (mainActivity.getSupportFragmentManager().getBackStackEntryCount() == 1) { mainActivity.getSupportActionBar().setDisplayShowTitleEnabled(true); if (mainActivity.getDrawerToggle() != null) { mainActivity.getDrawerToggle().setDrawerIndicatorEnabled(true); } EventBus.getDefault().post(new SwitchFragmentEvent(SwitchFragmentEvent.Direction.PARENT)); } } } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_attachment: showPopup(mainActivity.findViewById(R.id.menu_attachment)); break; case R.id.menu_tag: addTags(); break; case R.id.menu_category: categorizeNote(); break; case R.id.menu_share: shareNote(); break; case R.id.menu_checklist_on: toggleChecklist(); break; case R.id.menu_checklist_off: toggleChecklist(); break; case R.id.menu_lock: lockNote(); break; case R.id.menu_unlock: lockNote(); break; case R.id.menu_add_shortcut: addShortcut(); break; case R.id.menu_archive: archiveNote(true); break; case R.id.menu_unarchive: archiveNote(false); break; case R.id.menu_trash: trashNote(true); break; case R.id.menu_untrash: trashNote(false); break; case R.id.menu_discard_changes: discard(); break; case R.id.menu_delete: deleteNote(); break; } ((OmniNotes)getActivity().getApplication()).getAnalyticsHelper().trackActionFromResourceId(getActivity(), item.getItemId()); return super.onOptionsItemSelected(item); } private void navigateUp() { afterSavedReturnsToList = true; saveAndExit(this); } /** * */ private void toggleChecklist() { // In case checklist is active a prompt will ask about many options // to decide hot to convert back to simple text if (!noteTmp.isChecklist()) { toggleChecklist2(); return; } // If checklist is active but no items are checked the conversion in done automatically // without prompting user if (mChecklistManager.getCheckedCount() == 0) { toggleChecklist2(true, false); return; } // Inflate the popup_layout.xml LayoutInflater inflater = (LayoutInflater) mainActivity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE); final View layout = inflater.inflate(R.layout.dialog_remove_checklist_layout, (ViewGroup) getView().findViewById(R.id.layout_root)); // Retrieves options checkboxes and initialize their values final CheckBox keepChecked = (CheckBox) layout.findViewById(R.id.checklist_keep_checked); final CheckBox keepCheckmarks = (CheckBox) layout.findViewById(R.id.checklist_keep_checkmarks); keepChecked.setChecked(prefs.getBoolean(Constants.PREF_KEEP_CHECKED, true)); keepCheckmarks.setChecked(prefs.getBoolean(Constants.PREF_KEEP_CHECKMARKS, true)); new MaterialDialog.Builder(mainActivity) .customView(layout, false) .positiveText(R.string.ok) .callback(new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog materialDialog) { prefs.edit() .putBoolean(Constants.PREF_KEEP_CHECKED, keepChecked.isChecked()) .putBoolean(Constants.PREF_KEEP_CHECKMARKS, keepCheckmarks.isChecked()) .apply(); toggleChecklist2(); } }).build().show(); } /** * Toggles checklist view */ private void toggleChecklist2() { boolean keepChecked = prefs.getBoolean(Constants.PREF_KEEP_CHECKED, true); boolean showChecks = prefs.getBoolean(Constants.PREF_KEEP_CHECKMARKS, true); toggleChecklist2(keepChecked, showChecks); } @SuppressLint("NewApi") private void toggleChecklist2(final boolean keepChecked, final boolean showChecks) { // Get instance and set options to convert EditText to CheckListView mChecklistManager = mChecklistManager == null ? new ChecklistManager(mainActivity) : mChecklistManager; int checkedItemsBehavior = Integer.valueOf(prefs.getString("settings_checked_items_behavior", String.valueOf (it.feio.android.checklistview.Settings.CHECKED_HOLD))); mChecklistManager .showCheckMarks(showChecks) .newEntryHint(getString(R.string.checklist_item_hint)) .keepChecked(keepChecked) .undoBarContainerView(scrollView) .moveCheckedOnBottom(checkedItemsBehavior); // Links parsing options mChecklistManager.setOnTextLinkClickListener(textLinkClickListener); mChecklistManager.addTextChangedListener(mFragment); mChecklistManager.setCheckListChangedListener(mFragment); // Switches the views View newView = null; try { newView = mChecklistManager.convert(toggleChecklistView); } catch (ViewNotSupportedException e) { Log.e(Constants.TAG, "Error switching checklist view", e); } // Switches the views if (newView != null) { mChecklistManager.replaceViews(toggleChecklistView, newView); toggleChecklistView = newView; animate(toggleChecklistView).alpha(1).scaleXBy(0).scaleX(1).scaleYBy(0).scaleY(1); noteTmp.setChecklist(!noteTmp.isChecklist()); } } /** * Categorize note choosing from a list of previously created categories */ private void categorizeNote() { // Retrieves all available categories final ArrayList<Category> categories = DbHelper.getInstance().getCategories(); String currentCategory = noteTmp.getCategory() != null ? String.valueOf(noteTmp.getCategory().getId()) : null; final MaterialDialog dialog = new MaterialDialog.Builder(mainActivity) .title(R.string.categorize_as) .adapter(new NavDrawerCategoryAdapter(mainActivity, categories, currentCategory), null) .positiveText(R.string.add_category) .positiveColorRes(R.color.colorPrimary) .negativeText(R.string.remove_category) .negativeColorRes(R.color.colorAccent) .callback(new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog dialog) { Intent intent = new Intent(mainActivity, CategoryActivity.class); intent.putExtra("noHome", true); startActivityForResult(intent, CATEGORY); } @Override public void onNegative(MaterialDialog dialog) { noteTmp.setCategory(null); setTagMarkerColor(null); } }) .build(); dialog.getListView().setOnItemClickListener((parent, view, position, id) -> { noteTmp.setCategory(categories.get(position)); setTagMarkerColor(categories.get(position)); dialog.dismiss(); }); dialog.show(); } // The method that displays the popup. @SuppressWarnings("deprecation") private void showPopup(View anchor) { DisplayMetrics metrics = new DisplayMetrics(); mainActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics); // Inflate the popup_layout.xml LayoutInflater inflater = (LayoutInflater) mainActivity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE); View layout = inflater.inflate(R.layout.attachment_dialog, null); // Creating the PopupWindow attachmentDialog = new PopupWindow(mainActivity); attachmentDialog.setContentView(layout); attachmentDialog.setWidth(WindowManager.LayoutParams.WRAP_CONTENT); attachmentDialog.setHeight(WindowManager.LayoutParams.WRAP_CONTENT); attachmentDialog.setFocusable(true); attachmentDialog.setOnDismissListener(() -> { if (isRecording) { isRecording = false; stopRecording(); } }); // Clear the default translucent background attachmentDialog.setBackgroundDrawable(new BitmapDrawable()); // Camera android.widget.TextView cameraSelection = (android.widget.TextView) layout.findViewById(R.id.camera); cameraSelection.setOnClickListener(new AttachmentOnClickListener()); // Audio recording android.widget.TextView recordingSelection = (android.widget.TextView) layout.findViewById(R.id.recording); recordingSelection.setOnClickListener(new AttachmentOnClickListener()); // Video recording android.widget.TextView videoSelection = (android.widget.TextView) layout.findViewById(R.id.video); videoSelection.setOnClickListener(new AttachmentOnClickListener()); // Files android.widget.TextView filesSelection = (android.widget.TextView) layout.findViewById(R.id.files); filesSelection.setOnClickListener(new AttachmentOnClickListener()); // Sketch android.widget.TextView sketchSelection = (android.widget.TextView) layout.findViewById(R.id.sketch); sketchSelection.setOnClickListener(new AttachmentOnClickListener()); // Location android.widget.TextView locationSelection = (android.widget.TextView) layout.findViewById(R.id.location); locationSelection.setOnClickListener(new AttachmentOnClickListener()); // Desktop note with PushBullet if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { android.widget.TextView pushbulletSelection = (android.widget.TextView) layout.findViewById(R.id .pushbullet); pushbulletSelection.setVisibility(View.VISIBLE); pushbulletSelection.setOnClickListener(new AttachmentOnClickListener()); } try { attachmentDialog.showAsDropDown(anchor); } catch (Exception e) { mainActivity.showMessage(R.string.error, ONStyle.ALERT); } } private void takePhoto() { // Checks for camera app available Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (!IntentChecker.isAvailable(mainActivity, intent, new String[]{PackageManager.FEATURE_CAMERA})) { mainActivity.showMessage(R.string.feature_not_available_on_this_device, ONStyle.ALERT); return; } // Checks for created file validity File f = StorageHelper.createNewAttachmentFile(mainActivity, Constants.MIME_TYPE_IMAGE_EXT); if (f == null) { mainActivity.showMessage(R.string.error, ONStyle.ALERT); return; } // Launches intent attachmentUri = Uri.fromFile(f); intent.putExtra(MediaStore.EXTRA_OUTPUT, attachmentUri); startActivityForResult(intent, TAKE_PHOTO); } private void takeVideo() { Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); if (!IntentChecker.isAvailable(mainActivity, takeVideoIntent, new String[]{PackageManager.FEATURE_CAMERA})) { mainActivity.showMessage(R.string.feature_not_available_on_this_device, ONStyle.ALERT); return; } // File is stored in custom ON folder to speedup the attachment if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1) { File f = StorageHelper.createNewAttachmentFile(mainActivity, Constants.MIME_TYPE_VIDEO_EXT); if (f == null) { mainActivity.showMessage(R.string.error, ONStyle.ALERT); return; } attachmentUri = Uri.fromFile(f); takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, attachmentUri); } String maxVideoSizeStr = "".equals(prefs.getString("settings_max_video_size", "")) ? "0" : prefs.getString("settings_max_video_size", ""); int maxVideoSize = Integer.parseInt(maxVideoSizeStr); takeVideoIntent.putExtra(MediaStore.EXTRA_SIZE_LIMIT, Long.valueOf(maxVideoSize * 1024 * 1024)); startActivityForResult(takeVideoIntent, TAKE_VIDEO); } private void takeSketch(Attachment attachment) { File f = StorageHelper.createNewAttachmentFile(mainActivity, Constants.MIME_TYPE_SKETCH_EXT); if (f == null) { mainActivity.showMessage(R.string.error, ONStyle.ALERT); return; } attachmentUri = Uri.fromFile(f); // Forces portrait orientation to this fragment only mainActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); // Fragments replacing FragmentTransaction transaction = mainActivity.getSupportFragmentManager().beginTransaction(); mainActivity.animateTransition(transaction, mainActivity.TRANSITION_HORIZONTAL); SketchFragment mSketchFragment = new SketchFragment(); Bundle b = new Bundle(); b.putParcelable(MediaStore.EXTRA_OUTPUT, attachmentUri); if (attachment != null) { b.putParcelable("base", attachment.getUri()); } mSketchFragment.setArguments(b); transaction.replace(R.id.fragment_container, mSketchFragment, mainActivity.FRAGMENT_SKETCH_TAG) .addToBackStack(mainActivity.FRAGMENT_DETAIL_TAG).commit(); } @SuppressLint("NewApi") @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { // Fetch uri from activities, store into adapter and refresh adapter Attachment attachment; if (resultCode == Activity.RESULT_OK) { switch (requestCode) { case TAKE_PHOTO: attachment = new Attachment(attachmentUri, Constants.MIME_TYPE_IMAGE); addAttachment(attachment); mAttachmentAdapter.notifyDataSetChanged(); mGridView.autoresize(); break; case TAKE_VIDEO: // Gingerbread doesn't allow custom folder so data are retrieved from intent if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1) { attachment = new Attachment(attachmentUri, Constants.MIME_TYPE_VIDEO); } else { attachment = new Attachment(intent.getData(), Constants.MIME_TYPE_VIDEO); } addAttachment(attachment); mAttachmentAdapter.notifyDataSetChanged(); mGridView.autoresize(); break; case FILES: onActivityResultManageReceivedFiles(intent); break; case SET_PASSWORD: noteTmp.setPasswordChecked(true); lockUnlock(); break; case SKETCH: attachment = new Attachment(attachmentUri, Constants.MIME_TYPE_SKETCH); addAttachment(attachment); mAttachmentAdapter.notifyDataSetChanged(); mGridView.autoresize(); break; case CATEGORY: mainActivity.showMessage(R.string.category_saved, ONStyle.CONFIRM); Category category = intent.getParcelableExtra("category"); noteTmp.setCategory(category); setTagMarkerColor(category); break; case DETAIL: mainActivity.showMessage(R.string.note_updated, ONStyle.CONFIRM); break; } } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void onActivityResultManageReceivedFiles(Intent intent) { List<Uri> uris = new ArrayList<>(); if (Build.VERSION.SDK_INT > 16 && intent.getClipData() != null) { for (int i = 0; i < intent.getClipData().getItemCount(); i++) { uris.add(intent.getClipData().getItemAt(i).getUri()); } } else { uris.add(intent.getData()); } for (Uri uri : uris) { String name = FileHelper.getNameFromUri(mainActivity, uri); new AttachmentTask(this, uri, name, this).execute(); } } /** * Discards changes done to the note and eventually delete new attachments */ @SuppressLint("NewApi") private void discard() { // Checks if some new files have been attached and must be removed if (!noteTmp.getAttachmentsList().equals(note.getAttachmentsList())) { for (Attachment newAttachment : noteTmp.getAttachmentsList()) { if (!note.getAttachmentsList().contains(newAttachment)) { StorageHelper.delete(mainActivity, newAttachment.getUri().getPath()); } } } goBack = true; if (!noteTmp.equals(noteOriginal)) { // Restore original status of the note if (noteOriginal.get_id() == null) { mainActivity.deleteNote(noteTmp); goHome(); } else { SaveNoteTask saveNoteTask = new SaveNoteTask(this, false); if (Build.VERSION.SDK_INT >= 11) { saveNoteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, noteOriginal); } else { saveNoteTask.execute(noteOriginal); } } MainActivity.notifyAppWidgets(mainActivity); } else { goHome(); } } @SuppressLint("NewApi") private void archiveNote(boolean archive) { // Simply go back if is a new note if (noteTmp.get_id() == null) { goHome(); return; } noteTmp.setArchived(archive); goBack = true; exitMessage = archive ? getString(R.string.note_archived) : getString(R.string.note_unarchived); exitCroutonStyle = archive ? ONStyle.WARN : ONStyle.INFO; saveNote(this); } @SuppressLint("NewApi") private void trashNote(boolean trash) { // Simply go back if is a new note if (noteTmp.get_id() == null) { goHome(); return; } noteTmp.setTrashed(trash); goBack = true; exitMessage = trash ? getString(R.string.note_trashed) : getString(R.string.note_untrashed); exitCroutonStyle = trash ? ONStyle.WARN : ONStyle.INFO; if (trash) { ShortcutHelper.removeshortCut(OmniNotes.getAppContext(), noteTmp); ReminderHelper.removeReminder(OmniNotes.getAppContext(), noteTmp); } else { ReminderHelper.addReminder(OmniNotes.getAppContext(), note); } saveNote(this); } private void deleteNote() { new MaterialDialog.Builder(mainActivity) .content(R.string.delete_note_confirmation) .positiveText(R.string.ok) .callback(new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog materialDialog) { mainActivity.deleteNote(noteTmp); Log.d(Constants.TAG, "Deleted note with id '" + noteTmp.get_id() + "'"); mainActivity.showMessage(R.string.note_deleted, ONStyle.ALERT); goHome(); } }).build().show(); } public void saveAndExit(OnNoteSaved mOnNoteSaved) { if (isAdded()) { exitMessage = getString(R.string.note_updated); exitCroutonStyle = ONStyle.CONFIRM; goBack = true; saveNote(mOnNoteSaved); } } /** * Save new notes, modify them or archive */ void saveNote(OnNoteSaved mOnNoteSaved) { // Changed fields noteTmp.setTitle(getNoteTitle()); noteTmp.setContent(getNoteContent()); // Check if some text or attachments of any type have been inserted or // is an empty note if (goBack && TextUtils.isEmpty(noteTmp.getTitle()) && TextUtils.isEmpty(noteTmp.getContent()) && noteTmp.getAttachmentsList().size() == 0) { Log.d(Constants.TAG, "Empty note not saved"); exitMessage = getString(R.string.empty_note_not_saved); exitCroutonStyle = ONStyle.INFO; goHome(); return; } if (saveNotNeeded()) { exitMessage = ""; if (goBack) { goHome(); } return; } noteTmp.setAttachmentsListOld(note.getAttachmentsList()); new SaveNoteTask(mOnNoteSaved, lastModificationUpdatedNeeded()).executeOnExecutor(AsyncTask .THREAD_POOL_EXECUTOR, noteTmp); } /** * Checks if nothing is changed to avoid committing if possible (check) */ private boolean saveNotNeeded() { if (noteTmp.get_id() == null && prefs.getBoolean(Constants.PREF_AUTO_LOCATION, false)) { note.setLatitude(noteTmp.getLatitude()); note.setLongitude(noteTmp.getLongitude()); } return !noteTmp.isChanged(note) || (noteTmp.isLocked() && !noteTmp.isPasswordChecked()); } /** * Checks if only tag, archive or trash status have been changed * and then force to not update last modification date* */ private boolean lastModificationUpdatedNeeded() { note.setCategory(noteTmp.getCategory()); note.setArchived(noteTmp.isArchived()); note.setTrashed(noteTmp.isTrashed()); note.setLocked(noteTmp.isLocked()); return noteTmp.isChanged(note); } @Override public void onNoteSaved(Note noteSaved) { MainActivity.notifyAppWidgets(OmniNotes.getAppContext()); if (!activityPausing) { EventBus.getDefault().post(new NotesUpdatedEvent()); deleteMergedNotes(mergedNotesIds); if (noteTmp.getAlarm() != null && !noteTmp.getAlarm().equals(note.getAlarm())) { ReminderHelper.showReminderMessage(noteTmp.getAlarm()); } } note = new Note(noteSaved); if (goBack) { goHome(); } } private void deleteMergedNotes(List<String> mergedNotesIds) { ArrayList<Note> notesToDelete = new ArrayList<Note>(); if (mergedNotesIds != null) { for (String mergedNoteId : mergedNotesIds) { Note note = new Note(); note.set_id(Long.valueOf(mergedNoteId)); notesToDelete.add(note); } new NoteProcessorDelete(notesToDelete).process(); } } private String getNoteTitle() { if (title != null && !TextUtils.isEmpty(title.getText())) { return title.getText().toString(); } else { return ""; } } private String getNoteContent() { String contentText = ""; if (!noteTmp.isChecklist()) { // Due to checklist library introduction the returned EditText class is no more a // com.neopixl.pixlui.components.edittext.EditText but a standard android.widget.EditText View contentView = root.findViewById(R.id.detail_content); if (contentView instanceof EditText) { contentText = ((EditText) contentView).getText().toString(); } else if (contentView instanceof android.widget.EditText) { contentText = ((android.widget.EditText) contentView).getText().toString(); } } else { if (mChecklistManager != null) { mChecklistManager.keepChecked(true).showCheckMarks(true); contentText = mChecklistManager.getText(); } } return contentText; } /** * Updates share intent */ private void shareNote() { Note sharedNote = new Note(noteTmp); sharedNote.setTitle(getNoteTitle()); sharedNote.setContent(getNoteContent()); mainActivity.shareNote(sharedNote); } /** * Notes locking with security password to avoid viewing, editing or deleting from unauthorized */ private void lockNote() { Log.d(Constants.TAG, "Locking or unlocking note " + note.get_id()); // If security password is not set yes will be set right now if (prefs.getString(Constants.PREF_PASSWORD, null) == null) { Intent passwordIntent = new Intent(mainActivity, PasswordActivity.class); startActivityForResult(passwordIntent, SET_PASSWORD); return; } // If password has already been inserted will not be asked again if (noteTmp.isPasswordChecked() || prefs.getBoolean("settings_password_access", false)) { lockUnlock(); return; } // Password will be requested here PasswordHelper.requestPassword(mainActivity, passwordConfirmed -> { if (passwordConfirmed) { lockUnlock(); } }); } private void lockUnlock() { // Empty password has been set if (prefs.getString(Constants.PREF_PASSWORD, null) == null) { mainActivity.showMessage(R.string.password_not_set, ONStyle.WARN); return; } // Otherwise locking is performed if (noteTmp.isLocked()) { mainActivity.showMessage(R.string.save_note_to_lock_it, ONStyle.INFO); mainActivity.supportInvalidateOptionsMenu(); } else { mainActivity.showMessage(R.string.save_note_to_lock_it, ONStyle.INFO); mainActivity.supportInvalidateOptionsMenu(); } noteTmp.setLocked(!noteTmp.isLocked()); noteTmp.setPasswordChecked(true); } /** * Used to set actual reminder state when initializing a note to be edited */ private String initReminder(Note note) { if (noteTmp.getAlarm() == null) { return ""; } long reminder = Long.parseLong(note.getAlarm()); String rrule = note.getRecurrenceRule(); if (!TextUtils.isEmpty(rrule)) { return DateHelper.getNoteRecurrentReminderText(reminder, rrule); } else { return DateHelper.getNoteReminderText(reminder); } } /** * Audio recordings playback */ private void playback(View v, Uri uri) { // Some recording is playing right now if (mPlayer != null && mPlayer.isPlaying()) { if (isPlayingView != v) { // If the audio actually played is NOT the one from the click view the last one is played stopPlaying(); isPlayingView = v; startPlaying(uri); replacePlayingAudioBitmap(v); } else { // Otherwise just stops playing stopPlaying(); } } else { // If nothing is playing audio just plays isPlayingView = v; startPlaying(uri); replacePlayingAudioBitmap(v); } } private void replacePlayingAudioBitmap(View v) { Drawable d = ((ImageView) v.findViewById(R.id.gridview_item_picture)).getDrawable(); if (BitmapDrawable.class.isAssignableFrom(d.getClass())) { recordingBitmap = ((BitmapDrawable) d).getBitmap(); } else { recordingBitmap = ((GlideBitmapDrawable) d.getCurrent()).getBitmap(); } ((ImageView) v.findViewById(R.id.gridview_item_picture)).setImageBitmap(ThumbnailUtils .extractThumbnail(BitmapFactory.decodeResource(mainActivity.getResources(), R.drawable.stop), Constants.THUMBNAIL_SIZE, Constants.THUMBNAIL_SIZE)); } private void startPlaying(Uri uri) { if (mPlayer == null) { mPlayer = new MediaPlayer(); } try { mPlayer.setDataSource(mainActivity, uri); mPlayer.prepare(); mPlayer.start(); mPlayer.setOnCompletionListener(mp -> { mPlayer = null; if (isPlayingView != null) { ((ImageView) isPlayingView.findViewById(R.id.gridview_item_picture)).setImageBitmap (recordingBitmap); recordingBitmap = null; isPlayingView = null; } }); } catch (IOException e) { Log.e(Constants.TAG, "prepare() failed", e); mainActivity.showMessage(R.string.error, ONStyle.ALERT); } } private void stopPlaying() { if (mPlayer != null) { if (isPlayingView != null) { ((ImageView) isPlayingView.findViewById(R.id.gridview_item_picture)).setImageBitmap(recordingBitmap); } isPlayingView = null; recordingBitmap = null; mPlayer.release(); mPlayer = null; } } private void startRecording(View v) { PermissionsHelper.requestPermission(getActivity(), Manifest.permission.RECORD_AUDIO, R.string.permission_audio_recording, snackBarPlaceholder, () -> { isRecording = true; android.widget.TextView mTextView = (android.widget.TextView) v; mTextView.setText(getString(R.string.stop)); mTextView.setTextColor(Color.parseColor("#ff0000")); File f = StorageHelper.createNewAttachmentFile(mainActivity, Constants.MIME_TYPE_AUDIO_EXT); if (f == null) { mainActivity.showMessage(R.string.error, ONStyle.ALERT); return; } if (mRecorder == null) { mRecorder = new MediaRecorder(); mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mRecorder.setAudioEncodingBitRate(96000); mRecorder.setAudioSamplingRate(44100); } recordName = f.getAbsolutePath(); mRecorder.setOutputFile(recordName); try { audioRecordingTimeStart = Calendar.getInstance().getTimeInMillis(); mRecorder.prepare(); mRecorder.start(); } catch (IOException | IllegalStateException e) { Log.e(Constants.TAG, "prepare() failed", e); mainActivity.showMessage(R.string.error, ONStyle.ALERT); } }); } private void stopRecording() { if (mRecorder != null) { mRecorder.stop(); audioRecordingTime = Calendar.getInstance().getTimeInMillis() - audioRecordingTimeStart; mRecorder.release(); mRecorder = null; } } private void fade(final View v, boolean fadeIn) { int anim = R.animator.fade_out_support; int visibilityTemp = View.GONE; if (fadeIn) { anim = R.animator.fade_in_support; visibilityTemp = View.VISIBLE; } final int visibility = visibilityTemp; // Checks if user has left the app if (mainActivity != null) { Animation mAnimation = AnimationUtils.loadAnimation(mainActivity, anim); mAnimation.setAnimationListener(new AnimationListener() { @Override public void onAnimationStart(Animation animation) { // Nothind to do } @Override public void onAnimationRepeat(Animation animation) { // Nothind to do } @Override public void onAnimationEnd(Animation animation) { v.setVisibility(visibility); } }); v.startAnimation(mAnimation); } } /** * Adding shortcut on Home screen */ private void addShortcut() { ShortcutHelper.addShortcut(OmniNotes.getAppContext(), noteTmp); mainActivity.showMessage(R.string.shortcut_added, ONStyle.INFO); } TextLinkClickListener textLinkClickListener = new TextLinkClickListener() { @Override public void onTextLinkClick(View view, final String clickedString, final String url) { new MaterialDialog.Builder(mainActivity) .content(clickedString) .negativeColorRes(R.color.colorPrimary) .positiveText(R.string.open) .negativeText(R.string.copy) .callback(new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog dialog) { boolean error = false; Intent intent = null; try { intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); intent.addCategory(Intent.CATEGORY_BROWSABLE); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } catch (NullPointerException e) { error = true; } if (intent == null || error || !IntentChecker .isAvailable( mainActivity, intent, new String[]{PackageManager.FEATURE_CAMERA})) { mainActivity.showMessage(R.string.no_application_can_perform_this_action, ONStyle.ALERT); } else { startActivity(intent); } } @Override public void onNegative(MaterialDialog dialog) { android.content.ClipboardManager clipboard = (android.content.ClipboardManager) mainActivity .getSystemService(Activity.CLIPBOARD_SERVICE); android.content.ClipData clip = android.content.ClipData.newPlainText("text label", clickedString); clipboard.setPrimaryClip(clip); } }).build().show(); View clickedView = noteTmp.isChecklist() ? toggleChecklistView : content; clickedView.clearFocus(); KeyboardUtils.hideKeyboard(clickedView); new Handler().post(new Runnable() { @Override public void run() { View clickedView = noteTmp.isChecklist() ? toggleChecklistView : content; KeyboardUtils.hideKeyboard(clickedView); } }); } }; @SuppressLint("NewApi") @Override public boolean onTouch(View v, MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.v(Constants.TAG, "MotionEvent.ACTION_DOWN"); int w; Point displaySize = Display.getUsableSize(mainActivity); w = displaySize.x; if (x < Constants.SWIPE_MARGIN || x > w - Constants.SWIPE_MARGIN) { swiping = true; startSwipeX = x; } break; case MotionEvent.ACTION_UP: Log.v(Constants.TAG, "MotionEvent.ACTION_UP"); if (swiping) swiping = false; break; case MotionEvent.ACTION_MOVE: if (swiping) { Log.v(Constants.TAG, "MotionEvent.ACTION_MOVE at position " + x + ", " + y); if (Math.abs(x - startSwipeX) > Constants.SWIPE_OFFSET) { swiping = false; FragmentTransaction transaction = mainActivity.getSupportFragmentManager().beginTransaction(); mainActivity.animateTransition(transaction, mainActivity.TRANSITION_VERTICAL); DetailFragment mDetailFragment = new DetailFragment(); Bundle b = new Bundle(); b.putParcelable(Constants.INTENT_NOTE, new Note()); mDetailFragment.setArguments(b); transaction.replace(R.id.fragment_container, mDetailFragment, mainActivity.FRAGMENT_DETAIL_TAG).addToBackStack(mainActivity .FRAGMENT_DETAIL_TAG).commit(); } } break; } return true; } @Override public void onGlobalLayout() { int screenHeight = Display.getUsableSize(mainActivity).y; int navBarOffset = Display.orientationLandscape(mainActivity) ? 0 : DensityUtil.pxToDp(Display .getNavigationBarHeight(mainActivity.getWindow().getDecorView()), mainActivity); int heightDiff = screenHeight - Display.getVisibleSize(mainActivity).y + navBarOffset; boolean keyboardVisible = heightDiff > 150; if (keyboardVisible && keyboardPlaceholder == null) { shrinkLayouts(heightDiff); } else if (!keyboardVisible && keyboardPlaceholder != null) { restoreLayouts(); } } private void shrinkLayouts(int heightDiff) { detailWrapperView.removeView(timestampsView); keyboardPlaceholder = new View(mainActivity); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (!(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Display.orientationLandscape(mainActivity) )) { root.addView(keyboardPlaceholder, LinearLayout.LayoutParams.MATCH_PARENT, heightDiff); } } } private void restoreLayouts() { if (root != null) { ViewGroup wrapper = (ViewGroup) root.findViewById(R.id.detail_wrapper); if (root.indexOfChild(keyboardPlaceholder) != -1) { root.removeView(keyboardPlaceholder); } keyboardPlaceholder = null; if (wrapper.indexOfChild(timestampsView) == -1) { wrapper.addView(timestampsView); } } } @Override public void onAttachingFileErrorOccurred(Attachment mAttachment) { mainActivity.showMessage(R.string.error_saving_attachments, ONStyle.ALERT); if (noteTmp.getAttachmentsList().contains(mAttachment)) { removeAttachment(mAttachment); mAttachmentAdapter.notifyDataSetChanged(); mGridView.autoresize(); } } private void addAttachment(Attachment attachment) { noteTmp.addAttachment(attachment); // mAttachmentAdapter.getAttachmentsList().add(attachment); } private void removeAttachment(Attachment mAttachment) { noteTmp.removeAttachment(mAttachment); // mAttachmentAdapter.getAttachmentsList().remove(mAttachment); } private void removeAttachment(int position) { noteTmp.removeAttachment(noteTmp.getAttachmentsList().get(position)); // mAttachmentAdapter.getAttachmentsList().remove(position); } private void removeAllAttachments() { noteTmp.setAttachmentsList(new ArrayList<>()); mAttachmentAdapter = new AttachmentAdapter(mainActivity, new ArrayList<>(), mGridView); mGridView.invalidateViews(); mGridView.setAdapter(mAttachmentAdapter); } @Override public void onAttachingFileFinished(Attachment mAttachment) { addAttachment(mAttachment); mAttachmentAdapter.notifyDataSetChanged(); mGridView.autoresize(); } @Override public void onReminderPicked(long reminder) { noteTmp.setAlarm(reminder); if (mFragment.isAdded()) { reminderIcon.setImageResource(R.drawable.ic_alarm_black_18dp); datetime.setText(DateHelper.getNoteReminderText(reminder)); } } @Override public void onRecurrenceReminderPicked(String recurrenceRule) { noteTmp.setRecurrenceRule(recurrenceRule); if (!TextUtils.isEmpty(recurrenceRule)) { Log.d(Constants.TAG, "Recurrent reminder set: " + recurrenceRule); datetime.setText(DateHelper.getNoteRecurrentReminderText(Long.parseLong(noteTmp .getAlarm()), recurrenceRule)); } } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { scrollContent(); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { // Nothing to do } @Override public void afterTextChanged(Editable s) { // Nothing to do } @Override public void onCheckListChanged() { scrollContent(); } private void scrollContent() { if (noteTmp.isChecklist()) { if (mChecklistManager.getCount() > contentLineCounter) { scrollView.scrollBy(0, 60); } contentLineCounter = mChecklistManager.getCount(); } else { if (content.getLineCount() > contentLineCounter) { scrollView.scrollBy(0, 60); } contentLineCounter = content.getLineCount(); } } /** * Add previously created tags to content */ private void addTags() { contentCursorPosition = getCursorIndex(); // Retrieves all available categories final List<Tag> tags = TagsHelper.getAllTags(); // If there is no tag a message will be shown if (tags.size() == 0) { mainActivity.showMessage(R.string.no_tags_created, ONStyle.WARN); return; } final Note currentNote = new Note(); currentNote.setTitle(getNoteTitle()); currentNote.setContent(getNoteContent()); Integer[] preselectedTags = TagsHelper.getPreselectedTagsArray(currentNote, tags); // Dialog and events creation MaterialDialog dialog = new MaterialDialog.Builder(mainActivity) .title(R.string.select_tags) .positiveText(R.string.ok) .items(TagsHelper.getTagsArray(tags)) .itemsCallbackMultiChoice(preselectedTags, (dialog1, which, text) -> { dialog1.dismiss(); tagNote(tags, which, currentNote); return false; }).build(); dialog.show(); } private void tagNote(List<Tag> tags, Integer[] selectedTags, Note note) { Pair<String, List<Tag>> taggingResult = TagsHelper.addTagToNote(tags, selectedTags, note); StringBuilder sb; if (noteTmp.isChecklist()) { CheckListViewItem mCheckListViewItem = mChecklistManager.getFocusedItemView(); if (mCheckListViewItem != null) { sb = new StringBuilder(mCheckListViewItem.getText()); sb.insert(contentCursorPosition, " " + taggingResult.first + " "); mCheckListViewItem.setText(sb.toString()); mCheckListViewItem.getEditText().setSelection(contentCursorPosition + taggingResult.first.length() + 1); } else { title.append(" " + taggingResult.first); } } else { sb = new StringBuilder(getNoteContent()); if (content.hasFocus()) { sb.insert(contentCursorPosition, " " + taggingResult.first + " "); content.setText(sb.toString()); content.setSelection(contentCursorPosition + taggingResult.first.length() + 1); } else { if (getNoteContent().trim().length() > 0) { sb.append(System.getProperty("line.separator")) .append(System.getProperty("line.separator")); } sb.append(taggingResult.first); content.setText(sb.toString()); } } // Removes unchecked tags if (taggingResult.second.size() > 0) { if (noteTmp.isChecklist()) { toggleChecklist2(true, true); } Pair<String, String> titleAndContent = TagsHelper.removeTag(getNoteTitle(), getNoteContent(), taggingResult.second); title.setText(titleAndContent.first); content.setText(titleAndContent.second); if (noteTmp.isChecklist()) { toggleChecklist2(); } } } private int getCursorIndex() { if (!noteTmp.isChecklist()) { return content.getSelectionStart(); } else { CheckListViewItem mCheckListViewItem = mChecklistManager.getFocusedItemView(); if (mCheckListViewItem != null) { return mCheckListViewItem.getEditText().getSelectionStart(); } else { return 0; } } } /** * Used to check currently opened note from activity to avoid openind multiple times the same one */ public Note getCurrentNote() { return note; } private boolean isNoteLocationValid() { return noteTmp.getLatitude() != null && noteTmp.getLatitude() != 0 && noteTmp.getLongitude() != null && noteTmp.getLongitude() != 0; } /** * Manages clicks on attachment dialog */ @SuppressLint("InlinedApi") private class AttachmentOnClickListener implements OnClickListener { @Override public void onClick(View v) { switch (v.getId()) { // Photo from camera case R.id.camera: takePhoto(); attachmentDialog.dismiss(); break; case R.id.recording: if (!isRecording) { startRecording(v); } else { isRecording = false; stopRecording(); Attachment attachment = new Attachment(Uri.fromFile(new File(recordName)), Constants.MIME_TYPE_AUDIO); attachment.setLength(audioRecordingTime); addAttachment(attachment); mAttachmentAdapter.notifyDataSetChanged(); mGridView.autoresize(); attachmentDialog.dismiss(); } break; case R.id.video: takeVideo(); attachmentDialog.dismiss(); break; case R.id.files: Intent filesIntent; filesIntent = new Intent(Intent.ACTION_GET_CONTENT); filesIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); filesIntent.addCategory(Intent.CATEGORY_OPENABLE); filesIntent.setType("*/*"); startActivityForResult(filesIntent, FILES); attachmentDialog.dismiss(); break; case R.id.sketch: takeSketch(null); attachmentDialog.dismiss(); break; case R.id.location: displayLocationDialog(); attachmentDialog.dismiss(); break; case R.id.pushbullet: MessagingExtension.mirrorMessage(mainActivity, getString(R.string.app_name), getString(R.string.pushbullet), getNoteContent(), BitmapFactory.decodeResource(getResources(), R.drawable.ic_stat_literal_icon), null, 0); attachmentDialog.dismiss(); break; } } } public void onEventMainThread(PushbulletReplyEvent pushbulletReplyEvent) { content.setText(getNoteContent() + System.getProperty("line.separator") + pushbulletReplyEvent.message); } }