Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 apps/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,5 @@ dependencies {
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
implementation("com.ditto:dittochat:1.0.1")
implementation(project(":dittochat"))
}
7 changes: 7 additions & 0 deletions apps/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@
android:usesPermissionFlags="neverForLocation"
tools:targetApi="tiramisu" />

<!-- Required to post notifications on Android 13+ (API 33+). The runtime permission
prompt is shown in MainActivity.requestPermissions(). On Android 12 and below
the permission is granted automatically and this declaration is a no-op. -->
<uses-permission
android:name="android.permission.POST_NOTIFICATIONS"
tools:targetApi="tiramisu" />

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package com.ditto.dittochatandroiddemo

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
Expand All @@ -10,15 +15,37 @@ import com.ditto.dittochat.ui.ChatScreenViewModel
import com.ditto.dittochat.ui.DittoChatUI
import com.ditto.dittochat.ui.RoomEditViewModel
import com.ditto.dittochat.ui.RoomsListScreenViewModel
import kotlinx.coroutines.flow.StateFlow

/**
* Root navigation graph for the DittoChat demo app.
*
* @param notificationRoomId A [StateFlow] emitted by [MainActivity] when the user taps a
* DittoChat notification. Whenever a non-null value arrives, the nav graph navigates
* directly to that room's chat screen, bypassing the rooms list.
*/
@Composable
fun MyAppNavigation(dittoChat: DittoChat) {
fun MyAppNavigation(
dittoChat: DittoChat,
notificationRoomId: StateFlow<String?>
) {
val navController = rememberNavController()
var roomIdState = remember { "" }
var roomIdState by remember { mutableStateOf("") }
val chatViewModel = remember { ChatScreenViewModel(dittoChat.p2pStore, dittoChat) }

// Observe notification taps. When a room ID arrives, navigate to that room.
val pendingRoomId by notificationRoomId.collectAsState()
LaunchedEffect(pendingRoomId) {
val roomId = pendingRoomId ?: return@LaunchedEffect
roomIdState = roomId
// Navigate to the chatroom destination; popUpTo prevents stacking duplicates.
navController.navigate("chatroom") {
launchSingleTop = true
}
}

NavHost(navController = navController, startDestination = "home") {
composable("home") {
// Content for your Home Screen
DittoChatUI(dittoChat).RoomsListView(
RoomsListScreenViewModel(
dittoChat,
Expand All @@ -31,8 +58,6 @@ fun MyAppNavigation(dittoChat: DittoChat) {
}
}
composable("chatroom") {

// Content for your Detail Screen
DittoChatUI(dittoChat).ChatRoomView(
chatViewModel,
roomId = roomIdState,
Expand All @@ -43,4 +68,4 @@ fun MyAppNavigation(dittoChat: DittoChat) {
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package com.ditto.dittochatandroiddemo

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.ditto.dittochat.DittoChat
import com.ditto.dittochat.DittoChatImpl
import com.ditto.dittochat.DittoChatNotificationKey
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import live.ditto.Ditto
import live.ditto.DittoIdentity
Expand All @@ -25,31 +32,40 @@ class MainActivity : ComponentActivity() {

lateinit var dittoChat: DittoChat
lateinit var ditto: Ditto
private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { this.ditto.refreshPermissions() }

/**
* Emits the room ID extracted from a notification tap. Collected by [MyAppNavigation] via
* [LaunchedEffect] so the nav controller can navigate to the correct chat room.
*
* Set in both [onCreate] (cold start from tap) and [onNewIntent] (tap while app is running).
*/
val notificationRoomId = MutableStateFlow<String?>(null)

private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { this.ditto.refreshPermissions() }

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestPermissions()

val baseDir = File(this.filesDir, "ditto")
val userDir = File(baseDir, "user_ditto_chat_demo")

// Ensure directory exists
userDir.mkdirs()

val userDependencies = DefaultAndroidDittoDependencies(this)
val playgroundToken = ""
val appId = ""
val cloudEndpoint = ""
val userId = ""
// Create user Ditto instance with appropriate identity
val userIdentity =
// Use playground identity when playground token is available
DittoIdentity.OnlinePlayground(
dependencies = userDependencies,
appId = appId,
token = playgroundToken,
enableDittoCloudSync = false,
customAuthUrl = "https://${cloudEndpoint}"
)

val userIdentity = DittoIdentity.OnlinePlayground(
dependencies = userDependencies,
appId = appId,
token = playgroundToken,
enableDittoCloudSync = false,
customAuthUrl = "https://${cloudEndpoint}"
)

ditto = Ditto(userDependencies, userIdentity)
lifecycleScope.launch {
Expand All @@ -59,17 +75,52 @@ class MainActivity : ComponentActivity() {
ditto.disableSyncWithV3()
ditto.startSync()

enableEdgeToEdge()
// If this Activity was launched by tapping a DittoChat notification, extract the
// room ID and make it available to the Compose navigation graph.
handleNotificationIntent(intent)

setContent {
MyAppNavigation(dittoChat)
MyAppNavigation(dittoChat, notificationRoomId)
}
}

/**
* Called when the Activity is already running and a notification is tapped
* ([Intent.FLAG_ACTIVITY_SINGLE_TOP] is set in the PendingIntent).
*/
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleNotificationIntent(intent)
}

// -----------------------------------------------------------------------------------------
// Permission handling
// -----------------------------------------------------------------------------------------

fun requestPermissions() {
val missing = DittoSyncPermissions(this).missingPermissions()
val missing = DittoSyncPermissions(this).missingPermissions().toMutableList()

// Android 13+ requires POST_NOTIFICATIONS at runtime.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(
this, Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
missing.add(Manifest.permission.POST_NOTIFICATIONS)
}
}

if (missing.isNotEmpty()) {
requestPermissionLauncher.launch(missing)
requestPermissionLauncher.launch(missing.toTypedArray())
}
}
}

// -----------------------------------------------------------------------------------------
// Notification deep-link
// -----------------------------------------------------------------------------------------

private fun handleNotificationIntent(intent: Intent?) {
val roomId = intent?.getStringExtra(DittoChatNotificationKey.ROOM_ID) ?: return
notificationRoomId.value = roomId
}
}
3 changes: 3 additions & 0 deletions apps/android/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ gson = "2.13.2"
hiltAndroid = "2.57.1"
hiltNavigationCompose = "1.3.0"
kotlin = "2.2.10"
ksp = "2.2.10-2.0.2"
coreKtx = "1.17.0"
junit = "4.13.2"
junitVersion = "1.3.0"
Expand Down Expand Up @@ -56,3 +57,5 @@ android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
android-library = { id = "com.android.library", version.ref = "agp" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hiltAndroid" }
5 changes: 5 additions & 0 deletions apps/android/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ dependencyResolutionManagement {

rootProject.name = "DittoChatAndroidDemo"
include(":app")

// Pull the DittoChat SDK directly from the local repository so changes to sdks/kotlin
// are reflected in the demo app immediately without publishing to Maven Central.
include(":dittochat")
project(":dittochat").projectDir = File("../../sdks/kotlin")
40 changes: 20 additions & 20 deletions apps/ios/DittoChatDemo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
/* Begin PBXBuildFile section */
0750235F2E985C0100AF7194 /* DittoChatCore in Frameworks */ = {isa = PBXBuildFile; productRef = 0750235E2E985C0100AF7194 /* DittoChatCore */; };
075023612E985C0100AF7194 /* DittoChatUI in Frameworks */ = {isa = PBXBuildFile; productRef = 075023602E985C0100AF7194 /* DittoChatUI */; };
781173362F4E112300EB7936 /* DittoChatCore in Frameworks */ = {isa = PBXBuildFile; productRef = 781173352F4E112300EB7936 /* DittoChatCore */; };
781173382F4E112300EB7936 /* DittoChatUI in Frameworks */ = {isa = PBXBuildFile; productRef = 781173372F4E112300EB7936 /* DittoChatUI */; };
7873E56D2E68F390003DC9B2 /* DittoChatCore in Frameworks */ = {isa = PBXBuildFile; productRef = 7873E56C2E68F390003DC9B2 /* DittoChatCore */; };
7873E56F2E68F390003DC9B2 /* DittoChatUI in Frameworks */ = {isa = PBXBuildFile; productRef = 7873E56E2E68F390003DC9B2 /* DittoChatUI */; };
7873E5782E68F47B003DC9B2 /* DittoChatCore in Frameworks */ = {isa = PBXBuildFile; productRef = 7873E5772E68F47B003DC9B2 /* DittoChatCore */; };
Expand All @@ -21,6 +19,8 @@
7873E58A2E68F5A2003DC9B2 /* DittoChatUI in Frameworks */ = {isa = PBXBuildFile; productRef = 7873E5892E68F5A2003DC9B2 /* DittoChatUI */; };
78E90C082E6A1E3C00B01E9B /* DittoChatCore in Frameworks */ = {isa = PBXBuildFile; productRef = 78E90C072E6A1E3C00B01E9B /* DittoChatCore */; };
78E90C0A2E6A1E3C00B01E9B /* DittoChatUI in Frameworks */ = {isa = PBXBuildFile; productRef = 78E90C092E6A1E3C00B01E9B /* DittoChatUI */; };
78FDF4202F65CE2A000D1840 /* DittoChatCore in Frameworks */ = {isa = PBXBuildFile; productRef = 78FDF41F2F65CE2A000D1840 /* DittoChatCore */; };
78FDF4222F65CE2A000D1840 /* DittoChatUI in Frameworks */ = {isa = PBXBuildFile; productRef = 78FDF4212F65CE2A000D1840 /* DittoChatUI */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand Down Expand Up @@ -58,9 +58,9 @@
0750235F2E985C0100AF7194 /* DittoChatCore in Frameworks */,
7873E5882E68F5A2003DC9B2 /* DittoChatCore in Frameworks */,
7873E57D2E68F4F1003DC9B2 /* DittoChatCore in Frameworks */,
781173382F4E112300EB7936 /* DittoChatUI in Frameworks */,
78FDF4222F65CE2A000D1840 /* DittoChatUI in Frameworks */,
7873E57F2E68F4F1003DC9B2 /* DittoChatUI in Frameworks */,
781173362F4E112300EB7936 /* DittoChatCore in Frameworks */,
78FDF4202F65CE2A000D1840 /* DittoChatCore in Frameworks */,
7873E5782E68F47B003DC9B2 /* DittoChatCore in Frameworks */,
78E90C0A2E6A1E3C00B01E9B /* DittoChatUI in Frameworks */,
7873E57A2E68F47B003DC9B2 /* DittoChatUI in Frameworks */,
Expand Down Expand Up @@ -121,8 +121,8 @@
78E90C092E6A1E3C00B01E9B /* DittoChatUI */,
0750235E2E985C0100AF7194 /* DittoChatCore */,
075023602E985C0100AF7194 /* DittoChatUI */,
781173352F4E112300EB7936 /* DittoChatCore */,
781173372F4E112300EB7936 /* DittoChatUI */,
78FDF41F2F65CE2A000D1840 /* DittoChatCore */,
78FDF4212F65CE2A000D1840 /* DittoChatUI */,
);
productName = DittoChatDemo;
productReference = 7873E55D2E68F35E003DC9B2 /* DittoChatDemo.app */;
Expand Down Expand Up @@ -153,7 +153,7 @@
mainGroup = 7873E5542E68F35E003DC9B2;
minimizedProjectReferenceProxies = 1;
packageReferences = (
781173342F4E112300EB7936 /* XCRemoteSwiftPackageReference "DittoChat" */,
78FDF41E2F65CE2A000D1840 /* XCRemoteSwiftPackageReference "DittoChat" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = 7873E55E2E68F35E003DC9B2 /* Products */;
Expand Down Expand Up @@ -428,12 +428,12 @@
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
781173342F4E112300EB7936 /* XCRemoteSwiftPackageReference "DittoChat" */ = {
78FDF41E2F65CE2A000D1840 /* XCRemoteSwiftPackageReference "DittoChat" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/getditto/DittoChat";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 6.0.2;
branch = "feature/FORGE-740-Chat-Notifications";
kind = branch;
};
};
/* End XCRemoteSwiftPackageReference section */
Expand All @@ -447,16 +447,6 @@
isa = XCSwiftPackageProductDependency;
productName = DittoChatUI;
};
781173352F4E112300EB7936 /* DittoChatCore */ = {
isa = XCSwiftPackageProductDependency;
package = 781173342F4E112300EB7936 /* XCRemoteSwiftPackageReference "DittoChat" */;
productName = DittoChatCore;
};
781173372F4E112300EB7936 /* DittoChatUI */ = {
isa = XCSwiftPackageProductDependency;
package = 781173342F4E112300EB7936 /* XCRemoteSwiftPackageReference "DittoChat" */;
productName = DittoChatUI;
};
7873E56C2E68F390003DC9B2 /* DittoChatCore */ = {
isa = XCSwiftPackageProductDependency;
productName = DittoChatCore;
Expand Down Expand Up @@ -497,6 +487,16 @@
isa = XCSwiftPackageProductDependency;
productName = DittoChatUI;
};
78FDF41F2F65CE2A000D1840 /* DittoChatCore */ = {
isa = XCSwiftPackageProductDependency;
package = 78FDF41E2F65CE2A000D1840 /* XCRemoteSwiftPackageReference "DittoChat" */;
productName = DittoChatCore;
};
78FDF4212F65CE2A000D1840 /* DittoChatUI */ = {
isa = XCSwiftPackageProductDependency;
package = 78FDF41E2F65CE2A000D1840 /* XCRemoteSwiftPackageReference "DittoChat" */;
productName = DittoChatUI;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 7873E5552E68F35E003DC9B2 /* Project object */;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading