UI changes and hiding events

Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
This commit is contained in:
sowjanyakch 2025-04-08 17:14:30 +02:00 committed by Marcel Hibbe
parent 83b8915787
commit fd47146729
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
10 changed files with 216 additions and 21 deletions

View File

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 13,
"identityHash": "a521f027909f69f4c7d1855f84a2e67f",
"identityHash": "506abc931eb3b657cafe6ad1b25f635d",
"entities": [
{
"tableName": "User",
@ -138,7 +138,7 @@
},
{
"tableName": "Conversations",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `displayName` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `avatarVersion` TEXT NOT NULL, `callFlag` INTEGER NOT NULL, `callRecording` INTEGER NOT NULL, `callStartTime` INTEGER NOT NULL, `canDeleteConversation` INTEGER NOT NULL, `canLeaveConversation` INTEGER NOT NULL, `canStartCall` INTEGER NOT NULL, `description` TEXT NOT NULL, `hasCall` INTEGER NOT NULL, `hasPassword` INTEGER NOT NULL, `isCustomAvatar` INTEGER NOT NULL, `isFavorite` INTEGER NOT NULL, `lastActivity` INTEGER NOT NULL, `lastCommonReadMessage` INTEGER NOT NULL, `lastMessage` TEXT, `lastPing` INTEGER NOT NULL, `lastReadMessage` INTEGER NOT NULL, `lobbyState` TEXT NOT NULL, `lobbyTimer` INTEGER NOT NULL, `messageExpiration` INTEGER NOT NULL, `name` TEXT NOT NULL, `notificationCalls` INTEGER NOT NULL, `notificationLevel` TEXT NOT NULL, `objectType` TEXT NOT NULL, `participantType` TEXT NOT NULL, `permissions` INTEGER NOT NULL, `readOnly` TEXT NOT NULL, `recordingConsent` INTEGER NOT NULL, `remoteServer` TEXT, `remoteToken` TEXT, `sessionId` TEXT NOT NULL, `status` TEXT, `statusClearAt` INTEGER, `statusIcon` TEXT, `statusMessage` TEXT, `type` TEXT NOT NULL, `unreadMention` INTEGER NOT NULL, `unreadMentionDirect` INTEGER NOT NULL, `unreadMessages` INTEGER NOT NULL, `hasArchived` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`accountId`) REFERENCES `User`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `displayName` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `avatarVersion` TEXT NOT NULL, `callFlag` INTEGER NOT NULL, `callRecording` INTEGER NOT NULL, `callStartTime` INTEGER NOT NULL, `canDeleteConversation` INTEGER NOT NULL, `canLeaveConversation` INTEGER NOT NULL, `canStartCall` INTEGER NOT NULL, `description` TEXT NOT NULL, `hasCall` INTEGER NOT NULL, `hasPassword` INTEGER NOT NULL, `isCustomAvatar` INTEGER NOT NULL, `isFavorite` INTEGER NOT NULL, `lastActivity` INTEGER NOT NULL, `lastCommonReadMessage` INTEGER NOT NULL, `lastMessage` TEXT, `lastPing` INTEGER NOT NULL, `lastReadMessage` INTEGER NOT NULL, `lobbyState` TEXT NOT NULL, `lobbyTimer` INTEGER NOT NULL, `messageExpiration` INTEGER NOT NULL, `name` TEXT NOT NULL, `notificationCalls` INTEGER NOT NULL, `notificationLevel` TEXT NOT NULL, `objectType` TEXT NOT NULL, `objectId` TEXT NOT NULL, `participantType` TEXT NOT NULL, `permissions` INTEGER NOT NULL, `readOnly` TEXT NOT NULL, `recordingConsent` INTEGER NOT NULL, `remoteServer` TEXT, `remoteToken` TEXT, `sessionId` TEXT NOT NULL, `status` TEXT, `statusClearAt` INTEGER, `statusIcon` TEXT, `statusMessage` TEXT, `type` TEXT NOT NULL, `unreadMention` INTEGER NOT NULL, `unreadMentionDirect` INTEGER NOT NULL, `unreadMessages` INTEGER NOT NULL, `hasArchived` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`accountId`) REFERENCES `User`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "internalId",
@ -320,6 +320,12 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "objectId",
"columnName": "objectId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "participantType",
"columnName": "participantType",
@ -743,7 +749,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a521f027909f69f4c7d1855f84a2e67f')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '506abc931eb3b657cafe6ad1b25f635d')"
]
}
}

