appending package name with .pro

This commit is contained in:
tibbi
2018-11-07 11:48:09 +01:00
parent 20338bbe90
commit 0635dac078
34 changed files with 128 additions and 128 deletions

View File

@ -0,0 +1,11 @@
package com.simplemobiletools.notes.pro
import android.app.Application
import com.simplemobiletools.commons.extensions.checkUseEnglish
class App : Application() {
override fun onCreate() {
super.onCreate()
checkUseEnglish()
}
}

View File

@ -0,0 +1,670 @@
package com.simplemobiletools.notes.pro.activities
import android.content.Intent
import android.net.Uri
import android.os.Bundle
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.ConfirmationAdvancedDialog
import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.LICENSE_RTL
import com.simplemobiletools.commons.helpers.PERMISSION_READ_STORAGE
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE
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.pro.BuildConfig
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.adapters.NotesPagerAdapter
import com.simplemobiletools.notes.pro.dialogs.*
import com.simplemobiletools.notes.pro.extensions.config
import com.simplemobiletools.notes.pro.extensions.dbHelper
import com.simplemobiletools.notes.pro.extensions.getTextSize
import com.simplemobiletools.notes.pro.extensions.updateWidgets
import com.simplemobiletools.notes.pro.helpers.MIME_TEXT_PLAIN
import com.simplemobiletools.notes.pro.helpers.OPEN_NOTE_ID
import com.simplemobiletools.notes.pro.helpers.TYPE_NOTE
import com.simplemobiletools.notes.pro.models.Note
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File
import java.nio.charset.Charset
class MainActivity : SimpleActivity() {
private val EXPORT_FILE_SYNC = 1
private val EXPORT_FILE_NO_SYNC = 2
private lateinit var mCurrentNote: Note
private var mNotes = ArrayList<Note>()
private var mAdapter: NotesPagerAdapter? = null
private var noteViewWithTextSelected: MyEditText? = null
private var saveNoteButton: MenuItem? = null
private var wasInit = false
private var storedEnableLineWrap = true
private var showSaveButton = false
private var showUndoButton = false
private var showRedoButton = false
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 && savedInstanceState == null) {
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(false)
}
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()
}
}
override fun onBackPressed() {
if (!config.autosaveNotes && mAdapter?.anyHasUnsavedChanges() == true) {
ConfirmationAdvancedDialog(this, "", R.string.unsaved_changes_warning, R.string.save, R.string.discard) {
if (it) {
mAdapter?.saveAllFragmentTexts()
}
super.onBackPressed()
}
} else {
super.onBackPressed()
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
view_pager.currentItem = getWantedNoteIndex()
}
private fun storeStateVariables() {
config.apply {
storedEnableLineWrap = enableLineWrap
}
}
private fun handleText(text: String) {
val notes = dbHelper.getNotes()
val list = arrayListOf<RadioItem>().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]
mAdapter = NotesPagerAdapter(supportFragmentManager, mNotes, this)
view_pager.apply {
adapter = mAdapter
currentItem = getWantedNoteIndex()
onPageChangeListener {
mCurrentNote = mNotes[it]
config.currentNoteId = mCurrentNote.id
}
}
if (!config.showKeyboard) {
hideKeyboard()
}
}
private fun getWantedNoteIndex(): Int {
var wantedNoteId = intent.getIntExtra(OPEN_NOTE_ID, -1)
if (wantedNoteId == -1) {
wantedNoteId = config.currentNoteId
}
return getNoteIndexWithId(wantedNoteId)
}
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_RTL
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, canAddShowHiddenButton = true) {
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.isMediaFile()) {
toast(R.string.invalid_file_format)
} else if (file.length() > 10 * 1000 * 1000) {
toast(R.string.file_too_large)
} else if (checkTitle && dbHelper.doesNoteTitleExist(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.doesNoteTitleExist(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, canAddShowHiddenButton = true) {
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) {
showExportFilePickUpdateDialog(it)
}
}
}
private fun showExportFilePickUpdateDialog(exportPath: String) {
val items = arrayListOf(
RadioItem(EXPORT_FILE_SYNC, getString(R.string.update_file_at_note)),
RadioItem(EXPORT_FILE_NO_SYNC, getString(R.string.only_export_file_content)))
RadioGroupDialog(this, items) {
val syncFile = it as Int == EXPORT_FILE_SYNC
val currentNoteText = getCurrentNoteText()
if (currentNoteText == null) {
toast(R.string.unknown_error_occurred)
return@RadioGroupDialog
}
exportNoteValueToFile(exportPath, currentNoteText, true) {
if (syncFile) {
mCurrentNote.path = exportPath
dbHelper.updateNotePath(mCurrentNote)
if (mCurrentNote.getNoteStoredValue() == currentNoteText) {
mCurrentNote.value = ""
dbHelper.updateNoteValue(mCurrentNote)
}
}
}
}
}
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 getPagerAdapter() = view_pager.adapter as NotesPagerAdapter
private fun getCurrentNoteText() = getPagerAdapter().getCurrentNoteViewText(view_pager.currentItem)
private fun addTextToCurrentNote(text: String) = getPagerAdapter().appendText(view_pager.currentItem, text)
private fun saveCurrentNote(force: Boolean) = getPagerAdapter().saveCurrentNote(view_pager.currentItem, force)
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
updateWidgets()
}
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(true)
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, mCurrentNote.title)
putExtra(Intent.EXTRA_TEXT, text)
type = "text/plain"
startActivity(Intent.createChooser(this, shareTitle))
}
}
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<Release>().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))
add(Release(51, R.string.release_51))
checkWhatsNew(this, BuildConfig.VERSION_CODE)
}
}
}

View File

