Skip to content

Feat/chat/channel message screen #317

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1444623
feat: implement ChannelActivity and adapt MainActivity.kt
taghizadlaura Dec 13, 2024
2af41b9
feat: implement first working version of messages and channels betwee…
taghizadlaura Dec 13, 2024
a9f361f
feat: trying to extract channel creation
francelu Dec 13, 2024
9649773
fix: remove separators and dashes in generateChannelId()
taghizadlaura Dec 13, 2024
021c82a
feat: implement navigation to MessageScreen upon accepting an alert
taghizadlaura Dec 13, 2024
986bd4b
Merge branch 'refs/heads/feat/chat/client-setup' into feat/chat/chann…
taghizadlaura Dec 14, 2024
0b2f239
feat: update channel names to be "palName1 & palName2"
taghizadlaura Dec 14, 2024
69a878d
test: add ChatViewModel tests and adapt AlertListsScreenTest
taghizadlaura Dec 14, 2024
759710b
Merge branch 'refs/heads/main' into feat/chat/channel-message-screen
taghizadlaura Dec 16, 2024
62d8d12
feat: implement our own ChannelsScreen top app bar
francelu Dec 17, 2024
10fccd4
test: add runTest
francelu Dec 17, 2024
1aaa860
refactor: rename `ChannelsScreen.kt` to `ChannelsScreenContainer.kt`
francelu Dec 17, 2024
650ede3
test: implement tests for `ChannelsScreenContainer`
francelu Dec 17, 2024
c5b6b6e
fix: use try catch in `createChannel()`
francelu Dec 18, 2024
4e2b541
test: success createChannel test
francelu Dec 18, 2024
8511119
style: clean up code
taghizadlaura Dec 18, 2024
8e0ca69
Merge branch 'refs/heads/main' into feat/chat/channel-message-screen
taghizadlaura Dec 19, 2024
25405f2
feat: handle failure of generateCid in createChannel
taghizadlaura Dec 19, 2024
3f4a3eb
feat: adapt failure of createChannel in AlertLists
taghizadlaura Dec 19, 2024
c1c4ec5
test: add test for generateCid failure case in createChannel for Chat VM
taghizadlaura Dec 19, 2024
5d6c675
test: verify createChannel on accept pal's alert in AlertLists
taghizadlaura Dec 19, 2024
4e053b7
ci: add chat secrets to general worklfow
Dec 19, 2024
9630084
ci: update all secrets that were forgotten
Dec 19, 2024
7ca778f
Merge remote-tracking branch 'refs/remotes/origin/main' into feat/cha…
taghizadlaura Dec 19, 2024
e66d6dd
fix: add `com.android.periodpals.` prefix to Push Notifications in An…
taghizadlaura Dec 19, 2024
2364bf4
Merge remote-tracking branch 'refs/remotes/origin/main' into feat/cha…
taghizadlaura Dec 19, 2024
acff574
fix: implement nitpick from Bruno's review
taghizadlaura Dec 19, 2024
6abd522
Merge remote-tracking branch 'refs/remotes/origin/main' into feat/cha…
taghizadlaura Dec 19, 2024
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
21 changes: 20 additions & 1 deletion .github/workflows/AndroidBuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,31 @@ jobs:
SERVICE_KEY: ${{ secrets.SERVICE_KEY }}
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
POWERSYNC_URL: ${{ secrets.POWERSYNC_URL }}
STADIA_MAPS_KEY: ${{ secrets.STADIA_MAPS_KEY }}
STREAM_SDK_KEY: ${{ secrets.STREAM_SDK_KEY }}
STREAM_SDK_SECRET: ${{ secrets.STREAM_SDK_SECRET }}
run: |
sed -e "s|SAFE_DEFAULT_VALUE_SERVICE_KEY|$SERVICE_KEY|g;" \
-e "s|SAFE_DEFAULT_VALUE_SUPABASE_KEY|$SUPABASE_KEY|g;" \
-e "s|SAFE_DEFAULT_VALUE_SUPABASE_URL|$SUPABASE_URL|g;" \
-e "s|SAFE_DEFAULT_VALUE_POWERSYNC_URL|$POWERSYNC_URL|g;" secrets.defaults.properties > secrets.properties
-e "s|SAFE_DEFAULT_VALUE_POWERSYNC_URL|$POWERSYNC_URL|g;" \
-e "s|SAFE_DEFAULT_STADIA_MAPS_KEY|$STADIA_MAPS_KEY|g;" \
-e "s|SAFE_DEFAULT_VALUE_STREAM_SDK_KEY|$STREAM_SDK_KEY|g;" \
-e "s|SAFE_DEFAULT_VALUE_STREAM_SDK_SECRET|$STREAM_SDK_SECRET|g;" secrets.defaults.properties > secrets.properties

# Load google-services.json from the secrets
- name: Setup google-services.json
env:
GOOGLE_SERVICES: ${{ secrets.GOOGLE_SERVICES }}
run: |
echo "$GOOGLE_SERVICES" | base64 --decode > ./app/google-services.json

