package com.simplemobiletools.notes.activities import android.content.Intent import android.net.Uri import android.os.Bundle import android.support.v4.view.ViewPager import android.text.method.ArrowKeyMovementMethod import android.text.method.LinkMovementMethod import android.util.TypedValue import android.view.ActionMode import android.view.Gravity import android.view.Menu import android.view.MenuItem import com.simplemobiletools.commons.dialogs.FilePickerDialog import com.simplemobiletools.commons.dialogs.RadioGroupDialog import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.models.FAQItem import com.simplemobiletools.commons.models.FileDirItem import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.commons.models.Release import com.simplemobiletools.commons.views.MyEditText import com.simplemobiletools.notes.BuildConfig import com.simplemobiletools.notes.R import com.simplemobiletools.notes.adapters.NotesPagerAdapter import com.simplemobiletools.notes.dialogs.* import com.simplemobiletools.notes.extensions.config import com.simplemobiletools.notes.extensions.dbHelper import com.simplemobiletools.notes.extensions.getTextSize import com.simplemobiletools.notes.extensions.updateWidget import com.simplemobiletools.notes.helpers.MIME_TEXT_PLAIN import com.simplemobiletools.notes.helpers.OPEN_NOTE_ID import com.simplemobiletools.notes.helpers.TYPE_NOTE import com.simplemobiletools.notes.models.Note import kotlinx.android.synthetic.main.activity_main.* import java.io.File import java.nio.charset.Charset class MainActivity : SimpleActivity(), ViewPager.OnPageChangeListener { private var mAdapter: NotesPagerAdapter? = null lateinit var mCurrentNote: Note private var mNotes = ArrayList() private var noteViewWithTextSelected: MyEditText? = null private var wasInit = false private var storedEnableLineWrap = true private var showSaveButton = false private var showUndoButton = false private var showRedoButton = false private var saveNoteButton: MenuItem? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) appLaunched(BuildConfig.APPLICATION_ID) initViewPager() pager_title_strip.setTextSize(TypedValue.COMPLEX_UNIT_PX, getTextSize()) pager_title_strip.layoutParams.height = (pager_title_strip.height + resources.getDimension(R.dimen.activity_margin) * 2).toInt() checkWhatsNewDialog() intent.apply { if (action == Intent.ACTION_SEND && type == MIME_TEXT_PLAIN) { getStringExtra(Intent.EXTRA_TEXT)?.let { handleText(it) intent.removeExtra(Intent.EXTRA_TEXT) } } if (action == Intent.ACTION_VIEW) { handleUri(data) intent.removeCategory(Intent.CATEGORY_DEFAULT) intent.action = null } } storeStateVariables() if (config.showNotePicker) { displayOpenNoteDialog() } wasInit = true checkAppOnSDCard() } override fun onResume() { super.onResume() if (storedEnableLineWrap != config.enableLineWrap) { initViewPager() } invalidateOptionsMenu() pager_title_strip.apply { setTextSize(TypedValue.COMPLEX_UNIT_PX, getTextSize()) setGravity(Gravity.CENTER_VERTICAL) setNonPrimaryAlpha(0.4f) setTextColor(config.textColor) } updateTextColors(view_pager) } override fun onPause() { super.onPause() storeStateVariables() } override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu, menu) menu.apply { findItem(R.id.undo).isVisible = showUndoButton findItem(R.id.redo).isVisible = showRedoButton } return true } override fun onPrepareOptionsMenu(menu: Menu): Boolean { val shouldBeVisible = mNotes.size > 1 menu.apply { findItem(R.id.rename_note).isVisible = shouldBeVisible findItem(R.id.open_note).isVisible = shouldBeVisible findItem(R.id.delete_note).isVisible = shouldBeVisible findItem(R.id.export_all_notes).isVisible = shouldBeVisible saveNoteButton = findItem(R.id.save_note) saveNoteButton!!.isVisible = !config.autosaveNotes && showSaveButton } pager_title_strip.beVisibleIf(shouldBeVisible) return super.onPrepareOptionsMenu(menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean { if (config.autosaveNotes) { saveCurrentNote() } when (item.itemId) { R.id.open_note -> displayOpenNoteDialog() R.id.save_note -> saveNote() R.id.undo -> undo() R.id.redo -> redo() R.id.new_note -> displayNewNoteDialog() R.id.rename_note -> displayRenameDialog() R.id.share -> shareText() R.id.open_file -> tryOpenFile() R.id.import_folder -> tryOpenFolder() R.id.export_as_file -> tryExportAsFile() R.id.export_all_notes -> tryExportAllNotes() R.id.delete_note -> displayDeleteNotePrompt() R.id.settings -> startActivity(Intent(applicationContext, SettingsActivity::class.java)) R.id.about -> launchAbout() else -> return super.onOptionsItemSelected(item) } return true } // https://code.google.com/p/android/issues/detail?id=191430 quickfix override fun onActionModeStarted(mode: ActionMode?) { super.onActionModeStarted(mode) if (wasInit) { currentNotesView()?.apply { if (config.clickableLinks || movementMethod is LinkMovementMethod) { movementMethod = ArrowKeyMovementMethod.getInstance() noteViewWithTextSelected = this } } } } override fun onActionModeFinished(mode: ActionMode?) { super.onActionModeFinished(mode) if (config.clickableLinks) { noteViewWithTextSelected?.movementMethod = LinkMovementMethod.getInstance() } } private fun storeStateVariables() { config.apply { storedEnableLineWrap = enableLineWrap } } private fun handleText(text: String) { val notes = dbHelper.getNotes() val list = arrayListOf().apply { add(RadioItem(0, getString(R.string.create_new_note))) notes.forEachIndexed { index, note -> add(RadioItem(index + 1, note.title)) } } RadioGroupDialog(this, list, -1, R.string.add_to_note) { if (it as Int == 0) { displayNewNoteDialog(text) } else { updateSelectedNote(notes[it - 1].id) addTextToCurrentNote(if (mCurrentNote.value.isEmpty()) text else "\n$text") } } } private fun handleUri(uri: Uri) { val id = dbHelper.getNoteId(uri.path) if (dbHelper.isValidId(id)) { updateSelectedNote(id) return } handlePermission(PERMISSION_WRITE_STORAGE) { if (it) { importFileWithSync(uri) } } } private fun initViewPager() { mNotes = dbHelper.getNotes() mCurrentNote = mNotes[0] var wantedNoteId = intent.getIntExtra(OPEN_NOTE_ID, -1) if (wantedNoteId == -1) { wantedNoteId = config.currentNoteId } val itemIndex = getNoteIndexWithId(wantedNoteId) mAdapter = NotesPagerAdapter(supportFragmentManager, mNotes, this) view_pager.apply { adapter = mAdapter currentItem = itemIndex addOnPageChangeListener(this@MainActivity) } if (!config.showKeyboard) { hideKeyboard() } } private fun currentNotesView() = if (view_pager == null) { null } else { mAdapter?.getCurrentNotesView(view_pager.currentItem) } private fun displayRenameDialog() { RenameNoteDialog(this, mCurrentNote) { mCurrentNote = it initViewPager() } } private fun updateSelectedNote(id: Int) { config.currentNoteId = id val index = getNoteIndexWithId(id) view_pager.currentItem = index mCurrentNote = mNotes[index] } private fun displayNewNoteDialog(value: String = "") { NewNoteDialog(this, dbHelper) { val newNote = Note(0, it, value, TYPE_NOTE) addNewNote(newNote) } } private fun addNewNote(note: Note) { val id = dbHelper.insertNote(note) mNotes = dbHelper.getNotes() showSaveButton = false invalidateOptionsMenu() initViewPager() updateSelectedNote(id) view_pager.onGlobalLayout { mAdapter?.focusEditText(getNoteIndexWithId(id)) } } private fun launchAbout() { val licenses = LICENSE_STETHO or LICENSE_RTL or LICENSE_LEAK_CANARY val faqItems = arrayListOf( FAQItem(R.string.faq_1_title_commons, R.string.faq_1_text_commons), FAQItem(R.string.faq_4_title_commons, R.string.faq_4_text_commons), FAQItem(R.string.faq_2_title_commons, R.string.faq_2_text_commons) ) startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true) } private fun tryOpenFile() { handlePermission(PERMISSION_WRITE_STORAGE) { if (it) { openFile() } } } private fun openFile() { FilePickerDialog(this) { openFile(it, true) { OpenFileDialog(this, it.path) { addNewNote(it) } } } } private fun openFile(path: String, checkTitle: Boolean, onChecksPassed: (file: File) -> Unit) { val file = File(path) if (path.isImageVideoGif()) { toast(R.string.invalid_file_format) } else if (file.length() > 10 * 1000 * 1000) { toast(R.string.file_too_large) } else if (checkTitle && dbHelper.doesTitleExist(path.getFilenameFromPath())) { toast(R.string.title_taken) } else { onChecksPassed(file) } } private fun openFolder(path: String, onChecksPassed: (file: File) -> Unit) { val file = File(path) if (file.isDirectory) { onChecksPassed(file) } } private fun importFileWithSync(uri: Uri) { when (uri.scheme) { "file" -> openPath(uri.path) "content" -> { val realPath = getRealPathFromURI(uri) if (realPath != null) { openPath(realPath) } else { R.string.unknown_error_occurred } } } } private fun openPath(path: String) { openFile(path, false) { var title = path.getFilenameFromPath() if (dbHelper.doesTitleExist(title)) title += " (file)" val note = Note(0, title, "", TYPE_NOTE, path) addNewNote(note) } } private fun tryOpenFolder() { handlePermission(PERMISSION_READ_STORAGE) { if (it) { openFolder() } } } private fun openFolder() { FilePickerDialog(this, pickFile = false) { openFolder(it) { ImportFolderDialog(this, it.path) { mNotes = dbHelper.getNotes() showSaveButton = false invalidateOptionsMenu() initViewPager() updateSelectedNote(it) view_pager.onGlobalLayout { mAdapter?.focusEditText(getNoteIndexWithId(it)) } } } } } private fun tryExportAsFile() { handlePermission(PERMISSION_WRITE_STORAGE) { if (it) { exportAsFile() } } } private fun exportAsFile() { ExportFileDialog(this, mCurrentNote) { if (getCurrentNoteText()?.isNotEmpty() == true) { exportNoteValueToFile(it, getCurrentNoteText()!!, true) } } } private fun tryExportAllNotes() { handlePermission(PERMISSION_WRITE_STORAGE) { if (it) { exportAllNotes() } } } private fun exportAllNotes() { ExportFilesDialog(this, mNotes) { parent, extension -> var failCount = 0 mNotes = dbHelper.getNotes() mNotes.forEachIndexed { index, note -> val filename = if (extension.isEmpty()) note.title else "${note.title}.$extension" val file = File(parent, filename) if (!filename.isAValidFilename()) { toast(String.format(getString(R.string.filename_invalid_characters_placeholder, filename))) } else { exportNoteValueToFile(file.absolutePath, note.value, false) { if (!it) { failCount++ } if (index == mNotes.size - 1) { toast(if (failCount == 0) R.string.exporting_successful else R.string.exporting_some_entries_failed) } } } } } } fun exportNoteValueToFile(path: String, content: String, showSuccessToasts: Boolean, callback: ((success: Boolean) -> Unit)? = null) { try { if (getIsPathDirectory(path)) { toast(R.string.name_taken) return } if (needsStupidWritePermissions(path)) { handleSAFDialog(path) { var document = getDocumentFile(path) ?: return@handleSAFDialog if (!getDoesFilePathExist(path)) { document = document.createFile("", path.getFilenameFromPath()) } contentResolver.openOutputStream(document.uri).apply { write(content.toByteArray(Charset.forName("UTF-8")), 0, content.length) flush() close() } if (showSuccessToasts) { noteExportedSuccessfully(path.getFilenameFromPath()) } callback?.invoke(true) } } else { val file = File(path) file.printWriter().use { out -> out.write(content) } if (showSuccessToasts) { noteExportedSuccessfully(path.getFilenameFromPath()) } callback?.invoke(true) } } catch (e: Exception) { showErrorToast(e) callback?.invoke(false) } } private fun noteExportedSuccessfully(title: String) { val message = String.format(getString(R.string.note_exported_successfully), title) toast(message) } fun noteSavedSuccessfully(title: String) { if (config.displaySuccess) { val message = String.format(getString(R.string.note_saved_successfully), title) toast(message) } } private fun getCurrentNoteText() = (view_pager.adapter as NotesPagerAdapter).getCurrentNoteViewText(view_pager.currentItem) private fun addTextToCurrentNote(text: String) = (view_pager.adapter as NotesPagerAdapter).appendText(view_pager.currentItem, text) private fun saveCurrentNote() = (view_pager.adapter as NotesPagerAdapter).saveCurrentNote(view_pager.currentItem) private fun displayDeleteNotePrompt() { DeleteNoteDialog(this, mCurrentNote) { deleteNote(it) } } fun deleteNote(deleteFile: Boolean) { if (mNotes.size <= 1) { return } if (!deleteFile) { doDeleteNote(mCurrentNote, deleteFile) } else { handleSAFDialog(mCurrentNote.path) { doDeleteNote(mCurrentNote, deleteFile) } } } private fun doDeleteNote(note: Note, deleteFile: Boolean) { dbHelper.deleteNote(mCurrentNote.id) mNotes = dbHelper.getNotes() val firstNoteId = mNotes[0].id updateSelectedNote(firstNoteId) if (config.widgetNoteId == note.id) { config.widgetNoteId = mCurrentNote.id updateWidget() } invalidateOptionsMenu() initViewPager() if (deleteFile) { deleteFile(FileDirItem(note.path, note.title)) { if (!it) { toast(R.string.unknown_error_occurred) } } } } private fun displayOpenNoteDialog() { OpenNoteDialog(this) { updateSelectedNote(it) } } private fun saveNote() { saveCurrentNote() showSaveButton = false invalidateOptionsMenu() } private fun undo() { mAdapter?.undo(view_pager.currentItem) } private fun redo() { mAdapter?.redo(view_pager.currentItem) } private fun getNoteIndexWithId(id: Int): Int { for (i in 0 until mNotes.count()) { if (mNotes[i].id == id) { mCurrentNote = mNotes[i] return i } } return 0 } private fun shareText() { val text = getCurrentNoteText() if (text == null || text.isEmpty()) { toast(R.string.cannot_share_empty_text) return } val res = resources val shareTitle = res.getString(R.string.share_via) Intent().apply { action = Intent.ACTION_SEND putExtra(Intent.EXTRA_SUBJECT, res.getString(R.string.simple_note)) putExtra(Intent.EXTRA_TEXT, text) type = "text/plain" startActivity(Intent.createChooser(this, shareTitle)) } } override fun onPageScrollStateChanged(state: Int) { } override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { } override fun onPageSelected(position: Int) { mCurrentNote = mNotes[position] config.currentNoteId = mCurrentNote.id } fun currentNoteTextChanged(newText: String, showUndo: Boolean, showRedo: Boolean) { var shouldRecreateMenu = false if (showUndo != showUndoButton) { showUndoButton = showUndo shouldRecreateMenu = true } if (showRedo != showRedoButton) { showRedoButton = showRedo shouldRecreateMenu = true } if (!config.autosaveNotes) { showSaveButton = newText != mCurrentNote.value if (showSaveButton != saveNoteButton?.isVisible) { shouldRecreateMenu = true } } if (shouldRecreateMenu) { invalidateOptionsMenu() } } private fun checkWhatsNewDialog() { arrayListOf().apply { add(Release(25, R.string.release_25)) add(Release(28, R.string.release_28)) add(Release(29, R.string.release_29)) add(Release(39, R.string.release_39)) add(Release(45, R.string.release_45)) add(Release(49, R.string.release_49)) checkWhatsNew(this, BuildConfig.VERSION_CODE) } } }