@ -0,0 +1,207 @@
package com.simplemobiletools.notes.pro.activities
import android.content.Intent
import android.os.Bundle
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.extensions.getAdjustedPrimaryColor
import com.simplemobiletools.commons.extensions.updateTextColors
import com.simplemobiletools.commons.helpers.IS_CUSTOMIZING_COLORS
import com.simplemobiletools.commons.helpers.isOreoPlus
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.extensions.config
import com.simplemobiletools.notes.pro.extensions.dbHelper
import com.simplemobiletools.notes.pro.extensions.updateWidgets
import com.simplemobiletools.notes.pro.helpers.*
import kotlinx.android.synthetic.main.activity_settings.*
import java.util.*
class SettingsActivity : SimpleActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
}
override fun onResume() {
super.onResume()
setupCustomizeColors()
setupUseEnglish()
setupAutosaveNotes()
setupDisplaySuccess()
setupClickableLinks()
setupMonospacedFont()
setupShowKeyboard()
setupShowNotePicker()
setupShowWordCount()
setupEnableLineWrap()
setupFontSize()
setupGravity()
setupCursorPlacement()
setupIncognitoMode()
setupCustomizeWidgetColors()
updateTextColors(settings_scrollview)
setupSectionColors()
}
private fun setupSectionColors() {
val adjustedPrimaryColor = getAdjustedPrimaryColor()
arrayListOf(text_label, startup_label, saving_label, widgets_label).forEach {
it.setTextColor(adjustedPrimaryColor)
}
}
private fun setupCustomizeColors() {
settings_customize_colors_holder.setOnClickListener {
startCustomizationActivity()
}
}
private fun setupUseEnglish() {
settings_use_english_holder.beVisibleIf(config.wasUseEnglishToggled || Locale.getDefault().language != "en")
settings_use_english.isChecked = config.useEnglish
settings_use_english_holder.setOnClickListener {
settings_use_english.toggle()
config.useEnglish = settings_use_english.isChecked
System.exit(0)
}
}
private fun setupAutosaveNotes() {
settings_autosave_notes.isChecked = config.autosaveNotes
settings_autosave_notes_holder.setOnClickListener {
settings_autosave_notes.toggle()
config.autosaveNotes = settings_autosave_notes.isChecked
}
}
private fun setupDisplaySuccess() {
settings_display_success.isChecked = config.displaySuccess
settings_display_success_holder.setOnClickListener {
settings_display_success.toggle()
config.displaySuccess = settings_display_success.isChecked
}
}
private fun setupClickableLinks() {
settings_clickable_links.isChecked = config.clickableLinks
settings_clickable_links_holder.setOnClickListener {
settings_clickable_links.toggle()
config.clickableLinks = settings_clickable_links.isChecked
}
}
private fun setupMonospacedFont() {
settings_monospaced_font.isChecked = config.monospacedFont
settings_monospaced_font_holder.setOnClickListener {
settings_monospaced_font.toggle()
config.monospacedFont = settings_monospaced_font.isChecked
}
}
private fun setupShowKeyboard() {
settings_show_keyboard.isChecked = config.showKeyboard
settings_show_keyboard_holder.setOnClickListener {
settings_show_keyboard.toggle()
config.showKeyboard = settings_show_keyboard.isChecked
}
}
private fun setupShowNotePicker() {
settings_show_note_picker_holder.beVisibleIf(dbHelper.getNotes().size > 1)
settings_show_note_picker.isChecked = config.showNotePicker
settings_show_note_picker_holder.setOnClickListener {
settings_show_note_picker.toggle()
config.showNotePicker = settings_show_note_picker.isChecked
}
}
private fun setupShowWordCount() {
settings_show_word_count.isChecked = config.showWordCount
settings_show_word_count_holder.setOnClickListener {
settings_show_word_count.toggle()
config.showWordCount = settings_show_word_count.isChecked
}
}
private fun setupEnableLineWrap() {
settings_enable_line_wrap.isChecked = config.enableLineWrap
settings_enable_line_wrap_holder.setOnClickListener {
settings_enable_line_wrap.toggle()
config.enableLineWrap = settings_enable_line_wrap.isChecked
}
}
private fun setupFontSize() {
settings_font_size.text = getFontSizeText()
settings_font_size_holder.setOnClickListener {
val items = arrayListOf(
RadioItem(FONT_SIZE_SMALL, getString(R.string.small)),
RadioItem(FONT_SIZE_MEDIUM, getString(R.string.medium)),
RadioItem(FONT_SIZE_LARGE, getString(R.string.large)),
RadioItem(FONT_SIZE_EXTRA_LARGE, getString(R.string.extra_large)))
RadioGroupDialog(this@SettingsActivity, items, config.fontSize) {
config.fontSize = it as Int
settings_font_size.text = getFontSizeText()
updateWidgets()
}
}
}
private fun getFontSizeText() = getString(when (config.fontSize) {
FONT_SIZE_SMALL -> R.string.small
FONT_SIZE_MEDIUM -> R.string.medium
FONT_SIZE_LARGE -> R.string.large
else -> R.string.extra_large
})
private fun setupGravity() {
settings_gravity.text = getGravityText()
settings_gravity_holder.setOnClickListener {
val items = arrayListOf(
RadioItem(GRAVITY_LEFT, getString(R.string.left)),
RadioItem(GRAVITY_CENTER, getString(R.string.center)),
RadioItem(GRAVITY_RIGHT, getString(R.string.right)))
RadioGroupDialog(this@SettingsActivity, items, config.gravity) {
config.gravity = it as Int
settings_gravity.text = getGravityText()
updateWidgets()
}
}
}
private fun getGravityText() = getString(when (config.gravity) {
GRAVITY_LEFT -> R.string.left
GRAVITY_CENTER -> R.string.center
else -> R.string.right
})
private fun setupCursorPlacement() {
settings_cursor_placement.isChecked = config.placeCursorToEnd
settings_cursor_placement_holder.setOnClickListener {
settings_cursor_placement.toggle()
config.placeCursorToEnd = settings_cursor_placement.isChecked
}
}
private fun setupCustomizeWidgetColors() {
settings_customize_widget_colors_holder.setOnClickListener {
Intent(this, WidgetConfigureActivity::class.java).apply {
putExtra(IS_CUSTOMIZING_COLORS, true)
startActivity(this)
}
}
}
private fun setupIncognitoMode() {
settings_use_incognito_mode_holder.beVisibleIf(isOreoPlus())
settings_use_incognito_mode.isChecked = config.useIncognitoMode
settings_use_incognito_mode_holder.setOnClickListener {
settings_use_incognito_mode.toggle()
config.useIncognitoMode = settings_use_incognito_mode.isChecked
}
}
}

View File

@ -0,0 +1,30 @@
package com.simplemobiletools.notes.pro.activities
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.notes.pro.R
open class SimpleActivity : BaseSimpleActivity() {
override fun getAppIconIDs() = arrayListOf(
R.mipmap.ic_launcher_red,
R.mipmap.ic_launcher_pink,
R.mipmap.ic_launcher_purple,
R.mipmap.ic_launcher_deep_purple,
R.mipmap.ic_launcher_indigo,
R.mipmap.ic_launcher_blue,
R.mipmap.ic_launcher_light_blue,
R.mipmap.ic_launcher_cyan,
R.mipmap.ic_launcher_teal,
R.mipmap.ic_launcher_green,
R.mipmap.ic_launcher_light_green,
R.mipmap.ic_launcher_lime,
R.mipmap.ic_launcher_yellow,
R.mipmap.ic_launcher_amber,
R.mipmap.ic_launcher,
R.mipmap.ic_launcher_deep_orange,
R.mipmap.ic_launcher_brown,
R.mipmap.ic_launcher_blue_grey,
R.mipmap.ic_launcher_grey_black
)
override fun getAppLauncherName() = getString(R.string.app_launcher_name)
}

View File

@ -0,0 +1,19 @@
package com.simplemobiletools.notes.pro.activities
import android.content.Intent
import com.simplemobiletools.commons.activities.BaseSplashActivity
import com.simplemobiletools.notes.pro.helpers.OPEN_NOTE_ID
class SplashActivity : BaseSplashActivity() {
override fun initActivity() {
if (intent.extras?.containsKey(OPEN_NOTE_ID) == true) {
Intent(this, MainActivity::class.java).apply {
putExtra(OPEN_NOTE_ID, intent.getIntExtra(OPEN_NOTE_ID, -1))
startActivity(this)
}
} else {
startActivity(Intent(this, MainActivity::class.java))
}
finish()
}
}

View File

