Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
- 📱 **Kotlin Multiplatform**: Shared logic for Android and iOS.
- 💾 **Persistence**: Tasks are stored in a local SQLite database (via Room) and resumed after app restarts.
- 🔗 **Work Chaining**: Easily chain multiple tasks together with `then` operations.
- ⚙️ **Constraints**: Define requirements like `requiredNetwork` and `requireBatteryNotLow` for your tasks.
- ⚙️ **Constraints**: Define requirements like `requiredNetwork`, `requireBatteryNotLow` and `requireCharging` for your tasks.
- 🛠️ **DSL-based API**: Clean and intuitive DSL for initialization and task definition.
- 📊 **Monitoring**: Observe task status using Kotlin Flows.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ internal fun LorraineConstraints.asWorkManagerConstraints(): Constraints {
} else {
NetworkType.NOT_REQUIRED
},
requiresBatteryNotLow = requireBatteryNotLow
requiresBatteryNotLow = requireBatteryNotLow,
requiresCharging = requireCharging
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import io.dot.lorraine.db.dao.WorkerDao
import io.dot.lorraine.db.entity.WorkerEntity

@Database(
version = 3,
Comment thread
rteyssandier marked this conversation as resolved.
version = 4,
entities = [
WorkerEntity::class
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,22 @@ internal data class ConstraintEntity(
@ColumnInfo(name = "require_network")
val requireNetwork: Boolean,
@ColumnInfo(name = "require_battery_not_low")
val requireBatteryNotLow: Boolean
val requireBatteryNotLow: Boolean,
@ColumnInfo(name = "require_charging")
val requireCharging: Boolean

)

internal fun ConstraintEntity.toDomain() = LorraineConstraints(
requireNetwork = requireNetwork,
requireBatteryNotLow = requireBatteryNotLow
requireBatteryNotLow = requireBatteryNotLow,
requireCharging = requireCharging
)

internal fun LorraineConstraints.toEntity() = ConstraintEntity(
requireNetwork = requireNetwork,
requireBatteryNotLow = requireBatteryNotLow
requireBatteryNotLow = requireBatteryNotLow,
requireCharging = requireCharging
)


Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,28 @@ package io.dot.lorraine.dsl
data class LorraineConstraints internal constructor(
val requireNetwork: Boolean,
val requireBatteryNotLow: Boolean,
val requireCharging: Boolean,
) {

companion object {
val NONE = LorraineConstraints(
requireNetwork = false,
requireBatteryNotLow = false
requireBatteryNotLow = false,
requireCharging = false
)
}
}

class LorraineConstraintsDefinition internal constructor() {
var requiredNetwork: Boolean = false
var requiredBatteryNotLow: Boolean = false
var requireCharging: Boolean = false


fun build() = LorraineConstraints(
requireNetwork = requiredNetwork,
requireBatteryNotLow = requiredBatteryNotLow
requireBatteryNotLow = requiredBatteryNotLow,
requireCharging = requireCharging
)

}
8 changes: 7 additions & 1 deletion lorraine/src/iosMain/kotlin/io/dot/lorraine/Platform.ios.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package io.dot.lorraine

import io.dot.lorraine.constraint.BatteryNotLowCheck
import io.dot.lorraine.constraint.ChargingCheck
import io.dot.lorraine.constraint.ConnectivityCheck
import io.dot.lorraine.constraint.ConstraintCheck
import io.dot.lorraine.constraint.match
Expand Down Expand Up @@ -47,6 +48,11 @@ internal class IOSPlatform(
scope = scope,
onChange = ::constraintChanged,
logger = logger
),
ChargingCheck(
scope = scope,
onChange = ::constraintChanged,
logger = logger
)
)

Expand Down Expand Up @@ -111,7 +117,7 @@ internal class IOSPlatform(
operation: LorraineOperation
) {
requireNotNull(operation.operations.firstOrNull()) {
"Operations shoud not be empty"
"Operations should not be empty"
}
val queue = queues.getOrElse(queueId) { createQueue(queueId) }
var previous: NSOperation? = null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package io.dot.lorraine.constraint

import io.dot.lorraine.dsl.LorraineConstraints
import io.dot.lorraine.logger.LorraineLogger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import okio.Closeable
import platform.Foundation.NSNotificationCenter
import platform.Foundation.NSOperationQueue
import platform.UIKit.UIDevice
import platform.UIKit.UIDeviceBatteryState
import platform.UIKit.UIDeviceBatteryStateDidChangeNotification

internal class ChargingCheck(
scope: CoroutineScope,
onChange: () -> Unit,
logger: LorraineLogger
) : ConstraintCheck {

private val observer = AppleChargingObserver()

private val _value = MutableStateFlow(false)

init {
observer.setListener(
object : ChargingObserver.Listener {
override fun chargingChanged(isCharging: Boolean) {
_value.update { isCharging }
}
}
)

scope.launch {
_value.onEach { logger.info("ChargingCheck: $it") }.collect { onChange() }
}
}

override suspend fun match(constraints: LorraineConstraints): Boolean {
if (!constraints.requireCharging)
return true

return _value.value
}

}

internal class AppleChargingObserver : ChargingObserver, Closeable {
private var listener: ChargingObserver.Listener? = null
private var stateObserver: Any? = null

override fun setListener(listener: ChargingObserver.Listener) {
this.listener = listener
UIDevice.currentDevice.batteryMonitoringEnabled = true

val center = NSNotificationCenter.defaultCenter

stateObserver = center.addObserverForName(
UIDeviceBatteryStateDidChangeNotification,
null,
NSOperationQueue.mainQueue
) { _ -> notifyListener() }

notifyListener()
}

private fun notifyListener() {
listener?.chargingChanged(isCharging())
}

private fun isCharging(): Boolean {
val device = UIDevice.currentDevice
val batteryState = device.batteryState

return batteryState == UIDeviceBatteryState.UIDeviceBatteryStateCharging
}

override fun close() {
stateObserver?.let { NSNotificationCenter.defaultCenter.removeObserver(it) }
UIDevice.currentDevice.batteryMonitoringEnabled = false
}
}

internal interface ChargingObserver : Closeable {
/**
* Sets the listener
*
* Implementation must call [listener] shortly after [setListener] returns to let the callers know about the initial state.
*/
fun setListener(listener: Listener)

interface Listener {
fun chargingChanged(isCharging: Boolean)
}
}