View File

@ -38,12 +38,14 @@ import android.view.Gravity
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.AbsListView
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.PopupMenu
import android.widget.PopupWindow
import android.widget.TextView
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
@ -51,6 +53,7 @@ import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.view.ContextThemeWrapper
import androidx.cardview.widget.CardView
import androidx.compose.runtime.mutableStateOf
@ -82,6 +85,7 @@ import coil.request.CachePolicy
import coil.request.ImageRequest
import coil.target.Target
import coil.transform.CircleCropTransformation
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.nextcloud.android.common.ui.color.ColorUtil
import com.nextcloud.android.common.ui.theme.utils.ColorRole
@ -127,6 +131,7 @@ import com.nextcloud.talk.databinding.ActivityChatBinding
import com.nextcloud.talk.events.UserMentionClickEvent
import com.nextcloud.talk.events.WebSocketCommunicationEvent
import com.nextcloud.talk.extensions.loadAvatarOrImagePreview
import com.nextcloud.talk.jobs.DeleteConversationWorker
import com.nextcloud.talk.jobs.DownloadFileToCacheWorker
import com.nextcloud.talk.jobs.ShareOperationWorker
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
@ -210,11 +215,14 @@ import java.io.File
import java.io.IOException
import java.net.HttpURLConnection
import java.text.SimpleDateFormat
import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.util.Date
import java.util.Locale
import java.util.concurrent.ExecutionException
import javax.inject.Inject
import kotlin.collections.set
import kotlin.math.roundToInt
import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia
@ -2870,10 +2878,11 @@ class ChatActivity :
setActionBarTitle()
}
if (currentConversation?.objectType == ConversationEnums.ObjectType.EVENT) {
eventConversationMenuItem = menu.findItem(R.id.conversation_event_icon)
eventConversationMenuItem = menu.findItem(R.id.conversation_event)
} else {
menu.removeItem(R.id.conversation_event_icon)
menu.removeItem(R.id.conversation_event)
}
return true
}
@ -2922,7 +2931,6 @@ class ChatActivity :
menu.removeItem(R.id.conversation_voice_call)
}
}
return true
}
@ -2953,9 +2961,139 @@ class ChatActivity :
true
}
R.id.conversation_event -> {
val anchorView = findViewById<View>(R.id.conversation_event)
showPopupWindow(anchorView)
true
}
else -> super.onOptionsItemSelected(item)
}
private fun showPopupWindow(anchorView: View) {
val popupView = layoutInflater.inflate(R.layout.item_event_schedule, null)
val titleTextView = popupView.findViewById<TextView>(R.id.event_scheduled)
val subtitleTextView = popupView.findViewById<TextView>(R.id.meetingTime)
val deleteConversation = popupView.findViewById<TextView>(R.id.delete_conversation)
val popupWindow = PopupWindow(
popupView,
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
true
)
popupWindow.isOutsideTouchable = true
popupWindow.isFocusable = true
popupWindow.showAsDropDown(anchorView, 0, -anchorView.height)
val meetingStatus = showEventSchedule()
titleTextView.text = "Scheduled"
subtitleTextView.text = meetingStatus
if (meetingStatus == "Meeting Ended" && currentConversation?.canDeleteConversation == true) {
deleteConversation.visibility = View.VISIBLE
deleteConversation.setOnClickListener {
val dialogBuilder = MaterialAlertDialogBuilder(it.context)
.setIcon(
viewThemeUtils.dialog
.colorMaterialAlertDialogIcon(context, R.drawable.ic_delete_black_24dp)
)
.setTitle(R.string.nc_delete_call)
.setMessage(R.string.nc_delete_conversation_more)
.setPositiveButton(R.string.nc_delete) { _, _ ->
currentConversation?.let { conversation ->
deleteConversation(conversation)
}
}
.setNegativeButton(R.string.nc_cancel) { _, _ ->
}
viewThemeUtils.dialog
.colorMaterialAlertDialogBackground(it.context, dialogBuilder)
val dialog = dialogBuilder.show()
viewThemeUtils.platform.colorTextButtons(
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
)
}
} else {
deleteConversation.visibility = View.GONE
}
}
private fun deleteConversation(conversation: ConversationModel) {
val data = Data.Builder()
data.putLong(
KEY_INTERNAL_USER_ID,
conversationUser?.id!!
)
data.putString(KEY_ROOM_TOKEN, conversation.token)
val deleteConversationWorker =
OneTimeWorkRequest.Builder(DeleteConversationWorker::class.java).setInputData(data.build()).build()
WorkManager.getInstance().enqueue(deleteConversationWorker)
WorkManager.getInstance(context).getWorkInfoByIdLiveData(deleteConversationWorker.id)
.observeForever { workInfo: WorkInfo? ->
if (workInfo != null) {
when (workInfo.state) {
WorkInfo.State.SUCCEEDED -> {
val successMessage = String.format(
context.resources.getString(R.string.deleted_conversation),
conversation.displayName
)
Snackbar.make(binding.root, successMessage, Snackbar.LENGTH_LONG).show()
finish()
}
WorkInfo.State.FAILED -> {
val errorMessage = context.resources.getString(R.string.nc_common_error_sorry)
Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_LONG).show()
}
else -> {
}
}
}
}
}
private fun showEventSchedule(): String {
val objectId = currentConversation?.objectId ?: ""
val status = getMeetingSchedule(objectId)
return status
}
private fun getMeetingSchedule(objectId: String): String {
val timestamps = objectId.split("#")
if (timestamps.size != 2) return "Invalid Time"
val startEpoch = timestamps[0].toLong()
val endEpoch = timestamps[1].toLong()
val startDateTime = Instant.ofEpochSecond(startEpoch).atZone(ZoneId.systemDefault())
val endDateTime = Instant.ofEpochSecond(endEpoch).atZone(ZoneId.systemDefault())
val now = ZonedDateTime.now(ZoneId.systemDefault())
return when {
now.isBefore(startDateTime) -> {
val isToday = startDateTime.toLocalDate().isEqual(now.toLocalDate())
val isTomorrow = startDateTime.toLocalDate().isEqual(now.toLocalDate().plusDays(1))
when {
isToday -> "Today at ${startDateTime.format(DateTimeFormatter.ofPattern("HH:mm"))}"
isTomorrow -> "Tomorrow at ${startDateTime.format(DateTimeFormatter.ofPattern("HH:mm"))}"
else -> startDateTime.format(DateTimeFormatter.ofPattern("MMM d, yyyy, HH:mm"))
}
}
now.isAfter(endDateTime) -> "Meeting Ended"
else -> "Ongoing"
}
}
private fun showSharedItems() {
val intent = Intent(this, SharedItemsActivity::class.java)
intent.putExtra(KEY_CONVERSATION_NAME, currentConversation?.displayName)

View File

@ -185,6 +185,11 @@ class ConversationInfoEditActivity : BaseActivity() {
binding.conversationDescription.isEnabled = false
}
if (conversation?.objectType == ConversationEnums.ObjectType.EVENT) {
binding.conversationName.isEnabled = false
binding.conversationDescription.isEnabled = false
}
loadConversationAvatar()
}
@ -266,6 +271,7 @@ class ConversationInfoEditActivity : BaseActivity() {
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
super.onPrepareOptionsMenu(menu)
return true
}