@ -0,0 +1,169 @@
package com.simplemobiletools.notes.pro.activities
import android.app.Activity
import android.appwidget.AppWidgetManager
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.util.TypedValue
import android.widget.RemoteViews
import com.simplemobiletools.commons.dialogs.ColorPickerDialog
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.IS_CUSTOMIZING_COLORS
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.extensions.config
import com.simplemobiletools.notes.pro.extensions.dbHelper
import com.simplemobiletools.notes.pro.extensions.getTextSize
import com.simplemobiletools.notes.pro.helpers.MyWidgetProvider
import com.simplemobiletools.notes.pro.models.Note
import com.simplemobiletools.notes.pro.models.Widget
import kotlinx.android.synthetic.main.widget_config.*
class WidgetConfigureActivity : SimpleActivity() {
private var mBgAlpha = 0f
private var mWidgetId = 0
private var mBgColor = 0
private var mBgColorWithoutTransparency = 0
private var mTextColor = 0
private var mCurrentNoteId = 0
private var mIsCustomizingColors = false
private var mNotes = ArrayList<Note>()
public override fun onCreate(savedInstanceState: Bundle?) {
useDynamicTheme = false
super.onCreate(savedInstanceState)
setResult(RESULT_CANCELED)
setContentView(R.layout.widget_config)
initVariables()
mWidgetId = intent.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID) ?: AppWidgetManager.INVALID_APPWIDGET_ID
if (mWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID && !mIsCustomizingColors) {
finish()
}
updateTextColors(notes_picker_holder)
config_save.setOnClickListener { saveConfig() }
config_bg_color.setOnClickListener { pickBackgroundColor() }
config_text_color.setOnClickListener { pickTextColor() }
notes_picker_value.setOnClickListener { showNoteSelector() }
notes_picker_holder.background = ColorDrawable(config.backgroundColor)
}
override fun onResume() {
super.onResume()
notes_view.setTextSize(TypedValue.COMPLEX_UNIT_PX, applicationContext.getTextSize())
}
private fun initVariables() {
mBgColor = config.widgetBgColor
if (mBgColor == 1) {
mBgColor = Color.BLACK
mBgAlpha = .2f
} else {
mBgAlpha = Color.alpha(mBgColor) / 255f
}
mBgColorWithoutTransparency = Color.rgb(Color.red(mBgColor), Color.green(mBgColor), Color.blue(mBgColor))
config_bg_seekbar.apply {
progress = (mBgAlpha * 100).toInt()
onSeekBarChangeListener {
mBgAlpha = it / 100f
updateBackgroundColor()
}
}
updateBackgroundColor()
mTextColor = config.widgetTextColor
updateTextColor()
mNotes = dbHelper.getNotes()
mIsCustomizingColors = intent.extras?.getBoolean(IS_CUSTOMIZING_COLORS) ?: false
notes_picker_holder.beVisibleIf(mNotes.size > 1 && !mIsCustomizingColors)
updateCurrentNote(mNotes.first())
}
private fun showNoteSelector() {
val items = ArrayList<RadioItem>()
mNotes.forEach {
items.add(RadioItem(it.id, it.title))
}
RadioGroupDialog(this, items, mCurrentNoteId) {
val selectedId = it as Int
updateCurrentNote(mNotes.first { it.id == selectedId })
}
}
private fun updateCurrentNote(note: Note) {
mCurrentNoteId = note.id
notes_picker_value.text = note.title
val sampleValue = if (note.value.isEmpty() || mIsCustomizingColors) getString(R.string.widget_config) else note.value
notes_view.text = sampleValue
}
private fun saveConfig() {
val views = RemoteViews(packageName, R.layout.activity_main)
views.setBackgroundColor(R.id.notes_view, mBgColor)
AppWidgetManager.getInstance(this).updateAppWidget(mWidgetId, views)
val widget = Widget(mWidgetId, mCurrentNoteId)
dbHelper.insertWidget(widget)
storeWidgetBackground()
requestWidgetUpdate()
Intent().apply {
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId)
setResult(Activity.RESULT_OK, this)
}
finish()
}
private fun storeWidgetBackground() {
config.apply {
widgetBgColor = mBgColor
widgetTextColor = mTextColor
}
}
private fun requestWidgetUpdate() {
Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null, this, MyWidgetProvider::class.java).apply {
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(mWidgetId))
sendBroadcast(this)
}
}
private fun updateBackgroundColor() {
mBgColor = mBgColorWithoutTransparency.adjustAlpha(mBgAlpha)
notes_view.setBackgroundColor(mBgColor)
config_save.setBackgroundColor(mBgColor)
config_bg_color.setFillWithStroke(mBgColor, Color.BLACK)
}
private fun updateTextColor() {
config_save.setTextColor(mTextColor)
notes_view.setTextColor(mTextColor)
config_text_color.setFillWithStroke(mTextColor, Color.BLACK)
}
private fun pickBackgroundColor() {
ColorPickerDialog(this, mBgColorWithoutTransparency) { wasPositivePressed, color ->
if (wasPositivePressed) {
mBgColorWithoutTransparency = color
updateBackgroundColor()
}
}
}
private fun pickTextColor() {
ColorPickerDialog(this, mTextColor) { wasPositivePressed, color ->
if (wasPositivePressed) {
mTextColor = color
updateTextColor()
}
}
}
}

View File

@ -0,0 +1,71 @@
package com.simplemobiletools.notes.pro.adapters
import android.app.Activity
import android.os.Bundle
import android.view.ViewGroup
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.notes.pro.fragments.NoteFragment
import com.simplemobiletools.notes.pro.helpers.NOTE_ID
import com.simplemobiletools.notes.pro.models.Note
class NotesPagerAdapter(fm: FragmentManager, val notes: List<Note>, val activity: Activity) : FragmentStatePagerAdapter(fm) {
private var fragments: HashMap<Int, NoteFragment> = LinkedHashMap()
override fun getCount() = notes.size
override fun getItem(position: Int): NoteFragment {
val bundle = Bundle()
val id = notes[position].id
bundle.putInt(NOTE_ID, id)
if (fragments.containsKey(position)) {
return fragments[position]!!
}
val fragment = NoteFragment()
fragment.arguments = bundle
fragments[position] = fragment
return fragment
}
override fun getPageTitle(position: Int) = notes[position].title
fun getCurrentNotesView(position: Int) = fragments[position]?.getNotesView()
fun getCurrentNoteViewText(position: Int) = fragments[position]?.getCurrentNoteViewText()
fun appendText(position: Int, text: String) = fragments[position]?.getNotesView()?.append(text)
fun saveCurrentNote(position: Int, force: Boolean) = fragments[position]?.saveText(force)
fun focusEditText(position: Int) = fragments[position]?.focusEditText()
fun anyHasUnsavedChanges() = fragments.values.any { it.hasUnsavedChanges() }
fun saveAllFragmentTexts() = fragments.values.forEach { it.saveText(false) }
fun undo(position: Int) = fragments[position]?.undo()
fun redo(position: Int) = fragments[position]?.redo()
override fun finishUpdate(container: ViewGroup) {
try {
super.finishUpdate(container)
} catch (e: Exception) {
activity.showErrorToast(e)
}
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
super.destroyItem(container, position, `object`)
fragments.remove(position)
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val fragment = super.instantiateItem(container, position) as NoteFragment
fragments[position] = fragment
return fragment
}
}

View File

@ -0,0 +1,72 @@
package com.simplemobiletools.notes.pro.adapters
import android.content.Context
import android.content.Intent
import android.view.View
import android.widget.RemoteViews
import android.widget.RemoteViewsService
import com.simplemobiletools.commons.extensions.setText
import com.simplemobiletools.commons.extensions.setTextSize
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.R.id.widget_text_holder
import com.simplemobiletools.notes.pro.extensions.config
import com.simplemobiletools.notes.pro.extensions.dbHelper
import com.simplemobiletools.notes.pro.extensions.getTextSize
import com.simplemobiletools.notes.pro.helpers.GRAVITY_CENTER
import com.simplemobiletools.notes.pro.helpers.GRAVITY_RIGHT
import com.simplemobiletools.notes.pro.helpers.NOTE_ID
import com.simplemobiletools.notes.pro.helpers.OPEN_NOTE_ID
class WidgetAdapter(val context: Context, val intent: Intent) : RemoteViewsService.RemoteViewsFactory {
private val textIds = arrayOf(R.id.widget_text_left, R.id.widget_text_center, R.id.widget_text_right)
private var widgetTextColor = context.config.widgetTextColor
override fun getViewAt(position: Int): RemoteViews {
val noteId = intent.getIntExtra(NOTE_ID, 1)
val views = RemoteViews(context.packageName, R.layout.widget_text_layout).apply {
val note = context.dbHelper.getNoteWithId(noteId)
if (note != null) {
val noteText = note.getNoteStoredValue() ?: ""
val textSize = context.getTextSize() / context.resources.displayMetrics.density
for (id in textIds) {
setText(id, noteText)
setTextColor(id, widgetTextColor)
setTextSize(id, textSize)
setViewVisibility(id, View.GONE)
}
}
Intent().apply {
putExtra(OPEN_NOTE_ID, noteId)
setOnClickFillInIntent(widget_text_holder, this)
}
setViewVisibility(getProperTextView(context), View.VISIBLE)
}
return views
}
private fun getProperTextView(context: Context) = when (context.config.gravity) {
GRAVITY_CENTER -> R.id.widget_text_center
GRAVITY_RIGHT -> R.id.widget_text_right
else -> R.id.widget_text_left
}
override fun onCreate() {}
override fun getLoadingView() = null
override fun getItemId(position: Int) = position.toLong()
override fun onDataSetChanged() {
widgetTextColor = context.config.widgetTextColor
}
override fun hasStableIds() = true
override fun getCount() = 1
override fun getViewTypeCount() = 1
override fun onDestroy() {}
}

View File

