diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f8c11f441..3538868ce 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -221,6 +221,10 @@
+
+
+
diff --git a/app/src/main/java/com/orgzly/android/data/DataRepository.kt b/app/src/main/java/com/orgzly/android/data/DataRepository.kt
index d27570804..f427efb05 100644
--- a/app/src/main/java/com/orgzly/android/data/DataRepository.kt
+++ b/app/src/main/java/com/orgzly/android/data/DataRepository.kt
@@ -613,7 +613,7 @@ class DataRepository @Inject constructor(
} else {
db.runInTransaction(Callable {
- moveSubtrees(noteIds, Place.UNDER, target.noteId)
+ moveSubtrees(noteIds, target.place, target.noteId)
})
}
}
@@ -1280,6 +1280,22 @@ class DataRepository @Inject constructor(
return db.note().getNoteAndAncestors(noteId)
}
+ fun getNoteAtPath(fullPath: String): NoteView? {
+ val (bookName, path) = run {
+ val pathParts = fullPath.split("/")
+ if (pathParts.isEmpty()) return null
+ pathParts[0] to pathParts.drop(1).joinToString("/")
+ }
+ return 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): List {
return db.note().getNotesForSubtrees(ids)
}
diff --git a/app/src/main/java/com/orgzly/android/di/AppComponent.kt b/app/src/main/java/com/orgzly/android/di/AppComponent.kt
index f5b3efb86..d89a48fa4 100644
--- a/app/src/main/java/com/orgzly/android/di/AppComponent.kt
+++ b/app/src/main/java/com/orgzly/android/di/AppComponent.kt
@@ -7,6 +7,7 @@ import com.orgzly.android.TimeChangeBroadcastReceiver
import com.orgzly.android.di.module.ApplicationModule
import com.orgzly.android.di.module.DataModule
import com.orgzly.android.di.module.DatabaseModule
+import com.orgzly.android.external.actionhandlers.ExternalAccessActionHandler
import com.orgzly.android.reminders.NoteReminders
import com.orgzly.android.reminders.RemindersBroadcastReceiver
import com.orgzly.android.sync.SyncWorker
@@ -86,4 +87,5 @@ interface AppComponent {
fun inject(arg: RemindersBroadcastReceiver)
fun inject(arg: NotificationBroadcastReceiver)
fun inject(arg: SharingShortcutsManager)
+ fun inject(arg: ExternalAccessActionHandler)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/orgzly/android/external/ExternalAccessReceiver.kt b/app/src/main/java/com/orgzly/android/external/ExternalAccessReceiver.kt
new file mode 100644
index 000000000..97b8ec116
--- /dev/null
+++ b/app/src/main/java/com/orgzly/android/external/ExternalAccessReceiver.kt
@@ -0,0 +1,27 @@
+package com.orgzly.android.external
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import com.google.gson.GsonBuilder
+import com.orgzly.android.external.actionhandlers.*
+import com.orgzly.android.external.types.Response
+
+class ExternalAccessReceiver : BroadcastReceiver() {
+ val actionHandlers = listOf(
+ GetOrgInfo(),
+ RunSearch(),
+ EditNotes(),
+ EditSavedSearches(),
+ ManageWidgets()
+ )
+
+ override fun onReceive(context: Context?, intent: Intent?) {
+ val response = actionHandlers.asSequence()
+ .mapNotNull { it.handle(intent!!, context!!) }
+ .firstOrNull()
+ ?: Response(false, "Invalid action")
+ val gson = GsonBuilder().serializeNulls().create()
+ resultData = gson.toJson(response)
+ }
+}
diff --git a/app/src/main/java/com/orgzly/android/external/actionhandlers/EditNotes.kt b/app/src/main/java/com/orgzly/android/external/actionhandlers/EditNotes.kt
new file mode 100644
index 000000000..94d1cd749
--- /dev/null
+++ b/app/src/main/java/com/orgzly/android/external/actionhandlers/EditNotes.kt
@@ -0,0 +1,53 @@
+package com.orgzly.android.external.actionhandlers
+
+import android.content.Intent
+import com.orgzly.android.external.types.ExternalHandlerFailure
+
+class EditNotes : ExternalAccessActionHandler() {
+ override val actions = listOf(
+ action(::addNote, "ADD_NOTE"),
+ action(::editNote, "EDIT_NOTE"),
+ action(::refileNote, "REFILE_NOTE", "REFILE_NOTES"),
+ action(::moveNote, "MOVE_NOTE", "MOVE_NOTES"),
+ action(::deleteNote, "DELETE_NOTE", "DELETE_NOTES")
+ )
+
+ private fun addNote(intent: Intent): String {
+ val place = intent.getNotePlace()
+ val newNote = intent.getNotePayload()
+ val note = dataRepository.createNote(newNote, place)
+ return "${note.id}"
+ }
+
+ private fun editNote(intent: Intent) {
+ val noteView = intent.getNote()
+ val newNote = intent.getNotePayload(title=noteView.note.title)
+ dataRepository.updateNote(noteView.note.id, newNote)
+ }
+
+ private fun refileNote(intent: Intent) {
+ val notes = intent.getNoteIds()
+ val place = intent.getNotePlace()
+ dataRepository.refileNotes(notes, place)
+ }
+
+ private fun moveNote(intent: Intent) {
+ val notes = intent.getNoteIds()
+ with(dataRepository) { when (intent.getStringExtra("DIRECTION")) {
+ "UP" -> moveNote(intent.getBook().id, notes, -1)
+ "DOWN" -> moveNote(intent.getBook().id, notes, 1)
+ "LEFT" -> promoteNotes(notes)
+ "RIGHT" -> demoteNotes(notes)
+ else -> throw ExternalHandlerFailure("invalid direction")
+ } }
+ }
+
+ private fun deleteNote(intent: Intent) {
+ intent.getNoteIds().groupBy {
+ dataRepository.getNoteView(it)?.bookName
+ ?: throw ExternalHandlerFailure("invalid note id $it")
+ }.forEach { (bookName, notes) ->
+ dataRepository.deleteNotes(dataRepository.getBook(bookName)!!.id, notes.toSet())
+ }
+ }
+}
diff --git a/app/src/main/java/com/orgzly/android/external/actionhandlers/EditSavedSearches.kt b/app/src/main/java/com/orgzly/android/external/actionhandlers/EditSavedSearches.kt
new file mode 100644
index 000000000..4f8ced4ef
--- /dev/null
+++ b/app/src/main/java/com/orgzly/android/external/actionhandlers/EditSavedSearches.kt
@@ -0,0 +1,45 @@
+package com.orgzly.android.external.actionhandlers
+
+import android.content.Intent
+import com.orgzly.android.db.entity.SavedSearch
+import com.orgzly.android.external.types.ExternalHandlerFailure
+
+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): String {
+ val savedSearch = intent.getNewSavedSearch()
+ val id = dataRepository.createSavedSearch(savedSearch)
+ return "$id"
+ }
+
+ private fun editSavedSearch(intent: Intent) {
+ val savedSearch = intent.getSavedSearch()
+ val newSavedSearch = intent.getNewSavedSearch(allowBlank = true)
+ dataRepository.updateSavedSearch(SavedSearch(
+ savedSearch.id,
+ newSavedSearch.name.ifBlank { savedSearch.name },
+ newSavedSearch.query.ifBlank { savedSearch.query },
+ savedSearch.position
+ ))
+ }
+
+ private fun moveSavedSearch(intent: Intent) {
+ val savedSearch = intent.getSavedSearch()
+ when (intent.getStringExtra("DIRECTION")) {
+ "UP" -> dataRepository.moveSavedSearchUp(savedSearch.id)
+ "DOWN" -> dataRepository.moveSavedSearchDown(savedSearch.id)
+ else -> throw ExternalHandlerFailure("invalid direction")
+ }
+ }
+
+ private fun deleteSavedSearch(intent: Intent) {
+ val savedSearch = intent.getSavedSearch()
+ dataRepository.deleteSavedSearches(setOf(savedSearch.id))
+ }
+}
diff --git a/app/src/main/java/com/orgzly/android/external/actionhandlers/ExternalAccessActionHandler.kt b/app/src/main/java/com/orgzly/android/external/actionhandlers/ExternalAccessActionHandler.kt
new file mode 100644
index 000000000..a7cce4f05
--- /dev/null
+++ b/app/src/main/java/com/orgzly/android/external/actionhandlers/ExternalAccessActionHandler.kt
@@ -0,0 +1,45 @@
+package com.orgzly.android.external.actionhandlers
+
+import android.content.Context
+import android.content.Intent
+import com.orgzly.android.App
+import com.orgzly.android.data.DataRepository
+import com.orgzly.android.external.types.Response
+import javax.inject.Inject
+
+abstract class ExternalAccessActionHandler : ExternalIntentParser {
+ @Inject
+ override lateinit var dataRepository: DataRepository
+
+ init {
+ @Suppress("LeakingThis")
+ App.appComponent.inject(this)
+ }
+
+ abstract val actions: List Any>>>
+ private val fullNameActions by lazy {
+ actions.flatten().toMap().mapKeys { (key, _) -> "com.orgzly.android.$key" }
+ }
+
+ fun action(f: (Intent, Context) -> Any, vararg names: String) = names.map { it to f }
+
+ @JvmName("intentAction")
+ fun action(f: (Intent) -> Any, vararg names: String) =
+ action({ i, _ -> f(i) }, *names)
+
+ @JvmName("contextAction")
+ fun action(f: (Context) -> Any, vararg names: String) =
+ action({ _, c -> f(c) }, *names)
+
+ fun action(f: () -> Any, vararg names: String) =
+ action({ _, _ -> f() }, *names)
+
+
+ fun handle(intent: Intent, context: Context) = try {
+ fullNameActions[intent.action!!]
+ ?.let { it(intent, context) }
+ ?.let { Response(true, if (it is Unit) null else it) }
+ } catch (e: Exception) {
+ Response(false, e.message)
+ }
+}
diff --git a/app/src/main/java/com/orgzly/android/external/actionhandlers/ExternalIntentParser.kt b/app/src/main/java/com/orgzly/android/external/actionhandlers/ExternalIntentParser.kt
new file mode 100644
index 000000000..f8478c30b
--- /dev/null
+++ b/app/src/main/java/com/orgzly/android/external/actionhandlers/ExternalIntentParser.kt
@@ -0,0 +1,135 @@
+package com.orgzly.android.external.actionhandlers
+
+import android.content.Intent
+import com.google.gson.JsonElement
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
+import com.orgzly.android.data.DataRepository
+import com.orgzly.android.db.entity.NoteView
+import com.orgzly.android.db.entity.SavedSearch
+import com.orgzly.android.external.types.ExternalHandlerFailure
+import com.orgzly.android.query.user.InternalQueryParser
+import com.orgzly.android.ui.NotePlace
+import com.orgzly.android.ui.Place
+import com.orgzly.android.ui.note.NotePayload
+import com.orgzly.org.OrgProperties
+
+interface ExternalIntentParser {
+ val dataRepository: DataRepository
+
+ 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: Exception) {
+ throw ExternalHandlerFailure("failed to parse json: ${e.message}\n$rawJson")
+ }
+ return NotePayload(
+ json.getString("title") ?: title
+ ?: throw ExternalHandlerFailure("no title supplied!\n$rawJson"),
+ 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 }
+ }
+ )
+ }
+
+ private fun getNoteByQuery(rawQuery: String?): NoteView {
+ if (rawQuery == null)
+ throw ExternalHandlerFailure("couldn't find note")
+ val query = InternalQueryParser().parse(rawQuery)
+ val notes = dataRepository.selectNotesFromQuery(query)
+ if (notes.isEmpty())
+ throw ExternalHandlerFailure("couldn't find note")
+ if (notes.size > 1)
+ throw ExternalHandlerFailure("query \"$rawQuery\" gave multiple results")
+ return notes[0]
+ }
+
+ fun Intent.getNote(prefix: String = "") =
+ dataRepository.getNoteView(getLongExtra("${prefix}NOTE_ID", -1))
+ ?: dataRepository.getNoteAtPath(getStringExtra("${prefix}NOTE_PATH") ?: "")
+ ?: getNoteByQuery(getStringExtra("${prefix}NOTE_QUERY"))
+
+ fun Intent.getNoteAndProps(prefix: String = "") = getNote(prefix).let {
+ it to dataRepository.getNoteProperties(it.note.id)
+ }
+
+ fun Intent.getBook(prefix: String = "") =
+ dataRepository.getBook(getLongExtra("${prefix}BOOK_ID", -1))
+ ?: dataRepository.getBook(getStringExtra("${prefix}BOOK_NAME") ?: "")
+ ?: throw ExternalHandlerFailure("couldn't find book")
+
+ fun Intent.getNotePlace() = try {
+ getNote(prefix="PARENT_").let { noteView ->
+ val place = try {
+ Place.valueOf(getStringExtra("PLACEMENT") ?: "")
+ } catch (e: IllegalArgumentException) { Place.UNDER }
+ dataRepository.getBook(noteView.bookName)?.let { book ->
+ NotePlace(book.id, noteView.note.id, place)
+ }
+ }
+ } catch (e: ExternalHandlerFailure) { null } ?: try {
+ NotePlace(getBook(prefix="PARENT_").id)
+ } catch (e: ExternalHandlerFailure) {
+ throw ExternalHandlerFailure("couldn't find parent note/book")
+ }
+
+ fun Intent.getNoteIds(allowSingle: Boolean = true, allowEmpty: Boolean = false): Set {
+ val id = if (allowSingle) getLongExtra("NOTE_ID", -1) else null
+ val ids = getLongArrayExtra("NOTE_IDS")?.toTypedArray() ?: emptyArray()
+ val path =
+ if (allowSingle)
+ getStringExtra("NOTE_PATH")
+ ?.let { dataRepository.getNoteAtPath(it)?.note?.id }
+ else null
+ val paths = (getStringArrayExtra("NOTE_PATHS") ?: emptyArray())
+ .mapNotNull { dataRepository.getNoteAtPath(it)?.note?.id }
+ .toTypedArray()
+ return listOfNotNull(id, *ids, path, *paths).filter { it >= 0 }.toSet().also {
+ if (it.isEmpty() && !allowEmpty)
+ throw ExternalHandlerFailure("no notes specified")
+ }
+ }
+
+ fun Intent.getSavedSearch() =
+ dataRepository.getSavedSearch(getLongExtra("SAVED_SEARCH_ID", -1))
+ ?: dataRepository.getSavedSearches()
+ .find { it.name == getStringExtra("SAVED_SEARCH_NAME") }
+ ?: throw ExternalHandlerFailure("couldn't find saved search")
+
+ 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()))
+ throw ExternalHandlerFailure("invalid parameters for new saved search")
+ 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?
+ 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
+}
diff --git a/app/src/main/java/com/orgzly/android/external/actionhandlers/GetOrgInfo.kt b/app/src/main/java/com/orgzly/android/external/actionhandlers/GetOrgInfo.kt
new file mode 100644
index 000000000..2f7442f41
--- /dev/null
+++ b/app/src/main/java/com/orgzly/android/external/actionhandlers/GetOrgInfo.kt
@@ -0,0 +1,21 @@
+package com.orgzly.android.external.actionhandlers
+
+import android.content.Intent
+import com.orgzly.android.external.types.*
+
+class GetOrgInfo : ExternalAccessActionHandler() {
+ override val actions = listOf(
+ action(::getBooks, "GET_BOOKS"),
+ action(::getSavedSearches, "GET_SAVED_SEARCHES"),
+ action(::getNote, "GET_NOTE")
+ )
+
+ private fun getBooks() =
+ dataRepository.getBooks().map(Book::from).toTypedArray()
+
+ private fun getSavedSearches() =
+ dataRepository.getSavedSearches().map(SavedSearch::from).toTypedArray()
+
+ private fun getNote(intent: Intent) =
+ Note.from(intent.getNoteAndProps())
+}
diff --git a/app/src/main/java/com/orgzly/android/external/actionhandlers/ManageWidgets.kt b/app/src/main/java/com/orgzly/android/external/actionhandlers/ManageWidgets.kt
new file mode 100644
index 000000000..fb9374bfc
--- /dev/null
+++ b/app/src/main/java/com/orgzly/android/external/actionhandlers/ManageWidgets.kt
@@ -0,0 +1,36 @@
+package com.orgzly.android.external.actionhandlers
+
+import android.appwidget.AppWidgetManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import com.orgzly.android.AppIntent
+import com.orgzly.android.db.entity.SavedSearch
+import com.orgzly.android.external.types.ExternalHandlerFailure
+import com.orgzly.android.widgets.ListWidgetProvider
+
+class ManageWidgets : ExternalAccessActionHandler() {
+ override val actions = listOf(
+ action(::getWidgets, "GET_WIDGETS"),
+ action(::setWidget, "SET_WIDGET")
+ )
+
+ private fun getWidgets(context: Context): Map {
+ val widgetManager = AppWidgetManager.getInstance(context)
+ val componentName = ComponentName(context.packageName, ListWidgetProvider::class.java.name)
+ return widgetManager.getAppWidgetIds(componentName)
+ .associateWith { ListWidgetProvider.getSavedSearch(context, it, dataRepository) }
+ }
+
+ private fun setWidget(intent: Intent, context: Context) {
+ val widgetId = intent.getIntExtra("WIDGET_ID", -1)
+ if (widgetId < 0) throw ExternalHandlerFailure("invalid widget id")
+ val savedSearch = intent.getSavedSearch()
+
+ 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, savedSearch.id)
+ })
+ }
+}
diff --git a/app/src/main/java/com/orgzly/android/external/actionhandlers/RunSearch.kt b/app/src/main/java/com/orgzly/android/external/actionhandlers/RunSearch.kt
new file mode 100644
index 000000000..6542f7cc0
--- /dev/null
+++ b/app/src/main/java/com/orgzly/android/external/actionhandlers/RunSearch.kt
@@ -0,0 +1,21 @@
+package com.orgzly.android.external.actionhandlers
+
+import android.content.Intent
+import com.orgzly.android.external.types.ExternalHandlerFailure
+import com.orgzly.android.external.types.Note
+import com.orgzly.android.query.user.InternalQueryParser
+
+class RunSearch : ExternalAccessActionHandler() {
+ override val actions = listOf(
+ action(::runSearch, "SEARCH")
+ )
+
+ private fun runSearch(intent: Intent): List {
+ val searchTerm = intent.getStringExtra("QUERY")
+ if (searchTerm.isNullOrBlank()) throw ExternalHandlerFailure("invalid search term")
+ val query = InternalQueryParser().parse(searchTerm)
+ val notes = dataRepository.selectNotesFromQuery(query)
+ val notesWithProps = notes.map { it to dataRepository.getNoteProperties(it.note.id) }
+ return notesWithProps.map(Note::from)
+ }
+}
diff --git a/app/src/main/java/com/orgzly/android/external/types/Book.kt b/app/src/main/java/com/orgzly/android/external/types/Book.kt
new file mode 100644
index 000000000..31b624aaf
--- /dev/null
+++ b/app/src/main/java/com/orgzly/android/external/types/Book.kt
@@ -0,0 +1,10 @@
+package com.orgzly.android.external.types
+
+import com.orgzly.android.db.entity.BookView
+
+data class Book(val id: Long, val title: String) {
+ companion object {
+ fun from(view: BookView) =
+ Book(view.book.id, view.book.name)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/orgzly/android/external/types/ExternalHandlerFailure.kt b/app/src/main/java/com/orgzly/android/external/types/ExternalHandlerFailure.kt
new file mode 100644
index 000000000..81030ead5
--- /dev/null
+++ b/app/src/main/java/com/orgzly/android/external/types/ExternalHandlerFailure.kt
@@ -0,0 +1,3 @@
+package com.orgzly.android.external.types
+
+class ExternalHandlerFailure(msg: String) : Exception(msg)
\ No newline at end of file
diff --git a/app/src/main/java/com/orgzly/android/external/types/Note.kt b/app/src/main/java/com/orgzly/android/external/types/Note.kt
new file mode 100644
index 000000000..66c3fd03b
--- /dev/null
+++ b/app/src/main/java/com/orgzly/android/external/types/Note.kt
@@ -0,0 +1,82 @@
+package com.orgzly.android.external.types
+
+import com.orgzly.android.db.entity.NoteProperty
+import com.orgzly.android.db.entity.NoteView
+
+data class Note(
+ val id: Long,
+ val title: String,
+ val content: String?,
+ val tags: List,
+ val inheritedTags: List,
+ val bookName: String,
+ val scheduled: Timestamp?,
+ val deadline: Timestamp?,
+ val closed: Timestamp?,
+ val priority: String?,
+ val state: String?,
+ val createdAt: Long?,
+ val properties: Map
+) {
+ companion object {
+ fun from(view: NoteView, props: List): Note {
+ val note = view.note
+ return Note(
+ note.id,
+ note.title,
+ note.content,
+ note.tags?.split(" +".toRegex())
+ ?.filter { it.isNotEmpty() }
+ ?: emptyList(),
+ view.getInheritedTagsList()
+ .filter { it.isNotEmpty() },
+ view.bookName,
+ Timestamp.from(
+ view.scheduledRangeString,
+ view.scheduledTimeTimestamp,
+ view.scheduledTimeString,
+ view.scheduledTimeEndString,
+ ),
+ Timestamp.from(
+ view.deadlineRangeString,
+ view.deadlineTimeTimestamp,
+ view.deadlineTimeString,
+ view.deadlineTimeEndString,
+ ),
+ Timestamp.from(
+ view.closedRangeString,
+ view.closedTimeTimestamp,
+ view.closedTimeString,
+ view.closedTimeEndString,
+ ),
+ note.priority,
+ note.state,
+ note.createdAt,
+ props.associate { it.name to it.value }
+ )
+ }
+
+ fun from(noteAndProps: Pair>) =
+ from(noteAndProps.first, noteAndProps.second)
+ }
+
+ data class Timestamp(
+ val rangeString: String,
+ val timeTimestamp: Long,
+ val timeString: String?,
+ val timeEndString: String? = null,
+ ) {
+ companion object {
+ fun from(
+ rangeString: String?,
+ timeTimestamp: Long?,
+ timeString: String?,
+ timeEndString: String?,
+ ): Timestamp? {
+ return if (rangeString != null && timeTimestamp != null)
+ Timestamp(rangeString, timeTimestamp, timeString, timeEndString)
+ else null
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/orgzly/android/external/types/Response.kt b/app/src/main/java/com/orgzly/android/external/types/Response.kt
new file mode 100644
index 000000000..41bf95860
--- /dev/null
+++ b/app/src/main/java/com/orgzly/android/external/types/Response.kt
@@ -0,0 +1,8 @@
+package com.orgzly.android.external.types
+
+import java.io.Serializable
+
+data class Response(val success: Boolean = true, val result: Any? = null) {
+ constructor(success: Boolean, result: List) :
+ this(success, result.toTypedArray())
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/orgzly/android/external/types/SavedSearch.kt b/app/src/main/java/com/orgzly/android/external/types/SavedSearch.kt
new file mode 100644
index 000000000..4200e3e5b
--- /dev/null
+++ b/app/src/main/java/com/orgzly/android/external/types/SavedSearch.kt
@@ -0,0 +1,8 @@
+package com.orgzly.android.external.types
+
+data class SavedSearch(val id: Long, val name: String, val position: Int, val query: String) {
+ companion object {
+ fun from(search: com.orgzly.android.db.entity.SavedSearch) =
+ SavedSearch(search.id, search.name, search.position, search.query)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/orgzly/android/widgets/ListWidgetProvider.java b/app/src/main/java/com/orgzly/android/widgets/ListWidgetProvider.java
index fd42a9d60..b541eedc1 100644
--- a/app/src/main/java/com/orgzly/android/widgets/ListWidgetProvider.java
+++ b/app/src/main/java/com/orgzly/android/widgets/ListWidgetProvider.java
@@ -242,6 +242,10 @@ private void setSelectionFromIntent(Context context, Intent intent) {
}
private SavedSearch getSavedSearch(Context context, int appWidgetId) {
+ return getSavedSearch(context, appWidgetId, dataRepository);
+ }
+
+ public static SavedSearch getSavedSearch(Context context, int appWidgetId, DataRepository dataRepository) {
long filterId = context.getSharedPreferences(PREFERENCES_ID, Context.MODE_PRIVATE)
.getLong(getFilterPreferenceKey(appWidgetId), -1);
@@ -265,7 +269,7 @@ private void setFilter(Context context, int appWidgetId, long id) {
editor.apply();
}
- private String getFilterPreferenceKey(int appWidgetId) {
+ private static String getFilterPreferenceKey(int appWidgetId) {
return "widget-filter-" + appWidgetId;
}