View File

@ -18,6 +18,7 @@ import com.nextcloud.talk.data.database.model.ConversationEntity
import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.utils.CapabilitiesUtil.isUserStatusAvailable
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import io.reactivex.Observer
@ -121,7 +122,15 @@ class OfflineFirstConversationsRepository @Inject constructor(
.observeOn(AndroidSchedulers.mainThread())
.blockingSingle()
conversationsFromSync = conversationsList.map {
val currentTime = System.currentTimeMillis() / 1000
val conversationListWithoutEvents = conversationsList.filterNot { conversation ->
conversation.objectType == ConversationEnums.ObjectType.EVENT &&
conversation.objectId.split("#")[0].toLong() - currentTime >
AGE_THRESHOLD_FOR_EVENT_CONVERSATIONS
}
conversationsFromSync = conversationListWithoutEvents.map {
it.asEntity(user.id!!)
}
@ -156,5 +165,6 @@ class OfflineFirstConversationsRepository @Inject constructor(
companion object {
val TAG = OfflineFirstConversationsRepository::class.simpleName
private const val AGE_THRESHOLD_FOR_EVENT_CONVERSATIONS: Long = 86400
}
}

View File

@ -34,6 +34,7 @@ fun ConversationModel.asEntity() =
unreadMention = unreadMention,
lastMessage = lastMessage?.let { LoganSquare.serialize(lastMessage) },
objectType = objectType,
objectId = objectId,
notificationLevel = notificationLevel,
conversationReadOnlyState = conversationReadOnlyState,
lobbyState = lobbyState,
@ -85,6 +86,7 @@ fun ConversationEntity.asModel() =
lastMessage = lastMessage?.let
{ LoganSquare.parse(lastMessage, ChatMessageJson::class.java) },
objectType = objectType,
objectId = objectId,
notificationLevel = notificationLevel,
conversationReadOnlyState = conversationReadOnlyState,
lobbyState = lobbyState,
@ -135,6 +137,7 @@ fun Conversation.asEntity(accountId: Long) =
unreadMention = unreadMention,
lastMessage = lastMessage?.let { LoganSquare.serialize(lastMessage) },
objectType = objectType,
objectId = objectId,
notificationLevel = notificationLevel,
conversationReadOnlyState = conversationReadOnlyState,
lobbyState = lobbyState,

View File

@ -78,6 +78,7 @@ data class ConversationEntity(
@ColumnInfo(name = "notificationCalls") var notificationCalls: Int = 0,
@ColumnInfo(name = "notificationLevel") var notificationLevel: ConversationEnums.NotificationLevel,
@ColumnInfo(name = "objectType") var objectType: ConversationEnums.ObjectType,
@ColumnInfo(name = "objectId") var objectId: String,
@ColumnInfo(name = "participantType") var participantType: Participant.ParticipantType,
@ColumnInfo(name = "permissions") var permissions: Int = 0,
@ColumnInfo(name = "readOnly") var conversationReadOnlyState: ConversationEnums.ConversationReadOnlyState,

View File

@ -89,6 +89,7 @@ class ConversationModel(
unreadMention = conversation.unreadMention,
lastMessage = conversation.lastMessage,
objectType = conversation.objectType.let { ConversationEnums.ObjectType.valueOf(it.name) },
objectId = conversation.objectId,
notificationLevel = conversation.notificationLevel.let {
ConversationEnums.NotificationLevel.valueOf(
it.name

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2025 Sowjanya Kota <sowjanya.kota@gmail.com>
~ SPDX-License-Identifier: GPL-3.0-or-later
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="32dp"
android:background="@color/bg_bottom_sheet">
<TextView
android:id="@+id/event_scheduled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Scheduled"
android:textStyle="bold"
android:textSize="16sp" />
<TextView
android:id="@+id/meetingTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp"
android:text="Meeting at 8:00 pm"/>
<TextView
android:id="@+id/delete_conversation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Delete Conversation"
android:textColor="@android:color/holo_red_dark"
android:visibility = "gone"
android:paddingTop="16dp"/>
</LinearLayout>

View File

@ -9,23 +9,12 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/conversation_event_icon"
android:id="@+id/conversation_event"
android:icon="@drawable/calendar_clock"
android:orderInCategory="0"
android:title="@string/nc_event_conversation_menu"
app:showAsAction="ifRoom">
<menu>
<item
android:id="@+id/action_option_one"
android:title="Scheduled event" />
<item
android:id="@+id/action_option_two"
android:title="@string/nc_delete_call"
android:icon = "@drawable/ic_delete"/>
</menu>
</item>
<item

View File

@ -242,6 +242,7 @@ How to translate with transifex:
<string name="nc_rename">Rename conversation</string>
<string name="nc_rename_confirm">Rename</string>
<string name="nc_delete_call">Delete conversation</string>
<string name="nc_event_schedule">Schedule</string>
<string name="nc_delete">Delete</string>
<string name="nc_delete_all">Delete all</string>
<string name="nc_delete_conversation_more">If you delete the conversation, it will also be deleted for all other participants.</string>