Skip to content

Commit

Permalink
Add remaining "essential" external actions
Browse files Browse the repository at this point in the history
  • Loading branch information
NatKarmios committed Aug 18, 2021
1 parent 1314430 commit 8917bfc
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 89 deletions.
16 changes: 9 additions & 7 deletions app/src/main/java/com/orgzly/android/data/DataRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ class DataRepository @Inject constructor(

} else {
db.runInTransaction(Callable {
moveSubtrees(noteIds, Place.UNDER, target.noteId)
moveSubtrees(noteIds, target.place, target.noteId)
})
}
}
Expand Down Expand Up @@ -1239,12 +1239,14 @@ class DataRepository @Inject constructor(
}

fun getNoteAtPath(bookName: String, path: String) =
getNotes(bookName)
.filter { ("/$path").endsWith("/" + it.note.title) }
.firstOrNull { view ->
getNoteAndAncestors(view.note.id)
.joinToString("/") { it.title } == path
}
if (path.split("/").any { it.isNotEmpty() })
getNotes(bookName)
.filter { ("/$path").endsWith("/" + it.note.title) }
.firstOrNull { view ->
getNoteAndAncestors(view.note.id)
.joinToString("/") { it.title } == path
}
else null

fun getNotesAndSubtrees(ids: Set<Long>): List<Note> {
return db.note().getNotesForSubtrees(ids)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class ExternalAccessReceiver : BroadcastReceiver() {
GetOrgInfo(),
RunSearch(),
EditNotes(),
EditSavedSearches(),
ManageWidgets()
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,85 +1,63 @@
package com.orgzly.android.external.actionhandlers

import android.content.Context
import android.content.Intent
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonParseException
import com.google.gson.JsonParser
import com.orgzly.android.external.types.Response
import com.orgzly.android.ui.NotePlace
import com.orgzly.android.ui.Place
import com.orgzly.android.ui.note.NotePayload
import com.orgzly.org.OrgProperties

class EditNotes : ExternalAccessActionHandler() {
override val actions = listOf(
action(::addNote, "ADD_NOTE", "ADD_NOTES"),
action(::deleteNote, "DELETE_NOTE", "DELETE_NOTES")
action(::addNote, "ADD_NOTE", "ADD_NOTES"),
action(::editNote, "EDIT_NOTE"),
action(::refileNote, "REFILE_NOTE", "REFILE_NOTES"),
action(::moveNote, "MOVE_NOTE", "MOVE_NOTES"),
action(::deleteNote, "DELETE_NOTE", "DELETE_NOTES")
)

fun addNote(intent: Intent): Response {
val book = getBook(intent) ?: return Response(false, "Couldn't find specified book")
val newNote = notePayloadFromJson(intent.getStringExtra("PAYLOAD") ?: "")
private fun addNote(intent: Intent): Response {
val place = intent.getNotePlace()
?: return Response(false, "Could not find parent note")
val newNote = intent.getNotePayload()
?: return Response(false, "Invalid payload")
val path = intent.getStringExtra("PATH") ?: ""

val place = if (path.split("/").any { it.isNotEmpty() }) {
dataRepository.getNoteAtPath(book.name, path)?.let {
NotePlace(book.id, it.note.id, Place.UNDER)
}
} else null
place ?: return Response(false, "Couldn't find parent note at path")
dataRepository.createNote(newNote, place)
return Response(true, null)
val note = dataRepository.createNote(newNote, place)
return Response(true, "${note.id}")
}

// <editor-fold desc="Helpers">

private fun JsonObject.getString(name: String) = this[name]?.let {
if (it.isJsonPrimitive && it.asJsonPrimitive.isString)
it.asJsonPrimitive.asString
else null
private fun editNote(intent: Intent): Response {
val noteView = intent.getNote()
?: return Response(false, "Couldn't find note")
val newNote = intent.getNotePayload(title=noteView.note.title)
?: return Response(false, "Invalid payload")
dataRepository.updateNote(noteView.note.id, newNote)
return Response()
}

private val JsonElement.asMap: Map<String, String>?
get() = if (this.isJsonObject) {
this.asJsonObject
.entrySet()
.map {
if (it.value.isJsonPrimitive)
it.key to it.value.asJsonPrimitive.asString
else return null
}
.toMap()
} else null
private fun refileNote(intent: Intent): Response {
val notes = intent.getNoteIds()
if (notes.isEmpty())
return Response(false, "No notes specified")
val place = intent.getNotePlace()
?: return Response(false, "Couldn't find note")
dataRepository.refileNotes(notes, place)
return Response()
}

private fun notePayloadFromJson(rawJson: String): NotePayload? {
val json = try {
JsonParser.parseString(rawJson)
.let { if (it.isJsonObject) it.asJsonObject else null }
} catch (e: JsonParseException) {
null
}
return try {
json!!
NotePayload(
json.getString("title")!!,
json.getString("content"),
json.getString("state"),
json.getString("priority"),
json.getString("scheduled"),
json.getString("deadline"),
json.getString("closed"),
(json.getString("tags") ?: "")
.split(" +".toRegex())
.filter { it.isNotEmpty() },
OrgProperties().apply {
json["properties"]?.asMap?.forEach { (k, v) -> this[k] = v }
}
)
} catch (e: NullPointerException) { null }
private fun moveNote(intent: Intent): Response {
val notes = intent.getNoteIds()
if (notes.isEmpty()) return Response(false, "No notes specified")
with(dataRepository) { when (intent.getStringExtra("DIRECTION")) {
"UP" -> intent.getBook()?.id?.let { moveNote(it, notes, -1) }
"DOWN" -> intent.getBook()?.id?.let { moveNote(it, notes, 1) }
"LEFT" -> promoteNotes(notes)
"RIGHT" -> demoteNotes(notes)
else -> return Response(false, "Invalid direction")
} }
return Response()
}

// </editor-fold>
private fun deleteNote(intent: Intent): Response {
val book = intent.getBook() ?: return Response(false, "Couldn't find specified book")
val notes = intent.getNoteIds()
if (notes.isEmpty()) return Response(false, "No notes specified")
dataRepository.deleteNotes(book.id, notes)
return Response()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.orgzly.android.external.actionhandlers

import android.content.Intent
import com.orgzly.android.db.entity.SavedSearch
import com.orgzly.android.external.types.Response

class EditSavedSearches : ExternalAccessActionHandler() {
override val actions = listOf(
action(::addSavedSearch, "ADD_SAVED_SEARCH"),
action(::editSavedSearch, "EDIT_SAVED_SEARCH"),
action(::moveSavedSearch, "MOVE_SAVED_SEARCH"),
action(::deleteSavedSearch, "DELETE_SAVED_SEARCH"),
)

private fun addSavedSearch(intent: Intent) =
intent.getNewSavedSearch()?.let {
val id = dataRepository.createSavedSearch(it)
Response(true, "$id")
} ?: Response(false, "Invalid saved search details")

private fun editSavedSearch(intent: Intent) = intent.getSavedSearch()?.let { savedSearch ->
intent.getNewSavedSearch(allowBlank = true)?.let { newSavedSearch ->
dataRepository.updateSavedSearch(SavedSearch(
savedSearch.id,
(if (newSavedSearch.name.isBlank()) savedSearch.name
else newSavedSearch.name),
(if (newSavedSearch.query.isBlank()) savedSearch.query
else newSavedSearch.query),
savedSearch.position
))
return Response()
} ?: Response(false, "Invalid saved search details")
} ?: Response(false, "Couldn't find saved search")

private fun moveSavedSearch(intent: Intent) = intent.getSavedSearch()?.let { savedSearch ->
when (intent.getStringExtra("DIRECTION")) {
"UP" -> dataRepository.moveSavedSearchUp(savedSearch.id)
"DOWN" -> dataRepository.moveSavedSearchDown(savedSearch.id)
else -> return Response(false, "Invalid direction")
}
return Response()
} ?: Response(false, "Couldn't find saved search")

private fun deleteSavedSearch(intent: Intent) = intent.getSavedSearch()?.let { savedSearch ->
dataRepository.deleteSavedSearches(setOf(savedSearch.id))
return Response()
} ?: Response(false, "Couldn't find saved search")
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@ package com.orgzly.android.external.actionhandlers

import android.content.Context
import android.content.Intent
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonParseException
import com.google.gson.JsonParser
import com.orgzly.android.App
import com.orgzly.android.data.DataRepository
import com.orgzly.android.db.entity.Book
import com.orgzly.android.db.entity.SavedSearch
import com.orgzly.android.external.types.Response
import com.orgzly.android.ui.NotePlace
import com.orgzly.android.ui.Place
import com.orgzly.android.ui.note.NotePayload
import com.orgzly.org.OrgProperties
import javax.inject.Inject

abstract class ExternalAccessActionHandler {
Expand All @@ -21,9 +30,100 @@ abstract class ExternalAccessActionHandler {
actions.flatten().toMap().mapKeys { (key, _) -> "com.orgzly.android.$key" }
}

fun getBook(intent: Intent) =
dataRepository.getBook(intent.getLongExtra("BOOK_ID", -1))
?: dataRepository.getBook(intent.getStringExtra("BOOK_NAME") ?: "")
fun Intent.getNotePayload(title: String? = null): NotePayload? {
val rawJson = getStringExtra("NOTE_PAYLOAD")
val json = try {
JsonParser.parseString(rawJson)
.let { if (it.isJsonObject) it.asJsonObject else null }
} catch (e: JsonParseException) {
null
}

return try {
json!!
NotePayload(
(json.getString("title") ?: title)!!,
json.getString("content"),
json.getString("state"),
json.getString("priority"),
json.getString("scheduled"),
json.getString("deadline"),
json.getString("closed"),
(json.getString("tags") ?: "")
.split(" +".toRegex())
.filter { it.isNotEmpty() },
OrgProperties().apply {
json["properties"]?.asMap?.forEach { (k, v) -> this[k] = v }
}
)
} catch (e: NullPointerException) { null }
}

fun Intent.getNotePlace(book_: Book? = null) = (book_ ?: getBook(prefix="PARENT_"))
?.let { book -> getNote(book, prefix="PARENT_")?.let { noteView ->
val place = try {
Place.valueOf(getStringExtra("PLACEMENT") ?: "")
} catch (e: IllegalArgumentException) { Place.UNDER }
NotePlace(book.id, noteView.note.id, place)
} }

fun Intent.getNoteIds(allowSingle: Boolean = true): Set<Long> {
val id = if (allowSingle) getLongExtra("NOTE_ID", -1) else null
val ids = getLongArrayExtra("NOTE_IDS")?.toTypedArray() ?: emptyArray()
val book = getBook()?.name
val (path, paths) = if (book != null) {
val path =
if (allowSingle)
getStringExtra("NOTE_PATH")
?.let { dataRepository.getNoteAtPath(book, it)?.note?.id }
else null
val paths = (getStringArrayExtra("NOTE_PATHS") ?: emptyArray())
.mapNotNull { dataRepository.getNoteAtPath(book, it)?.note?.id }
.toTypedArray()
path to paths
} else null to emptyArray()
return listOfNotNull(id, *ids, path, *paths).filter { it >= 0 }.toSet()
}

fun Intent.getNote(book: Book? = null, prefix: String = "") =
dataRepository.getNoteView(getLongExtra("${prefix}NOTE_ID", -1))
?: (book ?: getBook(prefix=prefix))
?.let { dataRepository.getNoteAtPath(it.name,
getStringExtra("${prefix}NOTE_PATH") ?: "") }

fun Intent.getBook(prefix: String = "") =
dataRepository.getBook(getLongExtra("${prefix}BOOK_ID", -1))
?: dataRepository.getBook(getStringExtra("${prefix}BOOK_NAME") ?: "")

fun Intent.getSavedSearch() =
dataRepository.getSavedSearch(getLongExtra("SAVED_SEARCH_ID", -1))
?: dataRepository.getSavedSearches()
.find { it.name == getStringExtra("SAVED_SEARCH_NAME") }

fun Intent.getNewSavedSearch(allowBlank: Boolean = false): SavedSearch? {
val name = getStringExtra("SAVED_SEARCH_NEW_NAME")
val query = getStringExtra("SAVED_SEARCH_NEW_QUERY")
if (!allowBlank && (name.isNullOrBlank() || query.isNullOrBlank())) return null
return SavedSearch(0, name ?: "", query ?: "", 0)
}

private fun JsonObject.getString(name: String) = this[name]?.let {
if (it.isJsonPrimitive && it.asJsonPrimitive.isString)
it.asJsonPrimitive.asString
else null
}

private val JsonElement.asMap: Map<String, String>?
get() = if (this.isJsonObject) {
this.asJsonObject
.entrySet()
.map {
if (it.value.isJsonPrimitive)
it.key to it.value.asJsonPrimitive.asString
else return null
}
.toMap()
} else null

fun action(f: (Intent, Context) -> Response, vararg names: String) = names.map { it to f }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.orgzly.android.external.actionhandlers

import android.content.Context
import android.content.Intent
import com.orgzly.android.external.types.*

Expand All @@ -23,12 +22,8 @@ class GetOrgInfo : ExternalAccessActionHandler() {
.map(SavedSearch::from).toTypedArray()
)

private fun getNote(intent: Intent): Response {
val book = getBook(intent) ?: return Response(false, "Couldn't find specified book")
val path = intent.getStringExtra("PATH")
?: return Response(false, "Invalid arguments!")
return dataRepository.getNoteAtPath(book.name, path)
private fun getNote(intent: Intent) =
intent.getNote()
?.let { Response(true, Note.from(it)) }
?: Response(false, "Couldn't find note at specified path!")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ class ManageWidgets : ExternalAccessActionHandler() {
private fun setWidget(intent: Intent, context: Context): Response {
val widgetId = intent.getIntExtra("WIDGET_ID", -1)
if (widgetId < 0) return Response(false, "invalid widget ID")
val savedSearchId = intent.getLongExtra("SAVED_SEARCH_ID", -1)
if (savedSearchId < 0) return Response(false, "invalid saved search ID")
val savedSearch = intent.getSavedSearch()
?: return Response(false, "invalid saved search ID")

context.sendBroadcast(Intent(context, ListWidgetProvider::class.java).apply {
action = AppIntent.ACTION_SET_LIST_WIDGET_SELECTION
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
putExtra(AppIntent.EXTRA_SAVED_SEARCH_ID, savedSearchId)
putExtra(AppIntent.EXTRA_SAVED_SEARCH_ID, savedSearch.id)
})

return Response(true, null)
return Response()
}
}
Loading

0 comments on commit 8917bfc

Please sign in to comment.