@ -0,0 +1,36 @@
package com.simplemobiletools.notes.pro.dialogs
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.extensions.beVisible
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.activities.SimpleActivity
import com.simplemobiletools.notes.pro.models.Note
import kotlinx.android.synthetic.main.dialog_delete_note.view.*
class DeleteNoteDialog(val activity: SimpleActivity, val note: Note, val callback: (deleteFile: Boolean) -> Unit) {
var dialog: AlertDialog? = null
init {
val message = String.format(activity.getString(R.string.delete_note_prompt_message), note.title)
val view = activity.layoutInflater.inflate(R.layout.dialog_delete_note, null).apply {
if (note.path.isNotEmpty()) {
delete_note_checkbox.text = String.format(activity.getString(R.string.delete_file_itself), note.path)
delete_note_checkbox.beVisible()
}
delete_note_description.text = message
}
AlertDialog.Builder(activity)
.setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed(view.delete_note_checkbox.isChecked) }
.setNegativeButton(R.string.cancel, null)
.create().apply {
activity.setupDialogStuff(view, this)
}
}
private fun dialogConfirmed(deleteFile: Boolean) {
callback(deleteFile && note.path.isNotEmpty())
dialog?.dismiss()
}
}

View File

@ -0,0 +1,59 @@
package com.simplemobiletools.notes.pro.dialogs
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.activities.SimpleActivity
import com.simplemobiletools.notes.pro.extensions.config
import com.simplemobiletools.notes.pro.models.Note
import kotlinx.android.synthetic.main.dialog_export_file.view.*
import java.io.File
class ExportFileDialog(val activity: SimpleActivity, val note: Note, val callback: (exportPath: String) -> Unit) {
init {
var realPath = File(note.path).parent ?: activity.config.lastUsedSavePath
val view = activity.layoutInflater.inflate(R.layout.dialog_export_file, null).apply {
file_path.text = activity.humanizePath(realPath)
file_name.setText(note.title)
file_extension.setText(activity.config.lastUsedExtension)
file_path.setOnClickListener {
FilePickerDialog(activity, realPath, false, false, true, true) {
file_path.text = activity.humanizePath(it)
realPath = it
}
}
}
AlertDialog.Builder(activity)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.create().apply {
activity.setupDialogStuff(view, this, R.string.export_as_file) {
showKeyboard(view.file_name)
getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
val filename = view.file_name.value
val extension = view.file_extension.value
if (filename.isEmpty()) {
activity.toast(R.string.filename_cannot_be_empty)
return@setOnClickListener
}
val fullFilename = if (extension.isEmpty()) filename else "$filename.$extension"
if (!fullFilename.isAValidFilename()) {
activity.toast(String.format(activity.getString(R.string.filename_invalid_characters_placeholder, fullFilename)))
return@setOnClickListener
}
activity.config.lastUsedExtension = extension
activity.config.lastUsedSavePath = realPath
callback("$realPath/$fullFilename")
dismiss()
}
}
}
}
}

View File

@ -0,0 +1,48 @@
package com.simplemobiletools.notes.pro.dialogs
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.humanizePath
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.commons.extensions.showKeyboard
import com.simplemobiletools.commons.extensions.value
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.activities.SimpleActivity
import com.simplemobiletools.notes.pro.extensions.config
import com.simplemobiletools.notes.pro.models.Note
import kotlinx.android.synthetic.main.dialog_export_files.view.*
class ExportFilesDialog(val activity: SimpleActivity, val notes: ArrayList<Note>, val callback: (parent: String, extension: String) -> Unit) {
init {
var realPath = activity.config.lastUsedSavePath
val view = activity.layoutInflater.inflate(R.layout.dialog_export_files, null).apply {
folder_path.text = activity.humanizePath(realPath)
file_extension.setText(activity.config.lastUsedExtension)
folder_path.setOnClickListener {
FilePickerDialog(activity, realPath, false, false, true, true) {
folder_path.text = activity.humanizePath(it)
realPath = it
}
}
}
AlertDialog.Builder(activity)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.create().apply {
activity.setupDialogStuff(view, this, R.string.export_as_file) {
showKeyboard(view.file_extension)
getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
activity.handleSAFDialog(realPath) {
val extension = view.file_extension.value
activity.config.lastUsedExtension = extension
activity.config.lastUsedSavePath = realPath
callback(realPath, extension)
dismiss()
}
}
}
}
}
}

View File

@ -0,0 +1,75 @@
package com.simplemobiletools.notes.pro.dialogs
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.extensions.getFilenameFromPath
import com.simplemobiletools.commons.extensions.humanizePath
import com.simplemobiletools.commons.extensions.isMediaFile
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.activities.SimpleActivity
import com.simplemobiletools.notes.pro.extensions.dbHelper
import com.simplemobiletools.notes.pro.helpers.TYPE_NOTE
import com.simplemobiletools.notes.pro.models.Note
import kotlinx.android.synthetic.main.dialog_import_folder.view.*
import java.io.File
class ImportFolderDialog(val activity: SimpleActivity, val path: String, val callback: (id: Int) -> Unit) : AlertDialog.Builder(activity) {
private var dialog: AlertDialog
init {
val view = (activity.layoutInflater.inflate(R.layout.dialog_import_folder, null) as ViewGroup).apply {
open_file_filename.text = activity.humanizePath(path)
}
dialog = AlertDialog.Builder(activity)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.create().apply {
activity.setupDialogStuff(view, this, R.string.import_folder) {
getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
val updateFilesOnEdit = view.open_file_type.checkedRadioButtonId == R.id.open_file_update_file
saveFolder(updateFilesOnEdit)
}
}
}
}
private fun saveFolder(updateFilesOnEdit: Boolean) {
val folder = File(path)
var lastSavedNoteId = -1
folder.listFiles { file ->
val filename = file.path.getFilenameFromPath()
when {
file.isDirectory -> false
filename.isMediaFile() -> false
file.length() > 10 * 1000 * 1000 -> false
activity.dbHelper.doesNoteTitleExist(filename) -> false
else -> true
}
}.forEach {
val storePath = if (updateFilesOnEdit) it.absolutePath else ""
val title = it.absolutePath.getFilenameFromPath()
val value = if (updateFilesOnEdit) "" else it.readText()
if (updateFilesOnEdit) {
activity.handleSAFDialog(path) {
lastSavedNoteId = saveNote(title, value, storePath)
}
} else {
lastSavedNoteId = saveNote(title, value, storePath)
}
}
if (lastSavedNoteId != -1) {
callback(lastSavedNoteId)
}
dialog.dismiss()
}
private fun saveNote(title: String, value: String, path: String): Int {
val note = Note(0, title, value, TYPE_NOTE, path)
return activity.dbHelper.insertNote(note)
}
}

View File

@ -0,0 +1,38 @@
package com.simplemobiletools.notes.pro.dialogs
import android.app.Activity
import android.content.DialogInterface.BUTTON_POSITIVE
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.commons.extensions.showKeyboard
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.extensions.value
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.helpers.DBHelper
import kotlinx.android.synthetic.main.dialog_new_note.view.*
class NewNoteDialog(val activity: Activity, val db: DBHelper, callback: (title: String) -> Unit) {
init {
val view = activity.layoutInflater.inflate(R.layout.dialog_new_note, null)
AlertDialog.Builder(activity)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.create().apply {
activity.setupDialogStuff(view, this, R.string.new_note) {
showKeyboard(view.note_name)
getButton(BUTTON_POSITIVE).setOnClickListener {
val title = view.note_name.value
when {
title.isEmpty() -> activity.toast(R.string.no_title)
db.doesNoteTitleExist(title) -> activity.toast(R.string.title_taken)
else -> {
callback(title)
dismiss()
}
}
}
}
}
}
}

View File

@ -0,0 +1,52 @@
package com.simplemobiletools.notes.pro.dialogs
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.extensions.getFilenameFromPath
import com.simplemobiletools.commons.extensions.humanizePath
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.activities.SimpleActivity
import com.simplemobiletools.notes.pro.helpers.TYPE_NOTE
import com.simplemobiletools.notes.pro.models.Note
import kotlinx.android.synthetic.main.dialog_open_file.*
import kotlinx.android.synthetic.main.dialog_open_file.view.*
import java.io.File
class OpenFileDialog(val activity: SimpleActivity, val path: String, val callback: (note: Note) -> Unit) : AlertDialog.Builder(activity) {
private var dialog: AlertDialog
init {
val view = (activity.layoutInflater.inflate(R.layout.dialog_open_file, null) as ViewGroup).apply {
open_file_filename.text = activity.humanizePath(path)
}
dialog = AlertDialog.Builder(activity)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.create().apply {
activity.setupDialogStuff(view, this, R.string.open_file) {
getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
val updateFileOnEdit = view.open_file_type.checkedRadioButtonId == open_file_update_file.id
val storePath = if (updateFileOnEdit) path else ""
val storeContent = if (updateFileOnEdit) "" else File(path).readText()
if (updateFileOnEdit) {
activity.handleSAFDialog(path) {
saveNote(storeContent, storePath)
}
} else {
saveNote(storeContent, storePath)
}
}
}
}
}
private fun saveNote(storeContent: String, storePath: String) {
val filename = path.getFilenameFromPath()
val note = Note(0, filename, storeContent, TYPE_NOTE, storePath)
callback(note)
dialog.dismiss()
}
}

