Add search quick actions
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.mm20.launcher2.searchactions
|
||||
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
|
||||
val searchActionsModule = module {
|
||||
single<SearchActionRepository> { SearchActionRepositoryImpl() }
|
||||
single<SearchActionService> { SearchActionServiceImpl(androidContext(), get(), TextClassifierImpl()) }
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package de.mm20.launcher2.searchactions
|
||||
|
||||
import de.mm20.launcher2.searchactions.builders.SearchActionBuilder
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface SearchActionRepository {
|
||||
fun getSearchActionBuilders(filter: TextType?): Flow<List<SearchActionBuilder>>
|
||||
}
|
||||
|
||||
internal class SearchActionRepositoryImpl: SearchActionRepository {
|
||||
override fun getSearchActionBuilders(filter: TextType?): Flow<List<SearchActionBuilder>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package de.mm20.launcher2.searchactions
|
||||
|
||||
import android.content.Context
|
||||
import de.mm20.launcher2.preferences.Settings.SearchActionSettings
|
||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||
import de.mm20.launcher2.searchactions.builders.CallActionBuilder
|
||||
import de.mm20.launcher2.searchactions.builders.CreateContactActionBuilder
|
||||
import de.mm20.launcher2.searchactions.builders.EmailActionBuilder
|
||||
import de.mm20.launcher2.searchactions.builders.MessageActionBuilder
|
||||
import de.mm20.launcher2.searchactions.builders.OpenUrlActionBuilder
|
||||
import de.mm20.launcher2.searchactions.builders.ScheduleEventActionBuilder
|
||||
import de.mm20.launcher2.searchactions.builders.SearchActionBuilder
|
||||
import de.mm20.launcher2.searchactions.builders.SetAlarmActionBuilder
|
||||
import de.mm20.launcher2.searchactions.builders.TimerActionBuilder
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
interface SearchActionService {
|
||||
fun search(settings: SearchActionSettings, query: String): Flow<ImmutableList<SearchAction>>
|
||||
}
|
||||
|
||||
internal class SearchActionServiceImpl(
|
||||
private val context: Context,
|
||||
private val repository: SearchActionRepository,
|
||||
private val textClassifier: TextClassifier,
|
||||
) : SearchActionService {
|
||||
override fun search(settings: SearchActionSettings, query: String): Flow<ImmutableList<SearchAction>> = flow {
|
||||
if (query.isBlank()) {
|
||||
emit(persistentListOf())
|
||||
return@flow
|
||||
}
|
||||
|
||||
val builders = mutableListOf<SearchActionBuilder>()
|
||||
|
||||
if (settings.call) builders.add(CallActionBuilder)
|
||||
if (settings.message) builders.add(MessageActionBuilder)
|
||||
if (settings.contact) builders.add(CreateContactActionBuilder)
|
||||
if (settings.email) builders.add(EmailActionBuilder)
|
||||
if (settings.openUrl) builders.add(OpenUrlActionBuilder)
|
||||
if (settings.scheduleEvent) builders.add(ScheduleEventActionBuilder)
|
||||
if (settings.setAlarm) builders.add(SetAlarmActionBuilder)
|
||||
if (settings.startTimer) builders.add(TimerActionBuilder)
|
||||
|
||||
val classificationResult = textClassifier.classify(context, query)
|
||||
|
||||
|
||||
emit(builders.mapNotNull { it.build(context, classificationResult) }.toImmutableList())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package de.mm20.launcher2.searchactions
|
||||
|
||||
import android.content.Context
|
||||
import android.icu.text.SimpleDateFormat
|
||||
import android.text.format.DateFormat
|
||||
import java.text.ParseException
|
||||
import java.time.Duration
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
import java.time.ZoneId
|
||||
import java.util.Locale
|
||||
|
||||
internal interface TextClassifier {
|
||||
suspend fun classify(context: Context, query: String): TextClassificationResult
|
||||
}
|
||||
|
||||
internal class TextClassifierImpl : TextClassifier {
|
||||
override suspend fun classify(context: Context, query: String): TextClassificationResult {
|
||||
return when {
|
||||
query.matches(Regex("^\\S+@\\S+$")) -> TextClassificationResult(
|
||||
type = TextType.Email,
|
||||
text = query,
|
||||
email = query
|
||||
)
|
||||
|
||||
query.matches(Regex("^\\+?[0-9- ]{4,18}$")) -> TextClassificationResult(
|
||||
type = TextType.PhoneNumber,
|
||||
text = query,
|
||||
phoneNumber = query
|
||||
)
|
||||
|
||||
query.matches(Regex("^(http(s)?://.)?(www\\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_+.~#?&/=]*)$")) -> TextClassificationResult(
|
||||
type = TextType.Url,
|
||||
text = query,
|
||||
url = query
|
||||
)
|
||||
|
||||
else -> {
|
||||
parseDate(context, query)?.let { return it }
|
||||
TextClassificationResult(type = TextType.Text, text = query)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseDate(context: Context, query: String): TextClassificationResult? {
|
||||
val dateTimeFormat = SimpleDateFormat(
|
||||
DateFormat.getBestDateTimePattern(
|
||||
Locale.getDefault(),
|
||||
"yyyy-MM-dd, HH:mm"
|
||||
)
|
||||
)
|
||||
try {
|
||||
dateTimeFormat.parse(query)?.let {
|
||||
val dateTime = LocalDateTime.ofInstant(it.toInstant(), ZoneId.systemDefault())
|
||||
return TextClassificationResult(
|
||||
type = TextType.DateTime,
|
||||
text = query,
|
||||
time = dateTime.toLocalTime(),
|
||||
date = dateTime.toLocalDate(),
|
||||
)
|
||||
}
|
||||
} catch (_: ParseException) {
|
||||
// Not a datetime
|
||||
}
|
||||
val dateFormat = DateFormat.getDateFormat(context)
|
||||
try {
|
||||
dateFormat.parse(query)?.let {
|
||||
return TextClassificationResult(
|
||||
type = TextType.Date,
|
||||
text = query,
|
||||
date = LocalDateTime.ofInstant(it.toInstant(), ZoneId.systemDefault())
|
||||
.toLocalDate()
|
||||
)
|
||||
}
|
||||
} catch (_: ParseException) {
|
||||
// Not a date either
|
||||
}
|
||||
val timeFormat = DateFormat.getTimeFormat(context)
|
||||
try {
|
||||
timeFormat.parse(query)?.let {
|
||||
return TextClassificationResult(
|
||||
type = TextType.Time,
|
||||
text = query,
|
||||
time = LocalDateTime.ofInstant(it.toInstant(), ZoneId.systemDefault())
|
||||
.toLocalTime(),
|
||||
)
|
||||
}
|
||||
} catch (_: ParseException) {
|
||||
// Nope, not a time
|
||||
}
|
||||
|
||||
val seconds = context.getString(R.string.unit_second_symbol)
|
||||
if (query.matches(Regex("^[0-9]+ ${seconds}$"))) {
|
||||
val value = query.substringBefore(" ").toLong()
|
||||
return TextClassificationResult(
|
||||
type = TextType.Timespan,
|
||||
text = query,
|
||||
timespan = Duration.ofSeconds(value)
|
||||
)
|
||||
}
|
||||
|
||||
val days = context.getString(R.string.unit_day_symbol)
|
||||
if (query.matches(Regex("^[0-9]+ ${days}$"))) {
|
||||
val value = query.substringBefore(" ").toLong()
|
||||
return TextClassificationResult(
|
||||
type = TextType.Timespan,
|
||||
text = query,
|
||||
timespan = Duration.ofDays(value)
|
||||
)
|
||||
}
|
||||
val minutes = context.getString(R.string.unit_minute_symbol)
|
||||
if (query.matches(Regex("^[0-9]+ ${minutes}$"))) {
|
||||
val value = query.substringBefore(" ").toLong()
|
||||
val then = LocalDateTime.now().plusMinutes(value)
|
||||
return TextClassificationResult(
|
||||
type = TextType.Timespan,
|
||||
text = query,
|
||||
timespan = Duration.ofMinutes(value)
|
||||
)
|
||||
}
|
||||
val hours = context.getString(R.string.unit_hour_symbol)
|
||||
if (query.matches(Regex("^[0-9]+ ${hours}$"))) {
|
||||
val value = query.substringBefore(" ").toLong()
|
||||
return TextClassificationResult(
|
||||
type = TextType.Timespan,
|
||||
text = query,
|
||||
timespan = Duration.ofHours(value)
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
data class TextClassificationResult(
|
||||
val type: TextType,
|
||||
val text: String,
|
||||
val email: String? = null,
|
||||
val phoneNumber: String? = null,
|
||||
val time: LocalTime? = null,
|
||||
val date: LocalDate? = null,
|
||||
val timespan: Duration? = null,
|
||||
val url: String? = null,
|
||||
)
|
||||
|
||||
enum class TextType {
|
||||
Text,
|
||||
Email,
|
||||
Url,
|
||||
PhoneNumber,
|
||||
DateTime,
|
||||
Date,
|
||||
Time,
|
||||
Timespan,
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package de.mm20.launcher2.searchactions.actions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import de.mm20.launcher2.searchactions.R
|
||||
|
||||
data class CallAction(
|
||||
override val label: String,
|
||||
val number: String,
|
||||
): SearchAction {
|
||||
|
||||
override val icon: SearchActionIcon = SearchActionIcon.Phone
|
||||
override val iconColor: Int = 0
|
||||
|
||||
override fun start(context: Context) {
|
||||
val intent = Intent(Intent.ACTION_DIAL).apply {
|
||||
data = Uri.parse("tel:$number")
|
||||
}
|
||||
context.tryStartActivity(intent)
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package de.mm20.launcher2.searchactions.actions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.provider.ContactsContract
|
||||
import android.provider.ContactsContract.Intents.Insert
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
|
||||
class CreateContactAction(
|
||||
override val label: String,
|
||||
val phone: String? = null,
|
||||
val email: String? = null,
|
||||
) : SearchAction {
|
||||
override val icon: SearchActionIcon = SearchActionIcon.Contact
|
||||
override val iconColor: Int = 0
|
||||
|
||||
override fun start(context: Context) {
|
||||
val intent = Intent(Intent.ACTION_INSERT).apply {
|
||||
type = ContactsContract.Contacts.CONTENT_TYPE
|
||||
if (email != null) putExtra(Insert.EMAIL, email)
|
||||
if (phone != null) putExtra(Insert.PHONE, phone)
|
||||
}
|
||||
context.tryStartActivity(intent)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package de.mm20.launcher2.searchactions.actions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
|
||||
data class EmailAction(
|
||||
override val label: String,
|
||||
val email: String,
|
||||
) : SearchAction {
|
||||
override val icon: SearchActionIcon = SearchActionIcon.Email
|
||||
override val iconColor: Int = 0
|
||||
|
||||
override fun start(context: Context) {
|
||||
val intent = Intent(Intent.ACTION_SENDTO).apply {
|
||||
type = "*/*"
|
||||
data = Uri.parse("mailto:$email")
|
||||
}
|
||||
context.tryStartActivity(intent)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package de.mm20.launcher2.searchactions.actions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
|
||||
data class MessageAction(
|
||||
override val label: String,
|
||||
val number: String,
|
||||
): SearchAction {
|
||||
override val icon: SearchActionIcon = SearchActionIcon.Message
|
||||
override val iconColor: Int = 0
|
||||
|
||||
override fun start(context: Context) {
|
||||
val intent = Intent(Intent.ACTION_SENDTO).apply {
|
||||
data = Uri.parse("sms:$number")
|
||||
}
|
||||
context.tryStartActivity(intent)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package de.mm20.launcher2.searchactions.actions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
|
||||
data class OpenUrlAction(
|
||||
override val label: String,
|
||||
val url: String,
|
||||
) : SearchAction {
|
||||
|
||||
override val icon: SearchActionIcon = SearchActionIcon.Website
|
||||
override val iconColor: Int = 0
|
||||
|
||||
override fun start(context: Context) {
|
||||
val url = if (url.startsWith("https://") || url.startsWith("http://")) url else "https://$url"
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
data = Uri.parse(url)
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
}
|
||||
context.tryStartActivity(intent)
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package de.mm20.launcher2.searchactions.actions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.provider.CalendarContract
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
import java.time.ZoneId
|
||||
|
||||
data class ScheduleEventAction(
|
||||
override val label: String,
|
||||
val date: LocalDate,
|
||||
val time: LocalTime?,
|
||||
) : SearchAction {
|
||||
override val icon: SearchActionIcon = SearchActionIcon.Calendar
|
||||
override val iconColor: Int = 0
|
||||
|
||||
override fun start(context: Context) {
|
||||
|
||||
val startTime = date.let {
|
||||
if (time != null) it.atTime(time)
|
||||
else it.atTime(0, 0)
|
||||
}.atZone(ZoneId.systemDefault()).toEpochSecond() * 1000L
|
||||
|
||||
val intent = Intent(Intent.ACTION_INSERT).apply {
|
||||
type = "vnd.android.cursor.dir/event"
|
||||
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startTime)
|
||||
if (time == null) putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, true)
|
||||
}
|
||||
context.tryStartActivity(intent)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package de.mm20.launcher2.searchactions.actions
|
||||
|
||||
import android.content.Context
|
||||
import de.mm20.launcher2.search.Searchable
|
||||
|
||||
interface SearchAction : Searchable {
|
||||
val label: String
|
||||
val icon: SearchActionIcon
|
||||
val iconColor: Int
|
||||
fun start(context: Context)
|
||||
}
|
||||
|
||||
enum class SearchActionIcon {
|
||||
Search,
|
||||
Website,
|
||||
Alarm,
|
||||
Timer,
|
||||
Contact,
|
||||
Phone,
|
||||
Email,
|
||||
Message,
|
||||
Calendar,
|
||||
Translate,
|
||||
Custom,
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package de.mm20.launcher2.searchactions.actions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.provider.AlarmClock
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import java.time.LocalTime
|
||||
|
||||
data class SetAlarmAction(
|
||||
override val label: String,
|
||||
val time: LocalTime
|
||||
) : SearchAction {
|
||||
override val icon: SearchActionIcon = SearchActionIcon.Alarm
|
||||
override val iconColor: Int = 0
|
||||
|
||||
override fun start(context: Context) {
|
||||
val intent = Intent(AlarmClock.ACTION_SET_ALARM).apply {
|
||||
putExtra(AlarmClock.EXTRA_HOUR, time.hour)
|
||||
putExtra(AlarmClock.EXTRA_MINUTES, time.minute)
|
||||
}
|
||||
context.tryStartActivity(intent)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package de.mm20.launcher2.searchactions.actions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.provider.AlarmClock
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import java.time.Duration
|
||||
|
||||
data class TimerAction(
|
||||
override val label: String,
|
||||
val length: Duration
|
||||
): SearchAction {
|
||||
|
||||
override val icon: SearchActionIcon = SearchActionIcon.Timer
|
||||
override val iconColor: Int = 0
|
||||
|
||||
override fun start(context: Context) {
|
||||
val intent = Intent(AlarmClock.ACTION_SET_TIMER).apply {
|
||||
putExtra(AlarmClock.EXTRA_LENGTH, length.seconds.toInt())
|
||||
}
|
||||
context.tryStartActivity(intent)
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package de.mm20.launcher2.searchactions.builders
|
||||
|
||||
import android.content.Context
|
||||
import de.mm20.launcher2.searchactions.R
|
||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||
import de.mm20.launcher2.searchactions.actions.CallAction
|
||||
|
||||
object CallActionBuilder: SearchActionBuilder {
|
||||
|
||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||
if (classifiedQuery.phoneNumber != null) {
|
||||
return CallAction(
|
||||
context.getString(R.string.search_action_call), classifiedQuery.phoneNumber
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package de.mm20.launcher2.searchactions.builders
|
||||
|
||||
import android.content.Context
|
||||
import de.mm20.launcher2.searchactions.R
|
||||
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||
import de.mm20.launcher2.searchactions.actions.CreateContactAction
|
||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||
|
||||
object CreateContactActionBuilder : SearchActionBuilder {
|
||||
|
||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||
if (classifiedQuery.phoneNumber != null || classifiedQuery.email != null) {
|
||||
return CreateContactAction(
|
||||
context.getString(R.string.search_action_contact),
|
||||
phone = classifiedQuery.phoneNumber,
|
||||
email = classifiedQuery.email,
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package de.mm20.launcher2.searchactions.builders
|
||||
|
||||
import android.content.Context
|
||||
import de.mm20.launcher2.searchactions.R
|
||||
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||
import de.mm20.launcher2.searchactions.actions.EmailAction
|
||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||
|
||||
object EmailActionBuilder: SearchActionBuilder {
|
||||
|
||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||
if (classifiedQuery.email != null) {
|
||||
return EmailAction(
|
||||
context.getString(R.string.search_action_email), classifiedQuery.email
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package de.mm20.launcher2.searchactions.builders
|
||||
|
||||
import android.content.Context
|
||||
import de.mm20.launcher2.searchactions.R
|
||||
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||
import de.mm20.launcher2.searchactions.actions.MessageAction
|
||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||
|
||||
object MessageActionBuilder: SearchActionBuilder {
|
||||
|
||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||
if (classifiedQuery.phoneNumber != null) {
|
||||
return MessageAction(
|
||||
context.getString(R.string.search_action_message), classifiedQuery.phoneNumber
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package de.mm20.launcher2.searchactions.builders
|
||||
|
||||
import android.content.Context
|
||||
import de.mm20.launcher2.searchactions.R
|
||||
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||
import de.mm20.launcher2.searchactions.actions.MessageAction
|
||||
import de.mm20.launcher2.searchactions.actions.OpenUrlAction
|
||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||
|
||||
object OpenUrlActionBuilder : SearchActionBuilder {
|
||||
|
||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||
if (classifiedQuery.url != null) {
|
||||
return OpenUrlAction(
|
||||
context.getString(R.string.search_action_open_url), classifiedQuery.url
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package de.mm20.launcher2.searchactions.builders
|
||||
|
||||
import android.content.Context
|
||||
import de.mm20.launcher2.searchactions.R
|
||||
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||
import de.mm20.launcher2.searchactions.actions.MessageAction
|
||||
import de.mm20.launcher2.searchactions.actions.ScheduleEventAction
|
||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||
import java.time.LocalDateTime
|
||||
|
||||
object ScheduleEventActionBuilder : SearchActionBuilder {
|
||||
|
||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||
if (classifiedQuery.date != null) {
|
||||
return ScheduleEventAction(
|
||||
context.getString(R.string.search_action_event),
|
||||
date = classifiedQuery.date,
|
||||
time = classifiedQuery.time
|
||||
)
|
||||
}
|
||||
if (classifiedQuery.timespan != null && classifiedQuery.timespan.seconds > 86400) {
|
||||
val datetime = LocalDateTime.now().plus(classifiedQuery.timespan)
|
||||
return ScheduleEventAction(
|
||||
context.getString(R.string.search_action_event),
|
||||
date = datetime.toLocalDate(),
|
||||
time = datetime.toLocalTime(),
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package de.mm20.launcher2.searchactions.builders
|
||||
|
||||
import android.content.Context
|
||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||
|
||||
interface SearchActionBuilder {
|
||||
fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction?
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package de.mm20.launcher2.searchactions.builders
|
||||
|
||||
import android.content.Context
|
||||
import de.mm20.launcher2.searchactions.R
|
||||
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||
import de.mm20.launcher2.searchactions.actions.SetAlarmAction
|
||||
import java.time.LocalDate
|
||||
|
||||
object SetAlarmActionBuilder : SearchActionBuilder {
|
||||
|
||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||
if (classifiedQuery.time != null) {
|
||||
return SetAlarmAction(
|
||||
context.getString(R.string.search_action_alarm), classifiedQuery.time
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package de.mm20.launcher2.searchactions.builders
|
||||
|
||||
import android.content.Context
|
||||
import de.mm20.launcher2.searchactions.R
|
||||
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||
import de.mm20.launcher2.searchactions.actions.TimerAction
|
||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||
|
||||
object TimerActionBuilder : SearchActionBuilder {
|
||||
|
||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||
if (classifiedQuery.timespan != null && classifiedQuery.timespan.seconds <= 86400) {
|
||||
return TimerAction(
|
||||
context.getString(R.string.search_action_timer), classifiedQuery.timespan
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package de.mm20.launcher2.searchactions.builders
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import de.mm20.launcher2.searchactions.actions.SearchAction
|
||||
import de.mm20.launcher2.searchactions.TextClassificationResult
|
||||
import de.mm20.launcher2.searchactions.TextType
|
||||
import de.mm20.launcher2.searchactions.actions.OpenUrlAction
|
||||
import java.net.URLEncoder
|
||||
|
||||
class WebsearchActionBuilder(
|
||||
val label: String,
|
||||
val urlTemplate: String,
|
||||
val filter: TextType? = null,
|
||||
val encoding: QueryEncoding,
|
||||
) : SearchActionBuilder {
|
||||
|
||||
override fun build(context: Context, classifiedQuery: TextClassificationResult): SearchAction? {
|
||||
if (filter == null || classifiedQuery.type == filter) {
|
||||
val url = urlTemplate.replace("\${1}", encodeQuery(classifiedQuery.text, encoding))
|
||||
return OpenUrlAction(
|
||||
label = label,
|
||||
url = url,
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
private fun encodeQuery(query: String, encoding: QueryEncoding): String {
|
||||
return when (encoding) {
|
||||
QueryEncoding.UrlEncode -> Uri.encode(query)
|
||||
QueryEncoding.FormData -> URLEncoder.encode(query, "UTF-8")
|
||||
QueryEncoding.None -> query
|
||||
}
|
||||
}
|
||||
|
||||
enum class QueryEncoding {
|
||||
UrlEncode,
|
||||
FormData,
|
||||
None;
|
||||
|
||||
fun toInt(): Int {
|
||||
return when (this) {
|
||||
UrlEncode -> 0
|
||||
FormData -> 1
|
||||
None -> 2
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int?): QueryEncoding {
|
||||
return when (value) {
|
||||
1 -> FormData
|
||||
2 -> None
|
||||
else -> UrlEncode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user