Avoid to send conversation and user via intent

sending too much data via intent always is a bad pattern which can lead to TransactionTooLargeException.

When OpenAI translation is enabled, the capabilities contain a ton of translation combinations. These capabilities are contained in 'currentUser' as well in 'selectedConversation'. So, TransactionTooLargeException was thrown.

this PR:
- avoids passing too much data as parcelables in intents (esp. conversation and user)
- introduces MVVM patterns to load required data (esp conversation) from backend (for now via requests, in the future from database first)
- introduces ConversationModel which is created out of the Conversation json model
- loads user data via injection when possible
- creates some quickfixes in ConversationBottomDialog, EntryMenuController and OperationsMenuController.

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2023-06-21 17:18:36 +02:00
parent e4c06d8ee8
commit 817ea1ab64
42 changed files with 1455 additions and 870 deletions

View file

@ -140,7 +140,7 @@
android:theme="@style/AppTheme.CallLauncher" />
<activity
android:name=".activities.CallNotificationActivity"
android:name=".callnotification.CallNotificationActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:excludeFromRecents="true"
android:launchMode="singleTask"

View file

@ -100,6 +100,7 @@ import com.nextcloud.talk.utils.NotificationUtils;
import com.nextcloud.talk.utils.VibrationUtils;
import com.nextcloud.talk.utils.animations.PulseAnimation;
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew;
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew;
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil;
import com.nextcloud.talk.utils.power.PowerManagerUtils;
import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder;
@ -186,7 +187,6 @@ import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID;
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN;
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_START_CALL_AFTER_ROOM_SWITCH;
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SWITCH_TO_ROOM;
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY;
@AutoInjector(NextcloudTalkApplication.class)
public class CallActivity extends CallBaseActivity {
@ -199,6 +199,9 @@ public class CallActivity extends CallBaseActivity {
@Inject
NcApi ncApi;
@Inject
CurrentUserProviderNew currentUserProvider;
@Inject
UserManager userManager;
@ -386,10 +389,11 @@ public class CallActivity extends CallBaseActivity {
hideNavigationIfNoPipAvailable();
conversationUser = currentUserProvider.getCurrentUser().blockingGet();
Bundle extras = getIntent().getExtras();
roomId = extras.getString(KEY_ROOM_ID, "");
roomToken = extras.getString(KEY_ROOM_TOKEN, "");
conversationUser = extras.getParcelable(KEY_USER_ENTITY);
conversationPassword = extras.getString(KEY_CONVERSATION_PASSWORD, "");
conversationName = extras.getString(KEY_CONVERSATION_NAME, "");
isVoiceOnlyCall = extras.getBoolean(KEY_CALL_VOICE_ONLY, false);
@ -1970,7 +1974,6 @@ public class CallActivity extends CallBaseActivity {
bundle.putBoolean(KEY_SWITCH_TO_ROOM, true);
bundle.putBoolean(KEY_START_CALL_AFTER_ROOM_SWITCH, true);
bundle.putString(KEY_ROOM_TOKEN, switchToRoomToken);
bundle.putParcelable(KEY_USER_ENTITY, conversationUser);
bundle.putBoolean(KEY_CALL_VOICE_ONLY, isVoiceOnlyCall);
intent.putExtras(bundle);
startActivity(intent);
@ -3151,7 +3154,7 @@ public class CallActivity extends CallBaseActivity {
}
@Override
void suppressFitsSystemWindows() {
public void suppressFitsSystemWindows() {
binding.controllerCallLayout.setFitsSystemWindows(false);
}

View file

@ -74,7 +74,7 @@ public abstract class CallBaseActivity extends BaseActivity {
getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
}
void hideNavigationIfNoPipAvailable(){
public void hideNavigationIfNoPipAvailable(){
if (!isPipModePossible()) {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
@ -160,9 +160,9 @@ public abstract class CallBaseActivity extends BaseActivity {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
abstract void updateUiForPipMode();
public abstract void updateUiForPipMode();
abstract void updateUiForNormalMode();
public abstract void updateUiForNormalMode();
abstract void suppressFitsSystemWindows();
public abstract void suppressFitsSystemWindows();
}

View file

@ -47,6 +47,7 @@ import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.R
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.callnotification.CallNotificationActivity
import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.controllers.ServerSelectionController
import com.nextcloud.talk.controllers.WebViewLoginController
@ -61,16 +62,13 @@ import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.SecurityUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.bundle.BundleKeys.ADD_ACCOUNT
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
import io.reactivex.Observer
import io.reactivex.SingleObserver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.parceler.Parcels
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
@ -282,45 +280,12 @@ class MainActivity : BaseActivity(), ActionBarProvider {
override fun onNext(roomOverall: RoomOverall) {
val bundle = Bundle()
bundle.putParcelable(KEY_USER_ENTITY, currentUser)
bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
bundle.putString(KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
// FIXME once APIv2 or later is used only, the createRoom already returns all the data
ncApi.getRoom(
credentials,
ApiUtils.getUrlForRoom(
apiVersion,
currentUser?.baseUrl,
roomOverall.ocs!!.data!!.token
)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<RoomOverall> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(roomOverall: RoomOverall) {
bundle.putParcelable(
KEY_ACTIVE_CONVERSATION,
Parcels.wrap(roomOverall.ocs!!.data)
)
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle)
startActivity(chatIntent)
}
override fun onError(e: Throwable) {
// unused atm
}
override fun onComplete() {
// unused atm
}
})
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle)
startActivity(chatIntent)
}
override fun onError(e: Throwable) {
@ -337,7 +302,9 @@ class MainActivity : BaseActivity(), ActionBarProvider {
super.onNewIntent(intent)
Log.d(TAG, "onNewIntent Activity: " + System.identityHashCode(this).toString())
val user = intent.getParcelableExtra<User>(KEY_USER_ENTITY)
val internalUserId = intent.extras?.getLong(BundleKeys.KEY_INTERNAL_USER_ID)
val user = userManager.getUserWithId(internalUserId!!).blockingGet()
if (user != null && userManager.setUserAsActive(user).blockingGet()) {
handleIntent(intent)
}

View file

@ -19,7 +19,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.activities
package com.nextcloud.talk.callnotification
import android.annotation.SuppressLint
import android.content.Intent
@ -30,36 +30,36 @@ import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationManagerCompat
import androidx.lifecycle.ViewModelProvider
import autodagger.AutoInjector
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.CallActivity
import com.nextcloud.talk.activities.CallBaseActivity
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.callnotification.viewmodel.CallNotificationViewModel
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.CallNotificationActivityBinding
import com.nextcloud.talk.extensions.loadUserAvatar
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.ConversationUtils
import com.nextcloud.talk.utils.NotificationUtils
import com.nextcloud.talk.utils.ParticipantPermissions
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CALL_VOICE_ONLY
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.Cache
import org.parceler.Parcels
import java.io.IOException
import javax.inject.Inject
@ -77,13 +77,18 @@ class CallNotificationActivity : CallBaseActivity() {
@Inject
lateinit var userManager: UserManager
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var callNotificationViewModel: CallNotificationViewModel
private val disposablesList: MutableList<Disposable> = ArrayList()
private var originalBundle: Bundle? = null
private var roomToken: String? = null
private var notificationTimestamp: Int? = null
private var userBeingCalled: User? = null
private var credentials: String? = null
private var currentConversation: Conversation? = null
var currentConversation: ConversationModel? = null
private var leavingScreen = false
private var handler: Handler? = null
private var binding: CallNotificationActivityBinding? = null
@ -98,19 +103,20 @@ class CallNotificationActivity : CallBaseActivity() {
val extras = intent.extras
roomToken = extras!!.getString(KEY_ROOM_TOKEN, "")
notificationTimestamp = extras.getInt(BundleKeys.KEY_NOTIFICATION_TIMESTAMP)
currentConversation = Parcels.unwrap(extras.getParcelable(KEY_ROOM))
userBeingCalled = extras.getParcelable(KEY_USER_ENTITY)
val internalUserId = extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID)
userBeingCalled = userManager.getUserWithId(internalUserId).blockingGet()
originalBundle = extras
credentials = ApiUtils.getCredentials(userBeingCalled!!.username, userBeingCalled!!.token)
callNotificationViewModel = ViewModelProvider(this, viewModelFactory)[CallNotificationViewModel::class.java]
initObservers()
if (userManager.setUserAsActive(userBeingCalled!!).blockingGet()) {
setCallDescriptionText()
if (currentConversation == null) {
handleFromNotification()
} else {
setUpAfterConversationIsKnown()
}
initClickListeners()
callNotificationViewModel.getRoom(userBeingCalled!!, roomToken!!)
}
}
@ -140,6 +146,78 @@ class CallNotificationActivity : CallBaseActivity() {
binding!!.hangupButton.setOnClickListener { hangup() }
}
private fun initObservers() {
val apiVersion = ApiUtils.getConversationApiVersion(
userBeingCalled,
intArrayOf(
ApiUtils.APIv4,
ApiUtils.APIv3,
1
)
)
callNotificationViewModel.getRoomViewState.observe(this) { state ->
when (state) {
is CallNotificationViewModel.GetRoomSuccessState -> {
currentConversation = state.conversationModel
binding!!.conversationNameTextView.text = currentConversation!!.displayName
if (currentConversation!!.type === ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
binding!!.avatarImageView.loadUserAvatar(
userBeingCalled!!,
currentConversation!!.name!!,
true,
false
)
} else {
binding!!.avatarImageView.setImageResource(R.drawable.ic_circular_group)
}
val notificationHandler = Handler(Looper.getMainLooper())
notificationHandler.post(object : Runnable {
override fun run() {
if (NotificationUtils.isNotificationVisible(context, notificationTimestamp!!.toInt())) {
notificationHandler.postDelayed(this, ONE_SECOND)
} else {
finish()
}
}
})
showAnswerControls()
if (apiVersion >= ApiUtils.APIv3) {
val hasCallFlags = hasSpreedFeatureCapability(
userBeingCalled,
"conversation-call-flags"
)
if (hasCallFlags) {
if (isInCallWithVideo(currentConversation!!.callFlag)) {
binding!!.incomingCallVoiceOrVideoTextView.text = String.format(
resources.getString(R.string.nc_call_video),
resources.getString(R.string.nc_app_product_name)
)
} else {
binding!!.incomingCallVoiceOrVideoTextView.text = String.format(
resources.getString(R.string.nc_call_voice),
resources.getString(R.string.nc_app_product_name)
)
}
}
}
initClickListeners()
}
is CallNotificationViewModel.GetRoomErrorState -> {
Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
}
else -> {}
}
}
}
private fun setCallDescriptionText() {
val callDescriptionWithoutTypeInfo = String.format(
resources.getString(R.string.nc_call_unknown),
@ -178,7 +256,7 @@ class CallNotificationActivity : CallBaseActivity() {
)
originalBundle!!.putBoolean(
BundleKeys.KEY_IS_MODERATOR,
currentConversation!!.isParticipantOwnerOrModerator
ConversationUtils.isParticipantOwnerOrModerator(currentConversation!!)
)
val intent = Intent(this, CallActivity::class.java)
@ -189,85 +267,10 @@ class CallNotificationActivity : CallBaseActivity() {
}
}
@Suppress("MagicNumber")
private fun handleFromNotification() {
val apiVersion = ApiUtils.getConversationApiVersion(
userBeingCalled,
intArrayOf(
ApiUtils.APIv4,
ApiUtils.APIv3,
1
)
)
ncApi!!.getRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, userBeingCalled!!.baseUrl, roomToken))
.subscribeOn(Schedulers.io())
.retry(GET_ROOM_RETRY_COUNT)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<RoomOverall> {
override fun onSubscribe(d: Disposable) {
disposablesList.add(d)
}
override fun onNext(roomOverall: RoomOverall) {
currentConversation = roomOverall.ocs!!.data
setUpAfterConversationIsKnown()
if (apiVersion >= 3) {
val hasCallFlags = hasSpreedFeatureCapability(
userBeingCalled,
"conversation-call-flags"
)
if (hasCallFlags) {
if (isInCallWithVideo(currentConversation!!.callFlag)) {
binding!!.incomingCallVoiceOrVideoTextView.text = String.format(
resources.getString(R.string.nc_call_video),
resources.getString(R.string.nc_app_product_name)
)
} else {
binding!!.incomingCallVoiceOrVideoTextView.text = String.format(
resources.getString(R.string.nc_call_voice),
resources.getString(R.string.nc_app_product_name)
)
}
}
}
}
override fun onError(e: Throwable) {
Log.e(TAG, e.message, e)
}
override fun onComplete() {
// unused atm
}
})
}
private fun isInCallWithVideo(callFlag: Int): Boolean {
return (callFlag and Participant.InCallFlags.WITH_VIDEO) > 0
}
private fun setUpAfterConversationIsKnown() {
binding!!.conversationNameTextView.text = currentConversation!!.displayName
if (currentConversation!!.type === Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
binding!!.avatarImageView.loadUserAvatar(userBeingCalled!!, currentConversation!!.name!!, true, false)
} else {
binding!!.avatarImageView.setImageResource(R.drawable.ic_circular_group)
}
val notificationHandler = Handler(Looper.getMainLooper())
notificationHandler.post(object : Runnable {
override fun run() {
if (NotificationUtils.isNotificationVisible(context, notificationTimestamp!!.toInt())) {
notificationHandler.postDelayed(this, ONE_SECOND)
} else {
finish()
}
}
})
showAnswerControls()
}
override fun onStop() {
val notificationManager = NotificationManagerCompat.from(context)
notificationManager.cancel(notificationTimestamp!!)
@ -303,23 +306,22 @@ class CallNotificationActivity : CallBaseActivity() {
}
}
public override fun updateUiForPipMode() {
override fun updateUiForPipMode() {
binding!!.callAnswerButtons.visibility = View.INVISIBLE
binding!!.incomingCallRelativeLayout.visibility = View.INVISIBLE
}
public override fun updateUiForNormalMode() {
override fun updateUiForNormalMode() {
binding!!.callAnswerButtons.visibility = View.VISIBLE
binding!!.incomingCallRelativeLayout.visibility = View.VISIBLE
}
public override fun suppressFitsSystemWindows() {
override fun suppressFitsSystemWindows() {
binding!!.controllerCallNotificationLayout.fitsSystemWindows = false
}
companion object {
const val TAG = "CallNotificationActivity"
const val GET_ROOM_RETRY_COUNT: Long = 3
const val ONE_SECOND: Long = 1000
}
}

View file

@ -0,0 +1,79 @@
/*
* Nextcloud Talk application
*
* @author Marcel Hibbe
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.callnotification.viewmodel
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.nextcloud.talk.chat.data.ChatRepository
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
class CallNotificationViewModel @Inject constructor(private val repository: ChatRepository) :
ViewModel() {
sealed interface ViewState
object GetRoomStartState : ViewState
object GetRoomErrorState : ViewState
open class GetRoomSuccessState(val conversationModel: ConversationModel) : ViewState
private val _getRoomViewState: MutableLiveData<ViewState> = MutableLiveData(GetRoomStartState)
val getRoomViewState: LiveData<ViewState>
get() = _getRoomViewState
fun getRoom(user: User, token: String) {
_getRoomViewState.value = GetRoomStartState
repository.getRoom(user, token)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(GetRoomObserver())
}
inner class GetRoomObserver : Observer<ConversationModel> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(conversationModel: ConversationModel) {
_getRoomViewState.value = GetRoomSuccessState(conversationModel)
}
override fun onError(e: Throwable) {
Log.e(TAG, "Error when fetching room")
_getRoomViewState.value = GetRoomErrorState
}
override fun onComplete() {
// unused atm
}
}
companion object {
private val TAG = CallNotificationViewModel::class.simpleName
}
}

View file

@ -48,7 +48,6 @@ import android.os.Build
import android.os.Bundle
import android.os.CountDownTimer
import android.os.Handler
import android.os.Parcelable
import android.os.SystemClock
import android.provider.ContactsContract
import android.provider.MediaStore
@ -85,6 +84,7 @@ import androidx.core.text.bold
import androidx.core.widget.doAfterTextChanged
import androidx.emoji2.text.EmojiCompat
import androidx.emoji2.widget.EmojiTextView
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -130,6 +130,7 @@ import com.nextcloud.talk.adapters.messages.VoiceMessageInterface
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.callbacks.MentionAutocompleteCallback
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
import com.nextcloud.talk.conversationinfo.ConversationInfoActivity
import com.nextcloud.talk.conversationlist.ConversationsListActivity
import com.nextcloud.talk.data.user.model.User
@ -142,15 +143,18 @@ import com.nextcloud.talk.jobs.ShareOperationWorker
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
import com.nextcloud.talk.location.LocationPickerActivity
import com.nextcloud.talk.messagesearch.MessageSearchActivity
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.domain.ConversationReadOnlyState
import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.domain.LobbyState
import com.nextcloud.talk.models.domain.ObjectType
import com.nextcloud.talk.models.domain.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.chat.ChatOverall
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
import com.nextcloud.talk.models.json.chat.ReadStatus
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.conversations.RoomsOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.mention.Mention
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage
@ -170,6 +174,7 @@ import com.nextcloud.talk.ui.recyclerview.MessageSwipeActions
import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.ContactUtils
import com.nextcloud.talk.utils.ConversationUtils
import com.nextcloud.talk.utils.DateConstants
import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.DisplayUtils
@ -181,7 +186,6 @@ import com.nextcloud.talk.utils.NotificationUtils
import com.nextcloud.talk.utils.ParticipantPermissions
import com.nextcloud.talk.utils.VibrationUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CALL_VOICE_ONLY
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FILE_PATHS
@ -193,8 +197,8 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_START_CALL_AFTER_ROOM_SWITCH
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SWITCH_TO_ROOM
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
import com.nextcloud.talk.utils.rx.DisposableSet
import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder
@ -215,7 +219,6 @@ import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.parceler.Parcels
import retrofit2.HttpException
import retrofit2.Response
import java.io.File
@ -248,6 +251,9 @@ class ChatActivity :
@Inject
lateinit var ncApi: NcApi
@Inject
lateinit var currentUserProvider: CurrentUserProviderNew
@Inject
lateinit var reactionsRepository: ReactionsRepository
@ -257,6 +263,11 @@ class ChatActivity :
@Inject
lateinit var dateUtils: DateUtils
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var chatViewModel: ChatViewModel
override val view: View
get() = binding.root
@ -267,7 +278,7 @@ class ChatActivity :
var conversationUser: User? = null
private var roomPassword: String = ""
var credentials: String? = null
var currentConversation: Conversation? = null
var currentConversation: ConversationModel? = null
private var globalLastKnownFutureMessageId = -1
private var globalLastKnownPastMessageId = -1
var adapter: TalkMessagesListAdapter<ChatMessage>? = null
@ -383,14 +394,17 @@ class ChatActivity :
setContentView(binding.root)
setupSystemColors()
conversationUser = currentUserProvider.currentUser.blockingGet()
handleIntent(intent)
chatViewModel = ViewModelProvider(this, viewModelFactory)[ChatViewModel::class.java]
binding.progressBar.visibility = View.VISIBLE
initAdapter()
binding.messagesListView.setAdapter(adapter)
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
initObservers()
}
override fun onNewIntent(intent: Intent) {
@ -415,7 +429,6 @@ class ChatActivity :
private fun handleIntent(intent: Intent) {
val extras: Bundle? = intent.extras
conversationUser = extras?.getParcelable(KEY_USER_ENTITY)
roomId = extras?.getString(KEY_ROOM_ID).orEmpty()
roomToken = extras?.getString(KEY_ROOM_TOKEN).orEmpty()
@ -426,11 +439,6 @@ class ChatActivity :
Log.d(TAG, " roomToken was null or empty!")
}
if (intent.hasExtra(KEY_ACTIVE_CONVERSATION)) {
currentConversation = Parcels.unwrap<Conversation>(extras?.getParcelable(KEY_ACTIVE_CONVERSATION))
participantPermissions = ParticipantPermissions(conversationUser!!, currentConversation!!)
}
roomPassword = extras?.getString(BundleKeys.KEY_CONVERSATION_PASSWORD).orEmpty()
credentials = if (conversationUser?.userId == "?") {
@ -455,6 +463,106 @@ class ChatActivity :
active = false
}
private fun initObservers() {
chatViewModel.getRoomViewState.observe(this) { state ->
when (state) {
is ChatViewModel.GetRoomSuccessState -> {
currentConversation = state.conversationModel
logConversationInfos("GetRoomSuccessState")
if (adapter == null) {
initAdapter()
binding.messagesListView.setAdapter(adapter)
}
layoutManager = binding.messagesListView.layoutManager as LinearLayoutManager?
loadAvatarForStatusBar()
setActionBarTitle()
participantPermissions = ParticipantPermissions(conversationUser!!, currentConversation!!)
setupSwipeToReply()
setupMentionAutocomplete()
checkShowCallButtons()
checkShowMessageInputView()
checkLobbyState()
if (!validSessionId()) {
joinRoomWithPassword()
} else {
Log.d(TAG, "already inConversation. joinRoomWithPassword is skipped")
}
val delayForRecursiveCall = if (shouldShowLobby()) {
GET_ROOM_INFO_DELAY_LOBBY
} else {
GET_ROOM_INFO_DELAY_NORMAL
}
if (getRoomInfoTimerHandler == null) {
getRoomInfoTimerHandler = Handler()
}
getRoomInfoTimerHandler?.postDelayed(
{
chatViewModel.getRoom(conversationUser!!, roomToken)
},
delayForRecursiveCall
)
}
is ChatViewModel.GetRoomErrorState -> {
Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
}
else -> {}
}
}
chatViewModel.joinRoomViewState.observe(this) { state ->
when (state) {
is ChatViewModel.JoinRoomSuccessState -> {
currentConversation = state.conversationModel
sessionIdAfterRoomJoined = currentConversation!!.sessionId
ApplicationWideCurrentRoomHolder.getInstance().session = currentConversation!!.sessionId
ApplicationWideCurrentRoomHolder.getInstance().currentRoomId = currentConversation!!.roomId
ApplicationWideCurrentRoomHolder.getInstance().currentRoomToken = currentConversation!!.token
ApplicationWideCurrentRoomHolder.getInstance().userInRoom = conversationUser
logConversationInfos("joinRoomWithPassword#onNext")
if (isFirstMessagesProcessing) {
pullChatMessages(false)
} else {
pullChatMessages(true, false)
}
if (webSocketInstance != null) {
webSocketInstance?.joinRoomWithRoomTokenAndSession(
roomToken,
sessionIdAfterRoomJoined
)
}
if (startCallFromNotification != null && startCallFromNotification ?: false) {
startCallFromNotification = false
startACall(voiceOnly, false)
}
if (startCallFromRoomSwitch) {
startCallFromRoomSwitch = false
startACall(voiceOnly, true)
}
}
is ChatViewModel.JoinRoomErrorState -> {
Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
}
else -> {}
}
}
}
@Suppress("Detekt.TooGenericExceptionCaught")
override fun onResume() {
super.onResume()
@ -484,18 +592,12 @@ class ChatActivity :
cancelNotificationsForCurrentConversation()
if (TextUtils.isEmpty(roomToken)) {
handleFromNotification()
} else {
getRoomInfo()
}
chatViewModel.getRoom(conversationUser!!, roomToken)
actionBar?.show()
setupSwipeToReply()
layoutManager = binding?.messagesListView?.layoutManager as LinearLayoutManager?
binding?.popupBubbleView?.setRecyclerView(binding?.messagesListView)
binding?.popupBubbleView?.setPopupBubbleListener { context ->
@ -632,10 +734,8 @@ class ChatActivity :
binding?.messageInputView?.button?.let { viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY) }
if (currentConversation != null && currentConversation?.roomId != null) {
loadAvatarForStatusBar()
setActionBarTitle()
}
loadAvatarForStatusBar()
setActionBarTitle()
viewThemeUtils.material.colorToolbarOverflowIcon(binding.chatToolbar)
}
@ -694,8 +794,11 @@ class ChatActivity :
val messageHolders = MessageHolders()
val profileBottomSheet = ProfileBottomSheet(ncApi, conversationUser!!)
val payload =
MessagePayload(roomToken!!, currentConversation?.isParticipantOwnerOrModerator, profileBottomSheet)
val payload = MessagePayload(
roomToken,
ConversationUtils.isParticipantOwnerOrModerator(currentConversation!!),
profileBottomSheet
)
messageHolders.setIncomingTextConfig(
IncomingTextMessageViewHolder::class.java,
@ -1080,68 +1183,6 @@ class ChatActivity :
!CapabilitiesUtilNew.isTypingStatusPrivate(conversationUser!!)
}
private fun getRoomInfo() {
logConversationInfos("getRoomInfo")
conversationUser?.let {
val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
val startNanoTime = System.nanoTime()
Log.d(TAG, "getRoomInfo - getRoom - calling: $startNanoTime")
ncApi.getRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, it.baseUrl, roomToken))
?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer<RoomOverall> {
override fun onSubscribe(d: Disposable) {
disposables.add(d)
}
@Suppress("Detekt.TooGenericExceptionCaught")
override fun onNext(roomOverall: RoomOverall) {
Log.d(TAG, "getRoomInfo - getRoom - got response: $startNanoTime")
currentConversation = roomOverall.ocs!!.data
logConversationInfos("getRoomInfo#onNext")
loadAvatarForStatusBar()
setActionBarTitle()
participantPermissions = ParticipantPermissions(it, currentConversation!!)
setupSwipeToReply()
setupMentionAutocomplete()
checkShowCallButtons()
checkShowMessageInputView()
checkLobbyState()
if (!validSessionId()) {
joinRoomWithPassword()
} else {
Log.d(TAG, "already inConversation. joinRoomWithPassword is skipped")
}
}
override fun onError(e: Throwable) {
Log.e(TAG, "getRoomInfo - getRoom - ERROR", e)
}
override fun onComplete() {
Log.d(TAG, "getRoomInfo - getRoom - onComplete: $startNanoTime")
val delayForRecursiveCall = if (shouldShowLobby()) {
GET_ROOM_INFO_DELAY_LOBBY
} else {
GET_ROOM_INFO_DELAY_NORMAL
}
if (getRoomInfoTimerHandler == null) {
getRoomInfoTimerHandler = Handler()
}
getRoomInfoTimerHandler?.postDelayed({ getRoomInfo() }, delayForRecursiveCall)
}
})
}
}
private fun setupSwipeToReply() {
if (this::participantPermissions.isInitialized &&
participantPermissions.hasChatPermission() &&
@ -1162,46 +1203,11 @@ class ChatActivity :
}
}
private fun handleFromNotification() {
var apiVersion = 1
// FIXME Can this be called for guests?
if (conversationUser != null) {
apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
private fun loadAvatarForStatusBar() {
if (currentConversation == null) {
return
}
Log.d(TAG, "handleFromNotification - getRooms - calling")
ncApi.getRooms(credentials, ApiUtils.getUrlForRooms(apiVersion, conversationUser?.baseUrl), false)
?.subscribeOn(Schedulers.io())?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer<RoomsOverall> {
override fun onSubscribe(d: Disposable) {
disposables.add(d)
}
override fun onNext(roomsOverall: RoomsOverall) {
Log.d(TAG, "handleFromNotification - getRooms - got response")
for (conversation in roomsOverall.ocs!!.data!!) {
if (roomId == conversation.roomId) {
roomToken = conversation.token!!
currentConversation = conversation
participantPermissions = ParticipantPermissions(conversationUser!!, currentConversation!!)
setActionBarTitle()
getRoomInfo()
break
}
}
}
override fun onError(e: Throwable) {
Log.e(TAG, "handleFromNotification - getRooms - ERROR: ", e)
}
override fun onComplete() {
// unused atm
}
})
}
private fun loadAvatarForStatusBar() {
if (isOneToOneConversation()) {
var url = ApiUtils.getUrlForAvatar(
conversationUser!!.baseUrl,
@ -1254,18 +1260,18 @@ class ChatActivity :
}
fun isOneToOneConversation() = currentConversation != null && currentConversation?.type != null &&
currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
private fun isGroupConversation() = currentConversation != null && currentConversation?.type != null &&
currentConversation?.type == Conversation.ConversationType.ROOM_GROUP_CALL
currentConversation?.type == ConversationType.ROOM_GROUP_CALL
private fun isPublicConversation() = currentConversation != null && currentConversation?.type != null &&
currentConversation?.type == Conversation.ConversationType.ROOM_PUBLIC_CALL
currentConversation?.type == ConversationType.ROOM_PUBLIC_CALL
private fun switchToRoom(token: String, startCallAfterRoomSwitch: Boolean, isVoiceOnlyCall: Boolean) {
if (conversationUser != null) {
runOnUiThread {
if (currentConversation?.objectType == Conversation.ObjectType.ROOM) {
if (currentConversation?.objectType == ObjectType.ROOM) {
Toast.makeText(
context,
context.resources.getString(R.string.switch_to_main_room),
@ -1281,7 +1287,6 @@ class ChatActivity :
}
val bundle = Bundle()
bundle.putParcelable(KEY_USER_ENTITY, conversationUser)
bundle.putString(KEY_ROOM_TOKEN, token)
if (startCallAfterRoomSwitch) {
@ -1697,8 +1702,8 @@ class ChatActivity :
private fun shouldShowLobby(): Boolean {
if (currentConversation != null) {
return CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "webinary-lobby") &&
currentConversation?.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
currentConversation?.canModerate(conversationUser!!) == false &&
currentConversation?.lobbyState == LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
!ConversationUtils.canModerate(currentConversation!!, conversationUser!!) &&
!participantPermissions.canIgnoreLobby()
}
return false
@ -1733,12 +1738,12 @@ class ChatActivity :
private fun isReadOnlyConversation(): Boolean {
return currentConversation?.conversationReadOnlyState != null &&
currentConversation?.conversationReadOnlyState ==
Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY
ConversationReadOnlyState.CONVERSATION_READ_ONLY
}
private fun checkLobbyState() {
if (currentConversation != null &&
currentConversation?.isLobbyViewApplicable(conversationUser!!) == true
ConversationUtils.isLobbyViewApplicable(currentConversation!!, conversationUser!!)
) {
if (shouldShowLobby()) {
binding?.lobby?.lobbyView?.visibility = View.VISIBLE
@ -2119,7 +2124,7 @@ class ChatActivity :
private fun showConversationInfoScreen() {
val bundle = Bundle()
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, conversationUser)
bundle.putString(KEY_ROOM_TOKEN, roomToken)
bundle.putBoolean(BundleKeys.KEY_ROOM_ONE_TO_ONE, isOneToOneConversation())
@ -2274,65 +2279,8 @@ class ChatActivity :
val startNanoTime = System.nanoTime()
Log.d(TAG, "joinRoomWithPassword - joinRoom - calling: $startNanoTime")
ncApi.joinRoom(
credentials,
ApiUtils.getUrlForParticipantsActive(apiVersion, conversationUser?.baseUrl, roomToken),
roomPassword
)
?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.retry(RETRIES)
?.subscribe(object : Observer<RoomOverall> {
override fun onSubscribe(d: Disposable) {
disposables.add(d)
}
@Suppress("Detekt.TooGenericExceptionCaught")
override fun onNext(roomOverall: RoomOverall) {
Log.d(TAG, "joinRoomWithPassword - joinRoom - got response: $startNanoTime")
val conversation = roomOverall.ocs!!.data!!
currentConversation = conversation
sessionIdAfterRoomJoined = conversation.sessionId
ApplicationWideCurrentRoomHolder.getInstance().session = conversation.sessionId
ApplicationWideCurrentRoomHolder.getInstance().currentRoomId = conversation.roomId
ApplicationWideCurrentRoomHolder.getInstance().currentRoomToken = conversation.token
ApplicationWideCurrentRoomHolder.getInstance().userInRoom = conversationUser
logConversationInfos("joinRoomWithPassword#onNext")
if (isFirstMessagesProcessing) {
pullChatMessages(false)
} else {
pullChatMessages(true, false)
}
if (webSocketInstance != null) {
webSocketInstance?.joinRoomWithRoomTokenAndSession(
roomToken!!,
sessionIdAfterRoomJoined
)
}
if (startCallFromNotification != null && startCallFromNotification ?: false) {
startCallFromNotification = false
startACall(voiceOnly, false)
}
if (startCallFromRoomSwitch) {
startCallFromRoomSwitch = false
startACall(voiceOnly, true)
}
}
override fun onError(e: Throwable) {
Log.e(TAG, "joinRoomWithPassword - joinRoom - ERROR", e)
}
override fun onComplete() {
// unused atm
}
})
chatViewModel.joinRoom(conversationUser!!, roomToken, roomPassword)
} else {
Log.d(TAG, "sessionID was valid -> skip joinRoom")
@ -2801,9 +2749,9 @@ class ChatActivity :
GROUPED_MESSAGES_SAME_AUTHOR_THRESHOLD > 0
)
chatMessage.isOneToOneConversation =
(currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL)
(currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL)
chatMessage.isFormerOneToOneConversation =
(currentConversation?.type == Conversation.ConversationType.FORMER_ONE_TO_ONE)
(currentConversation?.type == ConversationType.FORMER_ONE_TO_ONE)
it.addToStart(chatMessage, shouldScroll)
}
}
@ -2845,9 +2793,9 @@ class ChatActivity :
val chatMessage = chatMessageList[i]
chatMessage.isOneToOneConversation =
currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
chatMessage.isFormerOneToOneConversation =
(currentConversation?.type == Conversation.ConversationType.FORMER_ONE_TO_ONE)
(currentConversation?.type == ConversationType.FORMER_ONE_TO_ONE)
chatMessage.activeUser = conversationUser
}
@ -3032,10 +2980,9 @@ class ChatActivity :
val intent = Intent(this, SharedItemsActivity::class.java)
intent.putExtra(KEY_CONVERSATION_NAME, currentConversation?.displayName)
intent.putExtra(KEY_ROOM_TOKEN, roomToken)
intent.putExtra(KEY_USER_ENTITY, conversationUser as Parcelable)
intent.putExtra(
SharedItemsActivity.KEY_USER_IS_OWNER_OR_MODERATOR,
currentConversation?.isParticipantOwnerOrModerator
ConversationUtils.isParticipantOwnerOrModerator(currentConversation!!)
)
startActivity(intent)
}
@ -3119,12 +3066,11 @@ class ChatActivity :
val bundle = Bundle()
bundle.putString(KEY_ROOM_TOKEN, roomToken)
bundle.putString(KEY_ROOM_ID, roomId)
bundle.putParcelable(KEY_USER_ENTITY, conversationUser)
bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, roomPassword)
bundle.putString(BundleKeys.KEY_MODIFIED_BASE_URL, conversationUser?.baseUrl)
bundle.putString(KEY_CONVERSATION_NAME, it.displayName)
bundle.putInt(KEY_RECORDING_STATE, it.callRecording)
bundle.putBoolean(KEY_IS_MODERATOR, it.isParticipantOwnerOrModerator)
bundle.putBoolean(KEY_IS_MODERATOR, ConversationUtils.isParticipantOwnerOrModerator(it))
bundle.putBoolean(
BundleKeys.KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_AUDIO,
participantPermissions.canPublishAudio()
@ -3141,7 +3087,7 @@ class ChatActivity :
bundle.putBoolean(BundleKeys.KEY_CALL_WITHOUT_NOTIFICATION, true)
}
if (it.objectType == Conversation.ObjectType.ROOM) {
if (it.objectType == ObjectType.ROOM) {
bundle.putBoolean(KEY_IS_BREAKOUT_ROOM, true)
}
@ -3156,12 +3102,12 @@ class ChatActivity :
override fun onClickReaction(chatMessage: ChatMessage, emoji: String) {
VibrationUtils.vibrateShort(context)
if (chatMessage.reactionsSelf?.contains(emoji) == true) {
reactionsRepository.deleteReaction(currentConversation!!, chatMessage, emoji)
reactionsRepository.deleteReaction(roomToken, chatMessage, emoji)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(ReactionDeletedObserver())
} else {
reactionsRepository.addReaction(currentConversation!!, chatMessage, emoji)
reactionsRepository.addReaction(roomToken, chatMessage, emoji)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(ReactionAddedObserver())
@ -3171,7 +3117,7 @@ class ChatActivity :
override fun onLongClickReactions(chatMessage: ChatMessage) {
ShowReactionsDialog(
this,
currentConversation,
roomToken,
chatMessage,
conversationUser,
participantPermissions.hasChatPermission(),
@ -3339,48 +3285,15 @@ class ChatActivity :
override fun onNext(roomOverall: RoomOverall) {
val bundle = Bundle()
bundle.putParcelable(KEY_USER_ENTITY, conversationUser)
bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
bundle.putString(KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
// FIXME once APIv2+ is used only, the createRoom already returns all the data
ncApi.getRoom(
credentials,
ApiUtils.getUrlForRoom(
apiVersion,
conversationUser?.baseUrl,
roomOverall.ocs!!.data!!.token
)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<RoomOverall> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(roomOverall: RoomOverall) {
bundle.putParcelable(
KEY_ACTIVE_CONVERSATION,
Parcels.wrap(roomOverall.ocs!!.data!!)
)
leaveRoom {
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle)
chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(chatIntent)
}
}
override fun onError(e: Throwable) {
Log.e(TAG, e.message, e)
}
override fun onComplete() {
// unused atm
}
})
leaveRoom {
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle)
chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(chatIntent)
}
}
override fun onError(e: Throwable) {
@ -3471,7 +3384,7 @@ class ChatActivity :
conversationUser?.userId?.isNotEmpty() == true && conversationUser!!.userId != "?" &&
message.user.id.startsWith("users/") &&
message.user.id.substring(ACTOR_LENGTH) != currentConversation?.actorId &&
currentConversation?.type != Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ||
currentConversation?.type != ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ||
isShowMessageDeletionButton(message) || // delete
ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() || // forward
message.previousMessageId > NO_PREVIOUS_MESSAGE_ID && // mark as unread
@ -3540,7 +3453,7 @@ class ChatActivity :
messageTemp.isDeleted = true
messageTemp.isOneToOneConversation =
currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
messageTemp.activeUser = conversationUser
adapter?.update(messageTemp)
@ -3550,7 +3463,7 @@ class ChatActivity :
val messageTemp = message as ChatMessage
messageTemp.isOneToOneConversation =
currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
messageTemp.activeUser = conversationUser
adapter?.update(messageTemp)
@ -3598,7 +3511,7 @@ class ChatActivity :
val isUserAllowedByPrivileges = if (message.actorId == conversationUser!!.userId) {
true
} else {
currentConversation!!.canModerate(conversationUser!!)
ConversationUtils.canModerate(currentConversation!!, conversationUser!!)
}
val isOlderThanSixHours = message
@ -3652,7 +3565,7 @@ class ChatActivity :
@Subscribe(threadMode = ThreadMode.BACKGROUND)
fun onMessageEvent(userMentionClickEvent: UserMentionClickEvent) {
if (currentConversation?.type != Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ||
if (currentConversation?.type != ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ||
currentConversation?.name != userMentionClickEvent.userId
) {
var apiVersion = 1
@ -3683,42 +3596,21 @@ class ChatActivity :
}
override fun onNext(roomOverall: RoomOverall) {
val conversationIntent = Intent(context, CallActivity::class.java)
val bundle = Bundle()
bundle.putParcelable(KEY_USER_ENTITY, conversationUser)
bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
bundle.putString(KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
bundle.putBoolean(KEY_IS_MODERATOR, roomOverall.ocs!!.data!!.isParticipantOwnerOrModerator)
if (conversationUser != null) {
bundle.putParcelable(
KEY_ACTIVE_CONVERSATION,
Parcels.wrap(roomOverall.ocs!!.data)
)
conversationIntent.putExtras(bundle)
leaveRoom {
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle)
chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(chatIntent)
}
} else {
conversationIntent.putExtras(bundle)
startActivity(conversationIntent)
Handler().postDelayed(
{
if (!isDestroyed) {
finish()
}
},
POP_CURRENT_CONTROLLER_DELAY
)
leaveRoom {
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle)
chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(chatIntent)
}
}
override fun onError(e: Throwable) {
// unused atm
Log.e(TAG, "error after clicking on user mention chip", e)
Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
}
override fun onComplete() {

View file

@ -0,0 +1,31 @@
/*
* Nextcloud Talk application
*
* @author Marcel Hibbe
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.chat.data
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import io.reactivex.Observable
interface ChatRepository {
fun getRoom(user: User, roomToken: String): Observable<ConversationModel>
fun joinRoom(user: User, roomToken: String, roomPassword: String): Observable<ConversationModel>
}

View file

@ -0,0 +1,57 @@
/*
* Nextcloud Talk application
*
* @author Marcel Hibbe
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.chat.data
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.utils.ApiUtils
import io.reactivex.Observable
class ChatRepositoryImpl(private val ncApi: NcApi) : ChatRepository {
override fun getRoom(
user: User,
roomToken: String
): Observable<ConversationModel> {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)
val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv3, 1))
return ncApi.getRoom(
credentials,
ApiUtils.getUrlForRoom(apiVersion, user.baseUrl, roomToken)
).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
}
override fun joinRoom(
user: User,
roomToken: String,
roomPassword: String
): Observable<ConversationModel> {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)
val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.APIv4, 1))
return ncApi.joinRoom(
credentials,
ApiUtils.getUrlForParticipantsActive(apiVersion, user.baseUrl, roomToken),
roomPassword
).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
}
}

View file

@ -0,0 +1,116 @@
/*
* Nextcloud Talk application
*
* @author Marcel Hibbe
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.chat.viewmodels
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.nextcloud.talk.chat.data.ChatRepository
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
class ChatViewModel @Inject constructor(private val repository: ChatRepository) :
ViewModel() {
sealed interface ViewState
object GetRoomStartState : ViewState
object GetRoomErrorState : ViewState
open class GetRoomSuccessState(val conversationModel: ConversationModel) : ViewState
private val _getRoomViewState: MutableLiveData<ViewState> = MutableLiveData(GetRoomStartState)
val getRoomViewState: LiveData<ViewState>
get() = _getRoomViewState
object JoinRoomStartState : ViewState
object JoinRoomErrorState : ViewState
open class JoinRoomSuccessState(val conversationModel: ConversationModel) : ViewState
private val _joinRoomViewState: MutableLiveData<ViewState> = MutableLiveData(JoinRoomStartState)
val joinRoomViewState: LiveData<ViewState>
get() = _joinRoomViewState
fun getRoom(user: User, token: String) {
_getRoomViewState.value = GetRoomStartState
repository.getRoom(user, token)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(GetRoomObserver())
}
fun joinRoom(user: User, token: String, roomPassword: String) {
_getRoomViewState.value = JoinRoomStartState
repository.joinRoom(user, token, roomPassword)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.retry(JOIN_ROOM_RETRY_COUNT)
?.subscribe(JoinRoomObserver())
}
inner class GetRoomObserver : Observer<ConversationModel> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(conversationModel: ConversationModel) {
_getRoomViewState.value = GetRoomSuccessState(conversationModel)
}
override fun onError(e: Throwable) {
Log.e(TAG, "Error when fetching room")
_getRoomViewState.value = GetRoomErrorState
}
override fun onComplete() {
// unused atm
}
}
inner class JoinRoomObserver : Observer<ConversationModel> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(conversationModel: ConversationModel) {
_joinRoomViewState.value = JoinRoomSuccessState(conversationModel)
}
override fun onError(e: Throwable) {
Log.e(TAG, "Error when fetching room")
_joinRoomViewState.value = JoinRoomErrorState
}
override fun onComplete() {
// unused atm
}
}
companion object {
private val TAG = ChatViewModel::class.simpleName
const val JOIN_ROOM_RETRY_COUNT: Long = 3
}
}

View file

@ -338,46 +338,13 @@ class ContactsActivity :
override fun onNext(roomOverall: RoomOverall) {
val bundle = Bundle()
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, currentUser)
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
// FIXME once APIv2 or later is used only, the createRoom already returns all the data
ncApi.getRoom(
credentials,
ApiUtils.getUrlForRoom(
apiVersion,
currentUser!!.baseUrl,
roomOverall.ocs!!.data!!.token
)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<RoomOverall> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(roomOverall: RoomOverall) {
bundle.putParcelable(
BundleKeys.KEY_ACTIVE_CONVERSATION,
Parcels.wrap(roomOverall.ocs!!.data!!)
)
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle)
chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(chatIntent)
}
override fun onError(e: Throwable) {
// unused atm
}
override fun onComplete() {
// unused atm
}
})
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle)
chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(chatIntent)
}
override fun onError(e: Throwable) {
@ -818,13 +785,8 @@ class ContactsActivity :
override fun onNext(roomOverall: RoomOverall) {
val bundle = Bundle()
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, currentUser)
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
bundle.putParcelable(
BundleKeys.KEY_ACTIVE_CONVERSATION,
Parcels.wrap(roomOverall.ocs!!.data!!)
)
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle)

View file

@ -23,14 +23,13 @@
*/
package com.nextcloud.talk.controllers.bottomsheet
import android.content.Intent
import android.content.res.ColorStateList
import android.os.Bundle
import android.os.Parcelable
import android.text.Editable
import android.text.InputType
import android.text.TextUtils
import android.text.TextWatcher
import android.util.Log
import android.view.View
import android.view.inputmethod.EditorInfo
import androidx.core.content.res.ResourcesCompat
@ -40,21 +39,28 @@ import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.google.android.material.textfield.TextInputLayout
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.R
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.controllers.base.BaseController
import com.nextcloud.talk.controllers.util.viewBinding
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ControllerEntryMenuBinding
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.UriUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
import com.vanniktech.emoji.EmojiPopup
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.internal.immutableListOf
import org.greenrobot.eventbus.EventBus
import org.parceler.Parcels
import org.parceler.Parcels.unwrap
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
@ -65,6 +71,9 @@ class EntryMenuController(args: Bundle) :
) {
private val binding: ControllerEntryMenuBinding? by viewBinding(ControllerEntryMenuBinding::bind)
@Inject
lateinit var ncApi: NcApi
@Inject
lateinit var eventBus: EventBus
@ -73,12 +82,13 @@ class EntryMenuController(args: Bundle) :
private val operation: ConversationOperationEnum
private var conversation: Conversation? = null
private var shareIntent: Intent? = null
private val packageName: String
private val name: String
private val callUrl: String
private var emojiPopup: EmojiPopup? = null
private val originalBundle: Bundle
private var currentUser: User? = null
private val roomToken: String
override val appBarLayoutType: AppBarLayoutType
get() = AppBarLayoutType.SEARCH_BAR
@ -100,74 +110,121 @@ class EntryMenuController(args: Bundle) :
override fun onViewBound(view: View) {
super.onViewBound(view)
if (conversation != null && operation === ConversationOperationEnum.OPS_CODE_RENAME_ROOM) {
binding?.textEdit?.setText(conversation!!.name)
}
currentUser = userManager.currentUser.blockingGet()
binding?.textEdit?.setOnEditorActionListener { v, actionId, event ->
@Suppress("IMPLICIT_BOXING_IN_IDENTITY_EQUALS")
if (actionId === EditorInfo.IME_ACTION_DONE && binding?.okButton?.isEnabled == true) {
binding?.okButton?.callOnClick()
return@setOnEditorActionListener true
}
false
}
if (operation == ConversationOperationEnum.OPS_CODE_GET_AND_JOIN_ROOM) {
var labelText = ""
labelText = resources!!.getString(R.string.nc_conversation_link)
binding?.textEdit?.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI
textEditAddChangedListener()
textEditAddChangedListener()
var labelText = ""
when (operation) {
ConversationOperationEnum.OPS_CODE_INVITE_USERS, ConversationOperationEnum.OPS_CODE_RENAME_ROOM -> {
labelText = resources!!.getString(R.string.nc_call_name)
binding?.textEdit?.inputType = InputType.TYPE_CLASS_TEXT
binding?.smileyButton?.visibility = View.VISIBLE
emojiPopup = binding?.let {
EmojiPopup(
rootView = view,
editText = it.textEdit,
onEmojiPopupShownListener = {
viewThemeUtils.platform.colorImageView(it.smileyButton, ColorRole.PRIMARY)
},
onEmojiPopupDismissListener = {
it.smileyButton.imageTintList = ColorStateList.valueOf(
ResourcesCompat.getColor(resources!!, R.color.medium_emphasis_text, context.theme)
)
},
onEmojiClickListener = {
binding?.textEdit?.editableText?.append(" ")
}
)
}
}
binding?.textInputLayout?.let { viewThemeUtils.material.colorTextInputLayout(it) }
binding?.okButton?.let { viewThemeUtils.material.colorMaterialButtonText(it) }
ConversationOperationEnum.OPS_CODE_JOIN_ROOM -> {
// 99 is joining a conversation via password
labelText = resources!!.getString(R.string.nc_password)
binding?.textEdit?.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
}
binding?.textInputLayout?.hint = labelText
binding?.textInputLayout?.requestFocus()
ConversationOperationEnum.OPS_CODE_GET_AND_JOIN_ROOM -> {
labelText = resources!!.getString(R.string.nc_conversation_link)
binding?.textEdit?.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI
}
else -> {
}
}
if (PASSWORD_ENTRY_OPERATIONS.contains(operation)) {
binding?.textInputLayout?.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
binding?.smileyButton?.setOnClickListener { onSmileyClick() }
binding?.okButton?.setOnClickListener { onOkButtonClick() }
} else {
binding?.textInputLayout?.endIconMode = TextInputLayout.END_ICON_NONE
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv1))
ncApi.getRoom(
ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
ApiUtils.getUrlForRoom(apiVersion, currentUser!!.baseUrl, roomToken)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry(1)
.subscribe(object : Observer<RoomOverall> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
@Suppress("Detekt.LongMethod")
override fun onNext(roomOverall: RoomOverall) {
conversation = roomOverall.ocs!!.data
if (conversation != null && operation === ConversationOperationEnum.OPS_CODE_RENAME_ROOM) {
binding?.textEdit?.setText(conversation!!.name)
}
binding?.textEdit?.setOnEditorActionListener { v, actionId, event ->
@Suppress("IMPLICIT_BOXING_IN_IDENTITY_EQUALS")
if (actionId === EditorInfo.IME_ACTION_DONE && binding?.okButton?.isEnabled == true) {
binding?.okButton?.callOnClick()
return@setOnEditorActionListener true
}
false
}
textEditAddChangedListener()
var labelText = ""
when (operation) {
ConversationOperationEnum.OPS_CODE_INVITE_USERS,
ConversationOperationEnum.OPS_CODE_RENAME_ROOM -> {
labelText = resources!!.getString(R.string.nc_call_name)
binding?.textEdit?.inputType = InputType.TYPE_CLASS_TEXT
binding?.smileyButton?.visibility = View.VISIBLE
emojiPopup = binding?.let {
EmojiPopup(
rootView = view,
editText = it.textEdit,
onEmojiPopupShownListener = {
viewThemeUtils.platform.colorImageView(it.smileyButton, ColorRole.PRIMARY)
},
onEmojiPopupDismissListener = {
it.smileyButton.imageTintList = ColorStateList.valueOf(
ResourcesCompat.getColor(
resources!!,
R.color.medium_emphasis_text,
context.theme
)
)
},
onEmojiClickListener = {
binding?.textEdit?.editableText?.append(" ")
}
)
}
}
ConversationOperationEnum.OPS_CODE_JOIN_ROOM -> {
// 99 is joining a conversation via password
labelText = resources!!.getString(R.string.nc_password)
binding?.textEdit?.inputType =
InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
}
else -> {
}
}
if (PASSWORD_ENTRY_OPERATIONS.contains(operation)) {
binding?.textInputLayout?.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
} else {
binding?.textInputLayout?.endIconMode = TextInputLayout.END_ICON_NONE
}
binding?.textInputLayout?.let { viewThemeUtils.material.colorTextInputLayout(it) }
binding?.okButton?.let { viewThemeUtils.material.colorMaterialButtonText(it) }
binding?.textInputLayout?.hint = labelText
binding?.textInputLayout?.requestFocus()
binding?.smileyButton?.setOnClickListener { onSmileyClick() }
binding?.okButton?.setOnClickListener { onOkButtonClick() }
}
override fun onError(e: Throwable) {
Log.e("EntryMenuController", "error")
}
override fun onComplete() {
// unused atm
}
})
}
binding?.textInputLayout?.let { viewThemeUtils.material.colorTextInputLayout(it) }
binding?.okButton?.let { viewThemeUtils.material.colorMaterialButtonText(it) }
binding?.textInputLayout?.hint = labelText
binding?.textInputLayout?.requestFocus()
binding?.smileyButton?.setOnClickListener { onSmileyClick() }
binding?.okButton?.setOnClickListener { onOkButtonClick() }
}
private fun textEditAddChangedListener() {
@ -242,7 +299,8 @@ class EntryMenuController(args: Bundle) :
) {
val bundle = Bundle()
conversation!!.name = binding?.textEdit?.text.toString()
bundle.putParcelable(BundleKeys.KEY_ROOM, Parcels.wrap<Any>(conversation))
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
bundle.putString(BundleKeys.KEY_NEW_ROOM_NAME, binding?.textEdit?.text.toString())
bundle.putSerializable(BundleKeys.KEY_OPERATION_CODE, operation)
router.pushController(
RouterTransaction.with(OperationsMenuController(bundle))
@ -274,16 +332,9 @@ class EntryMenuController(args: Bundle) :
private fun joinRoom() {
val bundle = Bundle()
bundle.putParcelable(BundleKeys.KEY_ROOM, Parcels.wrap<Any>(conversation))
bundle.putString(BundleKeys.KEY_CALL_URL, callUrl)
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, binding?.textEdit?.text.toString())
bundle.putSerializable(BundleKeys.KEY_OPERATION_CODE, operation)
if (originalBundle.containsKey(BundleKeys.KEY_SERVER_CAPABILITIES)) {
bundle.putParcelable(
BundleKeys.KEY_SERVER_CAPABILITIES,
originalBundle.getParcelable(BundleKeys.KEY_SERVER_CAPABILITIES)
)
}
router.pushController(
RouterTransaction.with(OperationsMenuController(bundle))
.pushChangeHandler(HorizontalChangeHandler())
@ -296,15 +347,10 @@ class EntryMenuController(args: Bundle) :
originalBundle = args
operation = args.getSerializable(BundleKeys.KEY_OPERATION_CODE) as ConversationOperationEnum
if (args.containsKey(BundleKeys.KEY_ROOM)) {
conversation = unwrap<Conversation>(args.getParcelable<Parcelable>(BundleKeys.KEY_ROOM))
}
if (args.containsKey(BundleKeys.KEY_SHARE_INTENT)) {
shareIntent = unwrap<Intent>(args.getParcelable<Parcelable>(BundleKeys.KEY_SHARE_INTENT))
}
roomToken = args.getString(KEY_ROOM_TOKEN, "")
name = args.getString(BundleKeys.KEY_APP_ITEM_NAME, "")
packageName = args.getString(BundleKeys.KEY_APP_ITEM_PACKAGE_NAME, "")
callUrl = args.getString(BundleKeys.KEY_CALL_URL, "")
// callUrl = args.getString(BundleKeys.KEY_CALL_URL, "")
}
companion object {

View file

@ -21,8 +21,6 @@
*/
package com.nextcloud.talk.controllers.bottomsheet
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
@ -42,7 +40,6 @@ import com.nextcloud.talk.databinding.ControllerOperationsMenuBinding
import com.nextcloud.talk.events.ConversationsListFetchDataEvent
import com.nextcloud.talk.events.OpenConversationEvent
import com.nextcloud.talk.models.RetrofitBucket
import com.nextcloud.talk.models.json.capabilities.Capabilities
import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType
@ -53,19 +50,17 @@ import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.NoSupportedApiException
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CALL_URL
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_PASSWORD
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_TYPE
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INVITED_GROUP
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INVITED_PARTICIPANTS
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NEW_ROOM_NAME
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_OPERATION_CODE
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SERVER_CAPABILITIES
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
import io.reactivex.Observer
@ -100,13 +95,14 @@ class OperationsMenuController(args: Bundle) : BaseController(
private var currentUser: User? = null
private val callPassword: String
private val callUrl: String
private var roomToken: String
private val roomNameNew: String
private var baseUrl: String? = null
private var conversationToken: String? = null
private var disposable: Disposable? = null
private var conversationType: ConversationType? = null
private var invitedUsers: ArrayList<String>? = ArrayList()
private var invitedGroups: ArrayList<String>? = ArrayList()
private var serverCapabilities: Capabilities? = null
private var credentials: String? = null
private val conversationName: String
@ -128,42 +124,44 @@ class OperationsMenuController(args: Bundle) : BaseController(
baseUrl = callUrl.substring(0, callUrl.indexOf("/call"))
}
}
if (!TextUtils.isEmpty(baseUrl) && baseUrl != currentUser!!.baseUrl) {
if (serverCapabilities != null) {
try {
useBundledCapabilitiesForGuest()
} catch (e: IOException) {
// Fall back to fetching capabilities again
fetchCapabilitiesForGuest()
}
} else {
fetchCapabilitiesForGuest()
}
if (roomToken.isNotEmpty()) {
val apiVersion = apiVersion()
ncApi.getRoom(
credentials,
ApiUtils.getUrlForRoom(apiVersion, currentUser!!.baseUrl, roomToken)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry(1)
.subscribe(object : Observer<RoomOverall> {
override fun onSubscribe(d: Disposable) {
disposable = d
}
override fun onNext(roomOverall: RoomOverall) {
conversation = roomOverall.ocs!!.data
if (!TextUtils.isEmpty(baseUrl) && baseUrl != currentUser!!.baseUrl) {
fetchCapabilitiesForGuest()
} else {
processOperation()
}
}
override fun onError(e: Throwable) {
Log.e(TAG, "error while fetching room", e)
}
override fun onComplete() {
// unused atm
}
})
} else {
processOperation()
}
}
@Throws(IOException::class)
private fun useBundledCapabilitiesForGuest() {
currentUser = User()
currentUser!!.baseUrl = baseUrl
currentUser!!.userId = "?"
try {
currentUser!!.capabilities = serverCapabilities
} catch (e: IOException) {
Log.e("OperationsMenu", "Failed to serialize capabilities")
throw e
}
try {
checkCapabilities(currentUser!!)
processOperation()
} catch (e: NoSupportedApiException) {
showResultImage(everythingOK = false, isGuestSupportError = false)
Log.d(TAG, "No supported server version found", e)
}
}
private fun fetchCapabilitiesForGuest() {
ncApi.getCapabilities(null, ApiUtils.getUrlForCapabilities(baseUrl))
.subscribeOn(Schedulers.io())
@ -218,6 +216,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
ConversationOperationEnum.OPS_CODE_MARK_AS_UNREAD -> operationMarkAsUnread()
ConversationOperationEnum.OPS_CODE_REMOVE_FAVORITE,
ConversationOperationEnum.OPS_CODE_ADD_FAVORITE -> operationToggleFavorite()
ConversationOperationEnum.OPS_CODE_JOIN_ROOM -> operationJoinRoom()
else -> {
}
@ -287,7 +286,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
currentUser!!.baseUrl,
conversation!!.token
),
conversation!!.name
roomNameNew
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@ -427,17 +426,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
if (conversation!!.hasPassword && conversation!!.isGuest) {
eventBus.post(ConversationsListFetchDataEvent())
val bundle = Bundle()
bundle.putParcelable(KEY_ROOM, Parcels.wrap(conversation))
bundle.putString(KEY_CALL_URL, callUrl)
try {
bundle.putParcelable(
KEY_SERVER_CAPABILITIES,
Parcels.wrap<Capabilities>(currentUser!!.capabilities)
)
} catch (e: IOException) {
Log.e(TAG, "Failed to parse capabilities for guest")
showResultImage(everythingOK = false, isGuestSupportError = false)
}
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
bundle.putSerializable(KEY_OPERATION_CODE, ConversationOperationEnum.OPS_CODE_JOIN_ROOM)
router.pushController(
RouterTransaction.with(EntryMenuController(bundle))
@ -519,16 +508,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
binding?.resultTextView?.setText(R.string.nc_all_ok_operation)
} else {
binding?.resultTextView?.setTextColor(resources!!.getColor(R.color.nc_darkRed, null))
if (!isGuestSupportError) {
binding?.resultTextView?.setText(R.string.nc_failed_to_perform_operation)
} else {
binding?.resultTextView?.setText(R.string.nc_failed_signaling_settings)
binding?.webButton?.setOnClickListener {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(callUrl))
startActivity(browserIntent)
}
binding?.webButton?.visibility = View.VISIBLE
}
binding?.resultTextView?.setText(R.string.nc_failed_to_perform_operation)
}
binding?.resultTextView?.visibility = View.VISIBLE
if (everythingOK) {
@ -608,6 +588,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(addParticipantOverall: AddParticipantOverall) {
// unused atm
}
@ -653,6 +634,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(addParticipantOverall: AddParticipantOverall) {
// unused atm
}
@ -679,8 +661,6 @@ class OperationsMenuController(args: Bundle) : BaseController(
bundle.putString(KEY_ROOM_TOKEN, conversation!!.token)
bundle.putString(KEY_ROOM_ID, conversation!!.roomId)
bundle.putString(KEY_CONVERSATION_NAME, conversation!!.displayName)
bundle.putParcelable(KEY_USER_ENTITY, currentUser)
bundle.putParcelable(KEY_ACTIVE_CONVERSATION, Parcels.wrap(conversation))
bundle.putString(KEY_CONVERSATION_PASSWORD, callPassword)
eventBus.post(OpenConversationEvent(conversation, bundle))
}
@ -755,11 +735,10 @@ class OperationsMenuController(args: Bundle) : BaseController(
init {
operation = args.getSerializable(KEY_OPERATION_CODE) as ConversationOperationEnum?
if (args.containsKey(KEY_ROOM)) {
conversation = Parcels.unwrap(args.getParcelable(KEY_ROOM))
}
callPassword = args.getString(KEY_CONVERSATION_PASSWORD, "")
callUrl = args.getString(KEY_CALL_URL, "")
roomToken = args.getString(KEY_ROOM_TOKEN, "")
roomNameNew = args.getString(KEY_NEW_ROOM_NAME, "")
if (args.containsKey(KEY_INVITED_PARTICIPANTS)) {
invitedUsers = args.getStringArrayList(KEY_INVITED_PARTICIPANTS)
}
@ -769,9 +748,6 @@ class OperationsMenuController(args: Bundle) : BaseController(
if (args.containsKey(KEY_CONVERSATION_TYPE)) {
conversationType = Parcels.unwrap(args.getParcelable(KEY_CONVERSATION_TYPE))
}
if (args.containsKey(KEY_SERVER_CAPABILITIES)) {
serverCapabilities = Parcels.unwrap(args.getParcelable(KEY_SERVER_CAPABILITIES))
}
conversationName = args.getString(KEY_CONVERSATION_NAME, "")
}
}

View file

@ -32,7 +32,6 @@ import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.os.Parcelable
import android.text.TextUtils
import android.util.Log
import android.view.Menu
@ -86,6 +85,7 @@ import com.nextcloud.talk.utils.DateConstants
import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.nextcloud.talk.utils.preferences.preferencestorage.DatabaseStorageModule
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
@ -95,7 +95,6 @@ import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.parceler.Parcels
import java.util.Calendar
import java.util.Collections
import java.util.Locale
@ -110,6 +109,9 @@ class ConversationInfoActivity :
@Inject
lateinit var ncApi: NcApi
@Inject
lateinit var currentUserProvider: CurrentUserProviderNew
@Inject
lateinit var conversationsRepository: ConversationsRepository
@ -152,7 +154,8 @@ class ConversationInfoActivity :
setContentView(binding.root)
setupSystemColors()
conversationUser = intent.getParcelableExtra(BundleKeys.KEY_USER_ENTITY)!!
conversationUser = currentUserProvider.currentUser.blockingGet()
conversationToken = intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)!!
hasAvatarSpacing = intent.getBooleanExtra(BundleKeys.KEY_ROOM_ONE_TO_ONE, false)
credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)
@ -225,11 +228,6 @@ class ConversationInfoActivity :
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.edit) {
val bundle = Bundle()
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, conversationUser)
bundle.putParcelable(
BundleKeys.KEY_ACTIVE_CONVERSATION,
Parcels.wrap(conversation)
)
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
val intent = Intent(this, ConversationInfoEditActivity::class.java)
@ -270,7 +268,6 @@ class ConversationInfoActivity :
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.putExtra(BundleKeys.KEY_CONVERSATION_NAME, conversation?.displayName)
intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
intent.putExtra(BundleKeys.KEY_USER_ENTITY, conversationUser as Parcelable)
intent.putExtra(SharedItemsActivity.KEY_USER_IS_OWNER_OR_MODERATOR, conversation?.isParticipantOwnerOrModerator)
startActivity(intent)
}

View file

@ -34,34 +34,31 @@ import android.view.View
import android.widget.Toast
import androidx.core.net.toFile
import androidx.core.view.ViewCompat
import androidx.lifecycle.ViewModelProvider
import autodagger.AutoInjector
import com.github.dhaval2404.imagepicker.ImagePicker
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.conversationinfoedit.viewmodel.ConversationInfoEditViewModel
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ActivityConversationInfoEditBinding
import com.nextcloud.talk.extensions.loadConversationAvatar
import com.nextcloud.talk.extensions.loadSystemAvatar
import com.nextcloud.talk.extensions.loadUserAvatar
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.repositories.conversations.ConversationsRepository
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.Mimetype
import com.nextcloud.talk.utils.PickImage
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import org.parceler.Parcels
import java.io.File
import javax.inject.Inject
@ -75,13 +72,18 @@ class ConversationInfoEditActivity :
lateinit var ncApi: NcApi
@Inject
lateinit var conversationsRepository: ConversationsRepository
lateinit var currentUserProvider: CurrentUserProviderNew
private lateinit var conversationToken: String
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var conversationInfoEditViewModel: ConversationInfoEditViewModel
private lateinit var roomToken: String
private lateinit var conversationUser: User
private lateinit var credentials: String
private var conversation: Conversation? = null
private var conversation: ConversationModel? = null
private lateinit var pickImage: PickImage
@ -96,12 +98,14 @@ class ConversationInfoEditActivity :
val extras: Bundle? = intent.extras
conversationUser = extras?.getParcelable(BundleKeys.KEY_USER_ENTITY)!!
conversationToken = extras.getString(BundleKeys.KEY_ROOM_TOKEN)!!
conversationUser = currentUserProvider.currentUser.blockingGet()
if (conversation == null && intent.hasExtra(BundleKeys.KEY_ACTIVE_CONVERSATION)) {
conversation = Parcels.unwrap<Conversation>(extras.getParcelable(BundleKeys.KEY_ACTIVE_CONVERSATION))
}
roomToken = extras?.getString(BundleKeys.KEY_ROOM_TOKEN)!!
conversationInfoEditViewModel =
ViewModelProvider(this, viewModelFactory)[ConversationInfoEditViewModel::class.java]
conversationInfoEditViewModel.getRoom(conversationUser, roomToken)
viewThemeUtils.material.colorTextInputLayout(binding.conversationNameInputLayout)
viewThemeUtils.material.colorTextInputLayout(binding.conversationDescriptionInputLayout)
@ -109,21 +113,57 @@ class ConversationInfoEditActivity :
credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)
pickImage = PickImage(this, conversationUser)
initObservers()
}
override fun onResume() {
super.onResume()
}
loadConversationAvatar()
private fun initObservers() {
conversationInfoEditViewModel.viewState.observe(this) { state ->
when (state) {
is ConversationInfoEditViewModel.GetRoomSuccessState -> {
conversation = state.conversationModel
binding.conversationName.setText(conversation!!.displayName)
binding.conversationName.setText(conversation!!.displayName)
if (conversation!!.description != null && conversation!!.description!!.isNotEmpty()) {
binding.conversationDescription.setText(conversation!!.description)
}
if (conversation!!.description != null && conversation!!.description!!.isNotEmpty()) {
binding.conversationDescription.setText(conversation!!.description)
}
if (!CapabilitiesUtilNew.isConversationDescriptionEndpointAvailable(conversationUser)) {
binding.conversationDescription.isEnabled = false
if (!CapabilitiesUtilNew.isConversationDescriptionEndpointAvailable(conversationUser)) {
binding.conversationDescription.isEnabled = false
}
loadConversationAvatar()
}
is ConversationInfoEditViewModel.GetRoomErrorState -> {
Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
}
is ConversationInfoEditViewModel.UploadAvatarSuccessState -> {
conversation = state.conversationModel
loadConversationAvatar()
}
is ConversationInfoEditViewModel.UploadAvatarErrorState -> {
Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
}
is ConversationInfoEditViewModel.DeleteAvatarSuccessState -> {
conversation = state.conversationModel
loadConversationAvatar()
}
is ConversationInfoEditViewModel.DeleteAvatarErrorState -> {
Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
}
else -> {}
}
}
}
@ -284,98 +324,27 @@ class ConversationInfoEditActivity :
}
}
private fun uploadAvatar(file: File?) {
val builder = MultipartBody.Builder()
builder.setType(MultipartBody.FORM)
builder.addFormDataPart(
"file",
file!!.name,
file.asRequestBody(Mimetype.IMAGE_PREFIX_GENERIC.toMediaTypeOrNull())
)
val filePart: MultipartBody.Part = MultipartBody.Part.createFormData(
"file",
file.name,
file.asRequestBody(Mimetype.IMAGE_JPG.toMediaTypeOrNull())
)
// upload file
ncApi.uploadConversationAvatar(
credentials,
ApiUtils.getUrlForConversationAvatar(1, conversationUser.baseUrl, conversation!!.token),
filePart
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<RoomOverall> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(roomOverall: RoomOverall) {
conversation = roomOverall.ocs!!.data
loadConversationAvatar()
}
override fun onError(e: Throwable) {
Toast.makeText(
applicationContext,
context.getString(R.string.default_error_msg),
Toast.LENGTH_LONG
).show()
Log.e(TAG, "Error uploading avatar", e)
}
override fun onComplete() {
// unused atm
}
})
private fun uploadAvatar(file: File) {
conversationInfoEditViewModel.uploadConversationAvatar(conversationUser, file, roomToken)
}
private fun deleteAvatar() {
ncApi.deleteConversationAvatar(
credentials,
ApiUtils.getUrlForConversationAvatar(1, conversationUser.baseUrl, conversationToken)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<RoomOverall> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(roomOverall: RoomOverall) {
conversation = roomOverall.ocs!!.data
loadConversationAvatar()
}
override fun onError(e: Throwable) {
Toast.makeText(
applicationContext,
context.getString(R.string.default_error_msg),
Toast.LENGTH_LONG
).show()
Log.e(TAG, "Failed to delete avatar", e)
}
override fun onComplete() {
// unused atm
}
})
conversationInfoEditViewModel.deleteConversationAvatar(conversationUser, roomToken)
}
private fun loadConversationAvatar() {
setupAvatarOptions()
when (conversation!!.type) {
Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(conversation!!.name)) {
ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(conversation!!.name)) {
conversation!!.name?.let { binding.avatarImage.loadUserAvatar(conversationUser, it, true, false) }
}
Conversation.ConversationType.ROOM_GROUP_CALL, Conversation.ConversationType.ROOM_PUBLIC_CALL -> {
ConversationType.ROOM_GROUP_CALL, ConversationType.ROOM_PUBLIC_CALL -> {
binding.avatarImage.loadConversationAvatar(conversationUser, conversation!!, false, viewThemeUtils)
}
Conversation.ConversationType.ROOM_SYSTEM -> {
ConversationType.ROOM_SYSTEM -> {
binding.avatarImage.loadSystemAvatar()
}

View file

@ -0,0 +1,33 @@
/*
* Nextcloud Talk application
*
* @author Marcel Hibbe
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.conversationinfoedit.data
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import io.reactivex.Observable
import java.io.File
interface ConversationInfoEditRepository {
fun uploadConversationAvatar(user: User, file: File, roomToken: String): Observable<ConversationModel>
fun deleteConversationAvatar(user: User, roomToken: String): Observable<ConversationModel>
}

View file

@ -0,0 +1,70 @@
/*
* Nextcloud Talk application
*
* @author Marcel Hibbe
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.conversationinfoedit.data
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.Mimetype
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import io.reactivex.Observable
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
class ConversationInfoEditRepositoryImpl(private val ncApi: NcApi, currentUserProvider: CurrentUserProviderNew) :
ConversationInfoEditRepository {
val currentUser: User = currentUserProvider.currentUser.blockingGet()
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv3, 1))
override fun uploadConversationAvatar(user: User, file: File, roomToken: String): Observable<ConversationModel> {
val builder = MultipartBody.Builder()
builder.setType(MultipartBody.FORM)
builder.addFormDataPart(
"file",
file!!.name,
file.asRequestBody(Mimetype.IMAGE_PREFIX_GENERIC.toMediaTypeOrNull())
)
val filePart: MultipartBody.Part = MultipartBody.Part.createFormData(
"file",
file.name,
file.asRequestBody(Mimetype.IMAGE_JPG.toMediaTypeOrNull())
)
return ncApi.uploadConversationAvatar(
credentials,
ApiUtils.getUrlForConversationAvatar(1, user.baseUrl, roomToken),
filePart
).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
}
override fun deleteConversationAvatar(user: User, roomToken: String): Observable<ConversationModel> {
return ncApi.deleteConversationAvatar(
credentials,
ApiUtils.getUrlForConversationAvatar(1, user.baseUrl, roomToken)
).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
}
}

View file

@ -0,0 +1,141 @@
/*
* Nextcloud Talk application
*
* @author Marcel Hibbe
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.conversationinfoedit.viewmodel
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.nextcloud.talk.chat.data.ChatRepository
import com.nextcloud.talk.conversationinfoedit.data.ConversationInfoEditRepository
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import java.io.File
import javax.inject.Inject
class ConversationInfoEditViewModel @Inject constructor(
private val repository: ChatRepository,
private val conversationInfoEditRepository: ConversationInfoEditRepository
) : ViewModel() {
sealed interface ViewState
object GetRoomStartState : ViewState
object GetRoomErrorState : ViewState
open class GetRoomSuccessState(val conversationModel: ConversationModel) : ViewState
object UploadAvatarErrorState : ViewState
open class UploadAvatarSuccessState(val conversationModel: ConversationModel) : ViewState
object DeleteAvatarErrorState : ViewState
open class DeleteAvatarSuccessState(val conversationModel: ConversationModel) : ViewState
private val _viewState: MutableLiveData<ViewState> = MutableLiveData(GetRoomStartState)
val viewState: LiveData<ViewState>
get() = _viewState
fun getRoom(user: User, token: String) {
_viewState.value = GetRoomStartState
repository.getRoom(user, token)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(GetRoomObserver())
}
fun uploadConversationAvatar(user: User, file: File, roomToken: String) {
conversationInfoEditRepository.uploadConversationAvatar(user, file, roomToken)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(UploadConversationAvatarObserver())
}
fun deleteConversationAvatar(user: User, roomToken: String) {
conversationInfoEditRepository.deleteConversationAvatar(user, roomToken)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(DeleteConversationAvatarObserver())
}
inner class GetRoomObserver : Observer<ConversationModel> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(conversationModel: ConversationModel) {
_viewState.value = GetRoomSuccessState(conversationModel)
}
override fun onError(e: Throwable) {
Log.e(TAG, "Error when fetching room")
_viewState.value = GetRoomErrorState
}
override fun onComplete() {
// unused atm
}
}
inner class UploadConversationAvatarObserver : Observer<ConversationModel> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(conversationModel: ConversationModel) {
_viewState.value = UploadAvatarSuccessState(conversationModel)
}
override fun onError(e: Throwable) {
Log.e(TAG, "Error when uploading avatar")
_viewState.value = UploadAvatarErrorState
}
override fun onComplete() {
// unused atm
}
}
inner class DeleteConversationAvatarObserver : Observer<ConversationModel> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(conversationModel: ConversationModel) {
_viewState.value = DeleteAvatarSuccessState(conversationModel)
}
override fun onError(e: Throwable) {
Log.e(TAG, "Error when deleting avatar")
_viewState.value = DeleteAvatarErrorState
}
override fun onComplete() {
// unused atm
}
}
companion object {
private val TAG = ConversationInfoEditViewModel::class.simpleName
}
}

View file

@ -106,17 +106,14 @@ import com.nextcloud.talk.utils.FileUtils
import com.nextcloud.talk.utils.Mimetype
import com.nextcloud.talk.utils.ParticipantPermissions
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_HIDE_SOURCE_ROOM
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_FLAG
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_TEXT
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NEW_CONVERSATION
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SHARED_TEXT
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isServerEOL
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isUnifiedSearchAvailable
@ -134,7 +131,6 @@ import io.reactivex.schedulers.Schedulers
import org.apache.commons.lang3.builder.CompareToBuilder
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.parceler.Parcels
import retrofit2.HttpException
import java.util.Objects
import java.util.concurrent.TimeUnit
@ -1074,7 +1070,6 @@ class ConversationsListActivity :
if (clickedItem != null) {
val conversation = (clickedItem as ConversationItem).model
conversationsListBottomDialog = ConversationsListBottomDialog(
this,
this,
userManager.currentUser.blockingGet(),
conversation
@ -1185,8 +1180,6 @@ class ConversationsListActivity :
}
val bundle = Bundle()
bundle.putParcelable(KEY_USER_ENTITY, currentUser)
bundle.putParcelable(KEY_ACTIVE_CONVERSATION, Parcels.wrap(selectedConversation))
bundle.putString(KEY_ROOM_TOKEN, selectedConversation!!.token)
bundle.putString(KEY_ROOM_ID, selectedConversation!!.roomId)
bundle.putString(KEY_SHARED_TEXT, textToPaste)
@ -1229,38 +1222,35 @@ class ConversationsListActivity :
if (conversationMenuBundle != null &&
isInternalUserEqualsCurrentUser(currentUser, conversationMenuBundle)
) {
val conversation = Parcels.unwrap<Conversation>(conversationMenuBundle!!.getParcelable(KEY_ROOM))
if (conversation != null) {
binding?.floatingActionButton?.let {
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) { _, _ ->
val data = Data.Builder()
data.putLong(
KEY_INTERNAL_USER_ID,
conversationMenuBundle!!.getLong(KEY_INTERNAL_USER_ID)
)
data.putString(KEY_ROOM_TOKEN, conversation.token)
conversationMenuBundle = null
deleteConversation(data.build())
}
.setNegativeButton(R.string.nc_cancel) { _, _ ->
conversationMenuBundle = null
}
viewThemeUtils.dialog
.colorMaterialAlertDialogBackground(it.context, dialogBuilder)
val dialog = dialogBuilder.show()
viewThemeUtils.platform.colorTextButtons(
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
binding?.floatingActionButton?.let {
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) { _, _ ->
val data = Data.Builder()
data.putLong(
KEY_INTERNAL_USER_ID,
conversationMenuBundle!!.getLong(KEY_INTERNAL_USER_ID)
)
data.putString(KEY_ROOM_TOKEN, bundle.getString(KEY_ROOM_TOKEN))
conversationMenuBundle = null
deleteConversation(data.build())
}
.setNegativeButton(R.string.nc_cancel) { _, _ ->
conversationMenuBundle = null
}
viewThemeUtils.dialog
.colorMaterialAlertDialogBackground(it.context, dialogBuilder)
val dialog = dialogBuilder.show()
viewThemeUtils.platform.colorTextButtons(
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
)
}
}
}

View file

@ -26,6 +26,10 @@
package com.nextcloud.talk.dagger.modules
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.chat.data.ChatRepository
import com.nextcloud.talk.chat.data.ChatRepositoryImpl
import com.nextcloud.talk.conversationinfoedit.data.ConversationInfoEditRepository
import com.nextcloud.talk.conversationinfoedit.data.ConversationInfoEditRepositoryImpl
import com.nextcloud.talk.data.source.local.TalkDatabase
import com.nextcloud.talk.data.storage.ArbitraryStoragesRepository
import com.nextcloud.talk.data.storage.ArbitraryStoragesRepositoryImpl
@ -123,4 +127,16 @@ class RepositoryModule {
TranslateRepository {
return TranslateRepositoryImpl(ncApi)
}
@Provides
fun provideChatRepository(ncApi: NcApi):
ChatRepository {
return ChatRepositoryImpl(ncApi)
}
@Provides
fun provideConversationInfoEditRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew):
ConversationInfoEditRepository {
return ConversationInfoEditRepositoryImpl(ncApi, userProvider)
}
}

View file

@ -23,6 +23,9 @@ package com.nextcloud.talk.dagger.modules
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.nextcloud.talk.callnotification.viewmodel.CallNotificationViewModel
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
import com.nextcloud.talk.conversationinfoedit.viewmodel.ConversationInfoEditViewModel
import com.nextcloud.talk.messagesearch.MessageSearchViewModel
import com.nextcloud.talk.openconversations.viewmodels.OpenConversationsViewModel
import com.nextcloud.talk.polls.viewmodels.PollCreateViewModel
@ -112,5 +115,20 @@ abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(OpenConversationsViewModel::class)
abstract fun openConversationsViewModelModel(viewModel: OpenConversationsViewModel): ViewModel
abstract fun openConversationsViewModel(viewModel: OpenConversationsViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(ChatViewModel::class)
abstract fun chatViewModel(viewModel: ChatViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(CallNotificationViewModel::class)
abstract fun callNotificationViewModel(viewModel: CallNotificationViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(ConversationInfoEditViewModel::class)
abstract fun conversationInfoEditViewModel(viewModel: ConversationInfoEditViewModel): ViewModel
}

View file

@ -94,6 +94,7 @@ abstract class TalkDatabase : RoomDatabase() {
return Room
.databaseBuilder(context.applicationContext, TalkDatabase::class.java, dbName)
// comment out openHelperFactory to view the database entries in Android Studio for debugging
.openHelperFactory(factory)
.addMigrations(Migrations.MIGRATION_6_8, Migrations.MIGRATION_7_8)
.allowMainThreadQueries()

View file

@ -44,6 +44,8 @@ import coil.transform.RoundedCornersTransformation
import com.amulyakhare.textdrawable.TextDrawable
import com.nextcloud.talk.R
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
@ -52,11 +54,26 @@ import com.nextcloud.talk.utils.DisplayUtils
private const val ROUNDING_PIXEL = 16f
private const val TAG = "ImageViewExtensions"
@Deprecated("use other constructor that expects com.nextcloud.talk.models.domain.ConversationModel")
fun ImageView.loadConversationAvatar(
user: User,
conversation: Conversation,
ignoreCache: Boolean,
viewThemeUtils: ViewThemeUtils?
): io.reactivex.disposables.Disposable {
return loadConversationAvatar(
user,
ConversationModel.mapToConversationModel(conversation),
ignoreCache,
viewThemeUtils
)
}
fun ImageView.loadConversationAvatar(
user: User,
conversation: ConversationModel,
ignoreCache: Boolean,
viewThemeUtils: ViewThemeUtils?
): io.reactivex.disposables.Disposable {
val imageRequestUri = ApiUtils.getUrlForConversationAvatarWithVersion(
1,
@ -68,10 +85,10 @@ fun ImageView.loadConversationAvatar(
if (conversation.avatarVersion.isNullOrEmpty() && viewThemeUtils != null) {
when (conversation.type) {
Conversation.ConversationType.ROOM_GROUP_CALL ->
ConversationType.ROOM_GROUP_CALL ->
return loadDefaultGroupCallAvatar(viewThemeUtils)
Conversation.ConversationType.ROOM_PUBLIC_CALL ->
ConversationType.ROOM_PUBLIC_CALL ->
return loadDefaultPublicCallAvatar(viewThemeUtils)
else -> {}
@ -82,10 +99,10 @@ fun ImageView.loadConversationAvatar(
// when no own images are set. (although these default avatars can not be themed for the android app..)
val errorPlaceholder =
when (conversation.type) {
Conversation.ConversationType.ROOM_GROUP_CALL ->
ConversationType.ROOM_GROUP_CALL ->
ContextCompat.getDrawable(context, R.drawable.ic_circular_group)
Conversation.ConversationType.ROOM_PUBLIC_CALL ->
ConversationType.ROOM_PUBLIC_CALL ->
ContextCompat.getDrawable(context, R.drawable.ic_circular_link)
else -> ContextCompat.getDrawable(context, R.drawable.account_circle_96dp)

View file

@ -55,12 +55,12 @@ import autodagger.AutoInjector
import com.bluelinelabs.logansquare.LoganSquare
import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.CallNotificationActivity
import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
import com.nextcloud.talk.callnotification.CallNotificationActivity
import com.nextcloud.talk.models.SignatureVerification
import com.nextcloud.talk.models.json.chat.ChatUtils.Companion.getParsedMessage
import com.nextcloud.talk.models.json.conversations.RoomOverall
@ -94,7 +94,6 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NOTIFICATION_TIMESTAMP
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SHARE_RECORDING_TO_CHAT_URL
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SYSTEM_NOTIFICATION_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder
import io.reactivex.Observable
@ -197,7 +196,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
val bundle = Bundle()
bundle.putString(KEY_ROOM_TOKEN, pushMessage.id)
bundle.putInt(KEY_NOTIFICATION_TIMESTAMP, pushMessage.timestamp.toInt())
bundle.putParcelable(KEY_USER_ENTITY, signatureVerification.user)
bundle.putLong(KEY_INTERNAL_USER_ID, signatureVerification.user!!.id!!)
bundle.putBoolean(KEY_FROM_NOTIFICATION_START_CALL, true)
fullScreenIntent.putExtras(bundle)
fullScreenIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
@ -680,7 +679,6 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
shareRecordingIntent.putExtra(KEY_SYSTEM_NOTIFICATION_ID, systemNotificationId)
shareRecordingIntent.putExtra(KEY_SHARE_RECORDING_TO_CHAT_URL, shareToChatUrl)
shareRecordingIntent.putExtra(KEY_ROOM_TOKEN, pushMessage.id)
shareRecordingIntent.putExtra(KEY_USER_ENTITY, signatureVerification.user)
val intentFlag: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
@ -924,7 +922,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
val bundle = Bundle()
bundle.putString(KEY_ROOM_TOKEN, pushMessage.id)
bundle.putParcelable(KEY_USER_ENTITY, signatureVerification.user)
bundle.putLong(KEY_INTERNAL_USER_ID, signatureVerification.user!!.id!!)
bundle.putBoolean(KEY_FROM_NOTIFICATION_START_CALL, false)
intent.putExtras(bundle)
return intent
@ -935,7 +933,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
val bundle = Bundle()
bundle.putString(KEY_ROOM_TOKEN, pushMessage.id)
bundle.putParcelable(KEY_USER_ENTITY, signatureVerification.user)
bundle.putLong(KEY_INTERNAL_USER_ID, signatureVerification.user!!.id!!)
bundle.putBoolean(KEY_FROM_NOTIFICATION_START_CALL, false)
intent.putExtras(bundle)

View file

@ -53,8 +53,8 @@ import com.nextcloud.talk.utils.FileUtils
import com.nextcloud.talk.utils.NotificationUtils
import com.nextcloud.talk.utils.RemoteFileUtils
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FROM_NOTIFICATION_START_CALL
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
import com.nextcloud.talk.utils.preferences.AppPreferences
@ -244,7 +244,7 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
bundle.putString(KEY_ROOM_TOKEN, roomToken)
bundle.putParcelable(KEY_USER_ENTITY, currentUser)
bundle.putLong(KEY_INTERNAL_USER_ID, currentUser.id!!)
bundle.putBoolean(KEY_FROM_NOTIFICATION_START_CALL, false)
intent.putExtras(bundle)

View file

@ -0,0 +1,136 @@
package com.nextcloud.talk.models.domain
import com.nextcloud.talk.models.json.conversations.Conversation
class ConversationModel(
var roomId: String?,
var token: String? = null,
var name: String? = null,
var displayName: String? = null,
var description: String? = null,
var type: ConversationType? = null,
var lastPing: Long = 0,
var participantType: ParticipantType? = null,
var hasPassword: Boolean = false,
var sessionId: String? = null,
var actorId: String? = null,
var actorType: String? = null,
var password: String? = null,
var favorite: Boolean = false,
var lastActivity: Long = 0,
var unreadMessages: Int = 0,
var unreadMention: Boolean = false,
// var lastMessage: .....? = null,
var objectType: ObjectType? = null,
var notificationLevel: NotificationLevel? = null,
var conversationReadOnlyState: ConversationReadOnlyState? = null,
var lobbyState: LobbyState? = null,
var lobbyTimer: Long? = null,
var lastReadMessage: Int = 0,
var hasCall: Boolean = false,
var callFlag: Int = 0,
var canStartCall: Boolean = false,
var canLeaveConversation: Boolean? = null,
var canDeleteConversation: Boolean? = null,
var unreadMentionDirect: Boolean? = null,
var notificationCalls: Int? = null,
var permissions: Int = 0,
var messageExpiration: Int = 0,
var status: String? = null,
var statusIcon: String? = null,
var statusMessage: String? = null,
var statusClearAt: Long? = 0,
var callRecording: Int = 0,
var avatarVersion: String? = null,
var hasCustomAvatar: Boolean? = null
) {
companion object {
fun mapToConversationModel(
conversation: Conversation
): ConversationModel {
return ConversationModel(
roomId = conversation.roomId,
token = conversation.token,
name = conversation.name,
displayName = conversation.displayName,
description = conversation.description,
type = conversation.type?.let { ConversationType.valueOf(it.name) },
lastPing = conversation.lastPing,
participantType = conversation.participantType?.let { ParticipantType.valueOf(it.name) },
hasPassword = conversation.hasPassword,
sessionId = conversation.sessionId,
actorId = conversation.actorId,
actorType = conversation.actorType,
password = conversation.password,
favorite = conversation.favorite,
lastActivity = conversation.lastActivity,
unreadMessages = conversation.unreadMessages,
unreadMention = conversation.unreadMention,
// lastMessage = conversation.lastMessage, to do...
objectType = conversation.objectType?.let { ObjectType.valueOf(it.name) },
notificationLevel = conversation.notificationLevel?.let {
NotificationLevel.valueOf(
it.name
)
},
conversationReadOnlyState = conversation.conversationReadOnlyState?.let {
ConversationReadOnlyState.valueOf(
it.name
)
},
lobbyState = conversation.lobbyState?.let { LobbyState.valueOf(it.name) },
lobbyTimer = conversation.lobbyTimer,
lastReadMessage = conversation.lastReadMessage,
hasCall = conversation.hasCall,
callFlag = conversation.callFlag,
canStartCall = conversation.canStartCall,
canLeaveConversation = conversation.canLeaveConversation,
canDeleteConversation = conversation.canDeleteConversation,
unreadMentionDirect = conversation.unreadMentionDirect,
notificationCalls = conversation.notificationCalls,
permissions = conversation.permissions,
messageExpiration = conversation.messageExpiration,
status = conversation.status,
statusIcon = conversation.statusIcon,
statusMessage = conversation.statusMessage,
statusClearAt = conversation.statusClearAt,
callRecording = conversation.callRecording,
avatarVersion = conversation.avatarVersion,
hasCustomAvatar = conversation.hasCustomAvatar
)
}
}
}
enum class ConversationType {
DUMMY,
ROOM_TYPE_ONE_TO_ONE_CALL,
ROOM_GROUP_CALL,
ROOM_PUBLIC_CALL,
ROOM_SYSTEM,
FORMER_ONE_TO_ONE
}
enum class ParticipantType {
DUMMY, OWNER, MODERATOR, USER, GUEST, USER_FOLLOWING_LINK, GUEST_MODERATOR
}
enum class ObjectType {
DEFAULT,
SHARE_PASSWORD,
FILE,
ROOM
}
enum class NotificationLevel {
DEFAULT, ALWAYS, MENTION, NEVER
}
enum class ConversationReadOnlyState {
CONVERSATION_READ_WRITE, CONVERSATION_READ_ONLY
}
enum class LobbyState {
LOBBY_STATE_ALL_PARTICIPANTS, LOBBY_STATE_MODERATORS_ONLY
}

View file

@ -158,39 +158,47 @@ data class Conversation(
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
@Deprecated("Use ConversationUtil")
val isPublic: Boolean
get() = ConversationType.ROOM_PUBLIC_CALL == type
@Deprecated("Use ConversationUtil")
val isGuest: Boolean
get() = ParticipantType.GUEST == participantType ||
ParticipantType.GUEST_MODERATOR == participantType ||
ParticipantType.USER_FOLLOWING_LINK == participantType
@Deprecated("Use ConversationUtil")
val isParticipantOwnerOrModerator: Boolean
get() = ParticipantType.OWNER == participantType ||
ParticipantType.GUEST_MODERATOR == participantType ||
ParticipantType.MODERATOR == participantType
@Deprecated("Use ConversationUtil")
private fun isLockedOneToOne(conversationUser: User): Boolean {
return type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL &&
CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "locked-one-to-one-rooms")
}
@Deprecated("Use ConversationUtil")
fun canModerate(conversationUser: User): Boolean {
return isParticipantOwnerOrModerator &&
!isLockedOneToOne(conversationUser) &&
type != ConversationType.FORMER_ONE_TO_ONE
}
@Deprecated("Use ConversationUtil")
fun isLobbyViewApplicable(conversationUser: User): Boolean {
return !canModerate(conversationUser) &&
(type == ConversationType.ROOM_GROUP_CALL || type == ConversationType.ROOM_PUBLIC_CALL)
}
@Deprecated("Use ConversationUtil")
fun isNameEditable(conversationUser: User): Boolean {
return canModerate(conversationUser) && ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL != type
}
@Deprecated("Use ConversationUtil")
fun canLeave(): Boolean {
return if (canLeaveConversation != null) {
// Available since APIv2
@ -200,6 +208,7 @@ data class Conversation(
}
}
@Deprecated("Use ConversationUtil")
fun canDelete(conversationUser: User): Boolean {
return if (canDeleteConversation != null) {
// Available since APIv2

View file

@ -79,10 +79,7 @@ class ListOpenConversationsActivity : BaseActivity() {
}
private fun adapterOnClick(conversation: OpenConversation) {
val user = userProvider.currentUser.blockingGet()
val bundle = Bundle()
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, user)
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversation.roomToken)
val chatIntent = Intent(context, ChatActivity::class.java)

View file

@ -37,6 +37,7 @@ import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.DialogPollMainBinding
import com.nextcloud.talk.polls.viewmodels.PollMainViewModel
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
@ -48,16 +49,22 @@ class PollMainDialogFragment : DialogFragment() {
@Inject
lateinit var viewThemeUtils: ViewThemeUtils
var currentUserProvider: CurrentUserProviderNew? = null
@Inject set
private lateinit var binding: DialogPollMainBinding
private lateinit var viewModel: PollMainViewModel
lateinit var user: User
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
viewModel = ViewModelProvider(this, viewModelFactory)[PollMainViewModel::class.java]
val user: User = arguments?.getParcelable(KEY_USER_ENTITY)!!
user = currentUserProvider?.currentUser?.blockingGet()!!
val roomToken = arguments?.getString(KEY_ROOM_TOKEN)!!
val isOwnerOrModerator = arguments?.getBoolean(KEY_OWNER_OR_MODERATOR)!!
val pollId = arguments?.getString(KEY_POLL_ID)!!

View file

@ -56,8 +56,6 @@ class ShareRecordingToChatReceiver : BroadcastReceiver() {
lateinit var currentUser: User
private var systemNotificationId: Int? = null
private var link: String? = null
var roomToken: String? = null
var conversationOfShareTarget: User? = null
init {
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
@ -68,9 +66,6 @@ class ShareRecordingToChatReceiver : BroadcastReceiver() {
systemNotificationId = intent!!.getIntExtra(KEY_SYSTEM_NOTIFICATION_ID, 0)
link = intent.getStringExtra(BundleKeys.KEY_SHARE_RECORDING_TO_CHAT_URL)
roomToken = intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)
conversationOfShareTarget = intent.getParcelableExtra<User>(BundleKeys.KEY_USER_ENTITY)
val id = intent.getLongExtra(KEY_INTERNAL_USER_ID, userManager.currentUser.blockingGet().id!!)
currentUser = userManager.getUserWithId(id).blockingGet()

View file

@ -23,19 +23,18 @@ package com.nextcloud.talk.repositories.reactions
import com.nextcloud.talk.models.domain.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.conversations.Conversation
import io.reactivex.Observable
interface ReactionsRepository {
fun addReaction(
currentConversation: Conversation,
roomToken: String,
message: ChatMessage,
emoji: String
): Observable<ReactionAddedModel>
fun deleteReaction(
currentConversation: Conversation,
roomToken: String,
message: ChatMessage,
emoji: String
): Observable<ReactionDeletedModel>

View file

@ -25,7 +25,6 @@ import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.generic.GenericMeta
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
@ -38,7 +37,7 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
override fun addReaction(
currentConversation: Conversation,
roomToken: String,
message: ChatMessage,
emoji: String
): Observable<ReactionAddedModel> {
@ -46,7 +45,7 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur
credentials,
ApiUtils.getUrlForMessageReaction(
currentUser.baseUrl,
currentConversation.token,
roomToken,
message.id
),
emoji
@ -54,7 +53,7 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur
}
override fun deleteReaction(
currentConversation: Conversation,
roomToken: String,
message: ChatMessage,
emoji: String
): Observable<ReactionDeletedModel> {
@ -62,7 +61,7 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur
credentials,
ApiUtils.getUrlForMessageReaction(
currentUser.baseUrl,
currentConversation.token,
roomToken,
message.id
),
emoji

View file

@ -49,12 +49,15 @@ import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class SharedItemsActivity : AppCompatActivity() {
@Inject
lateinit var currentUserProvider: CurrentUserProviderNew
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
@ -70,7 +73,9 @@ class SharedItemsActivity : AppCompatActivity() {
val roomToken = intent.getStringExtra(KEY_ROOM_TOKEN)!!
val conversationName = intent.getStringExtra(KEY_CONVERSATION_NAME)
val user = intent.getParcelableExtra<User>(KEY_USER_ENTITY)!!
val user = currentUserProvider.currentUser.blockingGet()
val isUserConversationOwnerOrModerator = intent.getBooleanExtra(KEY_USER_IS_OWNER_OR_MODERATOR, false)
binding = ActivitySharedItemsBinding.inflate(layoutInflater)

View file

@ -46,7 +46,6 @@ import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.parceler.Parcels
private const val TAG = "ProfileBottomSheet"
@ -144,46 +143,13 @@ class ProfileBottomSheet(val ncApi: NcApi, val userModel: User) {
override fun onNext(roomOverall: RoomOverall) {
val bundle = Bundle()
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, userModel)
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
// FIXME once APIv2+ is used only, the createRoom already returns all the data
ncApi.getRoom(
credentials,
ApiUtils.getUrlForRoom(
apiVersion,
userModel.baseUrl,
roomOverall.ocs!!.data!!.token
)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<RoomOverall> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(roomOverall: RoomOverall) {
bundle.putParcelable(
BundleKeys.KEY_ACTIVE_CONVERSATION,
Parcels.wrap(roomOverall.ocs!!.data)
)
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle)
chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
context.startActivity(chatIntent)
}
override fun onError(e: Throwable) {
Log.e(TAG, e.message, e)
}
override fun onComplete() {
// unused atm
}
})
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle)
chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
context.startActivity(chatIntent)
}
override fun onError(e: Throwable) {

View file

@ -203,7 +203,7 @@ public class ChooseAccountDialogFragment extends DialogFragment {
dismiss();
if (status != null) {
SetStatusDialogFragment setStatusDialog = SetStatusDialogFragment.newInstance(user, status);
SetStatusDialogFragment setStatusDialog = SetStatusDialogFragment.newInstance(status);
setStatusDialog.show(getActivity().getSupportFragmentManager(), "fragment_set_status");
} else {
Log.w(TAG, "status was null");

View file

@ -20,7 +20,6 @@
package com.nextcloud.talk.ui.dialog
import android.app.Activity
import android.os.Bundle
import android.text.TextUtils
import android.view.View
@ -55,16 +54,13 @@ import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_OPERATION_CODE
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import org.parceler.Parcels
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class ConversationsListBottomDialog(
val activity: Activity,
val controller: ConversationsListActivity,
val activity: ConversationsListActivity,
val currentUser: User,
val conversation: Conversation
) : BottomSheetDialog(activity) {
@ -175,9 +171,8 @@ class ConversationsListBottomDialog(
if (!TextUtils.isEmpty(conversation.token)) {
val bundle = Bundle()
bundle.putLong(KEY_INTERNAL_USER_ID, currentUser.id!!)
bundle.putParcelable(KEY_ROOM, Parcels.wrap(conversation))
controller.showDeleteConversationDialog(bundle)
bundle.putString(KEY_ROOM_TOKEN, conversation.token)
activity.showDeleteConversationDialog(bundle)
}
dismiss()
@ -198,8 +193,8 @@ class ConversationsListBottomDialog(
private fun executeOperationsMenuController(operation: ConversationOperationEnum) {
val bundle = Bundle()
bundle.putParcelable(KEY_ROOM, Parcels.wrap(conversation))
bundle.putSerializable(KEY_OPERATION_CODE, operation)
bundle.putString(KEY_ROOM_TOKEN, conversation.token)
binding.operationItemsLayout.visibility = View.GONE
@ -211,13 +206,13 @@ class ConversationsListBottomDialog(
.popChangeHandler(HorizontalChangeHandler())
)
controller.fetchRooms()
activity.fetchRooms()
}
private fun executeEntryMenuController(operation: ConversationOperationEnum) {
val bundle = Bundle()
bundle.putParcelable(KEY_ROOM, Parcels.wrap(conversation))
bundle.putSerializable(KEY_OPERATION_CODE, operation)
bundle.putString(KEY_ROOM_TOKEN, conversation.token)
binding.operationItemsLayout.visibility = View.GONE

View file

@ -40,10 +40,12 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.DialogMessageActionsBinding
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.domain.ConversationReadOnlyState
import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.domain.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.repositories.reactions.ReactionsRepository
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
@ -63,7 +65,7 @@ class MessageActionsDialog(
private val chatActivity: ChatActivity,
private val message: ChatMessage,
private val user: User?,
private val currentConversation: Conversation?,
private val currentConversation: ConversationModel?,
private val showMessageDeletionButton: Boolean,
private val hasChatPermission: Boolean
) : BottomSheetDialog(chatActivity) {
@ -100,7 +102,7 @@ class MessageActionsDialog(
message.replyable &&
hasUserId(user) &&
hasUserActorId(message) &&
currentConversation?.type != Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
currentConversation?.type != ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
)
initMenuDeleteMessage(showMessageDeletionButton)
initMenuForwardMessage(
@ -226,7 +228,7 @@ class MessageActionsDialog(
}
private fun isPermitted(hasChatPermission: Boolean): Boolean {
return hasChatPermission && Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY !=
return hasChatPermission && ConversationReadOnlyState.CONVERSATION_READ_ONLY !=
currentConversation?.conversationReadOnlyState
}
@ -338,12 +340,12 @@ class MessageActionsDialog(
private fun clickOnEmoji(message: ChatMessage, emoji: String) {
if (message.reactionsSelf?.contains(emoji) == true) {
reactionsRepository.deleteReaction(currentConversation!!, message, emoji)
reactionsRepository.deleteReaction(currentConversation!!.token!!, message, emoji)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(ReactionDeletedObserver())
} else {
reactionsRepository.addReaction(currentConversation!!, message, emoji)
reactionsRepository.addReaction(currentConversation!!.token!!, message, emoji)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(ReactionAddedObserver())

View file

@ -60,6 +60,7 @@ import com.nextcloud.talk.models.json.status.predefined.PredefinedStatusOverall
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.vanniktech.emoji.EmojiPopup
import com.vanniktech.emoji.installDisableKeyboardInput
import com.vanniktech.emoji.installForceSingleEmoji
@ -113,6 +114,9 @@ class SetStatusDialogFragment :
@Inject
lateinit var viewThemeUtils: ViewThemeUtils
var currentUserProvider: CurrentUserProviderNew? = null
@Inject set
lateinit var credentials: String
override fun onCreate(savedInstanceState: Bundle?) {
@ -121,7 +125,7 @@ class SetStatusDialogFragment :
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
arguments?.let {
currentUser = it.getParcelable(ARG_CURRENT_USER_PARAM)
currentUser = currentUserProvider?.currentUser?.blockingGet()
currentStatus = it.getParcelable(ARG_CURRENT_STATUS_PARAM)
credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token)
@ -559,9 +563,8 @@ class SetStatusDialogFragment :
private val TAG = SetStatusDialogFragment::class.simpleName
@JvmStatic
fun newInstance(user: User, status: Status): SetStatusDialogFragment {
fun newInstance(status: Status): SetStatusDialogFragment {
val args = Bundle()
args.putParcelable(ARG_CURRENT_USER_PARAM, user)
args.putParcelable(ARG_CURRENT_STATUS_PARAM, status)
val dialogFragment = SetStatusDialogFragment()

View file

@ -44,7 +44,6 @@ import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.DialogMessageReactionsBinding
import com.nextcloud.talk.databinding.ItemReactionsTabBinding
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.reactions.ReactionsOverall
import com.nextcloud.talk.ui.theme.ViewThemeUtils
@ -59,7 +58,7 @@ import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class ShowReactionsDialog(
activity: Activity,
private val currentConversation: Conversation?,
private val roomToken: String,
private val chatMessage: ChatMessage,
private val user: User?,
private val hasChatPermission: Boolean,
@ -156,7 +155,7 @@ class ShowReactionsDialog(
credentials,
ApiUtils.getUrlForMessageReaction(
user?.baseUrl,
currentConversation!!.token,
roomToken,
chatMessage.id
),
emoji
@ -211,7 +210,7 @@ class ShowReactionsDialog(
credentials,
ApiUtils.getUrlForMessageReaction(
user?.baseUrl,
currentConversation!!.token,
roomToken,
message.id
),
emoji

View file

@ -0,0 +1,89 @@
package com.nextcloud.talk.utils
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.domain.ParticipantType
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
/*
* Nextcloud Talk application
* @author Marcel Hibbe
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
object ConversationUtils {
private val TAG = ConversationUtils::class.java.simpleName
fun isPublic(conversation: ConversationModel): Boolean {
return ConversationType.ROOM_PUBLIC_CALL == conversation.type
}
fun isGuest(conversation: ConversationModel): Boolean {
return ParticipantType.GUEST == conversation.participantType ||
ParticipantType.GUEST_MODERATOR == conversation.participantType ||
ParticipantType.USER_FOLLOWING_LINK == conversation.participantType
}
fun isParticipantOwnerOrModerator(conversation: ConversationModel): Boolean {
return ParticipantType.OWNER == conversation.participantType ||
ParticipantType.GUEST_MODERATOR == conversation.participantType ||
ParticipantType.MODERATOR == conversation.participantType
}
private fun isLockedOneToOne(conversation: ConversationModel, conversationUser: User): Boolean {
return conversation.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL &&
CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "locked-one-to-one-rooms")
}
fun canModerate(conversation: ConversationModel, conversationUser: User): Boolean {
return isParticipantOwnerOrModerator(conversation) &&
!isLockedOneToOne(conversation, conversationUser) &&
conversation.type != ConversationType.FORMER_ONE_TO_ONE
}
fun isLobbyViewApplicable(conversation: ConversationModel, conversationUser: User): Boolean {
return !canModerate(conversation, conversationUser) &&
(
conversation.type == ConversationType.ROOM_GROUP_CALL ||
conversation.type == ConversationType.ROOM_PUBLIC_CALL
)
}
fun isNameEditable(conversation: ConversationModel, conversationUser: User): Boolean {
return canModerate(conversation, conversationUser) &&
ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL != conversation.type
}
fun canLeave(conversation: ConversationModel): Boolean {
return if (conversation.canLeaveConversation != null) {
// Available since APIv2
conversation.canLeaveConversation!!
} else {
true
}
}
fun canDelete(conversation: ConversationModel, conversationUser: User): Boolean {
return if (conversation.canDeleteConversation != null) {
// Available since APIv2
conversation.canDeleteConversation!!
} else {
canModerate(conversation, conversationUser)
// Fallback for APIv1
}
}
}

View file

@ -23,6 +23,7 @@
package com.nextcloud.talk.utils
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
@ -31,9 +32,15 @@ import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
*/
class ParticipantPermissions(
private val user: User,
private val conversation: Conversation
private val conversation: ConversationModel
) {
@Deprecated("Use ChatRepository.ConversationModel")
constructor(user: User, conversation: Conversation) : this(
user,
ConversationModel.mapToConversationModel(conversation)
)
val isDefault = (conversation.permissions and DEFAULT) == DEFAULT
val isCustom = (conversation.permissions and CUSTOM) == CUSTOM
private val canStartCall = (conversation.permissions and START_CALL) == START_CALL

View file

@ -33,19 +33,17 @@ object BundleKeys {
const val KEY_BASE_URL = "KEY_BASE_URL"
const val KEY_IS_ACCOUNT_IMPORT = "KEY_IS_ACCOUNT_IMPORT"
const val KEY_ORIGINAL_PROTOCOL = "KEY_ORIGINAL_PROTOCOL"
const val KEY_ROOM = "KEY_CONVERSATION"
const val KEY_OPERATION_CODE = "KEY_OPERATION_CODE"
const val KEY_SHARE_INTENT = "KEY_SHARE_INTENT"
const val KEY_APP_ITEM_PACKAGE_NAME = "KEY_APP_ITEM_PACKAGE_NAME"
const val KEY_APP_ITEM_NAME = "KEY_APP_ITEM_NAME"
const val KEY_CONVERSATION_PASSWORD = "KEY_CONVERSATION_PASSWORD"
const val KEY_ROOM_TOKEN = "KEY_ROOM_TOKEN"
const val KEY_ROOM_ONE_TO_ONE = "KEY_ROOM_ONE_TO_ONE"
const val KEY_USER_ENTITY = "KEY_USER_ENTITY"
const val KEY_NEW_CONVERSATION = "KEY_NEW_CONVERSATION"
const val KEY_ADD_PARTICIPANTS = "KEY_ADD_PARTICIPANTS"
const val KEY_EXISTING_PARTICIPANTS = "KEY_EXISTING_PARTICIPANTS"
const val KEY_CALL_URL = "KEY_CALL_URL"
const val KEY_NEW_ROOM_NAME = "KEY_NEW_ROOM_NAME"
const val KEY_MODIFIED_BASE_URL = "KEY_MODIFIED_BASE_URL"
const val KEY_NOTIFICATION_SUBJECT = "KEY_NOTIFICATION_SUBJECT"
const val KEY_NOTIFICATION_SIGNATURE = "KEY_NOTIFICATION_SIGNATURE"
@ -59,8 +57,6 @@ object BundleKeys {
const val KEY_RECORDING_STATE = "KEY_RECORDING_STATE"
const val KEY_CALL_VOICE_ONLY = "KEY_CALL_VOICE_ONLY"
const val KEY_CALL_WITHOUT_NOTIFICATION = "KEY_CALL_WITHOUT_NOTIFICATION"
const val KEY_ACTIVE_CONVERSATION = "KEY_ACTIVE_CONVERSATION"
const val KEY_SERVER_CAPABILITIES = "KEY_SERVER_CAPABILITIES"
const val KEY_FROM_NOTIFICATION_START_CALL = "KEY_FROM_NOTIFICATION_START_CALL"
const val KEY_ROOM_ID = "KEY_ROOM_ID"
const val KEY_ARE_CALL_SOUNDS = "KEY_ARE_CALL_SOUNDS"