View File

@ -0,0 +1,53 @@
package com.simplemobiletools.notes.pro.dialogs
import android.app.Activity
import android.view.ViewGroup
import android.widget.RadioGroup
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.extensions.applyColorFilter
import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.extensions.config
import com.simplemobiletools.notes.pro.extensions.dbHelper
import kotlinx.android.synthetic.main.dialog_open_note.view.*
import kotlinx.android.synthetic.main.open_note_item.view.*
class OpenNoteDialog(val activity: Activity, val callback: (checkedId: Int) -> Unit) {
lateinit var dialog: AlertDialog
init {
val view = activity.layoutInflater.inflate(R.layout.dialog_open_note, null)
val textColor = activity.config.textColor
val notes = activity.dbHelper.getNotes()
notes.forEach {
activity.layoutInflater.inflate(R.layout.open_note_item, null).apply {
val note = it
open_note_item_radio_button.apply {
text = note.title
isChecked = note.id == activity.config.currentNoteId
id = note.id
setOnClickListener {
callback(id)
dialog.dismiss()
}
}
open_note_item_icon.apply {
beVisibleIf(note.path.isNotEmpty())
applyColorFilter(textColor)
setOnClickListener {
activity.toast(note.path)
}
}
view.dialog_open_note_linear.addView(this, RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
}
}
dialog = AlertDialog.Builder(activity)
.create().apply {
activity.setupDialogStuff(view, this, R.string.pick_a_note)
}
}
}

View File

@ -0,0 +1,65 @@
package com.simplemobiletools.notes.pro.dialogs
import android.content.DialogInterface.BUTTON_POSITIVE
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.activities.SimpleActivity
import com.simplemobiletools.notes.pro.extensions.dbHelper
import com.simplemobiletools.notes.pro.models.Note
import kotlinx.android.synthetic.main.dialog_new_note.view.*
import java.io.File
class RenameNoteDialog(val activity: SimpleActivity, val note: Note, callback: (note: Note) -> Unit) {
init {
val view = activity.layoutInflater.inflate(R.layout.dialog_rename_note, null)
view.note_name.setText(note.title)
AlertDialog.Builder(activity)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.create().apply {
activity.setupDialogStuff(view, this, R.string.rename_note) {
showKeyboard(view.note_name)
getButton(BUTTON_POSITIVE).setOnClickListener {
val title = view.note_name.value
when {
title.isEmpty() -> activity.toast(R.string.no_title)
activity.dbHelper.doesNoteTitleExist(title) -> activity.toast(R.string.title_taken)
else -> {
note.title = title
val path = note.path
if (path.isNotEmpty()) {
if (title.isEmpty()) {
activity.toast(R.string.filename_cannot_be_empty)
return@setOnClickListener
}
val file = File(path)
val newFile = File(file.parent, title)
if (!newFile.name.isAValidFilename()) {
activity.toast(R.string.invalid_name)
return@setOnClickListener
}
activity.renameFile(file.absolutePath, newFile.absolutePath) {
if (it) {
note.path = newFile.absolutePath
activity.dbHelper.updateNotePath(note)
} else {
activity.toast(R.string.rename_file_error)
return@renameFile
}
}
}
activity.dbHelper.updateNoteTitle(note)
dismiss()
callback(note)
}
}
}
}
}
}
}

View File

@ -0,0 +1,30 @@
package com.simplemobiletools.notes.pro.extensions
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.helpers.*
val Context.config: Config get() = Config.newInstance(applicationContext)
val Context.dbHelper: DBHelper get() = DBHelper.newInstance(applicationContext)
fun Context.getTextSize() = when (config.fontSize) {
FONT_SIZE_SMALL -> resources.getDimension(R.dimen.smaller_text_size)
FONT_SIZE_LARGE -> resources.getDimension(R.dimen.big_text_size)
FONT_SIZE_EXTRA_LARGE -> resources.getDimension(R.dimen.extra_big_text_size)
else -> resources.getDimension(R.dimen.bigger_text_size)
}
fun Context.updateWidgets() {
val widgetIDs = AppWidgetManager.getInstance(applicationContext).getAppWidgetIds(ComponentName(applicationContext, MyWidgetProvider::class.java))
if (widgetIDs.isNotEmpty()) {
Intent(applicationContext, MyWidgetProvider::class.java).apply {
action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIDs)
sendBroadcast(this)
}
}
}

View File

@ -0,0 +1,6 @@
package com.simplemobiletools.notes.pro.extensions
import androidx.fragment.app.Fragment
import com.simplemobiletools.notes.pro.helpers.Config
val Fragment.config: Config? get() = if (context != null) Config.newInstance(context!!) else null

View File