# Load service-account.json from the secrets
- name: Setup service-account.json
env:
SERVICE_ACCOUNT: ${{ secrets.SERVICE_ACCOUNT }}
run: |
echo "$SERVICE_ACCOUNT" | base64 --decode > ./supabase/functions/service-account.json

- name: Build with Gradle
run: ./gradlew build
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,17 @@ jobs:
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
POWERSYNC_URL: ${{ secrets.POWERSYNC_URL }}
STADIA_MAPS_KEY: ${{ secrets.STADIA_MAPS_KEY }}
STREAM_SDK_KEY: ${{ secrets.STREAM_SDK_KEY }}
STREAM_SDK_SECRET: ${{ secrets.STREAM_SDK_SECRET }}
run: |
sed -e "s|SAFE_DEFAULT_VALUE_SERVICE_KEY|$SERVICE_KEY|g;" \
-e "s|SAFE_DEFAULT_VALUE_SUPABASE_KEY|$SUPABASE_KEY|g;" \
-e "s|SAFE_DEFAULT_VALUE_SUPABASE_URL|$SUPABASE_URL|g;" \
-e "s|SAFE_DEFAULT_VALUE_POWERSYNC_URL|$POWERSYNC_URL|g;" \
-e "s|SAFE_DEFAULT_STADIA_MAPS_KEY|$STADIA_MAPS_KEY|g;" secrets.defaults.properties > secrets.properties
-e "s|SAFE_DEFAULT_STADIA_MAPS_KEY|$STADIA_MAPS_KEY|g;" \
-e "s|SAFE_DEFAULT_VALUE_STREAM_SDK_KEY|$STREAM_SDK_KEY|g;" \
-e "s|SAFE_DEFAULT_VALUE_STREAM_SDK_SECRET|$STREAM_SDK_SECRET|g;" secrets.defaults.properties > secrets.properties


# Load google-services.json from the secrets
- name: Setup google-services.json
Expand Down
15 changes: 11 additions & 4 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,18 @@
android:usesCleartextTraffic="false"
tools:targetApi="31">
<activity
android:name="com.android.periodpals.MainActivity"
android:name=".ChannelActivity"
android:exported="false"
android:label="@string/title_activity_channel"
android:screenOrientation="portrait"
android:theme="@style/Theme.PeriodPals"
android:windowSoftInputMode="adjustResize"/>
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.PeriodPals"
android:screenOrientation="portrait">
android:screenOrientation="portrait"
android:theme="@style/Theme.PeriodPals">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

Expand All @@ -45,6 +52,6 @@
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>

</application>

</manifest>
44 changes: 44 additions & 0 deletions app/src/main/java/com/android/periodpals/ChannelActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.android.periodpals

import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import io.getstream.chat.android.compose.ui.messages.MessagesScreen
import io.getstream.chat.android.compose.ui.theme.ChatTheme
import io.getstream.chat.android.compose.viewmodel.messages.MessagesViewModelFactory

/**
* Activity that displays a chat channel. It uses the [MessagesScreen] to display the messages of a
* channel.
*/
class ChannelActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Load the ID of the selected channel
val channelId = intent.getStringExtra(KEY_CHANNEL_ID)!!

// Add the MessagesScreen to your UI
setContent {
ChatTheme {
MessagesScreen(
viewModelFactory =
MessagesViewModelFactory(context = this, channelId = channelId, messageLimit = 30),
onBackPressed = { finish() })
}
}
}

// Create an intent to start this Activity, with a given channelId
companion object {
private const val KEY_CHANNEL_ID = "channelId"

fun getIntent(context: Context, channelId: String): Intent {
return Intent(context, ChannelActivity::class.java).apply {
putExtra(KEY_CHANNEL_ID, channelId)
}
}
}
}
24 changes: 14 additions & 10 deletions app/src/main/java/com/android/periodpals/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import com.android.periodpals.ui.alert.CreateAlertScreen
import com.android.periodpals.ui.alert.EditAlertScreen
import com.android.periodpals.ui.authentication.SignInScreen
import com.android.periodpals.ui.authentication.SignUpScreen
import com.android.periodpals.ui.chat.ChannelsScreenContainer
import com.android.periodpals.ui.map.MapScreen
import com.android.periodpals.ui.navigation.NavigationActions
import com.android.periodpals.ui.navigation.Route
Expand All @@ -51,7 +52,6 @@ import com.android.periodpals.ui.timer.TimerScreen
import com.google.android.gms.common.GoogleApiAvailability
import io.getstream.chat.android.client.ChatClient
import io.getstream.chat.android.client.logger.ChatLogLevel
import io.getstream.chat.android.compose.ui.channels.ChannelsScreen
import io.getstream.chat.android.compose.ui.theme.ChatTheme
import io.getstream.chat.android.models.InitializationState
import io.getstream.chat.android.offline.plugin.factory.StreamOfflinePluginFactory
Expand All @@ -64,7 +64,6 @@ import io.github.jan.supabase.storage.Storage
import org.osmdroid.config.Configuration

private const val TAG = "MainActivity"
private const val CHANNEL_SCREEN_TITLE = "Your Chats"

