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" /> android:theme="@style/AppTheme.CallLauncher" />
<activity <activity
android:name=".activities.CallNotificationActivity" android:name=".callnotification.CallNotificationActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:launchMode="singleTask" 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.VibrationUtils;
import com.nextcloud.talk.utils.animations.PulseAnimation; import com.nextcloud.talk.utils.animations.PulseAnimation;
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew; 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.permissions.PlatformPermissionUtil;
import com.nextcloud.talk.utils.power.PowerManagerUtils; import com.nextcloud.talk.utils.power.PowerManagerUtils;
import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder; 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_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_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_SWITCH_TO_ROOM;
import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY;
@AutoInjector(NextcloudTalkApplication.class) @AutoInjector(NextcloudTalkApplication.class)
public class CallActivity extends CallBaseActivity { public class CallActivity extends CallBaseActivity {
@ -199,6 +199,9 @@ public class CallActivity extends CallBaseActivity {
@Inject @Inject
NcApi ncApi; NcApi ncApi;
@Inject
CurrentUserProviderNew currentUserProvider;
@Inject @Inject
UserManager userManager; UserManager userManager;
@ -386,10 +389,11 @@ public class CallActivity extends CallBaseActivity {
hideNavigationIfNoPipAvailable(); hideNavigationIfNoPipAvailable();
conversationUser = currentUserProvider.getCurrentUser().blockingGet();
Bundle extras = getIntent().getExtras(); Bundle extras = getIntent().getExtras();
roomId = extras.getString(KEY_ROOM_ID, ""); roomId = extras.getString(KEY_ROOM_ID, "");
roomToken = extras.getString(KEY_ROOM_TOKEN, ""); roomToken = extras.getString(KEY_ROOM_TOKEN, "");
conversationUser = extras.getParcelable(KEY_USER_ENTITY);
conversationPassword = extras.getString(KEY_CONVERSATION_PASSWORD, ""); conversationPassword = extras.getString(KEY_CONVERSATION_PASSWORD, "");
conversationName = extras.getString(KEY_CONVERSATION_NAME, ""); conversationName = extras.getString(KEY_CONVERSATION_NAME, "");
isVoiceOnlyCall = extras.getBoolean(KEY_CALL_VOICE_ONLY, false); 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_SWITCH_TO_ROOM, true);
bundle.putBoolean(KEY_START_CALL_AFTER_ROOM_SWITCH, true); bundle.putBoolean(KEY_START_CALL_AFTER_ROOM_SWITCH, true);
bundle.putString(KEY_ROOM_TOKEN, switchToRoomToken); bundle.putString(KEY_ROOM_TOKEN, switchToRoomToken);
bundle.putParcelable(KEY_USER_ENTITY, conversationUser);
bundle.putBoolean(KEY_CALL_VOICE_ONLY, isVoiceOnlyCall); bundle.putBoolean(KEY_CALL_VOICE_ONLY, isVoiceOnlyCall);
intent.putExtras(bundle); intent.putExtras(bundle);
startActivity(intent); startActivity(intent);
@ -3151,7 +3154,7 @@ public class CallActivity extends CallBaseActivity {
} }
@Override @Override
void suppressFitsSystemWindows() { public void suppressFitsSystemWindows() {
binding.controllerCallLayout.setFitsSystemWindows(false); binding.controllerCallLayout.setFitsSystemWindows(false);
} }

View File

@ -74,7 +74,7 @@ public abstract class CallBaseActivity extends BaseActivity {
getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback); getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
} }
void hideNavigationIfNoPipAvailable(){ public void hideNavigationIfNoPipAvailable(){
if (!isPipModePossible()) { if (!isPipModePossible()) {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | 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; 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.R
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.callnotification.CallNotificationActivity
import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.controllers.ServerSelectionController import com.nextcloud.talk.controllers.ServerSelectionController
import com.nextcloud.talk.controllers.WebViewLoginController 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.SecurityUtils
import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.bundle.BundleKeys.ADD_ACCOUNT 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_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN 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.Observer
import io.reactivex.SingleObserver import io.reactivex.SingleObserver
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import org.parceler.Parcels
import javax.inject.Inject import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
@ -282,45 +280,12 @@ class MainActivity : BaseActivity(), ActionBarProvider {
override fun onNext(roomOverall: RoomOverall) { override fun onNext(roomOverall: RoomOverall) {
val bundle = Bundle() val bundle = Bundle()
bundle.putParcelable(KEY_USER_ENTITY, currentUser)
bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token) bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
bundle.putString(KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId) bundle.putString(KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
// FIXME once APIv2 or later is used only, the createRoom already returns all the data val chatIntent = Intent(context, ChatActivity::class.java)
ncApi.getRoom( chatIntent.putExtras(bundle)
credentials, startActivity(chatIntent)
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
}
})
} }
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
@ -337,7 +302,9 @@ class MainActivity : BaseActivity(), ActionBarProvider {
super.onNewIntent(intent) super.onNewIntent(intent)
Log.d(TAG, "onNewIntent Activity: " + System.identityHashCode(this).toString()) 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()) { if (user != null && userManager.setUserAsActive(user).blockingGet()) {
handleIntent(intent) handleIntent(intent)
} }

View File

@ -19,7 +19,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * 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.annotation.SuppressLint
import android.content.Intent import android.content.Intent
@ -30,36 +30,36 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.lifecycle.ViewModelProvider
import autodagger.AutoInjector import autodagger.AutoInjector
import com.nextcloud.talk.R 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.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication 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.data.user.model.User
import com.nextcloud.talk.databinding.CallNotificationActivityBinding import com.nextcloud.talk.databinding.CallNotificationActivityBinding
import com.nextcloud.talk.extensions.loadUserAvatar import com.nextcloud.talk.extensions.loadUserAvatar
import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.ConversationUtils
import com.nextcloud.talk.utils.NotificationUtils import com.nextcloud.talk.utils.NotificationUtils
import com.nextcloud.talk.utils.ParticipantPermissions import com.nextcloud.talk.utils.ParticipantPermissions
import com.nextcloud.talk.utils.bundle.BundleKeys 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_CALL_VOICE_ONLY
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME 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_ROOM_TOKEN
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.hasSpreedFeatureCapability
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.Cache import okhttp3.Cache
import org.parceler.Parcels
import java.io.IOException import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
@ -77,13 +77,18 @@ class CallNotificationActivity : CallBaseActivity() {
@Inject @Inject
lateinit var userManager: UserManager lateinit var userManager: UserManager
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var callNotificationViewModel: CallNotificationViewModel
private val disposablesList: MutableList<Disposable> = ArrayList() private val disposablesList: MutableList<Disposable> = ArrayList()
private var originalBundle: Bundle? = null private var originalBundle: Bundle? = null
private var roomToken: String? = null private var roomToken: String? = null
private var notificationTimestamp: Int? = null private var notificationTimestamp: Int? = null
private var userBeingCalled: User? = null private var userBeingCalled: User? = null
private var credentials: String? = null private var credentials: String? = null
private var currentConversation: Conversation? = null var currentConversation: ConversationModel? = null
private var leavingScreen = false private var leavingScreen = false
private var handler: Handler? = null private var handler: Handler? = null
private var binding: CallNotificationActivityBinding? = null private var binding: CallNotificationActivityBinding? = null
@ -98,19 +103,20 @@ class CallNotificationActivity : CallBaseActivity() {
val extras = intent.extras val extras = intent.extras
roomToken = extras!!.getString(KEY_ROOM_TOKEN, "") roomToken = extras!!.getString(KEY_ROOM_TOKEN, "")
notificationTimestamp = extras.getInt(BundleKeys.KEY_NOTIFICATION_TIMESTAMP) 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 originalBundle = extras
credentials = ApiUtils.getCredentials(userBeingCalled!!.username, userBeingCalled!!.token) credentials = ApiUtils.getCredentials(userBeingCalled!!.username, userBeingCalled!!.token)
callNotificationViewModel = ViewModelProvider(this, viewModelFactory)[CallNotificationViewModel::class.java]
initObservers()
if (userManager.setUserAsActive(userBeingCalled!!).blockingGet()) { if (userManager.setUserAsActive(userBeingCalled!!).blockingGet()) {
setCallDescriptionText() setCallDescriptionText()
if (currentConversation == null) { callNotificationViewModel.getRoom(userBeingCalled!!, roomToken!!)
handleFromNotification()
} else {
setUpAfterConversationIsKnown()
}
initClickListeners()
} }
} }
@ -140,6 +146,78 @@ class CallNotificationActivity : CallBaseActivity() {
binding!!.hangupButton.setOnClickListener { hangup() } 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() { private fun setCallDescriptionText() {
val callDescriptionWithoutTypeInfo = String.format( val callDescriptionWithoutTypeInfo = String.format(
resources.getString(R.string.nc_call_unknown), resources.getString(R.string.nc_call_unknown),
@ -178,7 +256,7 @@ class CallNotificationActivity : CallBaseActivity() {
) )
originalBundle!!.putBoolean( originalBundle!!.putBoolean(
BundleKeys.KEY_IS_MODERATOR, BundleKeys.KEY_IS_MODERATOR,
currentConversation!!.isParticipantOwnerOrModerator ConversationUtils.isParticipantOwnerOrModerator(currentConversation!!)
) )
val intent = Intent(this, CallActivity::class.java) 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 { private fun isInCallWithVideo(callFlag: Int): Boolean {
return (callFlag and Participant.InCallFlags.WITH_VIDEO) > 0 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() { override fun onStop() {
val notificationManager = NotificationManagerCompat.from(context) val notificationManager = NotificationManagerCompat.from(context)
notificationManager.cancel(notificationTimestamp!!) notificationManager.cancel(notificationTimestamp!!)
@ -303,23 +306,22 @@ class CallNotificationActivity : CallBaseActivity() {
} }
} }
public override fun updateUiForPipMode() { override fun updateUiForPipMode() {
binding!!.callAnswerButtons.visibility = View.INVISIBLE binding!!.callAnswerButtons.visibility = View.INVISIBLE
binding!!.incomingCallRelativeLayout.visibility = View.INVISIBLE binding!!.incomingCallRelativeLayout.visibility = View.INVISIBLE
} }
public override fun updateUiForNormalMode() { override fun updateUiForNormalMode() {
binding!!.callAnswerButtons.visibility = View.VISIBLE binding!!.callAnswerButtons.visibility = View.VISIBLE
binding!!.incomingCallRelativeLayout.visibility = View.VISIBLE binding!!.incomingCallRelativeLayout.visibility = View.VISIBLE
} }
public override fun suppressFitsSystemWindows() { override fun suppressFitsSystemWindows() {
binding!!.controllerCallNotificationLayout.fitsSystemWindows = false binding!!.controllerCallNotificationLayout.fitsSystemWindows = false
} }
companion object { companion object {
const val TAG = "CallNotificationActivity" const val TAG = "CallNotificationActivity"
const val GET_ROOM_RETRY_COUNT: Long = 3
const val ONE_SECOND: Long = 1000 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.Bundle
import android.os.CountDownTimer import android.os.CountDownTimer
import android.os.Handler import android.os.Handler
import android.os.Parcelable
import android.os.SystemClock import android.os.SystemClock
import android.provider.ContactsContract import android.provider.ContactsContract
import android.provider.MediaStore import android.provider.MediaStore
@ -85,6 +84,7 @@ import androidx.core.text.bold
import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doAfterTextChanged
import androidx.emoji2.text.EmojiCompat import androidx.emoji2.text.EmojiCompat
import androidx.emoji2.widget.EmojiTextView import androidx.emoji2.widget.EmojiTextView
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView 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.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.callbacks.MentionAutocompleteCallback import com.nextcloud.talk.callbacks.MentionAutocompleteCallback
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
import com.nextcloud.talk.conversationinfo.ConversationInfoActivity import com.nextcloud.talk.conversationinfo.ConversationInfoActivity
import com.nextcloud.talk.conversationlist.ConversationsListActivity import com.nextcloud.talk.conversationlist.ConversationsListActivity
import com.nextcloud.talk.data.user.model.User 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.jobs.UploadAndShareFilesWorker
import com.nextcloud.talk.location.LocationPickerActivity import com.nextcloud.talk.location.LocationPickerActivity
import com.nextcloud.talk.messagesearch.MessageSearchActivity 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.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel import com.nextcloud.talk.models.domain.ReactionDeletedModel
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.chat.ChatOverall import com.nextcloud.talk.models.json.chat.ChatOverall
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
import com.nextcloud.talk.models.json.chat.ReadStatus 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.RoomOverall
import com.nextcloud.talk.models.json.conversations.RoomsOverall
import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.mention.Mention import com.nextcloud.talk.models.json.mention.Mention
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage 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.ui.recyclerview.MessageSwipeCallback
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.ContactUtils import com.nextcloud.talk.utils.ContactUtils
import com.nextcloud.talk.utils.ConversationUtils
import com.nextcloud.talk.utils.DateConstants import com.nextcloud.talk.utils.DateConstants
import com.nextcloud.talk.utils.DateUtils import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.DisplayUtils 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.ParticipantPermissions
import com.nextcloud.talk.utils.VibrationUtils import com.nextcloud.talk.utils.VibrationUtils
import com.nextcloud.talk.utils.bundle.BundleKeys 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_CALL_VOICE_ONLY
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FILE_PATHS 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_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_START_CALL_AFTER_ROOM_SWITCH 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_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.CapabilitiesUtilNew
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
import com.nextcloud.talk.utils.rx.DisposableSet import com.nextcloud.talk.utils.rx.DisposableSet
import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder
@ -215,7 +219,6 @@ import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import org.parceler.Parcels
import retrofit2.HttpException import retrofit2.HttpException
import retrofit2.Response import retrofit2.Response
import java.io.File import java.io.File
@ -248,6 +251,9 @@ class ChatActivity :
@Inject @Inject
lateinit var ncApi: NcApi lateinit var ncApi: NcApi
@Inject
lateinit var currentUserProvider: CurrentUserProviderNew
@Inject @Inject
lateinit var reactionsRepository: ReactionsRepository lateinit var reactionsRepository: ReactionsRepository
@ -257,6 +263,11 @@ class ChatActivity :
@Inject @Inject
lateinit var dateUtils: DateUtils lateinit var dateUtils: DateUtils
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var chatViewModel: ChatViewModel
override val view: View override val view: View
get() = binding.root get() = binding.root
@ -267,7 +278,7 @@ class ChatActivity :
var conversationUser: User? = null var conversationUser: User? = null
private var roomPassword: String = "" private var roomPassword: String = ""
var credentials: String? = null var credentials: String? = null
var currentConversation: Conversation? = null var currentConversation: ConversationModel? = null
private var globalLastKnownFutureMessageId = -1 private var globalLastKnownFutureMessageId = -1
private var globalLastKnownPastMessageId = -1 private var globalLastKnownPastMessageId = -1
var adapter: TalkMessagesListAdapter<ChatMessage>? = null var adapter: TalkMessagesListAdapter<ChatMessage>? = null
@ -383,14 +394,17 @@ class ChatActivity :
setContentView(binding.root) setContentView(binding.root)
setupSystemColors() setupSystemColors()
conversationUser = currentUserProvider.currentUser.blockingGet()
handleIntent(intent) handleIntent(intent)
chatViewModel = ViewModelProvider(this, viewModelFactory)[ChatViewModel::class.java]
binding.progressBar.visibility = View.VISIBLE binding.progressBar.visibility = View.VISIBLE
initAdapter()
binding.messagesListView.setAdapter(adapter)
onBackPressedDispatcher.addCallback(this, onBackPressedCallback) onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
initObservers()
} }
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
@ -415,7 +429,6 @@ class ChatActivity :
private fun handleIntent(intent: Intent) { private fun handleIntent(intent: Intent) {
val extras: Bundle? = intent.extras val extras: Bundle? = intent.extras
conversationUser = extras?.getParcelable(KEY_USER_ENTITY)
roomId = extras?.getString(KEY_ROOM_ID).orEmpty() roomId = extras?.getString(KEY_ROOM_ID).orEmpty()
roomToken = extras?.getString(KEY_ROOM_TOKEN).orEmpty() roomToken = extras?.getString(KEY_ROOM_TOKEN).orEmpty()
@ -426,11 +439,6 @@ class ChatActivity :
Log.d(TAG, " roomToken was null or empty!") 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() roomPassword = extras?.getString(BundleKeys.KEY_CONVERSATION_PASSWORD).orEmpty()
credentials = if (conversationUser?.userId == "?") { credentials = if (conversationUser?.userId == "?") {
@ -455,6 +463,106 @@ class ChatActivity :
active = false 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") @Suppress("Detekt.TooGenericExceptionCaught")
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@ -484,18 +592,12 @@ class ChatActivity :
cancelNotificationsForCurrentConversation() cancelNotificationsForCurrentConversation()
if (TextUtils.isEmpty(roomToken)) { chatViewModel.getRoom(conversationUser!!, roomToken)
handleFromNotification()
} else {
getRoomInfo()
}
actionBar?.show() actionBar?.show()
setupSwipeToReply() setupSwipeToReply()
layoutManager = binding?.messagesListView?.layoutManager as LinearLayoutManager?
binding?.popupBubbleView?.setRecyclerView(binding?.messagesListView) binding?.popupBubbleView?.setRecyclerView(binding?.messagesListView)
binding?.popupBubbleView?.setPopupBubbleListener { context -> binding?.popupBubbleView?.setPopupBubbleListener { context ->
@ -632,10 +734,8 @@ class ChatActivity :
binding?.messageInputView?.button?.let { viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY) } binding?.messageInputView?.button?.let { viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY) }
if (currentConversation != null && currentConversation?.roomId != null) { loadAvatarForStatusBar()
loadAvatarForStatusBar() setActionBarTitle()
setActionBarTitle()
}
viewThemeUtils.material.colorToolbarOverflowIcon(binding.chatToolbar) viewThemeUtils.material.colorToolbarOverflowIcon(binding.chatToolbar)
} }
@ -694,8 +794,11 @@ class ChatActivity :
val messageHolders = MessageHolders() val messageHolders = MessageHolders()
val profileBottomSheet = ProfileBottomSheet(ncApi, conversationUser!!) val profileBottomSheet = ProfileBottomSheet(ncApi, conversationUser!!)
val payload = val payload = MessagePayload(
MessagePayload(roomToken!!, currentConversation?.isParticipantOwnerOrModerator, profileBottomSheet) roomToken,
ConversationUtils.isParticipantOwnerOrModerator(currentConversation!!),
profileBottomSheet
)
messageHolders.setIncomingTextConfig( messageHolders.setIncomingTextConfig(
IncomingTextMessageViewHolder::class.java, IncomingTextMessageViewHolder::class.java,
@ -1080,68 +1183,6 @@ class ChatActivity :
!CapabilitiesUtilNew.isTypingStatusPrivate(conversationUser!!) !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() { private fun setupSwipeToReply() {
if (this::participantPermissions.isInitialized && if (this::participantPermissions.isInitialized &&
participantPermissions.hasChatPermission() && participantPermissions.hasChatPermission() &&
@ -1162,46 +1203,11 @@ class ChatActivity :
} }
} }
private fun handleFromNotification() { private fun loadAvatarForStatusBar() {
var apiVersion = 1 if (currentConversation == null) {
// FIXME Can this be called for guests? return
if (conversationUser != null) {
apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
} }
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()) { if (isOneToOneConversation()) {
var url = ApiUtils.getUrlForAvatar( var url = ApiUtils.getUrlForAvatar(
conversationUser!!.baseUrl, conversationUser!!.baseUrl,
@ -1254,18 +1260,18 @@ class ChatActivity :
} }
fun isOneToOneConversation() = currentConversation != null && currentConversation?.type != null && 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 && 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 && 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) { private fun switchToRoom(token: String, startCallAfterRoomSwitch: Boolean, isVoiceOnlyCall: Boolean) {
if (conversationUser != null) { if (conversationUser != null) {
runOnUiThread { runOnUiThread {
if (currentConversation?.objectType == Conversation.ObjectType.ROOM) { if (currentConversation?.objectType == ObjectType.ROOM) {
Toast.makeText( Toast.makeText(
context, context,
context.resources.getString(R.string.switch_to_main_room), context.resources.getString(R.string.switch_to_main_room),
@ -1281,7 +1287,6 @@ class ChatActivity :
} }
val bundle = Bundle() val bundle = Bundle()
bundle.putParcelable(KEY_USER_ENTITY, conversationUser)
bundle.putString(KEY_ROOM_TOKEN, token) bundle.putString(KEY_ROOM_TOKEN, token)
if (startCallAfterRoomSwitch) { if (startCallAfterRoomSwitch) {
@ -1697,8 +1702,8 @@ class ChatActivity :
private fun shouldShowLobby(): Boolean { private fun shouldShowLobby(): Boolean {
if (currentConversation != null) { if (currentConversation != null) {
return CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "webinary-lobby") && return CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "webinary-lobby") &&
currentConversation?.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY && currentConversation?.lobbyState == LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
currentConversation?.canModerate(conversationUser!!) == false && !ConversationUtils.canModerate(currentConversation!!, conversationUser!!) &&
!participantPermissions.canIgnoreLobby() !participantPermissions.canIgnoreLobby()
} }
return false return false
@ -1733,12 +1738,12 @@ class ChatActivity :
private fun isReadOnlyConversation(): Boolean { private fun isReadOnlyConversation(): Boolean {
return currentConversation?.conversationReadOnlyState != null && return currentConversation?.conversationReadOnlyState != null &&
currentConversation?.conversationReadOnlyState == currentConversation?.conversationReadOnlyState ==
Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY ConversationReadOnlyState.CONVERSATION_READ_ONLY
} }
private fun checkLobbyState() { private fun checkLobbyState() {
if (currentConversation != null && if (currentConversation != null &&
currentConversation?.isLobbyViewApplicable(conversationUser!!) == true ConversationUtils.isLobbyViewApplicable(currentConversation!!, conversationUser!!)
) { ) {
if (shouldShowLobby()) { if (shouldShowLobby()) {
binding?.lobby?.lobbyView?.visibility = View.VISIBLE binding?.lobby?.lobbyView?.visibility = View.VISIBLE
@ -2119,7 +2124,7 @@ class ChatActivity :
private fun showConversationInfoScreen() { private fun showConversationInfoScreen() {
val bundle = Bundle() val bundle = Bundle()
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, conversationUser)
bundle.putString(KEY_ROOM_TOKEN, roomToken) bundle.putString(KEY_ROOM_TOKEN, roomToken)
bundle.putBoolean(BundleKeys.KEY_ROOM_ONE_TO_ONE, isOneToOneConversation()) bundle.putBoolean(BundleKeys.KEY_ROOM_ONE_TO_ONE, isOneToOneConversation())
@ -2274,65 +2279,8 @@ class ChatActivity :
val startNanoTime = System.nanoTime() val startNanoTime = System.nanoTime()
Log.d(TAG, "joinRoomWithPassword - joinRoom - calling: $startNanoTime") 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") chatViewModel.joinRoom(conversationUser!!, roomToken, roomPassword)
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
}
})
} else { } else {
Log.d(TAG, "sessionID was valid -> skip joinRoom") Log.d(TAG, "sessionID was valid -> skip joinRoom")
@ -2801,9 +2749,9 @@ class ChatActivity :
GROUPED_MESSAGES_SAME_AUTHOR_THRESHOLD > 0 GROUPED_MESSAGES_SAME_AUTHOR_THRESHOLD > 0
) )
chatMessage.isOneToOneConversation = chatMessage.isOneToOneConversation =
(currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) (currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL)
chatMessage.isFormerOneToOneConversation = chatMessage.isFormerOneToOneConversation =
(currentConversation?.type == Conversation.ConversationType.FORMER_ONE_TO_ONE) (currentConversation?.type == ConversationType.FORMER_ONE_TO_ONE)
it.addToStart(chatMessage, shouldScroll) it.addToStart(chatMessage, shouldScroll)
} }
} }
@ -2845,9 +2793,9 @@ class ChatActivity :
val chatMessage = chatMessageList[i] val chatMessage = chatMessageList[i]
chatMessage.isOneToOneConversation = chatMessage.isOneToOneConversation =
currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
chatMessage.isFormerOneToOneConversation = chatMessage.isFormerOneToOneConversation =
(currentConversation?.type == Conversation.ConversationType.FORMER_ONE_TO_ONE) (currentConversation?.type == ConversationType.FORMER_ONE_TO_ONE)
chatMessage.activeUser = conversationUser chatMessage.activeUser = conversationUser
} }
@ -3032,10 +2980,9 @@ class ChatActivity :
val intent = Intent(this, SharedItemsActivity::class.java) val intent = Intent(this, SharedItemsActivity::class.java)
intent.putExtra(KEY_CONVERSATION_NAME, currentConversation?.displayName) intent.putExtra(KEY_CONVERSATION_NAME, currentConversation?.displayName)
intent.putExtra(KEY_ROOM_TOKEN, roomToken) intent.putExtra(KEY_ROOM_TOKEN, roomToken)
intent.putExtra(KEY_USER_ENTITY, conversationUser as Parcelable)
intent.putExtra( intent.putExtra(
SharedItemsActivity.KEY_USER_IS_OWNER_OR_MODERATOR, SharedItemsActivity.KEY_USER_IS_OWNER_OR_MODERATOR,
currentConversation?.isParticipantOwnerOrModerator ConversationUtils.isParticipantOwnerOrModerator(currentConversation!!)
) )
startActivity(intent) startActivity(intent)
} }
@ -3119,12 +3066,11 @@ class ChatActivity :
val bundle = Bundle() val bundle = Bundle()
bundle.putString(KEY_ROOM_TOKEN, roomToken) bundle.putString(KEY_ROOM_TOKEN, roomToken)
bundle.putString(KEY_ROOM_ID, roomId) bundle.putString(KEY_ROOM_ID, roomId)
bundle.putParcelable(KEY_USER_ENTITY, conversationUser)
bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, roomPassword) bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, roomPassword)
bundle.putString(BundleKeys.KEY_MODIFIED_BASE_URL, conversationUser?.baseUrl) bundle.putString(BundleKeys.KEY_MODIFIED_BASE_URL, conversationUser?.baseUrl)
bundle.putString(KEY_CONVERSATION_NAME, it.displayName) bundle.putString(KEY_CONVERSATION_NAME, it.displayName)
bundle.putInt(KEY_RECORDING_STATE, it.callRecording) bundle.putInt(KEY_RECORDING_STATE, it.callRecording)
bundle.putBoolean(KEY_IS_MODERATOR, it.isParticipantOwnerOrModerator) bundle.putBoolean(KEY_IS_MODERATOR, ConversationUtils.isParticipantOwnerOrModerator(it))
bundle.putBoolean( bundle.putBoolean(
BundleKeys.KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_AUDIO, BundleKeys.KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_AUDIO,
participantPermissions.canPublishAudio() participantPermissions.canPublishAudio()
@ -3141,7 +3087,7 @@ class ChatActivity :
bundle.putBoolean(BundleKeys.KEY_CALL_WITHOUT_NOTIFICATION, true) 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) bundle.putBoolean(KEY_IS_BREAKOUT_ROOM, true)
} }
@ -3156,12 +3102,12 @@ class ChatActivity :
override fun onClickReaction(chatMessage: ChatMessage, emoji: String) { override fun onClickReaction(chatMessage: ChatMessage, emoji: String) {
VibrationUtils.vibrateShort(context) VibrationUtils.vibrateShort(context)
if (chatMessage.reactionsSelf?.contains(emoji) == true) { if (chatMessage.reactionsSelf?.contains(emoji) == true) {
reactionsRepository.deleteReaction(currentConversation!!, chatMessage, emoji) reactionsRepository.deleteReaction(roomToken, chatMessage, emoji)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread()) ?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(ReactionDeletedObserver()) ?.subscribe(ReactionDeletedObserver())
} else { } else {
reactionsRepository.addReaction(currentConversation!!, chatMessage, emoji) reactionsRepository.addReaction(roomToken, chatMessage, emoji)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread()) ?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(ReactionAddedObserver()) ?.subscribe(ReactionAddedObserver())
@ -3171,7 +3117,7 @@ class ChatActivity :
override fun onLongClickReactions(chatMessage: ChatMessage) { override fun onLongClickReactions(chatMessage: ChatMessage) {
ShowReactionsDialog( ShowReactionsDialog(
this, this,
currentConversation, roomToken,
chatMessage, chatMessage,
conversationUser, conversationUser,
participantPermissions.hasChatPermission(), participantPermissions.hasChatPermission(),
@ -3339,48 +3285,15 @@ class ChatActivity :
override fun onNext(roomOverall: RoomOverall) { override fun onNext(roomOverall: RoomOverall) {
val bundle = Bundle() val bundle = Bundle()
bundle.putParcelable(KEY_USER_ENTITY, conversationUser)
bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token) bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
bundle.putString(KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId) bundle.putString(KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
// FIXME once APIv2+ is used only, the createRoom already returns all the data leaveRoom {
ncApi.getRoom( val chatIntent = Intent(context, ChatActivity::class.java)
credentials, chatIntent.putExtras(bundle)
ApiUtils.getUrlForRoom( chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
apiVersion, startActivity(chatIntent)
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
}
})
} }
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
@ -3471,7 +3384,7 @@ class ChatActivity :
conversationUser?.userId?.isNotEmpty() == true && conversationUser!!.userId != "?" && conversationUser?.userId?.isNotEmpty() == true && conversationUser!!.userId != "?" &&
message.user.id.startsWith("users/") && message.user.id.startsWith("users/") &&
message.user.id.substring(ACTOR_LENGTH) != currentConversation?.actorId && 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 isShowMessageDeletionButton(message) || // delete
ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() || // forward ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() || // forward
message.previousMessageId > NO_PREVIOUS_MESSAGE_ID && // mark as unread message.previousMessageId > NO_PREVIOUS_MESSAGE_ID && // mark as unread
@ -3540,7 +3453,7 @@ class ChatActivity :
messageTemp.isDeleted = true messageTemp.isDeleted = true
messageTemp.isOneToOneConversation = messageTemp.isOneToOneConversation =
currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
messageTemp.activeUser = conversationUser messageTemp.activeUser = conversationUser
adapter?.update(messageTemp) adapter?.update(messageTemp)
@ -3550,7 +3463,7 @@ class ChatActivity :
val messageTemp = message as ChatMessage val messageTemp = message as ChatMessage
messageTemp.isOneToOneConversation = messageTemp.isOneToOneConversation =
currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
messageTemp.activeUser = conversationUser messageTemp.activeUser = conversationUser
adapter?.update(messageTemp) adapter?.update(messageTemp)
@ -3598,7 +3511,7 @@ class ChatActivity :
val isUserAllowedByPrivileges = if (message.actorId == conversationUser!!.userId) { val isUserAllowedByPrivileges = if (message.actorId == conversationUser!!.userId) {
true true
} else { } else {
currentConversation!!.canModerate(conversationUser!!) ConversationUtils.canModerate(currentConversation!!, conversationUser!!)
} }
val isOlderThanSixHours = message val isOlderThanSixHours = message
@ -3652,7 +3565,7 @@ class ChatActivity :
@Subscribe(threadMode = ThreadMode.BACKGROUND) @Subscribe(threadMode = ThreadMode.BACKGROUND)
fun onMessageEvent(userMentionClickEvent: UserMentionClickEvent) { 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 currentConversation?.name != userMentionClickEvent.userId
) { ) {
var apiVersion = 1 var apiVersion = 1
@ -3683,42 +3596,21 @@ class ChatActivity :
} }
override fun onNext(roomOverall: RoomOverall) { override fun onNext(roomOverall: RoomOverall) {
val conversationIntent = Intent(context, CallActivity::class.java)
val bundle = Bundle() val bundle = Bundle()
bundle.putParcelable(KEY_USER_ENTITY, conversationUser)
bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token) bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
bundle.putString(KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId) bundle.putString(KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
bundle.putBoolean(KEY_IS_MODERATOR, roomOverall.ocs!!.data!!.isParticipantOwnerOrModerator)
if (conversationUser != null) { leaveRoom {
bundle.putParcelable( val chatIntent = Intent(context, ChatActivity::class.java)
KEY_ACTIVE_CONVERSATION, chatIntent.putExtras(bundle)
Parcels.wrap(roomOverall.ocs!!.data) chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
) startActivity(chatIntent)
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
)
} }
} }
override fun onError(e: Throwable) { 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() { 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) { override fun onNext(roomOverall: RoomOverall) {
val bundle = Bundle() 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_TOKEN, roomOverall.ocs!!.data!!.token)
bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId) 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 val chatIntent = Intent(context, ChatActivity::class.java)
ncApi.getRoom( chatIntent.putExtras(bundle)
credentials, chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
ApiUtils.getUrlForRoom( startActivity(chatIntent)
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
}
})
} }
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
@ -818,13 +785,8 @@ class ContactsActivity :
override fun onNext(roomOverall: RoomOverall) { override fun onNext(roomOverall: RoomOverall) {
val bundle = Bundle() 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_TOKEN, roomOverall.ocs!!.data!!.token)
bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId) 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) val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle) chatIntent.putExtras(bundle)

View File

@ -23,14 +23,13 @@
*/ */
package com.nextcloud.talk.controllers.bottomsheet package com.nextcloud.talk.controllers.bottomsheet
import android.content.Intent
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable
import android.text.Editable import android.text.Editable
import android.text.InputType import android.text.InputType
import android.text.TextUtils import android.text.TextUtils
import android.text.TextWatcher import android.text.TextWatcher
import android.util.Log
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.core.content.res.ResourcesCompat 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.google.android.material.textfield.TextInputLayout
import com.nextcloud.android.common.ui.theme.utils.ColorRole import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.controllers.base.BaseController import com.nextcloud.talk.controllers.base.BaseController
import com.nextcloud.talk.controllers.util.viewBinding 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.databinding.ControllerEntryMenuBinding
import com.nextcloud.talk.models.json.conversations.Conversation 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.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.UriUtils import com.nextcloud.talk.utils.UriUtils
import com.nextcloud.talk.utils.bundle.BundleKeys 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.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
import com.vanniktech.emoji.EmojiPopup 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 okhttp3.internal.immutableListOf
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.parceler.Parcels
import org.parceler.Parcels.unwrap
import javax.inject.Inject import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
@ -65,6 +71,9 @@ class EntryMenuController(args: Bundle) :
) { ) {
private val binding: ControllerEntryMenuBinding? by viewBinding(ControllerEntryMenuBinding::bind) private val binding: ControllerEntryMenuBinding? by viewBinding(ControllerEntryMenuBinding::bind)
@Inject
lateinit var ncApi: NcApi
@Inject @Inject
lateinit var eventBus: EventBus lateinit var eventBus: EventBus
@ -73,12 +82,13 @@ class EntryMenuController(args: Bundle) :
private val operation: ConversationOperationEnum private val operation: ConversationOperationEnum
private var conversation: Conversation? = null private var conversation: Conversation? = null
private var shareIntent: Intent? = null
private val packageName: String private val packageName: String
private val name: String private val name: String
private val callUrl: String
private var emojiPopup: EmojiPopup? = null private var emojiPopup: EmojiPopup? = null
private val originalBundle: Bundle private val originalBundle: Bundle
private var currentUser: User? = null
private val roomToken: String
override val appBarLayoutType: AppBarLayoutType override val appBarLayoutType: AppBarLayoutType
get() = AppBarLayoutType.SEARCH_BAR get() = AppBarLayoutType.SEARCH_BAR
@ -100,74 +110,121 @@ class EntryMenuController(args: Bundle) :
override fun onViewBound(view: View) { override fun onViewBound(view: View) {
super.onViewBound(view) super.onViewBound(view)
if (conversation != null && operation === ConversationOperationEnum.OPS_CODE_RENAME_ROOM) { currentUser = userManager.currentUser.blockingGet()
binding?.textEdit?.setText(conversation!!.name)
}
binding?.textEdit?.setOnEditorActionListener { v, actionId, event -> if (operation == ConversationOperationEnum.OPS_CODE_GET_AND_JOIN_ROOM) {
@Suppress("IMPLICIT_BOXING_IN_IDENTITY_EQUALS") var labelText = ""
if (actionId === EditorInfo.IME_ACTION_DONE && binding?.okButton?.isEnabled == true) { labelText = resources!!.getString(R.string.nc_conversation_link)
binding?.okButton?.callOnClick() binding?.textEdit?.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI
return@setOnEditorActionListener true
}
false
}
textEditAddChangedListener() textEditAddChangedListener()
var labelText = "" binding?.textInputLayout?.let { viewThemeUtils.material.colorTextInputLayout(it) }
when (operation) { binding?.okButton?.let { viewThemeUtils.material.colorMaterialButtonText(it) }
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 -> { binding?.textInputLayout?.hint = labelText
// 99 is joining a conversation via password binding?.textInputLayout?.requestFocus()
labelText = resources!!.getString(R.string.nc_password)
binding?.textEdit?.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
}
ConversationOperationEnum.OPS_CODE_GET_AND_JOIN_ROOM -> { binding?.smileyButton?.setOnClickListener { onSmileyClick() }
labelText = resources!!.getString(R.string.nc_conversation_link) binding?.okButton?.setOnClickListener { onOkButtonClick() }
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
} else { } 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() { private fun textEditAddChangedListener() {
@ -242,7 +299,8 @@ class EntryMenuController(args: Bundle) :
) { ) {
val bundle = Bundle() val bundle = Bundle()
conversation!!.name = binding?.textEdit?.text.toString() 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) bundle.putSerializable(BundleKeys.KEY_OPERATION_CODE, operation)
router.pushController( router.pushController(
RouterTransaction.with(OperationsMenuController(bundle)) RouterTransaction.with(OperationsMenuController(bundle))
@ -274,16 +332,9 @@ class EntryMenuController(args: Bundle) :
private fun joinRoom() { private fun joinRoom() {
val bundle = Bundle() val bundle = Bundle()
bundle.putParcelable(BundleKeys.KEY_ROOM, Parcels.wrap<Any>(conversation)) bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
bundle.putString(BundleKeys.KEY_CALL_URL, callUrl)
bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, binding?.textEdit?.text.toString()) bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, binding?.textEdit?.text.toString())
bundle.putSerializable(BundleKeys.KEY_OPERATION_CODE, operation) 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( router.pushController(
RouterTransaction.with(OperationsMenuController(bundle)) RouterTransaction.with(OperationsMenuController(bundle))
.pushChangeHandler(HorizontalChangeHandler()) .pushChangeHandler(HorizontalChangeHandler())
@ -296,15 +347,10 @@ class EntryMenuController(args: Bundle) :
originalBundle = args originalBundle = args
operation = args.getSerializable(BundleKeys.KEY_OPERATION_CODE) as ConversationOperationEnum operation = args.getSerializable(BundleKeys.KEY_OPERATION_CODE) as ConversationOperationEnum
if (args.containsKey(BundleKeys.KEY_ROOM)) { roomToken = args.getString(KEY_ROOM_TOKEN, "")
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))
}
name = args.getString(BundleKeys.KEY_APP_ITEM_NAME, "") name = args.getString(BundleKeys.KEY_APP_ITEM_NAME, "")
packageName = args.getString(BundleKeys.KEY_APP_ITEM_PACKAGE_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 { companion object {

View File

@ -21,8 +21,6 @@
*/ */
package com.nextcloud.talk.controllers.bottomsheet package com.nextcloud.talk.controllers.bottomsheet
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.util.Log 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.ConversationsListFetchDataEvent
import com.nextcloud.talk.events.OpenConversationEvent import com.nextcloud.talk.events.OpenConversationEvent
import com.nextcloud.talk.models.RetrofitBucket 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.capabilities.CapabilitiesOverall
import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType 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.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.NoSupportedApiException 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_CALL_URL
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME 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_PASSWORD
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_TYPE 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_GROUP
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INVITED_PARTICIPANTS 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_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_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN 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.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
import io.reactivex.Observer import io.reactivex.Observer
@ -100,13 +95,14 @@ class OperationsMenuController(args: Bundle) : BaseController(
private var currentUser: User? = null private var currentUser: User? = null
private val callPassword: String private val callPassword: String
private val callUrl: String private val callUrl: String
private var roomToken: String
private val roomNameNew: String
private var baseUrl: String? = null private var baseUrl: String? = null
private var conversationToken: String? = null private var conversationToken: String? = null
private var disposable: Disposable? = null private var disposable: Disposable? = null
private var conversationType: ConversationType? = null private var conversationType: ConversationType? = null
private var invitedUsers: ArrayList<String>? = ArrayList() private var invitedUsers: ArrayList<String>? = ArrayList()
private var invitedGroups: ArrayList<String>? = ArrayList() private var invitedGroups: ArrayList<String>? = ArrayList()
private var serverCapabilities: Capabilities? = null
private var credentials: String? = null private var credentials: String? = null
private val conversationName: String private val conversationName: String
@ -128,42 +124,44 @@ class OperationsMenuController(args: Bundle) : BaseController(
baseUrl = callUrl.substring(0, callUrl.indexOf("/call")) baseUrl = callUrl.substring(0, callUrl.indexOf("/call"))
} }
} }
if (!TextUtils.isEmpty(baseUrl) && baseUrl != currentUser!!.baseUrl) {
if (serverCapabilities != null) { if (roomToken.isNotEmpty()) {
try { val apiVersion = apiVersion()
useBundledCapabilitiesForGuest() ncApi.getRoom(
} catch (e: IOException) { credentials,
// Fall back to fetching capabilities again ApiUtils.getUrlForRoom(apiVersion, currentUser!!.baseUrl, roomToken)
fetchCapabilitiesForGuest() )
} .subscribeOn(Schedulers.io())
} else { .observeOn(AndroidSchedulers.mainThread())
fetchCapabilitiesForGuest() .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 { } else {
processOperation() 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() { private fun fetchCapabilitiesForGuest() {
ncApi.getCapabilities(null, ApiUtils.getUrlForCapabilities(baseUrl)) ncApi.getCapabilities(null, ApiUtils.getUrlForCapabilities(baseUrl))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
@ -218,6 +216,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
ConversationOperationEnum.OPS_CODE_MARK_AS_UNREAD -> operationMarkAsUnread() ConversationOperationEnum.OPS_CODE_MARK_AS_UNREAD -> operationMarkAsUnread()
ConversationOperationEnum.OPS_CODE_REMOVE_FAVORITE, ConversationOperationEnum.OPS_CODE_REMOVE_FAVORITE,
ConversationOperationEnum.OPS_CODE_ADD_FAVORITE -> operationToggleFavorite() ConversationOperationEnum.OPS_CODE_ADD_FAVORITE -> operationToggleFavorite()
ConversationOperationEnum.OPS_CODE_JOIN_ROOM -> operationJoinRoom() ConversationOperationEnum.OPS_CODE_JOIN_ROOM -> operationJoinRoom()
else -> { else -> {
} }
@ -287,7 +286,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
currentUser!!.baseUrl, currentUser!!.baseUrl,
conversation!!.token conversation!!.token
), ),
conversation!!.name roomNameNew
) )
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -427,17 +426,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
if (conversation!!.hasPassword && conversation!!.isGuest) { if (conversation!!.hasPassword && conversation!!.isGuest) {
eventBus.post(ConversationsListFetchDataEvent()) eventBus.post(ConversationsListFetchDataEvent())
val bundle = Bundle() val bundle = Bundle()
bundle.putParcelable(KEY_ROOM, Parcels.wrap(conversation)) bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
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.putSerializable(KEY_OPERATION_CODE, ConversationOperationEnum.OPS_CODE_JOIN_ROOM) bundle.putSerializable(KEY_OPERATION_CODE, ConversationOperationEnum.OPS_CODE_JOIN_ROOM)
router.pushController( router.pushController(
RouterTransaction.with(EntryMenuController(bundle)) RouterTransaction.with(EntryMenuController(bundle))
@ -519,16 +508,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
binding?.resultTextView?.setText(R.string.nc_all_ok_operation) binding?.resultTextView?.setText(R.string.nc_all_ok_operation)
} else { } else {
binding?.resultTextView?.setTextColor(resources!!.getColor(R.color.nc_darkRed, null)) binding?.resultTextView?.setTextColor(resources!!.getColor(R.color.nc_darkRed, null))
if (!isGuestSupportError) { binding?.resultTextView?.setText(R.string.nc_failed_to_perform_operation)
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?.visibility = View.VISIBLE binding?.resultTextView?.visibility = View.VISIBLE
if (everythingOK) { if (everythingOK) {
@ -608,6 +588,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
override fun onSubscribe(d: Disposable) { override fun onSubscribe(d: Disposable) {
// unused atm // unused atm
} }
override fun onNext(addParticipantOverall: AddParticipantOverall) { override fun onNext(addParticipantOverall: AddParticipantOverall) {
// unused atm // unused atm
} }
@ -653,6 +634,7 @@ class OperationsMenuController(args: Bundle) : BaseController(
override fun onSubscribe(d: Disposable) { override fun onSubscribe(d: Disposable) {
// unused atm // unused atm
} }
override fun onNext(addParticipantOverall: AddParticipantOverall) { override fun onNext(addParticipantOverall: AddParticipantOverall) {
// unused atm // unused atm
} }
@ -679,8 +661,6 @@ class OperationsMenuController(args: Bundle) : BaseController(
bundle.putString(KEY_ROOM_TOKEN, conversation!!.token) bundle.putString(KEY_ROOM_TOKEN, conversation!!.token)
bundle.putString(KEY_ROOM_ID, conversation!!.roomId) bundle.putString(KEY_ROOM_ID, conversation!!.roomId)
bundle.putString(KEY_CONVERSATION_NAME, conversation!!.displayName) 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) bundle.putString(KEY_CONVERSATION_PASSWORD, callPassword)
eventBus.post(OpenConversationEvent(conversation, bundle)) eventBus.post(OpenConversationEvent(conversation, bundle))
} }
@ -755,11 +735,10 @@ class OperationsMenuController(args: Bundle) : BaseController(
init { init {
operation = args.getSerializable(KEY_OPERATION_CODE) as ConversationOperationEnum? 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, "") callPassword = args.getString(KEY_CONVERSATION_PASSWORD, "")
callUrl = args.getString(KEY_CALL_URL, "") 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)) { if (args.containsKey(KEY_INVITED_PARTICIPANTS)) {
invitedUsers = args.getStringArrayList(KEY_INVITED_PARTICIPANTS) invitedUsers = args.getStringArrayList(KEY_INVITED_PARTICIPANTS)
} }
@ -769,9 +748,6 @@ class OperationsMenuController(args: Bundle) : BaseController(
if (args.containsKey(KEY_CONVERSATION_TYPE)) { if (args.containsKey(KEY_CONVERSATION_TYPE)) {
conversationType = Parcels.unwrap(args.getParcelable(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, "") conversationName = args.getString(KEY_CONVERSATION_NAME, "")
} }
} }

View File

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

View File

@ -34,34 +34,31 @@ import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.core.net.toFile import androidx.core.net.toFile
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.lifecycle.ViewModelProvider
import autodagger.AutoInjector import autodagger.AutoInjector
import com.github.dhaval2404.imagepicker.ImagePicker import com.github.dhaval2404.imagepicker.ImagePicker
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.activities.BaseActivity import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication 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.data.user.model.User
import com.nextcloud.talk.databinding.ActivityConversationInfoEditBinding import com.nextcloud.talk.databinding.ActivityConversationInfoEditBinding
import com.nextcloud.talk.extensions.loadConversationAvatar import com.nextcloud.talk.extensions.loadConversationAvatar
import com.nextcloud.talk.extensions.loadSystemAvatar import com.nextcloud.talk.extensions.loadSystemAvatar
import com.nextcloud.talk.extensions.loadUserAvatar import com.nextcloud.talk.extensions.loadUserAvatar
import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.json.generic.GenericOverall 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.ApiUtils
import com.nextcloud.talk.utils.Mimetype
import com.nextcloud.talk.utils.PickImage import com.nextcloud.talk.utils.PickImage
import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import io.reactivex.Observer import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers 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 java.io.File
import javax.inject.Inject import javax.inject.Inject
@ -75,13 +72,18 @@ class ConversationInfoEditActivity :
lateinit var ncApi: NcApi lateinit var ncApi: NcApi
@Inject @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 conversationUser: User
private lateinit var credentials: String private lateinit var credentials: String
private var conversation: Conversation? = null private var conversation: ConversationModel? = null
private lateinit var pickImage: PickImage private lateinit var pickImage: PickImage
@ -96,12 +98,14 @@ class ConversationInfoEditActivity :
val extras: Bundle? = intent.extras val extras: Bundle? = intent.extras
conversationUser = extras?.getParcelable(BundleKeys.KEY_USER_ENTITY)!! conversationUser = currentUserProvider.currentUser.blockingGet()
conversationToken = extras.getString(BundleKeys.KEY_ROOM_TOKEN)!!
if (conversation == null && intent.hasExtra(BundleKeys.KEY_ACTIVE_CONVERSATION)) { roomToken = extras?.getString(BundleKeys.KEY_ROOM_TOKEN)!!
conversation = Parcels.unwrap<Conversation>(extras.getParcelable(BundleKeys.KEY_ACTIVE_CONVERSATION))
} conversationInfoEditViewModel =
ViewModelProvider(this, viewModelFactory)[ConversationInfoEditViewModel::class.java]
conversationInfoEditViewModel.getRoom(conversationUser, roomToken)
viewThemeUtils.material.colorTextInputLayout(binding.conversationNameInputLayout) viewThemeUtils.material.colorTextInputLayout(binding.conversationNameInputLayout)
viewThemeUtils.material.colorTextInputLayout(binding.conversationDescriptionInputLayout) viewThemeUtils.material.colorTextInputLayout(binding.conversationDescriptionInputLayout)
@ -109,21 +113,57 @@ class ConversationInfoEditActivity :
credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token) credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)
pickImage = PickImage(this, conversationUser) pickImage = PickImage(this, conversationUser)
initObservers()
} }
override fun onResume() { override fun onResume() {
super.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()) { if (conversation!!.description != null && conversation!!.description!!.isNotEmpty()) {
binding.conversationDescription.setText(conversation!!.description) binding.conversationDescription.setText(conversation!!.description)
} }
if (!CapabilitiesUtilNew.isConversationDescriptionEndpointAvailable(conversationUser)) { if (!CapabilitiesUtilNew.isConversationDescriptionEndpointAvailable(conversationUser)) {
binding.conversationDescription.isEnabled = false 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?) { private fun uploadAvatar(file: File) {
val builder = MultipartBody.Builder() conversationInfoEditViewModel.uploadConversationAvatar(conversationUser, file, roomToken)
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 deleteAvatar() { private fun deleteAvatar() {
ncApi.deleteConversationAvatar( conversationInfoEditViewModel.deleteConversationAvatar(conversationUser, roomToken)
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
}
})
} }
private fun loadConversationAvatar() { private fun loadConversationAvatar() {
setupAvatarOptions() setupAvatarOptions()
when (conversation!!.type) { 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!!.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) binding.avatarImage.loadConversationAvatar(conversationUser, conversation!!, false, viewThemeUtils)
} }
Conversation.ConversationType.ROOM_SYSTEM -> { ConversationType.ROOM_SYSTEM -> {
binding.avatarImage.loadSystemAvatar() 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.Mimetype
import com.nextcloud.talk.utils.ParticipantPermissions import com.nextcloud.talk.utils.ParticipantPermissions
import com.nextcloud.talk.utils.bundle.BundleKeys 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_HIDE_SOURCE_ROOM
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_FLAG 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_FORWARD_MSG_TEXT
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID 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_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_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN 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_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.hasSpreedFeatureCapability
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isServerEOL import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isServerEOL
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isUnifiedSearchAvailable 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.apache.commons.lang3.builder.CompareToBuilder
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import org.parceler.Parcels
import retrofit2.HttpException import retrofit2.HttpException
import java.util.Objects import java.util.Objects
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -1074,7 +1070,6 @@ class ConversationsListActivity :
if (clickedItem != null) { if (clickedItem != null) {
val conversation = (clickedItem as ConversationItem).model val conversation = (clickedItem as ConversationItem).model
conversationsListBottomDialog = ConversationsListBottomDialog( conversationsListBottomDialog = ConversationsListBottomDialog(
this,
this, this,
userManager.currentUser.blockingGet(), userManager.currentUser.blockingGet(),
conversation conversation
@ -1185,8 +1180,6 @@ class ConversationsListActivity :
} }
val bundle = Bundle() 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_TOKEN, selectedConversation!!.token)
bundle.putString(KEY_ROOM_ID, selectedConversation!!.roomId) bundle.putString(KEY_ROOM_ID, selectedConversation!!.roomId)
bundle.putString(KEY_SHARED_TEXT, textToPaste) bundle.putString(KEY_SHARED_TEXT, textToPaste)
@ -1229,38 +1222,35 @@ class ConversationsListActivity :
if (conversationMenuBundle != null && if (conversationMenuBundle != null &&
isInternalUserEqualsCurrentUser(currentUser, conversationMenuBundle) isInternalUserEqualsCurrentUser(currentUser, conversationMenuBundle)
) { ) {
val conversation = Parcels.unwrap<Conversation>(conversationMenuBundle!!.getParcelable(KEY_ROOM)) binding?.floatingActionButton?.let {
if (conversation != null) { val dialogBuilder = MaterialAlertDialogBuilder(it.context)
binding?.floatingActionButton?.let { .setIcon(
val dialogBuilder = MaterialAlertDialogBuilder(it.context) viewThemeUtils.dialog
.setIcon( .colorMaterialAlertDialogIcon(context, R.drawable.ic_delete_black_24dp)
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)
) )
} .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 package com.nextcloud.talk.dagger.modules
import com.nextcloud.talk.api.NcApi 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.source.local.TalkDatabase
import com.nextcloud.talk.data.storage.ArbitraryStoragesRepository import com.nextcloud.talk.data.storage.ArbitraryStoragesRepository
import com.nextcloud.talk.data.storage.ArbitraryStoragesRepositoryImpl import com.nextcloud.talk.data.storage.ArbitraryStoragesRepositoryImpl
@ -123,4 +127,16 @@ class RepositoryModule {
TranslateRepository { TranslateRepository {
return TranslateRepositoryImpl(ncApi) 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.ViewModel
import androidx.lifecycle.ViewModelProvider 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.messagesearch.MessageSearchViewModel
import com.nextcloud.talk.openconversations.viewmodels.OpenConversationsViewModel import com.nextcloud.talk.openconversations.viewmodels.OpenConversationsViewModel
import com.nextcloud.talk.polls.viewmodels.PollCreateViewModel import com.nextcloud.talk.polls.viewmodels.PollCreateViewModel
@ -112,5 +115,20 @@ abstract class ViewModelModule {
@Binds @Binds
@IntoMap @IntoMap
@ViewModelKey(OpenConversationsViewModel::class) @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 return Room
.databaseBuilder(context.applicationContext, TalkDatabase::class.java, dbName) .databaseBuilder(context.applicationContext, TalkDatabase::class.java, dbName)
// comment out openHelperFactory to view the database entries in Android Studio for debugging
.openHelperFactory(factory) .openHelperFactory(factory)
.addMigrations(Migrations.MIGRATION_6_8, Migrations.MIGRATION_7_8) .addMigrations(Migrations.MIGRATION_6_8, Migrations.MIGRATION_7_8)
.allowMainThreadQueries() .allowMainThreadQueries()

View File

@ -44,6 +44,8 @@ import coil.transform.RoundedCornersTransformation
import com.amulyakhare.textdrawable.TextDrawable import com.amulyakhare.textdrawable.TextDrawable
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.data.user.model.User 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.models.json.conversations.Conversation
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils 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 ROUNDING_PIXEL = 16f
private const val TAG = "ImageViewExtensions" private const val TAG = "ImageViewExtensions"
@Deprecated("use other constructor that expects com.nextcloud.talk.models.domain.ConversationModel")
fun ImageView.loadConversationAvatar( fun ImageView.loadConversationAvatar(
user: User, user: User,
conversation: Conversation, conversation: Conversation,
ignoreCache: Boolean, ignoreCache: Boolean,
viewThemeUtils: ViewThemeUtils? 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 { ): io.reactivex.disposables.Disposable {
val imageRequestUri = ApiUtils.getUrlForConversationAvatarWithVersion( val imageRequestUri = ApiUtils.getUrlForConversationAvatarWithVersion(
1, 1,
@ -68,10 +85,10 @@ fun ImageView.loadConversationAvatar(
if (conversation.avatarVersion.isNullOrEmpty() && viewThemeUtils != null) { if (conversation.avatarVersion.isNullOrEmpty() && viewThemeUtils != null) {
when (conversation.type) { when (conversation.type) {
Conversation.ConversationType.ROOM_GROUP_CALL -> ConversationType.ROOM_GROUP_CALL ->
return loadDefaultGroupCallAvatar(viewThemeUtils) return loadDefaultGroupCallAvatar(viewThemeUtils)
Conversation.ConversationType.ROOM_PUBLIC_CALL -> ConversationType.ROOM_PUBLIC_CALL ->
return loadDefaultPublicCallAvatar(viewThemeUtils) return loadDefaultPublicCallAvatar(viewThemeUtils)
else -> {} 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..) // when no own images are set. (although these default avatars can not be themed for the android app..)
val errorPlaceholder = val errorPlaceholder =
when (conversation.type) { when (conversation.type) {
Conversation.ConversationType.ROOM_GROUP_CALL -> ConversationType.ROOM_GROUP_CALL ->
ContextCompat.getDrawable(context, R.drawable.ic_circular_group) 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) ContextCompat.getDrawable(context, R.drawable.ic_circular_link)
else -> ContextCompat.getDrawable(context, R.drawable.account_circle_96dp) 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.bluelinelabs.logansquare.LoganSquare
import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.activities.CallNotificationActivity
import com.nextcloud.talk.activities.MainActivity import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
import com.nextcloud.talk.callnotification.CallNotificationActivity
import com.nextcloud.talk.models.SignatureVerification import com.nextcloud.talk.models.SignatureVerification
import com.nextcloud.talk.models.json.chat.ChatUtils.Companion.getParsedMessage import com.nextcloud.talk.models.json.chat.ChatUtils.Companion.getParsedMessage
import com.nextcloud.talk.models.json.conversations.RoomOverall 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_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SHARE_RECORDING_TO_CHAT_URL 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_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.preferences.AppPreferences
import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder
import io.reactivex.Observable import io.reactivex.Observable
@ -197,7 +196,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
val bundle = Bundle() val bundle = Bundle()
bundle.putString(KEY_ROOM_TOKEN, pushMessage.id) bundle.putString(KEY_ROOM_TOKEN, pushMessage.id)
bundle.putInt(KEY_NOTIFICATION_TIMESTAMP, pushMessage.timestamp.toInt()) 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) bundle.putBoolean(KEY_FROM_NOTIFICATION_START_CALL, true)
fullScreenIntent.putExtras(bundle) fullScreenIntent.putExtras(bundle)
fullScreenIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK 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_SYSTEM_NOTIFICATION_ID, systemNotificationId)
shareRecordingIntent.putExtra(KEY_SHARE_RECORDING_TO_CHAT_URL, shareToChatUrl) shareRecordingIntent.putExtra(KEY_SHARE_RECORDING_TO_CHAT_URL, shareToChatUrl)
shareRecordingIntent.putExtra(KEY_ROOM_TOKEN, pushMessage.id) 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) { val intentFlag: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT 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 intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
val bundle = Bundle() val bundle = Bundle()
bundle.putString(KEY_ROOM_TOKEN, pushMessage.id) 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) bundle.putBoolean(KEY_FROM_NOTIFICATION_START_CALL, false)
intent.putExtras(bundle) intent.putExtras(bundle)
return intent 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 intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
val bundle = Bundle() val bundle = Bundle()
bundle.putString(KEY_ROOM_TOKEN, pushMessage.id) 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) bundle.putBoolean(KEY_FROM_NOTIFICATION_START_CALL, false)
intent.putExtras(bundle) 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.NotificationUtils
import com.nextcloud.talk.utils.RemoteFileUtils 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_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_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.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
import com.nextcloud.talk.utils.preferences.AppPreferences 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 intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
bundle.putString(KEY_ROOM_TOKEN, roomToken) 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) bundle.putBoolean(KEY_FROM_NOTIFICATION_START_CALL, false)
intent.putExtras(bundle) 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' // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null) constructor() : this(null, null)
@Deprecated("Use ConversationUtil")
val isPublic: Boolean val isPublic: Boolean
get() = ConversationType.ROOM_PUBLIC_CALL == type get() = ConversationType.ROOM_PUBLIC_CALL == type
@Deprecated("Use ConversationUtil")
val isGuest: Boolean val isGuest: Boolean
get() = ParticipantType.GUEST == participantType || get() = ParticipantType.GUEST == participantType ||
ParticipantType.GUEST_MODERATOR == participantType || ParticipantType.GUEST_MODERATOR == participantType ||
ParticipantType.USER_FOLLOWING_LINK == participantType ParticipantType.USER_FOLLOWING_LINK == participantType
@Deprecated("Use ConversationUtil")
val isParticipantOwnerOrModerator: Boolean val isParticipantOwnerOrModerator: Boolean
get() = ParticipantType.OWNER == participantType || get() = ParticipantType.OWNER == participantType ||
ParticipantType.GUEST_MODERATOR == participantType || ParticipantType.GUEST_MODERATOR == participantType ||
ParticipantType.MODERATOR == participantType ParticipantType.MODERATOR == participantType
@Deprecated("Use ConversationUtil")
private fun isLockedOneToOne(conversationUser: User): Boolean { private fun isLockedOneToOne(conversationUser: User): Boolean {
return type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL && return type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL &&
CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "locked-one-to-one-rooms") CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "locked-one-to-one-rooms")
} }
@Deprecated("Use ConversationUtil")
fun canModerate(conversationUser: User): Boolean { fun canModerate(conversationUser: User): Boolean {
return isParticipantOwnerOrModerator && return isParticipantOwnerOrModerator &&
!isLockedOneToOne(conversationUser) && !isLockedOneToOne(conversationUser) &&
type != ConversationType.FORMER_ONE_TO_ONE type != ConversationType.FORMER_ONE_TO_ONE
} }
@Deprecated("Use ConversationUtil")
fun isLobbyViewApplicable(conversationUser: User): Boolean { fun isLobbyViewApplicable(conversationUser: User): Boolean {
return !canModerate(conversationUser) && return !canModerate(conversationUser) &&
(type == ConversationType.ROOM_GROUP_CALL || type == ConversationType.ROOM_PUBLIC_CALL) (type == ConversationType.ROOM_GROUP_CALL || type == ConversationType.ROOM_PUBLIC_CALL)
} }
@Deprecated("Use ConversationUtil")
fun isNameEditable(conversationUser: User): Boolean { fun isNameEditable(conversationUser: User): Boolean {
return canModerate(conversationUser) && ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL != type return canModerate(conversationUser) && ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL != type
} }
@Deprecated("Use ConversationUtil")
fun canLeave(): Boolean { fun canLeave(): Boolean {
return if (canLeaveConversation != null) { return if (canLeaveConversation != null) {
// Available since APIv2 // Available since APIv2
@ -200,6 +208,7 @@ data class Conversation(
} }
} }
@Deprecated("Use ConversationUtil")
fun canDelete(conversationUser: User): Boolean { fun canDelete(conversationUser: User): Boolean {
return if (canDeleteConversation != null) { return if (canDeleteConversation != null) {
// Available since APIv2 // Available since APIv2

View File

@ -79,10 +79,7 @@ class ListOpenConversationsActivity : BaseActivity() {
} }
private fun adapterOnClick(conversation: OpenConversation) { private fun adapterOnClick(conversation: OpenConversation) {
val user = userProvider.currentUser.blockingGet()
val bundle = Bundle() val bundle = Bundle()
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, user)
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversation.roomToken) bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversation.roomToken)
val chatIntent = Intent(context, ChatActivity::class.java) 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.databinding.DialogPollMainBinding
import com.nextcloud.talk.polls.viewmodels.PollMainViewModel import com.nextcloud.talk.polls.viewmodels.PollMainViewModel
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import javax.inject.Inject import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
@ -48,16 +49,22 @@ class PollMainDialogFragment : DialogFragment() {
@Inject @Inject
lateinit var viewThemeUtils: ViewThemeUtils lateinit var viewThemeUtils: ViewThemeUtils
var currentUserProvider: CurrentUserProviderNew? = null
@Inject set
private lateinit var binding: DialogPollMainBinding private lateinit var binding: DialogPollMainBinding
private lateinit var viewModel: PollMainViewModel private lateinit var viewModel: PollMainViewModel
lateinit var user: User
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
viewModel = ViewModelProvider(this, viewModelFactory)[PollMainViewModel::class.java] 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 roomToken = arguments?.getString(KEY_ROOM_TOKEN)!!
val isOwnerOrModerator = arguments?.getBoolean(KEY_OWNER_OR_MODERATOR)!! val isOwnerOrModerator = arguments?.getBoolean(KEY_OWNER_OR_MODERATOR)!!
val pollId = arguments?.getString(KEY_POLL_ID)!! val pollId = arguments?.getString(KEY_POLL_ID)!!

View File

@ -56,8 +56,6 @@ class ShareRecordingToChatReceiver : BroadcastReceiver() {
lateinit var currentUser: User lateinit var currentUser: User
private var systemNotificationId: Int? = null private var systemNotificationId: Int? = null
private var link: String? = null private var link: String? = null
var roomToken: String? = null
var conversationOfShareTarget: User? = null
init { init {
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
@ -68,9 +66,6 @@ class ShareRecordingToChatReceiver : BroadcastReceiver() {
systemNotificationId = intent!!.getIntExtra(KEY_SYSTEM_NOTIFICATION_ID, 0) systemNotificationId = intent!!.getIntExtra(KEY_SYSTEM_NOTIFICATION_ID, 0)
link = intent.getStringExtra(BundleKeys.KEY_SHARE_RECORDING_TO_CHAT_URL) 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!!) val id = intent.getLongExtra(KEY_INTERNAL_USER_ID, userManager.currentUser.blockingGet().id!!)
currentUser = userManager.getUserWithId(id).blockingGet() 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.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel import com.nextcloud.talk.models.domain.ReactionDeletedModel
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.conversations.Conversation
import io.reactivex.Observable import io.reactivex.Observable
interface ReactionsRepository { interface ReactionsRepository {
fun addReaction( fun addReaction(
currentConversation: Conversation, roomToken: String,
message: ChatMessage, message: ChatMessage,
emoji: String emoji: String
): Observable<ReactionAddedModel> ): Observable<ReactionAddedModel>
fun deleteReaction( fun deleteReaction(
currentConversation: Conversation, roomToken: String,
message: ChatMessage, message: ChatMessage,
emoji: String emoji: String
): Observable<ReactionDeletedModel> ): 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.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel import com.nextcloud.talk.models.domain.ReactionDeletedModel
import com.nextcloud.talk.models.json.chat.ChatMessage 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.models.json.generic.GenericMeta
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew 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) val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
override fun addReaction( override fun addReaction(
currentConversation: Conversation, roomToken: String,
message: ChatMessage, message: ChatMessage,
emoji: String emoji: String
): Observable<ReactionAddedModel> { ): Observable<ReactionAddedModel> {
@ -46,7 +45,7 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur
credentials, credentials,
ApiUtils.getUrlForMessageReaction( ApiUtils.getUrlForMessageReaction(
currentUser.baseUrl, currentUser.baseUrl,
currentConversation.token, roomToken,
message.id message.id
), ),
emoji emoji
@ -54,7 +53,7 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur
} }
override fun deleteReaction( override fun deleteReaction(
currentConversation: Conversation, roomToken: String,
message: ChatMessage, message: ChatMessage,
emoji: String emoji: String
): Observable<ReactionDeletedModel> { ): Observable<ReactionDeletedModel> {
@ -62,7 +61,7 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur
credentials, credentials,
ApiUtils.getUrlForMessageReaction( ApiUtils.getUrlForMessageReaction(
currentUser.baseUrl, currentUser.baseUrl,
currentConversation.token, roomToken,
message.id message.id
), ),
emoji 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.DisplayUtils
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME 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_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import javax.inject.Inject import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
class SharedItemsActivity : AppCompatActivity() { class SharedItemsActivity : AppCompatActivity() {
@Inject
lateinit var currentUserProvider: CurrentUserProviderNew
@Inject @Inject
lateinit var viewModelFactory: ViewModelProvider.Factory lateinit var viewModelFactory: ViewModelProvider.Factory
@ -70,7 +73,9 @@ class SharedItemsActivity : AppCompatActivity() {
val roomToken = intent.getStringExtra(KEY_ROOM_TOKEN)!! val roomToken = intent.getStringExtra(KEY_ROOM_TOKEN)!!
val conversationName = intent.getStringExtra(KEY_CONVERSATION_NAME) 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) val isUserConversationOwnerOrModerator = intent.getBooleanExtra(KEY_USER_IS_OWNER_OR_MODERATOR, false)
binding = ActivitySharedItemsBinding.inflate(layoutInflater) binding = ActivitySharedItemsBinding.inflate(layoutInflater)

View File

@ -46,7 +46,6 @@ import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import org.parceler.Parcels
private const val TAG = "ProfileBottomSheet" private const val TAG = "ProfileBottomSheet"
@ -144,46 +143,13 @@ class ProfileBottomSheet(val ncApi: NcApi, val userModel: User) {
override fun onNext(roomOverall: RoomOverall) { override fun onNext(roomOverall: RoomOverall) {
val bundle = Bundle() 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_TOKEN, roomOverall.ocs!!.data!!.token)
bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId) bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
// FIXME once APIv2+ is used only, the createRoom already returns all the data val chatIntent = Intent(context, ChatActivity::class.java)
ncApi.getRoom( chatIntent.putExtras(bundle)
credentials, chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
ApiUtils.getUrlForRoom( context.startActivity(chatIntent)
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
}
})
} }
override fun onError(e: Throwable) { override fun onError(e: Throwable) {

View File

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

View File

@ -20,7 +20,6 @@
package com.nextcloud.talk.ui.dialog package com.nextcloud.talk.ui.dialog
import android.app.Activity
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.view.View 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.users.UserManager
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID 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_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.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import org.parceler.Parcels
import javax.inject.Inject import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
class ConversationsListBottomDialog( class ConversationsListBottomDialog(
val activity: Activity, val activity: ConversationsListActivity,
val controller: ConversationsListActivity,
val currentUser: User, val currentUser: User,
val conversation: Conversation val conversation: Conversation
) : BottomSheetDialog(activity) { ) : BottomSheetDialog(activity) {
@ -175,9 +171,8 @@ class ConversationsListBottomDialog(
if (!TextUtils.isEmpty(conversation.token)) { if (!TextUtils.isEmpty(conversation.token)) {
val bundle = Bundle() val bundle = Bundle()
bundle.putLong(KEY_INTERNAL_USER_ID, currentUser.id!!) bundle.putLong(KEY_INTERNAL_USER_ID, currentUser.id!!)
bundle.putParcelable(KEY_ROOM, Parcels.wrap(conversation)) bundle.putString(KEY_ROOM_TOKEN, conversation.token)
activity.showDeleteConversationDialog(bundle)
controller.showDeleteConversationDialog(bundle)
} }
dismiss() dismiss()
@ -198,8 +193,8 @@ class ConversationsListBottomDialog(
private fun executeOperationsMenuController(operation: ConversationOperationEnum) { private fun executeOperationsMenuController(operation: ConversationOperationEnum) {
val bundle = Bundle() val bundle = Bundle()
bundle.putParcelable(KEY_ROOM, Parcels.wrap(conversation))
bundle.putSerializable(KEY_OPERATION_CODE, operation) bundle.putSerializable(KEY_OPERATION_CODE, operation)
bundle.putString(KEY_ROOM_TOKEN, conversation.token)
binding.operationItemsLayout.visibility = View.GONE binding.operationItemsLayout.visibility = View.GONE
@ -211,13 +206,13 @@ class ConversationsListBottomDialog(
.popChangeHandler(HorizontalChangeHandler()) .popChangeHandler(HorizontalChangeHandler())
) )
controller.fetchRooms() activity.fetchRooms()
} }
private fun executeEntryMenuController(operation: ConversationOperationEnum) { private fun executeEntryMenuController(operation: ConversationOperationEnum) {
val bundle = Bundle() val bundle = Bundle()
bundle.putParcelable(KEY_ROOM, Parcels.wrap(conversation))
bundle.putSerializable(KEY_OPERATION_CODE, operation) bundle.putSerializable(KEY_OPERATION_CODE, operation)
bundle.putString(KEY_ROOM_TOKEN, conversation.token)
binding.operationItemsLayout.visibility = View.GONE 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.chat.ChatActivity
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.DialogMessageActionsBinding 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.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel import com.nextcloud.talk.models.domain.ReactionDeletedModel
import com.nextcloud.talk.models.json.chat.ChatMessage 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.repositories.reactions.ReactionsRepository
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
@ -63,7 +65,7 @@ class MessageActionsDialog(
private val chatActivity: ChatActivity, private val chatActivity: ChatActivity,
private val message: ChatMessage, private val message: ChatMessage,
private val user: User?, private val user: User?,
private val currentConversation: Conversation?, private val currentConversation: ConversationModel?,
private val showMessageDeletionButton: Boolean, private val showMessageDeletionButton: Boolean,
private val hasChatPermission: Boolean private val hasChatPermission: Boolean
) : BottomSheetDialog(chatActivity) { ) : BottomSheetDialog(chatActivity) {
@ -100,7 +102,7 @@ class MessageActionsDialog(
message.replyable && message.replyable &&
hasUserId(user) && hasUserId(user) &&
hasUserActorId(message) && hasUserActorId(message) &&
currentConversation?.type != Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL currentConversation?.type != ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
) )
initMenuDeleteMessage(showMessageDeletionButton) initMenuDeleteMessage(showMessageDeletionButton)
initMenuForwardMessage( initMenuForwardMessage(
@ -226,7 +228,7 @@ class MessageActionsDialog(
} }
private fun isPermitted(hasChatPermission: Boolean): Boolean { private fun isPermitted(hasChatPermission: Boolean): Boolean {
return hasChatPermission && Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY != return hasChatPermission && ConversationReadOnlyState.CONVERSATION_READ_ONLY !=
currentConversation?.conversationReadOnlyState currentConversation?.conversationReadOnlyState
} }
@ -338,12 +340,12 @@ class MessageActionsDialog(
private fun clickOnEmoji(message: ChatMessage, emoji: String) { private fun clickOnEmoji(message: ChatMessage, emoji: String) {
if (message.reactionsSelf?.contains(emoji) == true) { if (message.reactionsSelf?.contains(emoji) == true) {
reactionsRepository.deleteReaction(currentConversation!!, message, emoji) reactionsRepository.deleteReaction(currentConversation!!.token!!, message, emoji)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread()) ?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(ReactionDeletedObserver()) ?.subscribe(ReactionDeletedObserver())
} else { } else {
reactionsRepository.addReaction(currentConversation!!, message, emoji) reactionsRepository.addReaction(currentConversation!!.token!!, message, emoji)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread()) ?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(ReactionAddedObserver()) ?.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.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.vanniktech.emoji.EmojiPopup import com.vanniktech.emoji.EmojiPopup
import com.vanniktech.emoji.installDisableKeyboardInput import com.vanniktech.emoji.installDisableKeyboardInput
import com.vanniktech.emoji.installForceSingleEmoji import com.vanniktech.emoji.installForceSingleEmoji
@ -113,6 +114,9 @@ class SetStatusDialogFragment :
@Inject @Inject
lateinit var viewThemeUtils: ViewThemeUtils lateinit var viewThemeUtils: ViewThemeUtils
var currentUserProvider: CurrentUserProviderNew? = null
@Inject set
lateinit var credentials: String lateinit var credentials: String
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -121,7 +125,7 @@ class SetStatusDialogFragment :
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
arguments?.let { arguments?.let {
currentUser = it.getParcelable(ARG_CURRENT_USER_PARAM) currentUser = currentUserProvider?.currentUser?.blockingGet()
currentStatus = it.getParcelable(ARG_CURRENT_STATUS_PARAM) currentStatus = it.getParcelable(ARG_CURRENT_STATUS_PARAM)
credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token) credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token)
@ -559,9 +563,8 @@ class SetStatusDialogFragment :
private val TAG = SetStatusDialogFragment::class.simpleName private val TAG = SetStatusDialogFragment::class.simpleName
@JvmStatic @JvmStatic
fun newInstance(user: User, status: Status): SetStatusDialogFragment { fun newInstance(status: Status): SetStatusDialogFragment {
val args = Bundle() val args = Bundle()
args.putParcelable(ARG_CURRENT_USER_PARAM, user)
args.putParcelable(ARG_CURRENT_STATUS_PARAM, status) args.putParcelable(ARG_CURRENT_STATUS_PARAM, status)
val dialogFragment = SetStatusDialogFragment() 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.DialogMessageReactionsBinding
import com.nextcloud.talk.databinding.ItemReactionsTabBinding import com.nextcloud.talk.databinding.ItemReactionsTabBinding
import com.nextcloud.talk.models.json.chat.ChatMessage 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.generic.GenericOverall
import com.nextcloud.talk.models.json.reactions.ReactionsOverall import com.nextcloud.talk.models.json.reactions.ReactionsOverall
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
@ -59,7 +58,7 @@ import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
class ShowReactionsDialog( class ShowReactionsDialog(
activity: Activity, activity: Activity,
private val currentConversation: Conversation?, private val roomToken: String,
private val chatMessage: ChatMessage, private val chatMessage: ChatMessage,
private val user: User?, private val user: User?,
private val hasChatPermission: Boolean, private val hasChatPermission: Boolean,
@ -156,7 +155,7 @@ class ShowReactionsDialog(
credentials, credentials,
ApiUtils.getUrlForMessageReaction( ApiUtils.getUrlForMessageReaction(
user?.baseUrl, user?.baseUrl,
currentConversation!!.token, roomToken,
chatMessage.id chatMessage.id
), ),
emoji emoji
@ -211,7 +210,7 @@ class ShowReactionsDialog(
credentials, credentials,
ApiUtils.getUrlForMessageReaction( ApiUtils.getUrlForMessageReaction(
user?.baseUrl, user?.baseUrl,
currentConversation!!.token, roomToken,
message.id message.id
), ),
emoji 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 package com.nextcloud.talk.utils
import com.nextcloud.talk.data.user.model.User 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.models.json.conversations.Conversation
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
@ -31,9 +32,15 @@ import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
*/ */
class ParticipantPermissions( class ParticipantPermissions(
private val user: User, 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 isDefault = (conversation.permissions and DEFAULT) == DEFAULT
val isCustom = (conversation.permissions and CUSTOM) == CUSTOM val isCustom = (conversation.permissions and CUSTOM) == CUSTOM
private val canStartCall = (conversation.permissions and START_CALL) == START_CALL 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_BASE_URL = "KEY_BASE_URL"
const val KEY_IS_ACCOUNT_IMPORT = "KEY_IS_ACCOUNT_IMPORT" const val KEY_IS_ACCOUNT_IMPORT = "KEY_IS_ACCOUNT_IMPORT"
const val KEY_ORIGINAL_PROTOCOL = "KEY_ORIGINAL_PROTOCOL" 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_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_PACKAGE_NAME = "KEY_APP_ITEM_PACKAGE_NAME"
const val KEY_APP_ITEM_NAME = "KEY_APP_ITEM_NAME" const val KEY_APP_ITEM_NAME = "KEY_APP_ITEM_NAME"
const val KEY_CONVERSATION_PASSWORD = "KEY_CONVERSATION_PASSWORD" const val KEY_CONVERSATION_PASSWORD = "KEY_CONVERSATION_PASSWORD"
const val KEY_ROOM_TOKEN = "KEY_ROOM_TOKEN" const val KEY_ROOM_TOKEN = "KEY_ROOM_TOKEN"
const val KEY_ROOM_ONE_TO_ONE = "KEY_ROOM_ONE_TO_ONE" 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_NEW_CONVERSATION = "KEY_NEW_CONVERSATION"
const val KEY_ADD_PARTICIPANTS = "KEY_ADD_PARTICIPANTS" const val KEY_ADD_PARTICIPANTS = "KEY_ADD_PARTICIPANTS"
const val KEY_EXISTING_PARTICIPANTS = "KEY_EXISTING_PARTICIPANTS" const val KEY_EXISTING_PARTICIPANTS = "KEY_EXISTING_PARTICIPANTS"
const val KEY_CALL_URL = "KEY_CALL_URL" 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_MODIFIED_BASE_URL = "KEY_MODIFIED_BASE_URL"
const val KEY_NOTIFICATION_SUBJECT = "KEY_NOTIFICATION_SUBJECT" const val KEY_NOTIFICATION_SUBJECT = "KEY_NOTIFICATION_SUBJECT"
const val KEY_NOTIFICATION_SIGNATURE = "KEY_NOTIFICATION_SIGNATURE" 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_RECORDING_STATE = "KEY_RECORDING_STATE"
const val KEY_CALL_VOICE_ONLY = "KEY_CALL_VOICE_ONLY" const val KEY_CALL_VOICE_ONLY = "KEY_CALL_VOICE_ONLY"
const val KEY_CALL_WITHOUT_NOTIFICATION = "KEY_CALL_WITHOUT_NOTIFICATION" 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_FROM_NOTIFICATION_START_CALL = "KEY_FROM_NOTIFICATION_START_CALL"
const val KEY_ROOM_ID = "KEY_ROOM_ID" const val KEY_ROOM_ID = "KEY_ROOM_ID"
const val KEY_ARE_CALL_SOUNDS = "KEY_ARE_CALL_SOUNDS" const val KEY_ARE_CALL_SOUNDS = "KEY_ARE_CALL_SOUNDS"