@ -0,0 +1,283 @@
package com.simplemobiletools.notes.pro.fragments
import android.annotation.SuppressLint
import android.graphics.Typeface
import android.os.Build
import android.os.Bundle
import android.text.Editable
import android.text.Selection
import android.text.TextWatcher
import android.text.style.UnderlineSpan
import android.text.util.Linkify
import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import androidx.annotation.RequiresApi
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.activities.MainActivity
import com.simplemobiletools.notes.pro.extensions.config
import com.simplemobiletools.notes.pro.extensions.dbHelper
import com.simplemobiletools.notes.pro.extensions.getTextSize
import com.simplemobiletools.notes.pro.extensions.updateWidgets
import com.simplemobiletools.notes.pro.helpers.*
import com.simplemobiletools.notes.pro.models.Note
import com.simplemobiletools.notes.pro.models.TextHistory
import com.simplemobiletools.notes.pro.models.TextHistoryItem
import kotlinx.android.synthetic.main.fragment_note.*
import kotlinx.android.synthetic.main.fragment_note.view.*
import kotlinx.android.synthetic.main.note_view_horiz_scrollable.view.*
import java.io.File
// text history handling taken from https://gist.github.com/zeleven/0cfa738c1e8b65b23ff7df1fc30c9f7e
class NoteFragment : androidx.fragment.app.Fragment() {
private val TEXT = "text"
private var textHistory = TextHistory()
private var isUndoOrRedo = false
private var skipTextUpdating = false
private var noteId = 0
lateinit var note: Note
lateinit var view: ViewGroup
private lateinit var db: DBHelper
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
view = inflater.inflate(R.layout.fragment_note, container, false) as ViewGroup
noteId = arguments!!.getInt(NOTE_ID)
db = context!!.dbHelper
note = db.getNoteWithId(noteId) ?: return view
retainInstance = true
val layoutToInflate = if (config!!.enableLineWrap) R.layout.note_view_static else R.layout.note_view_horiz_scrollable
inflater.inflate(layoutToInflate, view.notes_relative_layout, true)
if (config!!.clickableLinks) {
view.notes_view.apply {
linksClickable = true
autoLinkMask = Linkify.WEB_URLS or Linkify.EMAIL_ADDRESSES
movementMethod = MyMovementMethod.getInstance()
}
}
view.notes_horizontal_scrollview?.onGlobalLayout {
view.notes_view.minWidth = view.notes_horizontal_scrollview.width
}
return view
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onResume() {
super.onResume()
val config = config!!
view.notes_view.apply {
typeface = if (config.monospacedFont) Typeface.MONOSPACE else Typeface.DEFAULT
val fileContents = note.getNoteStoredValue()
if (fileContents == null) {
(activity as MainActivity).deleteNote(false)
return
}
setColors(config.textColor, config.primaryColor, config.backgroundColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getTextSize())
gravity = getTextGravity()
if (text.toString() != fileContents) {
if (!skipTextUpdating) {
setText(fileContents)
}
skipTextUpdating = false
setSelection(if (config.placeCursorToEnd) text.length else 0)
}
if (config.showKeyboard) {
requestFocus()
}
imeOptions = if (config.useIncognitoMode) {
imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
} else {
imeOptions.removeBit(EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING)
}
}
if (config.showWordCount) {
view.notes_counter.beVisible()
view.notes_counter.setTextColor(config.textColor)
setWordCounter(view.notes_view.text.toString())
} else {
view.notes_counter.beGone()
}
view.notes_view.addTextChangedListener(textWatcher)
}
override fun onPause() {
super.onPause()
if (config!!.autosaveNotes) {
saveText(false)
}
view.notes_view.removeTextChangedListener(textWatcher)
}
override fun setMenuVisibility(menuVisible: Boolean) {
super.setMenuVisibility(menuVisible)
if (!menuVisible && noteId != 0 && config?.autosaveNotes == true) {
saveText(false)
}
if (menuVisible && noteId != 0) {
val currentText = getCurrentNoteViewText()
if (currentText != null) {
(activity as MainActivity).currentNoteTextChanged(currentText, isUndoAvailable(), isRedoAvailable())
}
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(TEXT, getCurrentNoteViewText())
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
if (savedInstanceState != null) {
skipTextUpdating = true
val newText = savedInstanceState.getString(TEXT) ?: ""
view.notes_view.setText(newText)
}
}
fun getNotesView() = view.notes_view
fun saveText(force: Boolean) {
if (note.path.isNotEmpty() && !File(note.path).exists()) {
return
}
if (context == null || activity == null) {
return
}
val newText = getCurrentNoteViewText()
val oldText = note.getNoteStoredValue()
if (newText != null && (newText != oldText || force)) {
note.value = newText
saveNoteValue(note)
context!!.updateWidgets()
}
}
fun hasUnsavedChanges() = getCurrentNoteViewText() != note.getNoteStoredValue()
fun focusEditText() {
view.notes_view.requestFocus()
}
private fun saveNoteValue(note: Note) {
if (note.path.isEmpty()) {
db.updateNoteValue(note)
(activity as MainActivity).noteSavedSuccessfully(note.title)
} else {
val currentText = getCurrentNoteViewText()
if (currentText != null) {
(activity as MainActivity).exportNoteValueToFile(note.path, currentText, true)
}
}
}
fun getCurrentNoteViewText() = view.notes_view?.text?.toString()
@SuppressLint("RtlHardcoded")
private fun getTextGravity() = when (config!!.gravity) {
GRAVITY_CENTER -> Gravity.CENTER_HORIZONTAL
GRAVITY_RIGHT -> Gravity.RIGHT
else -> Gravity.LEFT
}
private fun setWordCounter(text: String) {
val words = text.replace("\n", " ").split(" ")
notes_counter.text = words.count { it.isNotEmpty() }.toString()
}
fun undo() {
val edit = textHistory.getPrevious() ?: return
val text = view.notes_view.editableText
val start = edit.start
val end = start + if (edit.after != null) edit.after.length else 0
isUndoOrRedo = true
try {
text.replace(start, end, edit.before)
} catch (e: Exception) {
activity?.showErrorToast(e)
return
}
isUndoOrRedo = false
for (span in text.getSpans(0, text.length, UnderlineSpan::class.java)) {
text.removeSpan(span)
}
Selection.setSelection(text, if (edit.before == null) {
start
} else {
start + edit.before.length
})
}
fun redo() {
val edit = textHistory.getNext() ?: return
val text = view.notes_view.editableText
val start = edit.start
val end = start + if (edit.before != null) edit.before.length else 0
isUndoOrRedo = true
text.replace(start, end, edit.after)
isUndoOrRedo = false
for (o in text.getSpans(0, text.length, UnderlineSpan::class.java)) {
text.removeSpan(o)
}
Selection.setSelection(text, if (edit.after == null) {
start
} else {
start + edit.after.length
})
}
fun isUndoAvailable() = textHistory.position > 0
fun isRedoAvailable() = textHistory.position < textHistory.history.size
private var textWatcher: TextWatcher = object : TextWatcher {
private var beforeChange: CharSequence? = null
private var afterChange: CharSequence? = null
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
if (!isUndoOrRedo) {
beforeChange = s.subSequence(start, start + count)
}
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (!isUndoOrRedo) {
afterChange = s.subSequence(start, start + count)
textHistory.add(TextHistoryItem(start, beforeChange!!, afterChange!!))
}
}
override fun afterTextChanged(editable: Editable) {
val text = editable.toString()
setWordCounter(text)
(activity as MainActivity).currentNoteTextChanged(text, isUndoAvailable(), isRedoAvailable())
}
}
}

View File

@ -0,0 +1,75 @@
package com.simplemobiletools.notes.pro.helpers
import android.content.Context
import android.os.Environment
import com.simplemobiletools.commons.helpers.BaseConfig
class Config(context: Context) : BaseConfig(context) {
companion object {
fun newInstance(context: Context) = Config(context)
}
var autosaveNotes: Boolean
get() = prefs.getBoolean(AUTOSAVE_NOTES, true)
set(autosaveNotes) = prefs.edit().putBoolean(AUTOSAVE_NOTES, autosaveNotes).apply()
var displaySuccess: Boolean
get() = prefs.getBoolean(DISPLAY_SUCCESS, false)
set(displaySuccess) = prefs.edit().putBoolean(DISPLAY_SUCCESS, displaySuccess).apply()
var clickableLinks: Boolean
get() = prefs.getBoolean(CLICKABLE_LINKS, false)
set(clickableLinks) = prefs.edit().putBoolean(CLICKABLE_LINKS, clickableLinks).apply()
var monospacedFont: Boolean
get() = prefs.getBoolean(MONOSPACED_FONT, false)
set(monospacedFont) = prefs.edit().putBoolean(MONOSPACED_FONT, monospacedFont).apply()
var showKeyboard: Boolean
get() = prefs.getBoolean(SHOW_KEYBOARD, true)
set(showKeyboard) = prefs.edit().putBoolean(SHOW_KEYBOARD, showKeyboard).apply()
var showNotePicker: Boolean
get() = prefs.getBoolean(SHOW_NOTE_PICKER, false)
set(showNotePicker) = prefs.edit().putBoolean(SHOW_NOTE_PICKER, showNotePicker).apply()
var showWordCount: Boolean
get() = prefs.getBoolean(SHOW_WORD_COUNT, false)
set(showWordCount) = prefs.edit().putBoolean(SHOW_WORD_COUNT, showWordCount).apply()
var fontSize: Int
get() = prefs.getInt(FONT_SIZE, FONT_SIZE_MEDIUM)
set(size) = prefs.edit().putInt(FONT_SIZE, size).apply()
var gravity: Int
get() = prefs.getInt(GRAVITY, GRAVITY_LEFT)
set(size) = prefs.edit().putInt(GRAVITY, size).apply()
var currentNoteId: Int
get() = prefs.getInt(CURRENT_NOTE_ID, 1)
set(id) = prefs.edit().putInt(CURRENT_NOTE_ID, id).apply()
var widgetNoteId: Int
get() = prefs.getInt(WIDGET_NOTE_ID, 1)
set(id) = prefs.edit().putInt(WIDGET_NOTE_ID, id).apply()
var placeCursorToEnd: Boolean
get() = prefs.getBoolean(CURSOR_PLACEMENT, true)
set(placement) = prefs.edit().putBoolean(CURSOR_PLACEMENT, placement).apply()
var enableLineWrap: Boolean
get() = prefs.getBoolean(ENABLE_LINE_WRAP, true)
set(enableLineWrap) = prefs.edit().putBoolean(ENABLE_LINE_WRAP, enableLineWrap).apply()
var lastUsedExtension: String
get() = prefs.getString(LAST_USED_EXTENSION, "txt")
set(lastUsedExtension) = prefs.edit().putString(LAST_USED_EXTENSION, lastUsedExtension).apply()
var lastUsedSavePath: String
get() = prefs.getString(LAST_USED_SAVE_PATH, Environment.getExternalStorageDirectory().toString())
set(lastUsedSavePath) = prefs.edit().putString(LAST_USED_SAVE_PATH, lastUsedSavePath).apply()
var useIncognitoMode: Boolean
get() = prefs.getBoolean(USE_INCOGNITO_MODE, false)
set(useIncognitoMode) = prefs.edit().putBoolean(USE_INCOGNITO_MODE, useIncognitoMode).apply()
}

