Skip to content

Commit

Permalink
- fix issues on ios, setup permissions
Browse files Browse the repository at this point in the history
 - more memory for idea intellij backend (fleet config)
  • Loading branch information
rodvar committed Dec 4, 2024
1 parent 8ffd7ed commit 0cb54c9
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 70 deletions.
13 changes: 11 additions & 2 deletions iosClient/iosClient/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>network.bisq.mobile.ios.backgroundtask</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
Expand All @@ -20,13 +26,16 @@
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
<key>UILaunchScreen</key>
<dict/>
<key>UIRequiredDeviceCapabilities</key>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ class LifecycleAwareComposeViewController: UIViewController {
init(presenter: MainPresenter) {
self.presenter = presenter
super.init(nibName: nil, bundle: nil)
presenter.attachView(view: self)
}

required init?(coder: NSCoder) {
Expand Down Expand Up @@ -64,6 +63,7 @@ class LifecycleAwareComposeViewController: UIViewController {

// Notify the child view controller that it was moved to a parent
mainViewController.didMove(toParent: self)
presenter.attachView(view: self)
}

// Equivalent to `onDestroy` in Android for final cleanup
Expand Down
Original file line number Diff line number Diff line change
@@ -1,68 +1,107 @@
package network.bisq.mobile.domain.service.controller
package network.bisq.mobile.domain.service.controller

import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import network.bisq.mobile.utils.Logging
import platform.BackgroundTasks.*
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import network.bisq.mobile.utils.Logging
import platform.BackgroundTasks.*
import platform.Foundation.NSUUID
import platform.Foundation.setValue
import platform.UserNotifications.*

@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
actual class NotificationServiceController: ServiceController, Logging {

private val logScope = CoroutineScope(Dispatchers.Main)
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
actual class NotificationServiceController: ServiceController, Logging {

actual override fun startService() {
logDebug("Starting background service")
BGTaskScheduler.sharedScheduler.registerForTaskWithIdentifier(identifier = "network.bisq.mobile.ios.backgroundtask", usingQueue = null) { task ->
handleBackgroundTask(task as BGProcessingTask)
}
scheduleBackgroundTask()
logDebug("Background service started")
}
private var isRunning = false

actual override fun stopService() {
BGTaskScheduler.sharedScheduler.cancelAllTaskRequests()
logDebug("Background service stopped")
}
private val logScope = CoroutineScope(Dispatchers.Main)

actual fun pushNotification(title: String, message: String) {
// TODO
// val content = UNMutableNotificationContent().apply {
// this.title = title
// this.body = message
// TODO foreground notifications?
// UNUserNotificationCenter.currentNotificationCenter().delegate = object : UNUserNotificationCenterDelegateProtocol {
// override fun userNotificationCenter(
// center: UNUserNotificationCenter,
// willPresentNotification: UNNotification,
// withCompletionHandler: (UNNotificationPresentationOptions) -> Unit
// ) {
// withCompletionHandler(UNNotificationPresentationOptionsAlert or UNNotificationPresentationOptionsSound)
// }
// }
// val request = UNNotificationRequest.requestWithIdentifier(
// NSUUID().UUIDString,
// content,
// null
// )
// UNUserNotificationCenter.currentNotificationCenter().addNotificationRequest(request, null)
}

actual override fun isServiceRunning(): Boolean {
// iOS doesn't allow querying background task state directly
return false
}
actual override fun startService() {
if (isRunning) {
return
}
logDebug("Starting background service")
UNUserNotificationCenter.currentNotificationCenter().requestAuthorizationWithOptions(
UNAuthorizationOptionAlert or UNAuthorizationOptionSound or UNAuthorizationOptionBadge
) { granted, error ->
if (granted) {
logDebug("Notification permission granted.")

private fun handleBackgroundTask(task: BGProcessingTask) {
logDebug("Executing background task")
task.setTaskCompletedWithSuccess(true)
scheduleBackgroundTask() // Reschedule if needed
}
// TODO need to move to iOS callback -> didFinishLaunchingWithOptions
BGTaskScheduler.sharedScheduler.registerForTaskWithIdentifier(identifier = "network.bisq.mobile.ios.backgroundtask", usingQueue = null) { task ->
handleBackgroundTask(task as BGProcessingTask)
}
scheduleBackgroundTask()
logDebug("Background service started")
isRunning = true
} else {
logDebug("Notification permission denied: ${error?.localizedDescription}")
}
}
}

@OptIn(ExperimentalForeignApi::class)
private fun scheduleBackgroundTask() {
val request = BGProcessingTaskRequest("com.yourapp.backgroundtask").apply {
requiresNetworkConnectivity = true
actual override fun stopService() {
BGTaskScheduler.sharedScheduler.cancelAllTaskRequests()
logDebug("Background service stopped")
isRunning = false
}

actual fun pushNotification(title: String, message: String) {
val content = UNMutableNotificationContent().apply {
setValue(title, forKey = "title")
setValue(message, forKey = "body")
}

val request = UNNotificationRequest.requestWithIdentifier(
NSUUID().UUIDString, // Generates a unique identifier
content,
null // Trigger can be set to null for immediate delivery
)
UNUserNotificationCenter.currentNotificationCenter().addNotificationRequest(request) { error ->
if (error != null) {
println("Error adding notification request: ${error.localizedDescription}")
} else {
println("Notification added successfully")
}
}
}

actual override fun isServiceRunning(): Boolean {
// iOS doesn't allow querying background task state directly
return isRunning
}

private fun handleBackgroundTask(task: BGProcessingTask) {
logDebug("Executing background task")
task.setTaskCompletedWithSuccess(true)
scheduleBackgroundTask() // Reschedule if needed
}

@OptIn(ExperimentalForeignApi::class)
private fun scheduleBackgroundTask() {
val request = BGProcessingTaskRequest("com.yourapp.backgroundtask").apply {
requiresNetworkConnectivity = true
}
BGTaskScheduler.sharedScheduler.submitTaskRequest(request, null)
logDebug("Background task scheduled")
}
BGTaskScheduler.sharedScheduler.submitTaskRequest(request, null)
logDebug("Background task scheduled")
}

private fun logDebug(message: String) {
logScope.launch {
log.d { message }
private fun logDebug(message: String) {
logScope.launch {
log.d(message)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package network.bisq.mobile.presentation

import androidx.annotation.CallSuper
import androidx.navigation.NavHostController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import network.bisq.mobile.android.node.BuildNodeConfig
import network.bisq.mobile.client.shared.BuildConfig
import network.bisq.mobile.domain.getPlatformInfo
import network.bisq.mobile.domain.service.controller.NotificationServiceController
import network.bisq.mobile.presentation.ui.AppPresenter
import kotlin.random.Random


/**
Expand Down Expand Up @@ -40,9 +44,22 @@ open class MainPresenter(private val notificationServiceController: Notification
log.i { "iOS Client Version: ${BuildConfig.IOS_APP_VERSION}" }
log.i { "Android Client Version: ${BuildConfig.IOS_APP_VERSION}" }
log.i { "Android Node Version: ${BuildNodeConfig.APP_VERSION}" }
}

@CallSuper
override fun onViewAttached() {
super.onViewAttached()
notificationServiceController.startService()
// CoroutineScope(BackgroundDispatcher).launch {
// }
// sample code for push notifications sends a random message every 10 secs
CoroutineScope(BackgroundDispatcher).launch {
while (notificationServiceController.isServiceRunning()) {
val randomTitle = "Title ${Random.nextInt(1, 100)}"
val randomMessage = "Message ${Random.nextInt(1, 100)}"
notificationServiceController.pushNotification(randomTitle, randomMessage)
println("Pushed: $randomTitle - $randomMessage")
delay(10000) // 10 seconds
}
}
}

// Toggle action
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.jetbrains.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.flow.StateFlow
import network.bisq.mobile.presentation.ViewPresenter
import network.bisq.mobile.presentation.ui.components.SwipeBackIOSNavigationHandler
import network.bisq.mobile.presentation.ui.helpers.RememberPresenterLifecycle
import org.koin.compose.koinInject
import network.bisq.mobile.presentation.ui.navigation.Routes

Expand Down Expand Up @@ -41,20 +42,12 @@ fun App() {
var isNavControllerSet by remember { mutableStateOf(false) }
val presenter: AppPresenter = koinInject()

DisposableEffect(Unit) {
// For the main presenter use case we leave this for the moment the activity/viewcontroller respectively gets attached
// presenter.onViewAttached()
RememberPresenterLifecycle(presenter, {
getKoin().setProperty("RootNavController", rootNavController)
getKoin().setProperty("TabNavController", tabNavController)
presenter.setNavController(rootNavController)
isNavControllerSet = true

onDispose {
// Optional cleanup logic
// getKoin().setProperty("RootNavController", null)
// getKoin().setProperty("TabNavController", null)
}
}
})

val lyricist = rememberStrings()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,24 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import network.bisq.mobile.presentation.ViewPresenter

/**
* @param presenter
* @param onExecute <optional> callback after view attached
* @param onDispose <optional> callback before on view unnattaching
*/
@Composable
fun RememberPresenterLifecycle(presenter: ViewPresenter) {
fun RememberPresenterLifecycle(presenter: ViewPresenter, onExecute: (() -> Unit)? = null, onDispose: (() -> Unit)? = null) {
DisposableEffect(presenter) {
presenter.onViewAttached() // Called when the view is attached
onExecute?.let {
onExecute()
}

onDispose {
presenter.onViewUnattaching() // Called when the view is detached
onDispose?.let {
onDispose()
}
}
}
}

0 comments on commit 0cb54c9

Please sign in to comment.