class MainActivity : ComponentActivity() {
private lateinit var gpsService: GPSServiceImpl
Expand Down Expand Up @@ -227,8 +226,10 @@ fun PeriodPalsApp(
AlertListsScreen(
alertViewModel,
authenticationViewModel,
userViewModel,
locationViewModel,
gpsService,
chatViewModel,
navigationActions)
}
composable(Screen.EDIT_ALERT) {
Expand All @@ -247,14 +248,17 @@ fun PeriodPalsApp(
InitializationState.COMPLETE -> {
Log.d(TAG, "Client initialization completed")
Log.d(TAG, "Client connection state $clientConnectionState")
ChannelsScreen(
title = CHANNEL_SCREEN_TITLE,
isShowingHeader = true,
onChannelClick = {
/** TODO: implement channels here */
},
onBackPressed = { navigationActions.navigateTo(Screen.ALERT_LIST) },
)

ChannelsScreenContainer(navigationActions = navigationActions) {
io.getstream.chat.android.compose.ui.channels.ChannelsScreen(
isShowingHeader = false,
onChannelClick = { channel ->
val intent = ChannelActivity.getIntent(context, channel.cid)
context.startActivity(intent)
},
onBackPressed = { navigationActions.navigateTo(Screen.ALERT_LIST) },
)
}
}
InitializationState.INITIALIZING -> {
Log.d(TAG, "Client initializing")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class ChatViewModel(private val chatClient: ChatClient) : ViewModel() {
io.getstream.chat.android.models.User(
id = uid, name = profile.name, image = userImage)

chatClient.connectUser(user = user, token = token).enqueue()
chatClient.connectUser(user = user, token = token).execute()
Log.d(TAG, "User connected successfully.")
onSuccess()
},
Expand All @@ -56,4 +56,93 @@ class ChatViewModel(private val chatClient: ChatClient) : ViewModel() {
onFailure(it)
})
}

/**
* Creates a channel between the current user and a pal.
*
* @param myUid The current user's UID.
* @param palUid The pal's UID.
* @param myName The current user's name.
* @param palName The pal's name.
* @return The CID (Channel ID) of the created channel of the format `channelType:channelId`.
*/
fun createChannel(myUid: String, palUid: String, myName: String, palName: String): String? {
Log.d(TAG, "Creating channel between $myUid and $palUid.")
val channelId = generateChannelId(myUid, palUid)
val channelType = "messaging"
var channelCid = ""

try {
channelCid = generateCid(channelType, channelId)
} catch (e: Exception) {
Log.e(TAG, "Failed to generate channel CID: ${e.message}")
return null
}

// Create a unique channel name for the user and pal
val channelName = generateChannelName(myUid, palUid, myName, palName)

chatClient
.createChannel(
channelType = channelType,
channelId = channelId,
memberIds = listOf(myUid, palUid),
extraData = mapOf("name" to channelName) // Use dynamic name based on users
)
.enqueue { result ->
if (result.isSuccess) {
Log.d(TAG, "Channel created successfully!")
} else {
Log.e(TAG, "Failed to create channel!")
}
}
return channelCid
}

/**
* Generates a unique channel name by combining and sorting the names of the current user and the
* pal.
*
* @param myUid The current user's UID.
* @param palUid The pal's UID.
* @param myName The current user's name.
* @param palName The pal's name.
* @return A unique channel name generated by concatenating the sorted names with a separator.
*/
fun generateChannelName(myUid: String, palUid: String, myName: String, palName: String): String {
return if (myUid == palUid) {
"Self-Chat"
} else {
val sortedNames = listOf(myName, palName).sorted()
sortedNames.joinToString(separator = " & ")
}
}

/**
* Generates a unique channel ID by combining and sorting the UIDs of the current user and the
* pal.
*
* @param myUid The current user's UID.
* @param palUid The pal's UID.
* @return A unique channel ID generated by concatenating the sorted UIDs without separators or
* dashes.
*/
fun generateChannelId(myUid: String, palUid: String): String {
val sortedUids = listOf(myUid, palUid).sorted()
return sortedUids.joinToString(separator = "").replace("-", "")
}

/**
* Generates a channel CID (Channel ID) in the format `channelType:channelId`.
*
* @param channelType The type of the channel (e.g., messaging).
* @param channelId The unique identifier for the channel.
* @return The generated CID in the format `channelType:channelId`.
* @throws IllegalArgumentException if `channelType` or `channelId` is empty.
*/
fun generateCid(channelType: String, channelId: String): String {
require(channelType.isNotEmpty()) { "channelType must not be empty" }
require(channelId.isNotEmpty()) { "channelId must not be empty" }
return listOf(channelType, channelId).joinToString(separator = ":")
}
}
5 changes: 5 additions & 0 deletions app/src/main/java/com/android/periodpals/resources/C.kt
Original file line number Diff line number Diff line change
Expand Up @@ -214,5 +214,10 @@ object C {
const val USEFUL_TIP = "usefulTip"
const val USEFUL_TIP_TEXT = "usefulTipText"
}

object ChannelsScreen {
const val SCREEN = "channelsScreen"
const val CHANNELS = "channels"
}
}
}
Loading
Loading