View File

@ -0,0 +1,40 @@
package com.simplemobiletools.notes.pro.helpers
const val NOTE_ID = "note_id"
const val OPEN_NOTE_ID = "open_note_id"
// shared preferences
const val CURRENT_NOTE_ID = "current_note_id"
const val AUTOSAVE_NOTES = "autosave_notes"
const val DISPLAY_SUCCESS = "display_success"
const val CLICKABLE_LINKS = "clickable_links"
const val WIDGET_NOTE_ID = "widget_note_id"
const val MONOSPACED_FONT = "monospaced_font"
const val SHOW_KEYBOARD = "show_keyboard"
const val SHOW_NOTE_PICKER = "show_note_picker"
const val SHOW_WORD_COUNT = "show_word_count"
const val FONT_SIZE = "font_size"
const val GRAVITY = "gravity"
const val CURSOR_PLACEMENT = "cursor_placement"
const val LAST_USED_EXTENSION = "last_used_extension"
const val LAST_USED_SAVE_PATH = "last_used_save_path"
const val ENABLE_LINE_WRAP = "enable_line_wrap"
const val USE_INCOGNITO_MODE = "use_incognito_mode"
// gravity
const val GRAVITY_LEFT = 0
const val GRAVITY_CENTER = 1
const val GRAVITY_RIGHT = 2
// font sizes
const val FONT_SIZE_SMALL = 0
const val FONT_SIZE_MEDIUM = 1
const val FONT_SIZE_LARGE = 2
const val FONT_SIZE_EXTRA_LARGE = 3
// note types
const val TYPE_NOTE = 0
const val TYPE_CHECKLIST = 1
// mime types
const val MIME_TEXT_PLAIN = "text/plain"

View File

@ -0,0 +1,244 @@
package com.simplemobiletools.notes.pro.helpers
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteDatabase.CONFLICT_IGNORE
import android.database.sqlite.SQLiteOpenHelper
import com.simplemobiletools.commons.extensions.getIntValue
import com.simplemobiletools.commons.extensions.getStringValue
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.extensions.config
import com.simplemobiletools.notes.pro.models.Note
import com.simplemobiletools.notes.pro.models.Widget
import java.io.File
import java.util.*
class DBHelper private constructor(private val mContext: Context) : SQLiteOpenHelper(mContext, DB_NAME, null, DB_VERSION) {
private val mDb = writableDatabase
companion object {
private const val DB_NAME = "notes.db"
private const val DB_VERSION = 4
private const val NOTES_TABLE_NAME = "notes"
private const val WIDGETS_TABLE_NAME = "widgets"
private const val COL_ID = "id"
private const val COL_TITLE = "title"
private const val COL_VALUE = "value"
private const val COL_TYPE = "type"
private const val COL_PATH = "path"
private const val COL_WIDGET_ID = "widget_id"
private const val COL_NOTE_ID = "note_id"
fun newInstance(context: Context) = DBHelper(context)
}
override fun onCreate(db: SQLiteDatabase) {
db.execSQL("CREATE TABLE $NOTES_TABLE_NAME ($COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_TITLE TEXT UNIQUE, $COL_VALUE TEXT, $COL_TYPE INTEGER DEFAULT 0, $COL_PATH TEXT)")
db.execSQL("CREATE TABLE $WIDGETS_TABLE_NAME ($COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_WIDGET_ID INTEGER DEFAULT 0, $COL_NOTE_ID INTEGER DEFAULT 0)")
insertFirstNote(db)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
if (oldVersion < 2) {
db.execSQL("ALTER TABLE $NOTES_TABLE_NAME ADD COLUMN $COL_TYPE INTEGER DEFAULT 0")
}
if (oldVersion < 3) {
db.execSQL("ALTER TABLE $NOTES_TABLE_NAME ADD COLUMN $COL_PATH TEXT DEFAULT ''")
}
if (oldVersion < 4) {
db.execSQL("CREATE TABLE $WIDGETS_TABLE_NAME ($COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_WIDGET_ID INTEGER DEFAULT 0, $COL_NOTE_ID INTEGER DEFAULT 0)")
insertFirstWidget(db)
}
}
private fun insertFirstNote(db: SQLiteDatabase) {
val generalNote = mContext.resources.getString(R.string.general_note)
val note = Note(1, generalNote, "", TYPE_NOTE)
insertNote(note, db)
}
// if a user has exactly 1 widget active, prefill it. Can happen only at upgrading from older app versions
private fun insertFirstWidget(db: SQLiteDatabase) {
val widgetIDs = AppWidgetManager.getInstance(mContext).getAppWidgetIds(ComponentName(mContext, MyWidgetProvider::class.java))
if (widgetIDs.size == 1) {
val widget = Widget(widgetIDs.first(), mContext.config.widgetNoteId)
insertWidget(widget, db)
}
}
private fun insertNote(note: Note, db: SQLiteDatabase) {
val values = fillNoteContentValues(note)
db.insert(NOTES_TABLE_NAME, null, values)
}
private fun insertWidget(widget: Widget, db: SQLiteDatabase) {
val values = fillWidgetContentValues(widget)
db.insert(WIDGETS_TABLE_NAME, null, values)
}
fun insertNote(note: Note): Int {
val values = fillNoteContentValues(note)
return mDb.insertWithOnConflict(NOTES_TABLE_NAME, null, values, CONFLICT_IGNORE).toInt()
}
fun insertWidget(widget: Widget): Int {
val values = fillWidgetContentValues(widget)
return mDb.insertWithOnConflict(WIDGETS_TABLE_NAME, null, values, CONFLICT_IGNORE).toInt()
}
private fun fillNoteContentValues(note: Note): ContentValues {
return ContentValues().apply {
put(COL_TITLE, note.title)
put(COL_VALUE, note.value)
put(COL_PATH, note.path)
put(COL_TYPE, 0)
}
}
private fun fillWidgetContentValues(widget: Widget): ContentValues {
return ContentValues().apply {
put(COL_WIDGET_ID, widget.widgetId)
put(COL_NOTE_ID, widget.noteId)
}
}
fun deleteNote(id: Int) {
mDb.delete(NOTES_TABLE_NAME, "$COL_ID = $id", null)
mDb.delete(WIDGETS_TABLE_NAME, "$COL_NOTE_ID = $id", null)
}
fun doesNoteTitleExist(title: String): Boolean {
val cols = arrayOf(COL_ID)
val selection = "$COL_TITLE = ? COLLATE NOCASE"
val selectionArgs = arrayOf(title)
var cursor: Cursor? = null
try {
cursor = mDb.query(NOTES_TABLE_NAME, cols, selection, selectionArgs, null, null, null)
return cursor.count == 1
} finally {
cursor?.close()
}
}
fun getNotes(): ArrayList<Note> {
val notes = ArrayList<Note>()
val cols = arrayOf(COL_ID, COL_TITLE, COL_VALUE, COL_TYPE, COL_PATH)
var cursor: Cursor? = null
try {
cursor = mDb.query(NOTES_TABLE_NAME, cols, null, null, null, null, "$COL_TITLE COLLATE NOCASE ASC")
if (cursor?.moveToFirst() == true) {
do {
try {
val id = cursor.getIntValue(COL_ID)
val title = cursor.getStringValue(COL_TITLE)
val value = cursor.getStringValue(COL_VALUE)
val type = cursor.getIntValue(COL_TYPE)
val path = cursor.getStringValue(COL_PATH) ?: ""
if (path.isNotEmpty() && !File(path).exists()) {
deleteNote(id)
continue
}
val note = Note(id, title, value, type, path)
notes.add(note)
} catch (e: Exception) {
continue
}
} while (cursor.moveToNext())
}
} finally {
cursor?.close()
}
return notes
}
fun getNoteWithId(id: Int): Note? {
val cols = arrayOf(COL_TITLE, COL_VALUE, COL_TYPE, COL_PATH)
val selection = "$COL_ID = ?"
val selectionArgs = arrayOf(id.toString())
var note: Note? = null
var cursor: Cursor? = null
try {
cursor = mDb.query(NOTES_TABLE_NAME, cols, selection, selectionArgs, null, null, null)
if (cursor?.moveToFirst() == true) {
val title = cursor.getStringValue(COL_TITLE)
val value = cursor.getStringValue(COL_VALUE)
val type = cursor.getIntValue(COL_TYPE)
val path = cursor.getStringValue(COL_PATH) ?: ""
note = Note(id, title, value, type, path)
}
} finally {
cursor?.close()
}
return note
}
fun getNoteId(path: String): Int {
val cols = arrayOf(COL_ID)
val selection = "$COL_PATH = ?"
val selectionArgs = arrayOf(path)
var cursor: Cursor? = null
try {
cursor = mDb.query(NOTES_TABLE_NAME, cols, selection, selectionArgs, null, null, null)
if (cursor?.moveToFirst() == true) {
return cursor.getIntValue(COL_ID)
}
} finally {
cursor?.close()
}
return 0
}
fun updateNoteValue(note: Note) {
val values = ContentValues().apply { put(COL_VALUE, note.value) }
updateNote(note.id, values)
}
fun updateNoteTitle(note: Note) {
val values = ContentValues().apply { put(COL_TITLE, note.title) }
updateNote(note.id, values)
}
fun updateNotePath(note: Note) {
val values = ContentValues().apply { put(COL_PATH, note.path) }
updateNote(note.id, values)
}
private fun updateNote(id: Int, values: ContentValues) {
val selection = "$COL_ID = ?"
val selectionArgs = arrayOf(id.toString())
mDb.update(NOTES_TABLE_NAME, values, selection, selectionArgs)
}
fun isValidId(id: Int) = id > 0
fun getWidgets(): ArrayList<Widget> {
val widgets = ArrayList<Widget>()
val cols = arrayOf(COL_WIDGET_ID, COL_NOTE_ID)
var cursor: Cursor? = null
try {
cursor = mDb.query(WIDGETS_TABLE_NAME, cols, null, null, null, null, null)
if (cursor?.moveToFirst() == true) {
do {
val widgetId = cursor.getIntValue(COL_WIDGET_ID)
val noteId = cursor.getIntValue(COL_NOTE_ID)
val widget = Widget(widgetId, noteId)
widgets.add(widget)
} while (cursor.moveToNext())
}
} finally {
cursor?.close()
}
return widgets
}
}

