-
Notifications
You must be signed in to change notification settings - Fork 210
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #431 from joaomgcd/taskerplugin
Taskerplugin
- Loading branch information
Showing
25 changed files
with
814 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
apply from: "../../modules.gradle" | ||
|
||
android { | ||
namespace 'au.com.shiftyjelly.pocketcasts.taskerplugin' | ||
buildFeatures { | ||
viewBinding true | ||
dataBinding = true | ||
compose true | ||
} | ||
} | ||
|
||
dependencies { | ||
implementation project(':modules:services:localization') | ||
implementation project(':modules:services:ui') | ||
implementation project(':modules:services:compose') | ||
implementation project(':modules:services:repositories') | ||
implementation project(':modules:services:model') | ||
implementation project(':modules:services:views') | ||
implementation project(':modules:services:images') | ||
implementation 'com.joaomgcd:taskerpluginlibrary:0.4.3' | ||
} |
25 changes: 25 additions & 0 deletions
25
modules/features/taskerplugin/src/main/AndroidManifest.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest xmlns:android="http:https://schemas.android.com/apk/res/android"> | ||
|
||
<application> | ||
|
||
<activity | ||
android:name=".playplaylist.config.ActivityConfigPlayPlaylist" | ||
android:exported="true" | ||
android:icon="@mipmap/ic_launcher" | ||
android:label="Pocket Casts Play Filter"> | ||
<intent-filter> | ||
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" /> | ||
</intent-filter> | ||
</activity> | ||
<activity | ||
android:name=".controlplayback.config.ActivityConfigControlPlayback" | ||
android:exported="true" | ||
android:icon="@mipmap/ic_launcher" | ||
android:label="Pocket Casts Control Playback"> | ||
<intent-filter> | ||
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" /> | ||
</intent-filter> | ||
</activity> | ||
</application> | ||
</manifest> |
23 changes: 23 additions & 0 deletions
23
...ugin/src/main/java/au/com/shiftyjelly/pocketcasts/taskerplugin/base/ActivityConfigBase.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package au.com.shiftyjelly.pocketcasts.taskerplugin.base | ||
|
||
import android.os.Bundle | ||
import androidx.activity.ComponentActivity | ||
import androidx.activity.compose.setContent | ||
import androidx.compose.runtime.Composable | ||
import au.com.shiftyjelly.pocketcasts.compose.AppThemeWithBackground | ||
import au.com.shiftyjelly.pocketcasts.taskerplugin.base.hilt.appTheme | ||
|
||
abstract class ActivityConfigBase<TViewModel : ViewModelBase<*, *>> : ComponentActivity() { | ||
protected abstract val viewModel: TViewModel | ||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
viewModel.onCreate({ finish() }, { intent }, { code, data -> setResult(code, data) }) | ||
setContent { | ||
AppThemeWithBackground(themeType = appTheme.activeTheme) { | ||
Content() | ||
} | ||
} | ||
} | ||
@Composable | ||
protected abstract fun Content() | ||
} |
179 changes: 179 additions & 0 deletions
179
...erplugin/src/main/java/au/com/shiftyjelly/pocketcasts/taskerplugin/base/ComposableBase.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
package au.com.shiftyjelly.pocketcasts.taskerplugin.base | ||
|
||
import androidx.annotation.StringRes | ||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.Row | ||
import androidx.compose.foundation.layout.fillMaxHeight | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.layout.requiredSizeIn | ||
import androidx.compose.foundation.lazy.LazyColumn | ||
import androidx.compose.foundation.text.KeyboardActions | ||
import androidx.compose.foundation.text.KeyboardOptions | ||
import androidx.compose.material.Button | ||
import androidx.compose.material.DropdownMenu | ||
import androidx.compose.material.DropdownMenuItem | ||
import androidx.compose.material.Icon | ||
import androidx.compose.material.IconButton | ||
import androidx.compose.material.MaterialTheme | ||
import androidx.compose.material.OutlinedTextField | ||
import androidx.compose.material.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.runtime.setValue | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.ExperimentalComposeUiApi | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController | ||
import androidx.compose.ui.res.painterResource | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.compose.ui.text.input.ImeAction | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import androidx.compose.ui.unit.dp | ||
import androidx.compose.ui.window.PopupProperties | ||
import au.com.shiftyjelly.pocketcasts.compose.AppTheme | ||
import au.com.shiftyjelly.pocketcasts.compose.theme | ||
import au.com.shiftyjelly.pocketcasts.localization.R | ||
import au.com.shiftyjelly.pocketcasts.ui.theme.Theme | ||
|
||
class TaskerInputFieldState<T>(val content: Content<T>) { | ||
data class Content<T> constructor( | ||
val value: String?, | ||
@StringRes val labelResId: Int, | ||
val onTextChange: (String) -> Unit, | ||
val taskerVariables: List<String>, | ||
val possibleItems: List<T>? = null, | ||
val itemToString: (T?) -> String = { it?.toString() ?: "" }, | ||
val itemContent: @Composable (T) -> Unit = { Text(text = itemToString(it)) } | ||
) | ||
} | ||
|
||
private enum class TaskerInputFieldSelectMode { Variable, ItemList } | ||
|
||
@OptIn(ExperimentalComposeUiApi::class) | ||
@Composable | ||
fun <T> ComposableTaskerInputField(content: TaskerInputFieldState.Content<T>) { | ||
var selectionMode by remember { mutableStateOf(null as TaskerInputFieldSelectMode?) } | ||
val keyboardController = LocalSoftwareKeyboardController.current | ||
|
||
/** | ||
* @param selection if null, just hide dropdown and don't signal text change | ||
*/ | ||
fun finishSelecting(selection: String? = null) { | ||
selectionMode = null | ||
keyboardController?.hide() | ||
selection?.let { content.onTextChange(it) } | ||
} | ||
Box { | ||
|
||
Row { | ||
val possibleItems = content.possibleItems | ||
|
||
val hasSuggestedItems = !possibleItems.isNullOrEmpty() | ||
val hasTaskerVariables = content.taskerVariables.isNotEmpty() | ||
OutlinedTextField( | ||
modifier = Modifier.weight(1f), | ||
value = content.value ?: "", | ||
label = { Text(text = stringResource(id = content.labelResId)) }, | ||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), | ||
keyboardActions = KeyboardActions(onDone = { finishSelecting() }), | ||
onValueChange = { | ||
content.onTextChange(it) | ||
}, | ||
trailingIcon = if (!hasSuggestedItems && !hasTaskerVariables) null else { | ||
{ | ||
Row { | ||
if (hasTaskerVariables) { | ||
IconButton(onClick = { selectionMode = TaskerInputFieldSelectMode.Variable }) { | ||
Icon( | ||
painter = painterResource(au.com.shiftyjelly.pocketcasts.taskerplugin.R.drawable.label_outline), | ||
contentDescription = stringResource(R.string.tasker_variables), | ||
tint = MaterialTheme.theme.colors.primaryIcon01, | ||
modifier = Modifier.padding(end = 16.dp, start = 16.dp) | ||
) | ||
} | ||
} | ||
if (hasSuggestedItems) { | ||
IconButton(onClick = { selectionMode = TaskerInputFieldSelectMode.ItemList }) { | ||
Icon( | ||
painter = painterResource(au.com.shiftyjelly.pocketcasts.images.R.drawable.ic_search), | ||
contentDescription = stringResource(R.string.search), | ||
tint = MaterialTheme.theme.colors.primaryIcon01, | ||
modifier = Modifier.padding(end = 16.dp, start = 16.dp) | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
) | ||
val dropdownMaxHeight = screenSize.height / 6 * 2 //at most dropdown can be 2/6 of the screen size so it doesn't draw over its parent | ||
if (hasTaskerVariables) { | ||
DropdownMenu( | ||
modifier = Modifier.requiredSizeIn(maxHeight = dropdownMaxHeight), | ||
expanded = selectionMode == TaskerInputFieldSelectMode.Variable, | ||
onDismissRequest = { finishSelecting() }, | ||
properties = PopupProperties(focusable = false) | ||
) { | ||
content.taskerVariables.forEach { | ||
DropdownMenuItem(onClick = { | ||
finishSelecting(it) | ||
}) { | ||
Text(it) | ||
} | ||
} | ||
} | ||
} | ||
if (possibleItems != null && possibleItems.isNotEmpty()) { | ||
DropdownMenu( | ||
modifier = Modifier.requiredSizeIn(maxHeight = dropdownMaxHeight), | ||
expanded = selectionMode == TaskerInputFieldSelectMode.ItemList, | ||
onDismissRequest = { finishSelecting() }, | ||
properties = PopupProperties(focusable = false) | ||
) { | ||
possibleItems.forEach { | ||
DropdownMenuItem(onClick = { | ||
finishSelecting(content.itemToString(it)) | ||
}) { | ||
content.itemContent(it) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Preview(showBackground = true) | ||
@Composable | ||
private fun ComposableTaskerInputFieldPreview() { | ||
AppTheme(Theme.ThemeType.CLASSIC_LIGHT) { | ||
ComposableTaskerInputField( | ||
TaskerInputFieldState.Content( | ||
"some value", R.string.archive, {}, | ||
listOf("%test"), | ||
listOf("Hi", "Hello") | ||
) | ||
) | ||
} | ||
} | ||
|
||
@Composable | ||
fun ComposableTaskerInputFieldList( | ||
fieldContents: List<TaskerInputFieldState.Content<*>>, | ||
onFinish: () -> Unit | ||
) { | ||
Box(modifier = Modifier.fillMaxHeight()) { | ||
LazyColumn { | ||
fieldContents.forEach { content -> | ||
item { | ||
ComposableTaskerInputField(content) | ||
} | ||
} | ||
} | ||
Button(onClick = onFinish, modifier = Modifier.align(Alignment.BottomEnd)) { | ||
Text(stringResource(R.string.ok)) | ||
} | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
...taskerplugin/src/main/java/au/com/shiftyjelly/pocketcasts/taskerplugin/base/Extensions.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package au.com.shiftyjelly.pocketcasts.taskerplugin.base | ||
|
||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.platform.LocalConfiguration | ||
import androidx.compose.ui.unit.DpSize | ||
import androidx.compose.ui.unit.dp | ||
|
||
val String?.nullIfEmpty get() = if (isNullOrEmpty()) null else this | ||
fun <T> tryOrNull(handleError: ((Throwable) -> T?)? = null, block: () -> T?): T? = try { | ||
block() | ||
} catch (t: Throwable) { | ||
handleError?.invoke(t) | ||
} | ||
|
||
val screenSize | ||
@Composable | ||
get() :DpSize { | ||
val configuration = LocalConfiguration.current | ||
|
||
val screenHeight = configuration.screenHeightDp.dp | ||
val screenWidth = configuration.screenWidthDp.dp | ||
return DpSize(width = screenWidth, height = screenHeight) | ||
} |
52 changes: 52 additions & 0 deletions
52
...kerplugin/src/main/java/au/com/shiftyjelly/pocketcasts/taskerplugin/base/ViewModelBase.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package au.com.shiftyjelly.pocketcasts.taskerplugin.base | ||
|
||
import android.app.Application | ||
import android.content.Intent | ||
import androidx.lifecycle.AndroidViewModel | ||
import au.com.shiftyjelly.pocketcasts.taskerplugin.controlplayback.InputControlPlayback | ||
import com.joaomgcd.taskerpluginlibrary.action.TaskerPluginRunnerActionNoOutput | ||
import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfig | ||
import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfigHelperNoOutput | ||
import com.joaomgcd.taskerpluginlibrary.input.TaskerInput | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
|
||
abstract class ViewModelBase<TInput : Any, THelper : TaskerPluginConfigHelperNoOutput<TInput, out TaskerPluginRunnerActionNoOutput<TInput>>>(application: Application) : AndroidViewModel(application), TaskerPluginConfig<TInput> { | ||
override val context get() = getApplication<Application>() | ||
abstract val helperClass: Class<THelper> | ||
private val taskerHelper by lazy { helperClass.getConstructor(TaskerPluginConfig::class.java).newInstance(this) } | ||
protected var input: TInput? = null | ||
|
||
private var taskerInput | ||
get() = TaskerInput(input ?: taskerHelper.inputClass.newInstance()) | ||
set(value) { | ||
input = value.regular | ||
} | ||
|
||
override val inputForTasker: TaskerInput<TInput> | ||
get() = taskerInput | ||
|
||
fun getDescription(command: InputControlPlayback.PlaybackCommand) = command.getDescription(context) | ||
override fun assignFromInput(input: TaskerInput<TInput>) { | ||
taskerInput = input | ||
} | ||
|
||
fun onCreate(finishFunc: (() -> Unit), getIntentFunc: (() -> Intent?), setResultFunc: ((Int, Intent) -> Unit)) { | ||
this.finishFunc = finishFunc | ||
this.getIntentFunc = getIntentFunc | ||
this.setResultFunc = setResultFunc | ||
taskerHelper.onCreate() | ||
} | ||
|
||
private var finishFunc: (() -> Unit)? = null | ||
override fun finish() = finishFunc?.invoke() ?: Unit | ||
|
||
private var getIntentFunc: (() -> Intent?)? = null | ||
override fun getIntent() = getIntentFunc?.invoke() | ||
|
||
private var setResultFunc: ((Int, Intent) -> Unit)? = null | ||
override fun setResult(resultCode: Int, data: Intent) = setResultFunc?.invoke(resultCode, data) ?: Unit | ||
|
||
fun finishForTasker() = taskerHelper.finishForTasker() | ||
|
||
val taskerVariables by lazy { taskerHelper.relevantVariables.distinct().sortedBy { it } } | ||
} |
39 changes: 39 additions & 0 deletions
39
...in/src/main/java/au/com/shiftyjelly/pocketcasts/taskerplugin/base/hilt/HiltEntryPoints.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package au.com.shiftyjelly.pocketcasts.taskerplugin.base.hilt | ||
|
||
import android.content.Context | ||
import au.com.shiftyjelly.pocketcasts.repositories.playback.PlaybackManager | ||
import au.com.shiftyjelly.pocketcasts.repositories.podcast.EpisodeManager | ||
import au.com.shiftyjelly.pocketcasts.repositories.podcast.PlaylistManager | ||
import au.com.shiftyjelly.pocketcasts.ui.theme.Theme | ||
import dagger.hilt.EntryPoint | ||
import dagger.hilt.InstallIn | ||
import dagger.hilt.android.EntryPointAccessors | ||
import dagger.hilt.components.SingletonComponent | ||
|
||
@InstallIn(SingletonComponent::class) | ||
@EntryPoint | ||
interface ThemeEntryPoint { | ||
fun getTheme(): Theme | ||
} | ||
@InstallIn(SingletonComponent::class) | ||
@EntryPoint | ||
interface PlaybackManagerEntryPoint { | ||
fun getPlaybackManager(): PlaybackManager | ||
} | ||
|
||
@InstallIn(SingletonComponent::class) | ||
@EntryPoint | ||
interface PlaylistManagerEntryPoint { | ||
fun getPlaylistManager(): PlaylistManager | ||
} | ||
|
||
@InstallIn(SingletonComponent::class) | ||
@EntryPoint | ||
interface EpisodeManagerEntryPoint { | ||
fun getEpisodeManager(): EpisodeManager | ||
} | ||
|
||
val Context.appTheme get() = EntryPointAccessors.fromApplication(applicationContext, ThemeEntryPoint::class.java).getTheme() | ||
val Context.playbackManager get() = EntryPointAccessors.fromApplication(applicationContext, PlaybackManagerEntryPoint::class.java).getPlaybackManager() | ||
val Context.playlistManager get() = EntryPointAccessors.fromApplication(applicationContext, PlaylistManagerEntryPoint::class.java).getPlaylistManager() | ||
val Context.episodeManager get() = EntryPointAccessors.fromApplication(applicationContext, EpisodeManagerEntryPoint::class.java).getEpisodeManager() |
Oops, something went wrong.