View File

@ -0,0 +1,54 @@
package com.simplemobiletools.notes.pro.helpers
import android.text.Selection
import android.text.Spannable
import android.text.method.ArrowKeyMovementMethod
import android.text.style.ClickableSpan
import android.view.MotionEvent
import android.widget.TextView
class MyMovementMethod : ArrowKeyMovementMethod() {
companion object {
private var sInstance: MyMovementMethod? = null
fun getInstance(): MyMovementMethod {
if (sInstance == null) {
sInstance = MyMovementMethod()
}
return sInstance!!
}
}
override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean {
val action = event.action
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
var x = event.x.toInt()
var y = event.y.toInt()
x -= widget.totalPaddingLeft
y -= widget.totalPaddingTop
x += widget.scrollX
y += widget.scrollY
val layout = widget.layout
val line = layout.getLineForVertical(y)
val off = layout.getOffsetForHorizontal(line, x.toFloat())
val links = buffer.getSpans(off, off, ClickableSpan::class.java)
if (links.isNotEmpty()) {
if (action == MotionEvent.ACTION_UP) {
links[0].onClick(widget)
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(links[0]),
buffer.getSpanEnd(links[0]))
}
return true
}
}
return super.onTouchEvent(widget, buffer, event)
}
}

View File

@ -0,0 +1,50 @@
package com.simplemobiletools.notes.pro.helpers
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.widget.RemoteViews
import com.simplemobiletools.commons.extensions.getLaunchIntent
import com.simplemobiletools.commons.extensions.setBackgroundColor
import com.simplemobiletools.notes.pro.R
import com.simplemobiletools.notes.pro.activities.SplashActivity
import com.simplemobiletools.notes.pro.extensions.config
import com.simplemobiletools.notes.pro.extensions.dbHelper
import com.simplemobiletools.notes.pro.models.Widget
import com.simplemobiletools.notes.pro.services.WidgetService
class MyWidgetProvider : AppWidgetProvider() {
private fun setupAppOpenIntent(context: Context, views: RemoteViews, id: Int, widget: Widget) {
val intent = context.getLaunchIntent() ?: Intent(context, SplashActivity::class.java)
intent.putExtra(OPEN_NOTE_ID, widget.noteId)
val pendingIntent = PendingIntent.getActivity(context, widget.widgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
views.setOnClickPendingIntent(id, pendingIntent)
}
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
val widgets = context.dbHelper.getWidgets()
widgets.forEach {
val views = RemoteViews(context.packageName, R.layout.widget)
views.setBackgroundColor(R.id.notes_widget_holder, context.config.widgetBgColor)
setupAppOpenIntent(context, views, R.id.notes_widget_holder, it)
Intent(context, WidgetService::class.java).apply {
putExtra(NOTE_ID, it.noteId)
data = Uri.parse(this.toUri(Intent.URI_INTENT_SCHEME))
views.setRemoteAdapter(R.id.notes_widget_listview, this)
}
val startActivityIntent = context.getLaunchIntent() ?: Intent(context, SplashActivity::class.java)
startActivityIntent.putExtra(OPEN_NOTE_ID, it.noteId)
val startActivityPendingIntent = PendingIntent.getActivity(context, it.widgetId, startActivityIntent, PendingIntent.FLAG_UPDATE_CURRENT)
views.setPendingIntentTemplate(R.id.notes_widget_listview, startActivityPendingIntent)
appWidgetManager.updateAppWidget(it.widgetId, views)
appWidgetManager.notifyAppWidgetViewDataChanged(it.widgetId, R.id.notes_widget_listview)
}
}
}

View File

@ -0,0 +1,18 @@
package com.simplemobiletools.notes.pro.models
import java.io.File
import java.io.FileNotFoundException
data class Note(var id: Int, var title: String, var value: String, val type: Int, var path: String = "") {
fun getNoteStoredValue(): String? {
return if (path.isNotEmpty()) {
return try {
File(path).readText()
} catch (e: FileNotFoundException) {
null
}
} else {
value
}
}
}

View File

@ -0,0 +1,35 @@
package com.simplemobiletools.notes.pro.models
import java.util.*
class TextHistory {
var position = 0
val history = LinkedList<TextHistoryItem>()
fun getPrevious(): TextHistoryItem? {
if (position == 0) {
return null
}
position--
return history[position]
}
fun getNext(): TextHistoryItem? {
if (position >= history.size) {
return null
}
val item = history[position]
position++
return item
}
fun add(item: TextHistoryItem) {
while (history.size > position) {
history.removeLast()
}
history.add(item)
position++
}
}

View File

@ -0,0 +1,3 @@
package com.simplemobiletools.notes.pro.models
data class TextHistoryItem(val start: Int, val before: CharSequence?, val after: CharSequence?)

View File

@ -0,0 +1,3 @@
package com.simplemobiletools.notes.pro.models
data class Widget(var widgetId: Int, var noteId: Int)

View File

@ -0,0 +1,9 @@
package com.simplemobiletools.notes.pro.services
import android.content.Intent
import android.widget.RemoteViewsService
import com.simplemobiletools.notes.pro.adapters.WidgetAdapter
class WidgetService : RemoteViewsService() {
override fun onGetViewFactory(intent: Intent) = WidgetAdapter(applicationContext, intent)
}

View File

@ -0,0 +1,17 @@
package com.simplemobiletools.notes.pro.views
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.HorizontalScrollView
class MyHorizontalScrollView : HorizontalScrollView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
override fun onTouchEvent(ev: MotionEvent): Boolean {
parent.requestDisallowInterceptTouchEvent(false)
return super.onTouchEvent(ev)
}
}