pass spreedCapabilities instead user to CapabilitiesUtil

To support federated rooms, capabilities have to be checked from the room which now also has capabilities.
If room is not federated, capabilities fromuser are still checked.
This is why CapabilitiesUtil had to be refactored to accept SpreedCapabilities which can come from room or user.

Other than that, many other changes were made as a result of this change.

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2024-02-23 16:54:31 +01:00
parent 513127e481
commit 754b825096
No known key found for this signature in database
GPG key ID: C793F8B59F43CE7B
101 changed files with 2115 additions and 1556 deletions

View file

@ -199,7 +199,7 @@ class AccountVerificationActivity : BaseActivity() {
val credentials = ApiUtils.getCredentials(username, token)
cookieManager.cookieStore.removeAll()
ncApi.getCapabilities(credentials, ApiUtils.getUrlForCapabilities(baseUrl))
ncApi.getCapabilities(credentials, ApiUtils.getUrlForCapabilities(baseUrl!!))
.subscribeOn(Schedulers.io())
.subscribe(object : Observer<CapabilitiesOverall> {
override fun onSubscribe(d: Disposable) {
@ -213,7 +213,7 @@ class AccountVerificationActivity : BaseActivity() {
capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability!!.features != null &&
!capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability!!.features!!.isEmpty()
if (hasTalk) {
fetchProfile(credentials, capabilitiesOverall)
fetchProfile(credentials!!, capabilitiesOverall)
} else {
if (resources != null) {
runOnUiThread {
@ -305,7 +305,7 @@ class AccountVerificationActivity : BaseActivity() {
private fun fetchProfile(credentials: String, capabilitiesOverall: CapabilitiesOverall) {
ncApi.getUserProfile(
credentials,
ApiUtils.getUrlForUserProfile(baseUrl)
ApiUtils.getUrlForUserProfile(baseUrl!!)
)
.subscribeOn(Schedulers.io())
.subscribe(object : Observer<UserProfileOverall> {

View file

@ -52,7 +52,7 @@ import com.nextcloud.talk.utils.UriUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.bundle.BundleKeys.ADD_ADDITIONAL_ACCOUNT
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_ACCOUNT_IMPORT
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.CapabilitiesUtil
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
@ -336,7 +336,7 @@ class ServerSelectionActivity : BaseActivity() {
if (hasTalk) {
runOnUiThread {
if (CapabilitiesUtilNew.isServerEOL(capabilities)) {
if (CapabilitiesUtil.isServerEOL(capabilitiesOverall.ocs?.data?.serverVersion?.major!!)) {
if (resources != null) {
runOnUiThread {
setErrorText(resources!!.getString(R.string.nc_settings_server_eol))

View file

@ -110,6 +110,7 @@ import com.nextcloud.talk.ui.dialog.AudioOutputDialog
import com.nextcloud.talk.ui.dialog.MoreCallActionsDialog
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.NotificationUtils.cancelExistingNotificationsForRoom
import com.nextcloud.talk.utils.NotificationUtils.getCallRingtoneUri
@ -131,9 +132,9 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_START_CALL_AFTER_ROOM_SWITCH
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SWITCH_TO_ROOM
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isCallRecordingAvailable
import com.nextcloud.talk.utils.CapabilitiesUtil
import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
import com.nextcloud.talk.utils.CapabilitiesUtil.isCallRecordingAvailable
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
import com.nextcloud.talk.utils.power.PowerManagerUtils
@ -234,7 +235,7 @@ class CallActivity : CallBaseActivity() {
private var iceServers: MutableList<PeerConnection.IceServer>? = null
private var cameraEnumerator: CameraEnumerator? = null
private var roomToken: String? = null
var conversationUser: User? = null
lateinit var conversationUser: User
private var conversationName: String? = null
private var callSession: String? = null
private var localStream: MediaStream? = null
@ -530,13 +531,13 @@ class CallActivity : CallBaseActivity() {
)
}
when (CapabilitiesUtilNew.getRecordingConsentType(conversationUser)) {
CapabilitiesUtilNew.RECORDING_CONSENT_NOT_REQUIRED -> initiateCall()
CapabilitiesUtilNew.RECORDING_CONSENT_REQUIRED -> askForRecordingConsent()
CapabilitiesUtilNew.RECORDING_CONSENT_DEPEND_ON_CONVERSATION -> {
when (CapabilitiesUtil.getRecordingConsentType(conversationUser!!.capabilities!!.spreedCapability!!)) {
CapabilitiesUtil.RECORDING_CONSENT_NOT_REQUIRED -> initiateCall()
CapabilitiesUtil.RECORDING_CONSENT_REQUIRED -> askForRecordingConsent()
CapabilitiesUtil.RECORDING_CONSENT_DEPEND_ON_CONVERSATION -> {
val getRoomApiVersion = ApiUtils.getConversationApiVersion(
conversationUser,
intArrayOf(ApiUtils.APIv4, 1)
conversationUser!!,
intArrayOf(ApiUtils.API_V4, 1)
)
ncApi!!.getRoom(credentials, ApiUtils.getUrlForRoom(getRoomApiVersion, baseUrl, roomToken))
.retry(API_RETRIES)
@ -571,7 +572,10 @@ class CallActivity : CallBaseActivity() {
override fun onResume() {
super.onResume()
if (hasSpreedFeatureCapability(conversationUser, "recording-v1") &&
if (hasSpreedFeatureCapability(
conversationUser.capabilities!!.spreedCapability!!,
SpreedFeatures.RECORDING_V1
) &&
othersInCall &&
elapsedSeconds.toInt() >= CALL_TIME_ONE_HOUR
) {
@ -1468,7 +1472,7 @@ class CallActivity : CallBaseActivity() {
private fun fetchSignalingSettings() {
Log.d(TAG, "fetchSignalingSettings")
val apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, intArrayOf(ApiUtils.APIv3, 2, 1))
val apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, intArrayOf(ApiUtils.API_V3, 2, 1))
ncApi!!.getSignalingSettings(credentials, ApiUtils.getUrlForSignalingSettings(apiVersion, baseUrl))
.subscribeOn(Schedulers.io())
.retry(API_RETRIES)
@ -1531,7 +1535,7 @@ class CallActivity : CallBaseActivity() {
private fun addIceServers(signalingSettingsOverall: SignalingSettingsOverall, apiVersion: Int) {
if (signalingSettingsOverall.ocs!!.settings!!.stunServers != null) {
val stunServers = signalingSettingsOverall.ocs!!.settings!!.stunServers
if (apiVersion == ApiUtils.APIv3) {
if (apiVersion == ApiUtils.API_V3) {
for ((_, urls) in stunServers!!) {
if (urls != null) {
for (url in urls) {
@ -1564,7 +1568,7 @@ class CallActivity : CallBaseActivity() {
}
private fun checkCapabilities() {
ncApi!!.getCapabilities(credentials, ApiUtils.getUrlForCapabilities(baseUrl))
ncApi!!.getCapabilities(credentials, ApiUtils.getUrlForCapabilities(baseUrl!!))
.retry(API_RETRIES)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@ -1600,7 +1604,7 @@ class CallActivity : CallBaseActivity() {
private fun joinRoomAndCall() {
callSession = ApplicationWideCurrentRoomHolder.getInstance().session
val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
Log.d(TAG, "joinRoomAndCall")
Log.d(TAG, " baseUrl= $baseUrl")
Log.d(TAG, " roomToken= $roomToken")
@ -1656,7 +1660,7 @@ class CallActivity : CallBaseActivity() {
fun getRoomAndContinue() {
val getRoomApiVersion = ApiUtils.getConversationApiVersion(
conversationUser,
intArrayOf(ApiUtils.APIv4, 1)
intArrayOf(ApiUtils.API_V4, 1)
)
ncApi!!.getRoom(credentials, ApiUtils.getUrlForRoom(getRoomApiVersion, baseUrl, roomToken))
.retry(API_RETRIES)
@ -1715,10 +1719,10 @@ class CallActivity : CallBaseActivity() {
callParticipantList = CallParticipantList(signalingMessageReceiver)
callParticipantList!!.addObserver(callParticipantListObserver)
val apiVersion = ApiUtils.getCallApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
val apiVersion = ApiUtils.getCallApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
ncApi!!.joinCall(
credentials,
ApiUtils.getUrlForCall(apiVersion, baseUrl, roomToken),
ApiUtils.getUrlForCall(apiVersion, baseUrl, roomToken!!),
inCallFlag,
isCallWithoutNotification,
recordingConsentGiven
@ -1756,7 +1760,10 @@ class CallActivity : CallBaseActivity() {
}
private fun startCallTimeCounter(callStartTime: Long?) {
if (callStartTime != null && hasSpreedFeatureCapability(conversationUser, "recording-v1")) {
if (callStartTime != null && hasSpreedFeatureCapability(
conversationUser!!.capabilities!!.spreedCapability!!, SpreedFeatures.RECORDING_V1
)
) {
binding!!.callDuration.visibility = View.VISIBLE
val currentTimeInSec = System.currentTimeMillis() / SECOND_IN_MILLIES
elapsedSeconds = currentTimeInSec - callStartTime
@ -1793,7 +1800,7 @@ class CallActivity : CallBaseActivity() {
}
private fun pullSignalingMessages() {
val signalingApiVersion = ApiUtils.getSignalingApiVersion(conversationUser, intArrayOf(ApiUtils.APIv3, 2, 1))
val signalingApiVersion = ApiUtils.getSignalingApiVersion(conversationUser, intArrayOf(ApiUtils.API_V3, 2, 1))
val delayOnError = AtomicInteger(0)
ncApi!!.pullSignalingMessages(
@ -1801,7 +1808,7 @@ class CallActivity : CallBaseActivity() {
ApiUtils.getUrlForSignaling(
signalingApiVersion,
baseUrl,
roomToken
roomToken!!
)
)
.subscribeOn(Schedulers.io())
@ -2031,12 +2038,12 @@ class CallActivity : CallBaseActivity() {
private fun hangupNetworkCalls(shutDownView: Boolean) {
Log.d(TAG, "hangupNetworkCalls. shutDownView=$shutDownView")
val apiVersion = ApiUtils.getCallApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
val apiVersion = ApiUtils.getCallApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
if (callParticipantList != null) {
callParticipantList!!.removeObserver(callParticipantListObserver)
callParticipantList!!.destroy()
}
ncApi!!.leaveCall(credentials, ApiUtils.getUrlForCall(apiVersion, baseUrl, roomToken))
ncApi!!.leaveCall(credentials, ApiUtils.getUrlForCall(apiVersion, baseUrl, roomToken!!))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<GenericOverall> {
@ -2919,10 +2926,10 @@ class CallActivity : CallBaseActivity() {
val strings: MutableList<String> = ArrayList()
val stringToSend = stringBuilder.toString()
strings.add(stringToSend)
val apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, intArrayOf(ApiUtils.APIv3, 2, 1))
val apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, intArrayOf(ApiUtils.API_V3, 2, 1))
ncApi!!.sendSignalingMessages(
credentials,
ApiUtils.getUrlForSignaling(apiVersion, baseUrl, roomToken),
ApiUtils.getUrlForSignaling(apiVersion, baseUrl, roomToken!!),
strings.toString()
)
.retry(API_RETRIES)
@ -3099,12 +3106,14 @@ class CallActivity : CallBaseActivity() {
val isAllowedToStartOrStopRecording: Boolean
get() = (
isCallRecordingAvailable(conversationUser!!) &&
isCallRecordingAvailable(conversationUser!!.capabilities!!.spreedCapability!!) &&
isModerator
)
val isAllowedToRaiseHand: Boolean
get() = hasSpreedFeatureCapability(conversationUser, "raise-hand") ||
isBreakoutRoom
get() = hasSpreedFeatureCapability(
conversationUser.capabilities!!.spreedCapability!!,
SpreedFeatures.RAISE_HAND
) || isBreakoutRoom
private inner class SelfVideoTouchListener : OnTouchListener {
@SuppressLint("ClickableViewAccessibility")

View file

@ -181,7 +181,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
val user = userId.substringBeforeLast("@")
val baseUrl = userId.substringAfterLast("@")
if (userManager.currentUser.blockingGet()?.baseUrl?.endsWith(baseUrl) == true) {
if (userManager.currentUser.blockingGet()?.baseUrl!!.endsWith(baseUrl) == true) {
startConversation(user)
} else {
Snackbar.make(
@ -200,11 +200,11 @@ class MainActivity : BaseActivity(), ActionBarProvider {
val currentUser = userManager.currentUser.blockingGet()
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, 1))
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.API_V4, 1))
val credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token)
val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
apiVersion,
currentUser?.baseUrl,
currentUser?.baseUrl!!,
roomType,
null,
userId,

View file

@ -50,9 +50,10 @@ import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType
import com.nextcloud.talk.ui.StatusDrawable
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.ConversationUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability
import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFilterable
@ -312,7 +313,7 @@ class ConversationItem(
if (model.type === ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble)
} else if (model.unreadMention) {
if (hasSpreedFeatureCapability(user, "direct-mention-flag")) {
if (hasSpreedFeatureCapability(user.capabilities?.spreedCapability!!, SpreedFeatures.DIRECT_MENTION_FLAG)) {
if (model.unreadMentionDirect!!) {
viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble)
} else {

View file

@ -77,12 +77,12 @@ class CallStartedViewHolder(incomingView: View, payload: Any) :
val user = userManager.currentUser.blockingGet()
val url: String = if (message.actorType == "guests" || message.actorType == "guest") {
ApiUtils.getUrlForGuestAvatar(
user!!.baseUrl,
user!!.baseUrl!!,
message.actorDisplayName,
true
)
} else {
ApiUtils.getUrlForAvatar(user!!.baseUrl, message.actorDisplayName, false)
ApiUtils.getUrlForAvatar(user!!.baseUrl!!, message.actorDisplayName, false)
}
val imageRequest: ImageRequest = ImageRequest.Builder(context)

View file

@ -188,7 +188,7 @@ class IncomingLinkPreviewMessageViewHolder(incomingView: View, payload: Any) :
binding.messageQuote.quotedMessageImage.load(it) {
addHeader(
"Authorization",
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
)
}
} ?: run {

View file

@ -172,7 +172,7 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) :
binding.messageQuote.quotedMessageImage.load(it) {
addHeader(
"Authorization",
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
)
}
} ?: run {

View file

@ -195,7 +195,7 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) :
binding.messageQuote.quotedMessageImage.load(it) {
addHeader(
"Authorization",
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
)
}
} ?: run {

View file

@ -197,7 +197,7 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
binding.messageQuote.quotedMessageImage.load(it) {
addHeader(
"Authorization",
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
)
}
} ?: run {

View file

@ -301,7 +301,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
binding.messageQuote.quotedMessageImage.load(it) {
addHeader(
"Authorization",
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
)
}
} ?: run {

View file

@ -45,8 +45,8 @@ class LinkPreview {
binding.referenceThumbImage.setImageDrawable(null)
if (!message.extractedUrlToPreview.isNullOrEmpty()) {
val credentials: String = ApiUtils.getCredentials(message.activeUser?.username, message.activeUser?.token)
val openGraphLink = ApiUtils.getUrlForOpenGraph(message.activeUser?.baseUrl)
val credentials: String = ApiUtils.getCredentials(message.activeUser?.username, message.activeUser?.token)!!
val openGraphLink = ApiUtils.getUrlForOpenGraph(message.activeUser?.baseUrl!!)
ncApi.getOpenGraph(
credentials,
openGraphLink,

View file

@ -161,7 +161,7 @@ class OutcomingLinkPreviewMessageViewHolder(outcomingView: View, payload: Any) :
binding.messageQuote.quotedMessageImage.load(it) {
addHeader(
"Authorization",
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
)
}
} ?: run {

View file

@ -213,7 +213,7 @@ class OutcomingLocationMessageViewHolder(incomingView: View) :
binding.messageQuote.quotedMessageImage.load(it) {
addHeader(
"Authorization",
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
)
}
} ?: run {

View file

@ -175,7 +175,7 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) :
binding.messageQuote.quotedMessageImage.load(it) {
addHeader(
"Authorization",
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
)
}
} ?: run {

View file

@ -170,7 +170,7 @@ class OutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewH
binding.messageQuote.quotedMessageImage.load(it) {
addHeader(
"Authorization",
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
)
}
} ?: run {

View file

@ -285,7 +285,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
binding.messageQuote.quotedMessageImage.load(it) {
addHeader(
"Authorization",
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
)
}
} ?: run {

View file

@ -24,6 +24,7 @@
package com.nextcloud.talk.api;
import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall;
import com.nextcloud.talk.models.json.capabilities.RoomCapabilitiesOverall;
import com.nextcloud.talk.models.json.chat.ChatOverall;
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage;
import com.nextcloud.talk.models.json.chat.ChatShareOverall;
@ -367,6 +368,10 @@ public interface NcApi {
@GET
Observable<CapabilitiesOverall> getCapabilities(@Url String url);
@GET
Observable<RoomCapabilitiesOverall> getRoomCapabilities(@Header("Authorization") String authorization,
@Url String url);
/*
QueryMap items are as follows:
- "lookIntoFuture": int (0 or 1),

View file

@ -50,6 +50,7 @@ import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.ConversationUtils
import com.nextcloud.talk.utils.NotificationUtils
import com.nextcloud.talk.utils.ParticipantPermissions
@ -57,7 +58,7 @@ import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CALL_VOICE_ONLY
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability
import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
import io.reactivex.disposables.Disposable
import okhttp3.Cache
import java.io.IOException
@ -148,10 +149,10 @@ class CallNotificationActivity : CallBaseActivity() {
private fun initObservers() {
val apiVersion = ApiUtils.getConversationApiVersion(
userBeingCalled,
userBeingCalled!!,
intArrayOf(
ApiUtils.APIv4,
ApiUtils.APIv3,
ApiUtils.API_V4,
ApiUtils.API_V3,
1
)
)
@ -186,10 +187,10 @@ class CallNotificationActivity : CallBaseActivity() {
showAnswerControls()
if (apiVersion >= ApiUtils.APIv3) {
if (apiVersion >= ApiUtils.API_V3) {
val hasCallFlags = hasSpreedFeatureCapability(
userBeingCalled,
"conversation-call-flags"
userBeingCalled?.capabilities?.spreedCapability!!,
SpreedFeatures.CONVERSATION_CALL_FLAGS
)
if (hasCallFlags) {
if (isInCallWithVideo(currentConversation!!.callFlag)) {
@ -243,7 +244,7 @@ class CallNotificationActivity : CallBaseActivity() {
originalBundle!!.putString(KEY_CONVERSATION_NAME, currentConversation!!.displayName)
val participantPermission = ParticipantPermissions(
userBeingCalled!!,
userBeingCalled!!.capabilities!!.spreedCapability!!,
currentConversation!!
)
originalBundle!!.putBoolean(

View file

@ -167,6 +167,7 @@ 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.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.chat.ChatOverall
import com.nextcloud.talk.models.json.chat.ReadStatus
@ -192,6 +193,7 @@ import com.nextcloud.talk.ui.recyclerview.MessageSwipeActions
import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.AudioUtils
import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.ContactUtils
import com.nextcloud.talk.utils.ConversationUtils
import com.nextcloud.talk.utils.DateConstants
@ -218,7 +220,7 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_START_CALL_AFTER_ROOM_SWITCH
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SWITCH_TO_ROOM
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.CapabilitiesUtil
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
import com.nextcloud.talk.utils.rx.DisposableSet
@ -301,6 +303,8 @@ class ChatActivity :
var sessionIdAfterRoomJoined: String? = null
lateinit var roomToken: String
var conversationUser: User? = null
lateinit var spreedCapabilities: SpreedCapability
var chatApiVersion: Int = 1
private var roomPassword: String = ""
var credentials: String? = null
var currentConversation: ConversationModel? = null
@ -351,6 +355,7 @@ class ChatActivity :
RELEASED,
ERROR
}
private val editableBehaviorSubject = BehaviorSubject.createDefault(false)
private val editedTextBehaviorSubject = BehaviorSubject.createDefault("")
@ -541,14 +546,6 @@ class ChatActivity :
}
this.lifecycle.addObserver(AudioUtils)
this.lifecycle.addObserver(ChatViewModel.LifeCycleObserver)
chatViewModel.refreshChatParams(
setupFieldsForPullChatMessages(
false,
0,
false
)
)
}
override fun onStop() {
@ -587,6 +584,30 @@ class ChatActivity :
is ChatViewModel.GetRoomSuccessState -> {
currentConversation = state.conversationModel
logConversationInfos("GetRoomSuccessState")
chatViewModel.getCapabilities(conversationUser!!, roomToken, currentConversation!!)
}
is ChatViewModel.GetRoomErrorState -> {
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
}
else -> {}
}
}
chatViewModel.getCapabilitiesViewState.observe(this) { state ->
when (state) {
is ChatViewModel.GetCapabilitiesSuccessState -> {
spreedCapabilities = state.spreedCapabilities
chatApiVersion = ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(1))
initMessageInputView()
if (conversationUser?.userId != "?" &&
CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MENTION_FLAG)
) {
binding.chatToolbar.setOnClickListener { v -> showConversationInfoScreen() }
}
if (adapter == null) {
initAdapter()
@ -597,7 +618,7 @@ class ChatActivity :
loadAvatarForStatusBar()
setActionBarTitle()
participantPermissions = ParticipantPermissions(conversationUser!!, currentConversation!!)
participantPermissions = ParticipantPermissions(spreedCapabilities, currentConversation!!)
setupSwipeToReply()
setupMentionAutocomplete()
@ -626,9 +647,17 @@ class ChatActivity :
},
delayForRecursiveCall
)
chatViewModel.refreshChatParams(
setupFieldsForPullChatMessages(
false,
0,
false
)
)
}
is ChatViewModel.GetRoomErrorState -> {
is ChatViewModel.GetCapabilitiesErrorState -> {
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
}
@ -716,6 +745,7 @@ class ChatActivity :
}
binding.messagesListView.smoothScrollToPosition(0)
}
is ChatViewModel.SendChatMessageErrorState -> {
if (state.e is HttpException) {
val code = state.e.code()
@ -730,6 +760,7 @@ class ChatActivity :
}
}
}
else -> {}
}
}
@ -753,9 +784,11 @@ class ChatActivity :
)
)
}
is ChatViewModel.DeleteChatMessageErrorState -> {
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
}
else -> {}
}
}
@ -774,25 +807,23 @@ class ChatActivity :
startActivity(chatIntent)
}
}
is ChatViewModel.CreateRoomErrorState -> {
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
}
else -> {}
}
}
var apiVersion = 1
// FIXME this is a best guess, guests would need to get the capabilities themselves
if (conversationUser != null) {
apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1))
}
chatViewModel.getFieldMapForChat.observe(this) { _ ->
chatViewModel.getFieldMapForChat.observe(this) { fieldMap ->
if (fieldMap.isNotEmpty()) {
chatViewModel.pullChatMessages(
credentials!!,
ApiUtils.getUrlForChat(apiVersion, conversationUser?.baseUrl, roomToken)
ApiUtils.getUrlForChat(chatApiVersion, conversationUser?.baseUrl, roomToken)
)
}
}
chatViewModel.pullChatMessageViewState.observe(this) { state ->
when (state) {
@ -865,6 +896,7 @@ class ChatActivity :
)
)
}
HTTP_CODE_NOT_MODIFIED -> {
processHeaderChatLastGiven(state.response, state.lookIntoFuture)
chatViewModel.refreshChatParams(
@ -875,6 +907,7 @@ class ChatActivity :
)
)
}
HTTP_CODE_PRECONDITION_FAILED -> {
processHeaderChatLastGiven(state.response, state.lookIntoFuture)
chatViewModel.refreshChatParams(
@ -885,6 +918,7 @@ class ChatActivity :
)
)
}
else -> {}
}
@ -898,12 +932,15 @@ class ChatActivity :
collapseSystemMessages()
}
}
is ChatViewModel.PullChatMessageCompleteState -> {
Log.d(TAG, "PullChatMessageCompleted")
}
is ChatViewModel.PullChatMessageErrorState -> {
Log.d(TAG, "PullChatMessageError")
}
else -> {}
}
}
@ -916,6 +953,7 @@ class ChatActivity :
state.reactionDeletedModel.emoji
)
}
else -> {}
}
}
@ -928,6 +966,7 @@ class ChatActivity :
state.reactionAddedModel.emoji
)
}
else -> {}
}
}
@ -943,6 +982,7 @@ class ChatActivity :
Snackbar.LENGTH_LONG
).show()
}
HTTP_FORBIDDEN -> {
Snackbar.make(
binding.root,
@ -950,6 +990,7 @@ class ChatActivity :
Snackbar.LENGTH_LONG
).show()
}
HTTP_NOT_FOUND -> {
Snackbar.make(
binding.root,
@ -960,9 +1001,11 @@ class ChatActivity :
}
clearEditUI()
}
is ChatViewModel.EditMessageErrorState -> {
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
}
else -> {}
}
}
@ -980,12 +1023,6 @@ class ChatActivity :
webSocketInstance?.getSignalingMessageReceiver()?.addListener(localParticipantMessageListener)
webSocketInstance?.getSignalingMessageReceiver()?.addListener(conversationMessageListener)
if (conversationUser?.userId != "?" &&
CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "mention-flag")
) {
binding.chatToolbar.setOnClickListener { v -> showConversationInfoScreen() }
}
initSmileyKeyboardToggler()
themeMessageInputView()
@ -1053,7 +1090,6 @@ class ChatActivity :
}
})
initMessageInputView()
loadAvatarForStatusBar()
setActionBarTitle()
viewThemeUtils.material.colorToolbarOverflowIcon(binding.chatToolbar)
@ -1061,7 +1097,7 @@ class ChatActivity :
private fun initMessageInputView() {
val filters = arrayOfNulls<InputFilter>(1)
val lengthFilter = CapabilitiesUtilNew.getMessageMaxLength(conversationUser)
val lengthFilter = CapabilitiesUtil.getMessageMaxLength(spreedCapabilities)
binding.editView.editMessageView.visibility = View.GONE
@ -1160,7 +1196,7 @@ class ChatActivity :
clearEditUI()
}
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "silent-send")) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.SILENT_SEND)) {
binding.messageInputView.button?.setOnLongClickListener {
showSendButtonMenu()
true
@ -1175,14 +1211,14 @@ class ChatActivity :
var apiVersion = 1
// FIXME Fix API checking with guests?
if (conversationUser != null) {
apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1))
apiVersion = ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(1))
}
chatViewModel.editChatMessage(
credentials!!,
ApiUtils.getUrlForChatMessage(
apiVersion,
conversationUser?.baseUrl,
conversationUser?.baseUrl!!,
roomToken,
message.id
),
@ -2016,7 +2052,7 @@ class ChatActivity :
private fun isTypingStatusEnabled(): Boolean {
return webSocketInstance != null &&
!CapabilitiesUtilNew.isTypingStatusPrivate(conversationUser!!)
!CapabilitiesUtil.isTypingStatusPrivate(conversationUser!!)
}
private fun setupSwipeToReply() {
@ -2048,7 +2084,7 @@ class ChatActivity :
if (isOneToOneConversation()) {
var url = ApiUtils.getUrlForAvatar(
conversationUser!!.baseUrl,
conversationUser!!.baseUrl!!,
currentConversation!!.name,
true
)
@ -2097,7 +2133,7 @@ class ChatActivity :
}
val credentials = ApiUtils.getCredentials(conversationUser!!.username, conversationUser!!.token)
if (credentials != null) {
context.imageLoader.enqueue(
ImageRequest.Builder(context)
.data(url)
@ -2109,6 +2145,7 @@ class ChatActivity :
.diskCachePolicy(CachePolicy.DISABLED)
.build()
)
}
} else {
binding.chatToolbar.findViewById<FrameLayout>(R.id.chat_toolbar_avatar_container).visibility = View.GONE
}
@ -2463,7 +2500,10 @@ class ChatActivity :
val baseUrl = message.activeUser!!.baseUrl
val userId = message.activeUser!!.userId
val attachmentFolder = CapabilitiesUtilNew.getAttachmentFolder(message.activeUser!!)
val attachmentFolder = CapabilitiesUtil.getAttachmentFolder(
message.activeUser!!.capabilities!!
.spreedCapability!!
)
val fileName = message.selectedIndividualHashMap!!["name"]
var size = message.selectedIndividualHashMap!!["size"]
if (size == null) {
@ -2801,16 +2841,16 @@ class ChatActivity :
private fun shouldShowLobby(): Boolean {
if (currentConversation != null) {
return CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "webinary-lobby") &&
return CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.WEBINARY_LOBBY) &&
currentConversation?.lobbyState == LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
!ConversationUtils.canModerate(currentConversation!!, conversationUser!!) &&
!ConversationUtils.canModerate(currentConversation!!, spreedCapabilities) &&
!participantPermissions.canIgnoreLobby()
}
return false
}
private fun disableCallButtons() {
if (CapabilitiesUtilNew.isAbleToCall(conversationUser)) {
if (CapabilitiesUtil.isAbleToCall(spreedCapabilities)) {
if (conversationVoiceCallMenuItem != null && conversationVideoMenuItem != null) {
conversationVoiceCallMenuItem?.icon?.alpha = SEMI_TRANSPARENT_INT
conversationVideoMenuItem?.icon?.alpha = SEMI_TRANSPARENT_INT
@ -2823,7 +2863,7 @@ class ChatActivity :
}
private fun enableCallButtons() {
if (CapabilitiesUtilNew.isAbleToCall(conversationUser)) {
if (CapabilitiesUtil.isAbleToCall(spreedCapabilities)) {
if (conversationVoiceCallMenuItem != null && conversationVideoMenuItem != null) {
conversationVoiceCallMenuItem?.icon?.alpha = FULLY_OPAQUE_INT
conversationVideoMenuItem?.icon?.alpha = FULLY_OPAQUE_INT
@ -2843,7 +2883,7 @@ class ChatActivity :
private fun checkLobbyState() {
if (currentConversation != null &&
ConversationUtils.isLobbyViewApplicable(currentConversation!!, conversationUser!!)
ConversationUtils.isLobbyViewApplicable(currentConversation!!, spreedCapabilities)
) {
if (shouldShowLobby()) {
binding.lobby.lobbyView.visibility = View.VISIBLE
@ -3252,6 +3292,7 @@ class ChatActivity :
val intent = Intent(this, LocationPickerActivity::class.java)
intent.putExtra(KEY_ROOM_TOKEN, roomToken)
intent.putExtra(BundleKeys.KEY_CHAT_API_VERSION, chatApiVersion)
startActivity(intent)
}
@ -3270,7 +3311,7 @@ class ChatActivity :
val elevation = MENTION_AUTO_COMPLETE_ELEVATION
resources?.let {
val backgroundDrawable = ColorDrawable(it.getColor(R.color.bg_default, null))
val presenter = MentionAutocompletePresenter(this, roomToken)
val presenter = MentionAutocompletePresenter(this, roomToken, chatApiVersion)
val callback = MentionAutocompleteCallback(
this,
conversationUser!!,
@ -3430,12 +3471,6 @@ class ChatActivity :
if (!validSessionId()) {
Log.d(TAG, "sessionID was not valid -> joinRoom")
var apiVersion = 1
// FIXME Fix API checking with guests?
if (conversationUser != null) {
apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
}
val startNanoTime = System.nanoTime()
Log.d(TAG, "joinRoomWithPassword - joinRoom - calling: $startNanoTime")
@ -3458,7 +3493,7 @@ class ChatActivity :
var apiVersion = 1
// FIXME Fix API checking with guests?
if (conversationUser != null) {
apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
apiVersion = ApiUtils.getConversationApiVersion(conversationUser!!, intArrayOf(ApiUtils.API_V4, 1))
}
val startNanoTime = System.nanoTime()
@ -3467,7 +3502,7 @@ class ChatActivity :
credentials!!,
ApiUtils.getUrlForParticipantsActive(
apiVersion,
conversationUser?.baseUrl,
conversationUser?.baseUrl!!,
roomToken
),
funToCallWhenLeaveSuccessful
@ -3513,11 +3548,9 @@ class ChatActivity :
private fun sendMessage(message: CharSequence, replyTo: Int?, sendWithoutNotification: Boolean) {
if (conversationUser != null) {
val apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1))
chatViewModel.sendChatMessage(
credentials!!,
ApiUtils.getUrlForChat(apiVersion, conversationUser!!.baseUrl, roomToken),
ApiUtils.getUrlForChat(chatApiVersion, conversationUser!!.baseUrl!!, roomToken),
message,
conversationUser!!.displayName ?: "",
replyTo ?: 0,
@ -3637,7 +3670,7 @@ class ChatActivity :
}
}
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "message-expiration")) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MESSAGE_EXPIRATION)) {
deleteExpiredMessages()
}
}
@ -3871,23 +3904,38 @@ class ChatActivity :
if (conversationUser?.userId == "?") {
menu.removeItem(R.id.conversation_info)
} else {
conversationInfoMenuItem = menu.findItem(R.id.conversation_info)
loadAvatarForStatusBar()
setActionBarTitle()
}
return true
}
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "rich-object-list-media")) {
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
super.onPrepareOptionsMenu(menu)
if (this::spreedCapabilities.isInitialized) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.READ_ONLY_ROOMS)) {
checkShowCallButtons()
}
val searchItem = menu.findItem(R.id.conversation_search)
searchItem.isVisible = CapabilitiesUtil.isUnifiedSearchAvailable(spreedCapabilities)
if (CapabilitiesUtil.hasSpreedFeatureCapability(
spreedCapabilities,
SpreedFeatures.RICH_OBJECT_LIST_MEDIA
)
) {
conversationSharedItemsItem = menu.findItem(R.id.shared_items)
} else {
menu.removeItem(R.id.shared_items)
}
loadAvatarForStatusBar()
setActionBarTitle()
}
if (CapabilitiesUtilNew.isAbleToCall(conversationUser)) {
if (CapabilitiesUtil.isAbleToCall(spreedCapabilities)) {
conversationVoiceCallMenuItem = menu.findItem(R.id.conversation_voice_call)
conversationVideoMenuItem = menu.findItem(R.id.conversation_video_call)
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "silent-call")) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.SILENT_CALL)) {
Handler().post {
findViewById<View?>(R.id.conversation_voice_call)?.setOnLongClickListener {
showCallButtonMenu(true)
@ -3906,18 +3954,8 @@ class ChatActivity :
menu.removeItem(R.id.conversation_video_call)
menu.removeItem(R.id.conversation_voice_call)
}
return true
}
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
super.onPrepareOptionsMenu(menu)
conversationUser?.let {
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(it, "read-only-rooms")) {
checkShowCallButtons()
}
val searchItem = menu.findItem(R.id.conversation_search)
searchItem.isVisible = CapabilitiesUtilNew.isUnifiedSearchAvailable(it)
}
return true
}
@ -4054,7 +4092,7 @@ class ChatActivity :
private fun startACall(isVoiceOnlyCall: Boolean, callWithoutNotification: Boolean) {
currentConversation?.let {
if (conversationUser != null) {
val pp = ParticipantPermissions(conversationUser!!, it)
val pp = ParticipantPermissions(spreedCapabilities, it)
if (!pp.canStartCall() && currentConversation?.hasCall == false) {
Snackbar.make(binding.root, R.string.startCallForbidden, Snackbar.LENGTH_LONG).show()
} else {
@ -4074,7 +4112,7 @@ class ChatActivity :
bundle.putString(KEY_ROOM_TOKEN, roomToken)
bundle.putString(KEY_ROOM_ID, roomId)
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.putInt(KEY_RECORDING_STATE, it.callRecording)
bundle.putBoolean(KEY_IS_MODERATOR, ConversationUtils.isParticipantOwnerOrModerator(it))
@ -4147,7 +4185,8 @@ class ChatActivity :
conversationUser,
currentConversation,
isShowMessageDeletionButton(message),
participantPermissions.hasChatPermission()
participantPermissions.hasChatPermission(),
spreedCapabilities
).show()
}
}
@ -4156,7 +4195,7 @@ class ChatActivity :
return ChatMessage.MessageType.SYSTEM_MESSAGE == message.getCalculateMessageType()
}
fun deleteMessage(message: IMessage?) {
fun deleteMessage(message: IMessage) {
if (!participantPermissions.hasChatPermission()) {
Log.w(
TAG,
@ -4168,28 +4207,28 @@ class ChatActivity :
var apiVersion = 1
// FIXME Fix API checking with guests?
if (conversationUser != null) {
apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1))
apiVersion = ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(1))
}
chatViewModel.deleteChatMessages(
credentials!!,
ApiUtils.getUrlForChatMessage(
apiVersion,
conversationUser?.baseUrl,
conversationUser?.baseUrl!!,
roomToken,
message?.id
message.id!!
),
message?.id!!
message.id!!
)
}
}
fun replyPrivately(message: IMessage?) {
val apiVersion =
ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
ApiUtils.getConversationApiVersion(conversationUser!!, intArrayOf(ApiUtils.API_V4, 1))
val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
apiVersion,
conversationUser?.baseUrl,
conversationUser?.baseUrl!!,
"1",
null,
message?.user?.id?.substring(INVITE_LENGTH),
@ -4215,10 +4254,14 @@ class ChatActivity :
fun remindMeLater(message: ChatMessage?) {
Log.d(TAG, "remindMeLater called")
val chatApiVersion = ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(ApiUtils.API_V1, 1))
val newFragment: DialogFragment = DateTimePickerFragment.newInstance(
roomToken,
message!!.id,
chatViewModel
chatViewModel,
chatApiVersion
)
newFragment.show(supportFragmentManager, DateTimePickerFragment.TAG)
}
@ -4229,8 +4272,8 @@ class ChatActivity :
chatViewModel.setChatReadMarker(
credentials!!,
ApiUtils.getUrlForChatReadMarker(
ApiUtils.getChatApiVersion(conversationUser, intArrayOf(ApiUtils.APIv1)),
conversationUser?.baseUrl,
ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(ApiUtils.API_V1)),
conversationUser?.baseUrl!!,
roomToken
),
chatMessage.previousMessageId
@ -4312,10 +4355,10 @@ class ChatActivity :
}
fun shareToNotes(message: ChatMessage, roomToken: String) {
val apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1))
val apiVersion = ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(1))
val type = message.getCalculateMessageType()
var shareUri: Uri? = null
var data: HashMap<String?, String?>?
val data: HashMap<String?, String?>?
var metaData: String = ""
var objectId: String = ""
if (message.hasFileAttachment()) {
@ -4335,9 +4378,9 @@ class ChatActivity :
} else if (message.hasGeoLocation()) {
data = message.messageParameters?.get("object")
objectId = data?.get("id")!!
val name = data.get("name")!!
val lat = data.get("latitude")!!
val lon = data.get("longitude")!!
val name = data["name"]!!
val lat = data["latitude"]!!
val lon = data["longitude"]!!
metaData =
"{\"type\":\"geo-location\",\"id\":\"geo:$lat,$lon\",\"latitude\":\"$lat\"," +
"\"longitude\":\"$lon\",\"name\":\"$name\"}"
@ -4348,6 +4391,7 @@ class ChatActivity :
uploadFile(shareUri.toString(), true, token = roomToken)
Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show()
}
ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE -> {
val caption = if (message.message != "{file}") message.message else ""
if (null != shareUri) {
@ -4364,25 +4408,28 @@ class ChatActivity :
}
}
}
ChatMessage.MessageType.SINGLE_NC_GEOLOCATION_MESSAGE -> {
chatViewModel.shareLocationToNotes(
credentials!!,
ApiUtils.getUrlToSendLocation(apiVersion, conversationUser!!.baseUrl, roomToken),
ApiUtils.getUrlToSendLocation(apiVersion, conversationUser!!.baseUrl!!, roomToken),
"geo-location",
objectId,
metaData
)
Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show()
}
ChatMessage.MessageType.REGULAR_TEXT_MESSAGE -> {
chatViewModel.shareToNotes(
credentials!!,
ApiUtils.getUrlForChat(apiVersion, conversationUser!!.baseUrl, roomToken),
ApiUtils.getUrlForChat(apiVersion, conversationUser!!.baseUrl!!, roomToken),
message.message!!,
conversationUser!!.displayName!!
)
Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show()
}
else -> {}
}
}
@ -4456,7 +4503,10 @@ class ChatActivity :
}
private fun showMicrophoneButton(show: Boolean) {
if (show && CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "voice-message-sharing")) {
if (show && CapabilitiesUtil.hasSpreedFeatureCapability(
spreedCapabilities, SpreedFeatures.VOICE_MESSAGE_SHARING
)
) {
Log.d(TAG, "Microphone shown")
binding.messageInputView.messageSendButton.visibility = View.GONE
binding.messageInputView.recordAudioButton.visibility = View.VISIBLE
@ -4542,7 +4592,7 @@ class ChatActivity :
!isUserAllowedByPrivileges -> false
message.systemMessageType != ChatMessage.SystemMessageType.DUMMY -> false
message.isDeleted -> false
!CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "delete-messages") -> false
!CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.DELETE_MESSAGES) -> false
!participantPermissions.hasChatPermission() -> false
else -> true
}
@ -4554,7 +4604,7 @@ class ChatActivity :
val isUserAllowedByPrivileges = if (message.actorId == conversationUser!!.userId) {
true
} else {
ConversationUtils.canModerate(currentConversation!!, conversationUser!!)
ConversationUtils.canModerate(currentConversation!!, spreedCapabilities)
}
return isUserAllowedByPrivileges
}
@ -4628,12 +4678,12 @@ class ChatActivity :
var apiVersion = 1
// FIXME Fix API checking with guests?
if (conversationUser != null) {
apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
apiVersion = ApiUtils.getConversationApiVersion(conversationUser!!, intArrayOf(ApiUtils.API_V4, 1))
}
val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
apiVersion,
conversationUser?.baseUrl,
conversationUser?.baseUrl!!,
"1",
null,
userMentionClickEvent.userId,

View file

@ -22,6 +22,7 @@ package com.nextcloud.talk.chat.data
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.conversations.RoomsOverall
@ -33,10 +34,17 @@ import retrofit2.Response
@Suppress("LongParameterList", "TooManyFunctions")
interface ChatRepository {
fun getRoom(user: User, roomToken: String): Observable<ConversationModel>
fun getCapabilities(user: User, roomToken: String): Observable<SpreedCapability>
fun joinRoom(user: User, roomToken: String, roomPassword: String): Observable<ConversationModel>
fun setReminder(user: User, roomToken: String, messageId: String, timeStamp: Int): Observable<Reminder>
fun getReminder(user: User, roomToken: String, messageId: String): Observable<Reminder>
fun deleteReminder(user: User, roomToken: String, messageId: String): Observable<GenericOverall>
fun setReminder(
user: User,
roomToken: String,
messageId: String,
timeStamp: Int,
chatApiVersion: Int
): Observable<Reminder>
fun getReminder(user: User, roomToken: String, messageId: String, apiVersion: Int): Observable<Reminder>
fun deleteReminder(user: User, roomToken: String, messageId: String, apiVersion: Int): Observable<GenericOverall>
fun shareToNotes(
credentials: String,
url: String,

View file

@ -24,6 +24,7 @@ import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.chat.data.ChatRepository
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.conversations.RoomsOverall
@ -35,55 +36,78 @@ import retrofit2.Response
class NetworkChatRepositoryImpl(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))
val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))
return ncApi.getRoom(
credentials,
ApiUtils.getUrlForRoom(apiVersion, user.baseUrl, roomToken)
ApiUtils.getUrlForRoom(apiVersion, user.baseUrl!!, roomToken)
).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
}
override fun getCapabilities(user: User, roomToken: String): Observable<SpreedCapability> {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))
return ncApi.getRoomCapabilities(
credentials,
ApiUtils.getUrlForRoomCapabilities(apiVersion, user.baseUrl!!, roomToken)
).map { 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))
val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, 1))
return ncApi.joinRoom(
credentials,
ApiUtils.getUrlForParticipantsActive(apiVersion, user.baseUrl, roomToken),
ApiUtils.getUrlForParticipantsActive(apiVersion, user.baseUrl!!, roomToken),
roomPassword
).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
}
override fun setReminder(user: User, roomToken: String, messageId: String, timeStamp: Int): Observable<Reminder> {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)
val apiVersion = ApiUtils.getChatApiVersion(user, intArrayOf(ApiUtils.APIv1, 1))
override fun setReminder(
user: User,
roomToken: String,
messageId: String,
timeStamp: Int,
chatApiVersion: Int
): Observable<Reminder> {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
return ncApi.setReminder(
credentials,
ApiUtils.getUrlForReminder(user, roomToken, messageId, apiVersion),
ApiUtils.getUrlForReminder(user, roomToken, messageId, chatApiVersion),
timeStamp
).map {
it.ocs!!.data
}
}
override fun getReminder(user: User, roomToken: String, messageId: String): Observable<Reminder> {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)
val apiVersion = ApiUtils.getChatApiVersion(user, intArrayOf(ApiUtils.APIv1, 1))
override fun getReminder(
user: User,
roomToken: String,
messageId: String,
chatApiVersion: Int
): Observable<Reminder> {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
return ncApi.getReminder(
credentials,
ApiUtils.getUrlForReminder(user, roomToken, messageId, apiVersion)
ApiUtils.getUrlForReminder(user, roomToken, messageId, chatApiVersion)
).map {
it.ocs!!.data
}
}
override fun deleteReminder(user: User, roomToken: String, messageId: String): Observable<GenericOverall> {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)
val apiVersion = ApiUtils.getChatApiVersion(user, intArrayOf(ApiUtils.APIv1, 1))
override fun deleteReminder(
user: User,
roomToken: String,
messageId: String,
chatApiVersion: Int
): Observable<GenericOverall> {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
return ncApi.deleteReminder(
credentials,
ApiUtils.getUrlForReminder(user, roomToken, messageId, apiVersion)
ApiUtils.getUrlForReminder(user, roomToken, messageId, chatApiVersion)
).map {
it
}

View file

@ -31,6 +31,7 @@ import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.domain.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
import com.nextcloud.talk.models.json.conversations.RoomOverall
@ -105,6 +106,14 @@ class ChatViewModel @Inject constructor(
val getRoomViewState: LiveData<ViewState>
get() = _getRoomViewState
object GetCapabilitiesStartState : ViewState
object GetCapabilitiesErrorState : ViewState
open class GetCapabilitiesSuccessState(val spreedCapabilities: SpreedCapability) : ViewState
private val _getCapabilitiesViewState: MutableLiveData<ViewState> = MutableLiveData(GetCapabilitiesStartState)
val getCapabilitiesViewState: LiveData<ViewState>
get() = _getCapabilitiesViewState
object JoinRoomStartState : ViewState
object JoinRoomErrorState : ViewState
open class JoinRoomSuccessState(val conversationModel: ConversationModel) : ViewState
@ -184,6 +193,36 @@ class ChatViewModel @Inject constructor(
?.subscribe(GetRoomObserver())
}
fun getCapabilities(user: User, token: String, conversationModel: ConversationModel) {
_getCapabilitiesViewState.value = GetCapabilitiesStartState
if (conversationModel.remoteServer.isNullOrEmpty()) {
_getCapabilitiesViewState.value = GetCapabilitiesSuccessState(user.capabilities!!.spreedCapability!!)
} else {
chatRepository.getCapabilities(user, token)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer<SpreedCapability> {
override fun onSubscribe(d: Disposable) {
LifeCycleObserver.disposableSet.add(d)
}
override fun onNext(spreedCapabilities: SpreedCapability) {
_getCapabilitiesViewState.value = GetCapabilitiesSuccessState(spreedCapabilities)
}
override fun onError(e: Throwable) {
Log.e(TAG, "Error when fetching spreed capabilities", e)
_getCapabilitiesViewState.value = GetCapabilitiesErrorState
}
override fun onComplete() {
// unused atm
}
})
}
}
fun joinRoom(user: User, token: String, roomPassword: String) {
_joinRoomViewState.value = JoinRoomStartState
chatRepository.joinRoom(user, token, roomPassword)
@ -193,6 +232,43 @@ class ChatViewModel @Inject constructor(
?.subscribe(JoinRoomObserver())
}
fun setReminder(user: User, roomToken: String, messageId: String, timestamp: Int, chatApiVersion: Int) {
chatRepository.setReminder(user, roomToken, messageId, timestamp, chatApiVersion)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(SetReminderObserver())
}
fun getReminder(user: User, roomToken: String, messageId: String, chatApiVersion: Int) {
chatRepository.getReminder(user, roomToken, messageId, chatApiVersion)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(GetReminderObserver())
}
fun deleteReminder(user: User, roomToken: String, messageId: String, chatApiVersion: Int) {
chatRepository.deleteReminder(user, roomToken, messageId, chatApiVersion)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer<GenericOverall> {
override fun onSubscribe(d: Disposable) {
LifeCycleObserver.disposableSet.add(d)
}
override fun onNext(genericOverall: GenericOverall) {
_getReminderExistState.value = GetReminderStartState
}
override fun onError(e: Throwable) {
Log.d(TAG, "Error when deleting reminder", e)
}
override fun onComplete() {
// unused atm
}
})
}
fun leaveRoom(credentials: String, url: String, funToCallWhenLeaveSuccessful: (() -> Unit)?) {
val startNanoTime = System.nanoTime()
chatRepository.leaveRoom(credentials, url)
@ -357,43 +433,6 @@ class ChatViewModel @Inject constructor(
})
}
fun setReminder(user: User, roomToken: String, messageId: String, timestamp: Int) {
chatRepository.setReminder(user, roomToken, messageId, timestamp)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(SetReminderObserver())
}
fun getReminder(user: User, roomToken: String, messageId: String) {
chatRepository.getReminder(user, roomToken, messageId)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(GetReminderObserver())
}
fun deleteReminder(user: User, roomToken: String, messageId: String) {
chatRepository.deleteReminder(user, roomToken, messageId)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer<GenericOverall> {
override fun onSubscribe(d: Disposable) {
LifeCycleObserver.disposableSet.add(d)
}
override fun onNext(genericOverall: GenericOverall) {
_getReminderExistState.value = GetReminderStartState
}
override fun onError(e: Throwable) {
Log.d(TAG, "Error when deleting reminder $e")
}
override fun onComplete() {
// unused atm
}
})
}
fun shareToNotes(credentials: String, url: String, message: String, displayName: String) {
chatRepository.shareToNotes(credentials, url, message, displayName)
.subscribeOn(Schedulers.io())
@ -522,7 +561,7 @@ class ChatViewModel @Inject constructor(
inner class GetRoomObserver : Observer<ConversationModel> {
override fun onSubscribe(d: Disposable) {
LifeCycleObserver.disposableSet.add(d)
// unused atm
}
override fun onNext(conversationModel: ConversationModel) {

View file

@ -65,7 +65,7 @@ class ReadFolderListingOperation(okHttpClient: OkHttpClient, currentUser: User,
ApiUtils.getCredentials(
currentUser.username,
currentUser.token
),
)!!,
"Authorization"
)
)

View file

@ -68,9 +68,10 @@ import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.openconversations.ListOpenConversationsActivity
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.UserIdUtils.getIdForUser
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.CapabilitiesUtil
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
@ -318,10 +319,10 @@ class ContactsActivity :
}
private fun createRoom(roomType: String, sourceType: String?, userId: String) {
val apiVersion: Int = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, 1))
val apiVersion: Int = ApiUtils.getConversationApiVersion(currentUser!!, intArrayOf(ApiUtils.API_V4, 1))
val retrofitBucket: RetrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
apiVersion,
currentUser!!.baseUrl,
currentUser!!.baseUrl!!,
roomType,
sourceType,
userId,
@ -438,7 +439,7 @@ class ContactsActivity :
userHeaderItems = HashMap()
val query = adapter!!.getFilter(String::class.java)
val retrofitBucket: RetrofitBucket =
ApiUtils.getRetrofitBucketForContactsSearchFor14(currentUser!!.baseUrl, query)
ApiUtils.getRetrofitBucketForContactsSearchFor14(currentUser!!.baseUrl!!, query)
val modifiedQueryMap: HashMap<String, Any?> = HashMap(retrofitBucket.queryMap)
modifiedQueryMap["limit"] = CONTACTS_BATCH_SIZE
if (isAddingParticipantsView) {
@ -450,13 +451,21 @@ class ContactsActivity :
if (!isAddingParticipantsView) {
// groups
shareTypesList.add("1")
} else if (CapabilitiesUtilNew.hasSpreedFeatureCapability(currentUser, "invite-groups-and-mails")) {
} else if (CapabilitiesUtil.hasSpreedFeatureCapability(
currentUser?.capabilities?.spreedCapability!!,
SpreedFeatures.INVITE_GROUPS_AND_MAILS
)
) {
// groups
shareTypesList.add("1")
// emails
shareTypesList.add("4")
}
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(currentUser, "circles-support")) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(
currentUser?.capabilities?.spreedCapability!!,
SpreedFeatures.CIRCLES_SUPPORT
)
) {
// circles
shareTypesList.add("7")
}
@ -745,8 +754,12 @@ class ContactsActivity :
private fun updateSelection(contactItem: ContactItem) {
contactItem.model.selected = !contactItem.model.selected
updateSelectionLists(contactItem.model)
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(currentUser, "last-room-activity") &&
!CapabilitiesUtilNew.hasSpreedFeatureCapability(currentUser, "invite-groups-and-mails") &&
if (CapabilitiesUtil.hasSpreedFeatureCapability(
currentUser?.capabilities?.spreedCapability!!, SpreedFeatures.LAST_ROOM_ACTIVITY
) &&
!CapabilitiesUtil.hasSpreedFeatureCapability(
currentUser?.capabilities?.spreedCapability!!, SpreedFeatures.INVITE_GROUPS_AND_MAILS
) &&
isValidGroupSelection(contactItem, contactItem.model, adapter)
) {
val currentItems: List<ContactItem> = adapter?.currentItems as List<ContactItem>
@ -771,10 +784,10 @@ class ContactsActivity :
if ("groups" == contactItem.model.source) {
roomType = "2"
}
val apiVersion: Int = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, 1))
val apiVersion: Int = ApiUtils.getConversationApiVersion(currentUser!!, intArrayOf(ApiUtils.API_V4, 1))
val retrofitBucket: RetrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
apiVersion,
currentUser!!.baseUrl,
currentUser!!.baseUrl!!,
roomType,
null,
contactItem.model.calculatedActorId,

View file

@ -36,16 +36,16 @@ class ConversationRepositoryImpl(private val ncApi: NcApi, currentUserProvider:
ConversationRepository {
val currentUser: User = currentUserProvider.currentUser.blockingGet()
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
override fun renameConversation(roomToken: String, roomNameNew: String): Observable<GenericOverall> {
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv1))
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
return ncApi.renameRoom(
credentials,
ApiUtils.getUrlForRoom(
apiVersion,
currentUser.baseUrl,
currentUser.baseUrl!!,
roomToken
),
roomNameNew
@ -59,12 +59,12 @@ class ConversationRepositoryImpl(private val ncApi: NcApi, currentUserProvider:
roomName: String,
conversationType: Conversation.ConversationType?
): Observable<RoomOverall> {
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv1))
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
val retrofitBucket: RetrofitBucket = if (conversationType == Conversation.ConversationType.ROOM_PUBLIC_CALL) {
ApiUtils.getRetrofitBucketForCreateRoom(
apiVersion,
currentUser.baseUrl,
currentUser.baseUrl!!,
ROOM_TYPE_PUBLIC,
null,
null,
@ -73,7 +73,7 @@ class ConversationRepositoryImpl(private val ncApi: NcApi, currentUserProvider:
} else {
ApiUtils.getRetrofitBucketForCreateRoom(
apiVersion,
currentUser.baseUrl,
currentUser.baseUrl!!,
ROOM_TYPE_GROUP,
null,
null,

View file

@ -40,6 +40,7 @@ import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModelProvider
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
@ -61,6 +62,7 @@ import com.nextcloud.talk.bottomsheet.items.BasicListItemWithImage
import com.nextcloud.talk.bottomsheet.items.listItemsWithImage
import com.nextcloud.talk.contacts.ContactsActivity
import com.nextcloud.talk.conversationinfoedit.ConversationInfoEditActivity
import com.nextcloud.talk.conversationinfo.viewmodel.ConversationInfoViewModel
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ActivityConversationInfoBinding
import com.nextcloud.talk.events.EventStatus
@ -71,9 +73,11 @@ import com.nextcloud.talk.extensions.loadUserAvatar
import com.nextcloud.talk.jobs.DeleteConversationWorker
import com.nextcloud.talk.jobs.LeaveConversationWorker
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.converters.EnumNotificationLevelConverter
import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.domain.LobbyState
import com.nextcloud.talk.models.domain.NotificationLevel
import com.nextcloud.talk.models.domain.converters.DomainEnumNotificationLevelConverter
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.models.json.participants.Participant.ActorType.CIRCLES
@ -83,11 +87,12 @@ import com.nextcloud.talk.models.json.participants.ParticipantsOverall
import com.nextcloud.talk.repositories.conversations.ConversationsRepository
import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.ConversationUtils
import com.nextcloud.talk.utils.DateConstants
import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.CapabilitiesUtil
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.nextcloud.talk.utils.preferences.preferencestorage.DatabaseStorageModule
import eu.davidea.flexibleadapter.FlexibleAdapter
@ -109,6 +114,9 @@ class ConversationInfoActivity :
FlexibleAdapter.OnItemClickListener {
private lateinit var binding: ActivityConversationInfoBinding
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
@Inject
lateinit var ncApi: NcApi
@ -121,6 +129,10 @@ class ConversationInfoActivity :
@Inject
lateinit var dateUtils: DateUtils
lateinit var viewModel: ConversationInfoViewModel
private lateinit var spreedCapabilities: SpreedCapability
private lateinit var conversationToken: String
private lateinit var conversationUser: User
private var hasAvatarSpacing: Boolean = false
@ -129,7 +141,9 @@ class ConversationInfoActivity :
private var participantsDisposable: Disposable? = null
private var databaseStorageModule: DatabaseStorageModule? = null
private var conversation: Conversation? = null
// private var conversation: Conversation? = null
private var conversation: ConversationModel? = null
private var adapter: FlexibleAdapter<ParticipantItem>? = null
private var userItems: MutableList<ParticipantItem> = ArrayList()
@ -157,11 +171,26 @@ class ConversationInfoActivity :
setContentView(binding.root)
setupSystemColors()
viewModel =
ViewModelProvider(this, viewModelFactory)[ConversationInfoViewModel::class.java]
conversationUser = currentUserProvider.currentUser.blockingGet()
conversationToken = intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)!!
hasAvatarSpacing = intent.getBooleanExtra(BundleKeys.KEY_ROOM_ONE_TO_ONE, false)
credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)
credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)!!
initObservers()
}
override fun onStart() {
super.onStart()
this.lifecycle.addObserver(ConversationInfoViewModel.LifeCycleObserver)
}
override fun onStop() {
super.onStop()
this.lifecycle.removeObserver(ConversationInfoViewModel.LifeCycleObserver)
}
override fun onResume() {
@ -176,13 +205,7 @@ class ConversationInfoActivity :
binding.clearConversationHistory.setOnClickListener { showClearHistoryDialog() }
binding.addParticipantsAction.setOnClickListener { addParticipants() }
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "rich-object-list-media")) {
binding.sharedItemsButton.setOnClickListener { showSharedItems() }
} else {
binding.sharedItems.visibility = GONE
}
fetchRoomInfo()
viewModel.getRoom(conversationUser, conversationToken)
themeTextViews()
themeSwitchPreferences()
@ -192,6 +215,35 @@ class ConversationInfoActivity :
binding.progressBar.let { viewThemeUtils.platform.colorCircularProgressBar(it, ColorRole.PRIMARY) }
}
private fun initObservers() {
viewModel.viewState.observe(this) { state ->
when (state) {
is ConversationInfoViewModel.GetRoomSuccessState -> {
conversation = state.conversationModel
viewModel.getCapabilities(conversationUser, conversationToken, conversation!!)
}
is ConversationInfoViewModel.GetRoomErrorState -> {
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
}
else -> {}
}
}
viewModel.getCapabilitiesViewState.observe(this) { state ->
when (state) {
is ConversationInfoViewModel.GetCapabilitiesSuccessState -> {
spreedCapabilities = state.spreedCapabilities
handleConversation()
}
else -> {}
}
}
}
private fun setupActionBar() {
setSupportActionBar(binding.conversationInfoToolbar)
binding.conversationInfoToolbar.setNavigationOnClickListener {
@ -217,7 +269,7 @@ class ConversationInfoActivity :
fun showOptionsMenu() {
if (::optionsMenu.isInitialized) {
optionsMenu.clear()
if (CapabilitiesUtilNew.isConversationAvatarEndpointAvailable(conversationUser)) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.AVATAR)) {
menuInflater.inflate(R.menu.menu_conversation_info, optionsMenu)
}
}
@ -273,19 +325,22 @@ class ConversationInfoActivity :
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.putExtra(BundleKeys.KEY_CONVERSATION_NAME, conversation?.displayName)
intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
intent.putExtra(SharedItemsActivity.KEY_USER_IS_OWNER_OR_MODERATOR, conversation?.isParticipantOwnerOrModerator)
intent.putExtra(
SharedItemsActivity.KEY_USER_IS_OWNER_OR_MODERATOR,
ConversationUtils.isParticipantOwnerOrModerator(conversation!!)
)
startActivity(intent)
}
private fun setupWebinaryView() {
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "webinary-lobby") &&
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.WEBINARY_LOBBY) &&
webinaryRoomType(conversation!!) &&
conversation!!.canModerate(conversationUser)
ConversationUtils.canModerate(conversation!!, spreedCapabilities)
) {
binding.webinarInfoView.webinarSettings.visibility = VISIBLE
val isLobbyOpenToModeratorsOnly =
conversation!!.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY
conversation!!.lobbyState == LobbyState.LOBBY_STATE_MODERATORS_ONLY
binding.webinarInfoView.lobbySwitch.isChecked = isLobbyOpenToModeratorsOnly
reconfigureLobbyTimerView()
@ -320,9 +375,9 @@ class ConversationInfoActivity :
}
}
private fun webinaryRoomType(conversation: Conversation): Boolean {
return conversation.type == Conversation.ConversationType.ROOM_GROUP_CALL ||
conversation.type == Conversation.ConversationType.ROOM_PUBLIC_CALL
private fun webinaryRoomType(conversation: ConversationModel): Boolean {
return conversation.type == ConversationType.ROOM_GROUP_CALL ||
conversation.type == ConversationType.ROOM_PUBLIC_CALL
}
private fun reconfigureLobbyTimerView(dateTime: Calendar? = null) {
@ -337,9 +392,9 @@ class ConversationInfoActivity :
}
conversation!!.lobbyState = if (isChecked) {
Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY
LobbyState.LOBBY_STATE_MODERATORS_ONLY
} else {
Conversation.LobbyState.LOBBY_STATE_ALL_PARTICIPANTS
LobbyState.LOBBY_STATE_ALL_PARTICIPANTS
}
if (
@ -370,11 +425,11 @@ class ConversationInfoActivity :
0
}
val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
ncApi.setLobbyForConversation(
ApiUtils.getCredentials(conversationUser.username, conversationUser.token),
ApiUtils.getUrlForRoomWebinaryLobby(apiVersion, conversationUser.baseUrl, conversation!!.token),
ApiUtils.getUrlForRoomWebinaryLobby(apiVersion, conversationUser.baseUrl!!, conversation!!.token),
state,
conversation!!.lobbyTimer
)
@ -487,7 +542,7 @@ class ConversationInfoActivity :
private fun getListOfParticipants() {
// FIXME Fix API checking with guests?
val apiVersion: Int = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
val apiVersion: Int = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
val fieldMap = HashMap<String, Boolean>()
fieldMap["includeStatus"] = true
@ -496,7 +551,7 @@ class ConversationInfoActivity :
credentials,
ApiUtils.getUrlForParticipants(
apiVersion,
conversationUser.baseUrl,
conversationUser.baseUrl!!,
conversationToken
),
fieldMap
@ -586,11 +641,11 @@ class ConversationInfoActivity :
}
private fun clearHistory() {
val apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1))
val apiVersion = ApiUtils.getChatApiVersion(spreedCapabilities, intArrayOf(1))
ncApi.clearChatHistory(
credentials,
ApiUtils.getUrlForChat(apiVersion, conversationUser.baseUrl, conversationToken)
ApiUtils.getUrlForChat(apiVersion, conversationUser.baseUrl!!, conversationToken)
)
?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
@ -631,30 +686,21 @@ class ConversationInfoActivity :
}
}
private fun fetchRoomInfo() {
val apiVersion: Int
// FIXME Fix API checking with guests?
apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
@Suppress("LongMethod")
private fun handleConversation() {
val conversationCopy = conversation!!
ncApi.getRoom(credentials, ApiUtils.getUrlForRoom(apiVersion, conversationUser.baseUrl, conversationToken))
?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer<RoomOverall> {
override fun onSubscribe(d: Disposable) {
roomDisposable = d
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.RICH_OBJECT_LIST_MEDIA)) {
binding.sharedItemsButton.setOnClickListener { showSharedItems() }
} else {
binding.sharedItems.visibility = GONE
}
@Suppress("Detekt.TooGenericExceptionCaught")
override fun onNext(roomOverall: RoomOverall) {
conversation = roomOverall.ocs!!.data
val conversationCopy = conversation
if (conversationCopy!!.canModerate(conversationUser)) {
if (ConversationUtils.canModerate(conversationCopy, spreedCapabilities)) {
binding.addParticipantsAction.visibility = VISIBLE
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(
conversationUser,
"clear-history"
if (CapabilitiesUtil.hasSpreedFeatureCapability(
spreedCapabilities,
SpreedFeatures.CLEAR_HISTORY
)
) {
binding.clearConversationHistory.visibility = VISIBLE
@ -665,10 +711,7 @@ class ConversationInfoActivity :
} else {
binding.addParticipantsAction.visibility = GONE
if (ConversationUtils.isNoteToSelfConversation(
ConversationModel.mapToConversationModel(conversation!!)
)
) {
if (ConversationUtils.isNoteToSelfConversation(conversation)) {
binding.notificationSettingsView.notificationSettings.visibility = VISIBLE
} else {
binding.clearConversationHistory.visibility = GONE
@ -680,19 +723,19 @@ class ConversationInfoActivity :
setupWebinaryView()
if (!conversation!!.canLeave()) {
if (ConversationUtils.canLeave(conversation!!)) {
binding.leaveConversationAction.visibility = GONE
} else {
binding.leaveConversationAction.visibility = VISIBLE
}
if (!conversation!!.canDelete(conversationUser)) {
if (ConversationUtils.canDelete(conversation!!, spreedCapabilities)) {
binding.deleteConversationAction.visibility = GONE
} else {
binding.deleteConversationAction.visibility = VISIBLE
}
if (Conversation.ConversationType.ROOM_SYSTEM == conversation!!.type) {
if (ConversationType.ROOM_SYSTEM == conversation!!.type) {
binding.notificationSettingsView.callNotificationsSwitch.visibility = GONE
}
@ -726,13 +769,11 @@ class ConversationInfoActivity :
this@ConversationInfoActivity,
it,
conversation!!,
spreedCapabilities,
conversationUser
).setupGuestAccess()
}
if (ConversationUtils.isNoteToSelfConversation(
ConversationModel.mapToConversationModel(conversation!!)
)
) {
if (ConversationUtils.isNoteToSelfConversation(conversation!!)) {
binding.notificationSettingsView.notificationSettings.visibility = GONE
} else {
binding.notificationSettingsView.notificationSettings.visibility = VISIBLE
@ -740,16 +781,6 @@ class ConversationInfoActivity :
}
}
override fun onError(e: Throwable) {
Log.e(TAG, "failed to fetch room info", e)
}
override fun onComplete() {
roomDisposable!!.dispose()
}
})
}
private fun initRecordingConsentOption() {
fun hide() {
binding.recordingConsentView.recordingConsentSettingsCategory.visibility = GONE
@ -781,13 +812,13 @@ class ConversationInfoActivity :
}
}
if (conversation!!.isParticipantOwnerOrModerator &&
!ConversationUtils.isNoteToSelfConversation(ConversationModel.mapToConversationModel(conversation!!))
if (ConversationUtils.isParticipantOwnerOrModerator(conversation!!) &&
!ConversationUtils.isNoteToSelfConversation(conversation!!)
) {
when (CapabilitiesUtilNew.getRecordingConsentType(conversationUser)) {
CapabilitiesUtilNew.RECORDING_CONSENT_NOT_REQUIRED -> hide()
CapabilitiesUtilNew.RECORDING_CONSENT_REQUIRED -> showAlwaysRequiredInfo()
CapabilitiesUtilNew.RECORDING_CONSENT_DEPEND_ON_CONVERSATION -> showSwitch()
when (CapabilitiesUtil.getRecordingConsentType(spreedCapabilities)) {
CapabilitiesUtil.RECORDING_CONSENT_NOT_REQUIRED -> hide()
CapabilitiesUtil.RECORDING_CONSENT_REQUIRED -> showAlwaysRequiredInfo()
CapabilitiesUtil.RECORDING_CONSENT_DEPEND_ON_CONVERSATION -> showSwitch()
}
} else {
hide()
@ -801,11 +832,11 @@ class ConversationInfoActivity :
RECORDING_CONSENT_NOT_REQUIRED_FOR_CONVERSATION
}
val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
ncApi.setRecordingConsent(
ApiUtils.getCredentials(conversationUser.username, conversationUser.token),
ApiUtils.getUrlForRecordingConsent(apiVersion, conversationUser.baseUrl, conversation!!.token),
ApiUtils.getUrlForRecordingConsent(apiVersion, conversationUser.baseUrl!!, conversation!!.token),
state
)
?.subscribeOn(Schedulers.io())
@ -831,8 +862,8 @@ class ConversationInfoActivity :
}
private fun initExpiringMessageOption() {
if (conversation!!.isParticipantOwnerOrModerator &&
CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "message-expiration")
if (ConversationUtils.isParticipantOwnerOrModerator(conversation!!) &&
CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MESSAGE_EXPIRATION)
) {
databaseStorageModule?.setMessageExpiration(conversation!!.messageExpiration)
val value = databaseStorageModule!!.getString("conversation_settings_dropdown", "")
@ -855,13 +886,16 @@ class ConversationInfoActivity :
private fun adjustNotificationLevelUI() {
if (conversation != null) {
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "notification-levels")) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.NOTIFICATION_LEVELS)) {
binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.isEnabled = true
binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.alpha = 1.0f
if (conversation!!.notificationLevel != Conversation.NotificationLevel.DEFAULT) {
if (conversation!!.notificationLevel != NotificationLevel.DEFAULT) {
val stringValue: String =
when (EnumNotificationLevelConverter().convertToInt(conversation!!.notificationLevel)) {
when (
DomainEnumNotificationLevelConverter()
.convertToInt(conversation!!.notificationLevel!!)
) {
NOTIFICATION_LEVEL_ALWAYS -> resources.getString(R.string.nc_notify_me_always)
NOTIFICATION_LEVEL_MENTION -> resources.getString(R.string.nc_notify_me_mention)
NOTIFICATION_LEVEL_NEVER -> resources.getString(R.string.nc_notify_me_never)
@ -885,9 +919,9 @@ class ConversationInfoActivity :
}
}
private fun setProperNotificationValue(conversation: Conversation?) {
if (conversation!!.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "mention-flag")) {
private fun setProperNotificationValue(conversation: ConversationModel?) {
if (conversation!!.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MENTION_FLAG)) {
binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.setText(
resources.getString(R.string.nc_notify_me_always)
)
@ -905,7 +939,7 @@ class ConversationInfoActivity :
private fun loadConversationAvatar() {
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,
@ -916,7 +950,7 @@ class ConversationInfoActivity :
}
}
Conversation.ConversationType.ROOM_GROUP_CALL, Conversation.ConversationType.ROOM_PUBLIC_CALL -> {
ConversationType.ROOM_GROUP_CALL, ConversationType.ROOM_PUBLIC_CALL -> {
binding.avatarImage.loadConversationAvatar(
conversationUser,
conversation!!,
@ -925,15 +959,12 @@ class ConversationInfoActivity :
)
}
Conversation.ConversationType.ROOM_SYSTEM -> {
ConversationType.ROOM_SYSTEM -> {
binding.avatarImage.loadSystemAvatar()
}
Conversation.ConversationType.DUMMY -> {
if (ConversationUtils.isNoteToSelfConversation(
ConversationModel.mapToConversationModel(conversation!!)
)
) {
ConversationType.DUMMY -> {
if (ConversationUtils.isNoteToSelfConversation(conversation!!)) {
binding.avatarImage.loadNoteToSelfAvatar()
}
}
@ -971,7 +1002,7 @@ class ConversationInfoActivity :
credentials,
ApiUtils.getUrlForRoomModerators(
apiVersion,
conversationUser.baseUrl,
conversationUser.baseUrl!!,
conversation!!.token
),
participant.attendeeId
@ -986,7 +1017,7 @@ class ConversationInfoActivity :
credentials,
ApiUtils.getUrlForRoomModerators(
apiVersion,
conversationUser.baseUrl,
conversationUser.baseUrl!!,
conversation!!.token
),
participant.attendeeId
@ -1022,7 +1053,7 @@ class ConversationInfoActivity :
credentials,
ApiUtils.getUrlForRoomModerators(
apiVersion,
conversationUser.baseUrl,
conversationUser.baseUrl!!,
conversation!!.token
),
participant.userId
@ -1035,7 +1066,7 @@ class ConversationInfoActivity :
credentials,
ApiUtils.getUrlForRoomModerators(
apiVersion,
conversationUser.baseUrl,
conversationUser.baseUrl!!,
conversation!!.token
),
participant.userId
@ -1047,12 +1078,12 @@ class ConversationInfoActivity :
}
private fun removeAttendeeFromConversation(apiVersion: Int, participant: Participant) {
if (apiVersion >= ApiUtils.APIv4) {
if (apiVersion >= ApiUtils.API_V4) {
ncApi.removeAttendeeFromConversation(
credentials,
ApiUtils.getUrlForAttendees(
apiVersion,
conversationUser.baseUrl,
conversationUser.baseUrl!!,
conversation!!.token
),
participant.attendeeId
@ -1084,7 +1115,7 @@ class ConversationInfoActivity :
ncApi.removeParticipantFromConversation(
credentials,
ApiUtils.getUrlForRemovingParticipantFromConversation(
conversationUser.baseUrl,
conversationUser.baseUrl!!,
conversation!!.token,
true
),
@ -1114,7 +1145,7 @@ class ConversationInfoActivity :
ncApi.removeParticipantFromConversation(
credentials,
ApiUtils.getUrlForRemovingParticipantFromConversation(
conversationUser.baseUrl,
conversationUser.baseUrl!!,
conversation!!.token,
false
),
@ -1146,14 +1177,14 @@ class ConversationInfoActivity :
@SuppressLint("CheckResult")
override fun onItemClick(view: View?, position: Int): Boolean {
if (!conversation!!.canModerate(conversationUser)) {
if (ConversationUtils.canModerate(conversation!!, spreedCapabilities)) {
return true
}
val userItem = adapter?.getItem(position) as ParticipantItem
val participant = userItem.model
val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1))
if (participant.calculatedActorType == USERS && participant.calculatedActorId == conversationUser.userId) {
if (participant.attendeePin?.isNotEmpty() == true) {
@ -1277,7 +1308,7 @@ class ConversationInfoActivity :
// Pin, nothing to do
} else if (actionToTrigger == 1) {
// Promote/demote
if (apiVersion >= ApiUtils.APIv4) {
if (apiVersion >= ApiUtils.API_V4) {
toggleModeratorStatus(apiVersion, participant)
} else {
toggleModeratorStatusLegacy(apiVersion, participant)

View file

@ -11,8 +11,11 @@ import com.nextcloud.talk.R
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ActivityConversationInfoBinding
import com.nextcloud.talk.databinding.DialogPasswordBinding
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.repositories.conversations.ConversationsRepository
import com.nextcloud.talk.utils.ConversationUtils
import com.nextcloud.talk.utils.Mimetype
import com.nextcloud.talk.utils.ShareUtils
import io.reactivex.Observer
@ -23,7 +26,8 @@ import io.reactivex.schedulers.Schedulers
class GuestAccessHelper(
private val activity: ConversationInfoActivity,
private val binding: ActivityConversationInfoBinding,
private val conversation: Conversation,
private val conversation: ConversationModel,
private val spreedCapabilities: SpreedCapability,
private val conversationUser: User
) {
@ -32,13 +36,13 @@ class GuestAccessHelper(
private val context = activity.context
fun setupGuestAccess() {
if (conversation.canModerate(conversationUser)) {
if (ConversationUtils.canModerate(conversation, spreedCapabilities)) {
binding.guestAccessView.guestAccessSettings.visibility = View.VISIBLE
} else {
binding.guestAccessView.guestAccessSettings.visibility = View.GONE
}
if (conversation.type == Conversation.ConversationType.ROOM_PUBLIC_CALL) {
if (conversation.type == ConversationType.ROOM_PUBLIC_CALL) {
binding.guestAccessView.allowGuestsSwitch.isChecked = true
showAllOptions()
if (conversation.hasPassword) {

View file

@ -0,0 +1,142 @@
/*
* 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.conversationinfo.viewmodel
import android.util.Log
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
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 com.nextcloud.talk.models.json.capabilities.SpreedCapability
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 ConversationInfoViewModel @Inject constructor(
private val chatRepository: ChatRepository
) : ViewModel() {
object LifeCycleObserver : DefaultLifecycleObserver {
enum class LifeCycleFlag {
PAUSED,
RESUMED
}
lateinit var currentLifeCycleFlag: LifeCycleFlag
public val disposableSet = mutableSetOf<Disposable>()
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
currentLifeCycleFlag = LifeCycleFlag.RESUMED
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
currentLifeCycleFlag = LifeCycleFlag.PAUSED
disposableSet.forEach { disposable -> disposable.dispose() }
disposableSet.clear()
}
}
sealed interface ViewState
object GetRoomStartState : ViewState
object GetRoomErrorState : ViewState
open class GetRoomSuccessState(val conversationModel: ConversationModel) : ViewState
private val _viewState: MutableLiveData<ViewState> = MutableLiveData(GetRoomStartState)
val viewState: LiveData<ViewState>
get() = _viewState
object GetCapabilitiesStartState : ViewState
object GetCapabilitiesErrorState : ViewState
open class GetCapabilitiesSuccessState(val spreedCapabilities: SpreedCapability) : ViewState
private val _getCapabilitiesViewState: MutableLiveData<ViewState> = MutableLiveData(GetCapabilitiesStartState)
val getCapabilitiesViewState: LiveData<ViewState>
get() = _getCapabilitiesViewState
fun getRoom(user: User, token: String) {
_viewState.value = GetRoomStartState
chatRepository.getRoom(user, token)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(GetRoomObserver())
}
fun getCapabilities(user: User, token: String, conversationModel: ConversationModel) {
_getCapabilitiesViewState.value = GetCapabilitiesStartState
if (conversationModel.remoteServer.isNullOrEmpty()) {
_getCapabilitiesViewState.value = GetCapabilitiesSuccessState(user.capabilities!!.spreedCapability!!)
} else {
chatRepository.getCapabilities(user, token)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer<SpreedCapability> {
override fun onSubscribe(d: Disposable) {
LifeCycleObserver.disposableSet.add(d)
}
override fun onNext(spreedCapabilities: SpreedCapability) {
_getCapabilitiesViewState.value = GetCapabilitiesSuccessState(spreedCapabilities)
}
override fun onError(e: Throwable) {
Log.e(TAG, "Error when fetching spreed capabilities", e)
_getCapabilitiesViewState.value = GetCapabilitiesErrorState
}
override fun onComplete() {
// unused atm
}
})
}
}
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
}
}
companion object {
private val TAG = ConversationInfoViewModel::class.simpleName
}
}

View file

@ -49,11 +49,12 @@ import com.nextcloud.talk.extensions.loadSystemAvatar
import com.nextcloud.talk.extensions.loadUserAvatar
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.CapabilitiesUtil
import com.nextcloud.talk.utils.PickImage
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
@ -87,6 +88,8 @@ class ConversationInfoEditActivity :
private lateinit var pickImage: PickImage
private lateinit var spreedCapabilities: SpreedCapability
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
@ -110,7 +113,7 @@ class ConversationInfoEditActivity :
viewThemeUtils.material.colorTextInputLayout(binding.conversationNameInputLayout)
viewThemeUtils.material.colorTextInputLayout(binding.conversationDescriptionInputLayout)
credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)
credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)!!
pickImage = PickImage(this, conversationUser)
@ -127,13 +130,15 @@ class ConversationInfoEditActivity :
is ConversationInfoEditViewModel.GetRoomSuccessState -> {
conversation = state.conversationModel
spreedCapabilities = conversationUser.capabilities!!.spreedCapability!!
binding.conversationName.setText(conversation!!.displayName)
if (conversation!!.description != null && conversation!!.description!!.isNotEmpty()) {
binding.conversationDescription.setText(conversation!!.description)
}
if (!CapabilitiesUtilNew.isConversationDescriptionEndpointAvailable(conversationUser)) {
if (!CapabilitiesUtil.isConversationDescriptionEndpointAvailable(spreedCapabilities)) {
binding.conversationDescription.isEnabled = false
}
@ -221,13 +226,13 @@ class ConversationInfoEditActivity :
private fun saveConversationNameAndDescription() {
val apiVersion =
ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv1))
ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
ncApi.renameRoom(
credentials,
ApiUtils.getUrlForRoom(
apiVersion,
conversationUser.baseUrl,
conversationUser.baseUrl!!,
conversation!!.token
),
binding.conversationName.text.toString()
@ -241,7 +246,7 @@ class ConversationInfoEditActivity :
}
override fun onNext(genericOverall: GenericOverall) {
if (CapabilitiesUtilNew.isConversationDescriptionEndpointAvailable(conversationUser)) {
if (CapabilitiesUtil.isConversationDescriptionEndpointAvailable(spreedCapabilities)) {
saveConversationDescription()
} else {
finish()
@ -265,13 +270,13 @@ class ConversationInfoEditActivity :
fun saveConversationDescription() {
val apiVersion =
ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv1))
ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
ncApi.setConversationDescription(
credentials,
ApiUtils.getUrlForConversationDescription(
apiVersion,
conversationUser.baseUrl,
conversationUser.baseUrl!!,
conversation!!.token
),
binding.conversationDescription.text.toString()

View file

@ -36,9 +36,9 @@ class ConversationInfoEditRepositoryImpl(private val ncApi: NcApi, currentUserPr
ConversationInfoEditRepository {
val currentUser: User = currentUserProvider.currentUser.blockingGet()
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv3, 1))
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))
override fun uploadConversationAvatar(user: User, file: File, roomToken: String): Observable<ConversationModel> {
val builder = MultipartBody.Builder()
@ -56,7 +56,7 @@ class ConversationInfoEditRepositoryImpl(private val ncApi: NcApi, currentUserPr
return ncApi.uploadConversationAvatar(
credentials,
ApiUtils.getUrlForConversationAvatar(1, user.baseUrl, roomToken),
ApiUtils.getUrlForConversationAvatar(1, user.baseUrl!!, roomToken),
filePart
).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
}
@ -64,7 +64,7 @@ class ConversationInfoEditRepositoryImpl(private val ncApi: NcApi, currentUserPr
override fun deleteConversationAvatar(user: User, roomToken: String): Observable<ConversationModel> {
return ncApi.deleteConversationAvatar(
credentials,
ApiUtils.getUrlForConversationAvatar(1, user.baseUrl, roomToken)
ApiUtils.getUrlForConversationAvatar(1, user.baseUrl!!, roomToken)
).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
}
}

View file

@ -115,6 +115,7 @@ import com.nextcloud.talk.ui.dialog.ConversationsListBottomDialog
import com.nextcloud.talk.ui.dialog.FilterConversationFragment
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.ClosedInterfaceImpl
import com.nextcloud.talk.utils.FileUtils
import com.nextcloud.talk.utils.Mimetype
@ -130,10 +131,10 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NEW_CONVERSATION
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SHARED_TEXT
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatureCapability
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isServerEOL
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isUnifiedSearchAvailable
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isUserStatusAvailable
import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
import com.nextcloud.talk.utils.CapabilitiesUtil.isServerEOL
import com.nextcloud.talk.utils.CapabilitiesUtil.isUnifiedSearchAvailable
import com.nextcloud.talk.utils.CapabilitiesUtil.isUserStatusAvailable
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
import com.nextcloud.talk.utils.power.PowerManagerUtils
import com.nextcloud.talk.utils.rx.SearchViewObservable.Companion.observeSearchView
@ -283,11 +284,11 @@ class ConversationsListActivity :
}
currentUser = userManager.currentUser.blockingGet()
if (currentUser != null) {
if (isServerEOL(currentUser!!.capabilities)) {
if (isServerEOL(currentUser!!.serverVersion!!.major)) {
showServerEOLDialog()
return
}
if (isUnifiedSearchAvailable(currentUser!!)) {
if (isUnifiedSearchAvailable(currentUser!!.capabilities!!.spreedCapability!!)) {
searchHelper = MessageSearchHelper(unifiedSearchRepository)
}
credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
@ -423,7 +424,7 @@ class ConversationsListActivity :
private fun loadUserAvatar(target: Target) {
if (currentUser != null) {
val url = ApiUtils.getUrlForAvatar(
currentUser!!.baseUrl,
currentUser!!.baseUrl!!,
currentUser!!.userId,
true
)
@ -433,7 +434,7 @@ class ConversationsListActivity :
context.imageLoader.enqueue(
ImageRequest.Builder(context)
.data(url)
.addHeader("Authorization", credentials)
.addHeader("Authorization", credentials!!)
.placeholder(R.drawable.ic_user)
.transformations(CircleCropTransformation())
.crossfade(true)
@ -698,7 +699,10 @@ class ConversationsListActivity :
isRefreshing = true
conversationItems = ArrayList()
conversationItemsWithHeader = ArrayList()
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv3, 1))
val apiVersion = ApiUtils.getConversationApiVersion(
currentUser!!,
intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1)
)
val startNanoTime = System.nanoTime()
Log.d(TAG, "fetchData - getRooms - calling: $startNanoTime")
roomsQueryDisposable = ncApi.getRooms(
@ -868,11 +872,15 @@ class ConversationsListActivity :
private fun fetchOpenConversations(apiVersion: Int) {
searchableConversationItems.clear()
searchableConversationItems.addAll(conversationItemsWithHeader)
if (hasSpreedFeatureCapability(currentUser, "listable-rooms")) {
if (hasSpreedFeatureCapability(
currentUser!!.capabilities!!.spreedCapability!!,
SpreedFeatures.LISTABLE_ROOMS
)
) {
val openConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
openConversationsQueryDisposable = ncApi.getOpenConversations(
credentials,
ApiUtils.getUrlForOpenConversations(apiVersion, currentUser!!.baseUrl)
ApiUtils.getUrlForOpenConversations(apiVersion, currentUser!!.baseUrl!!)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@ -1087,7 +1095,7 @@ class ConversationsListActivity :
clearMessageSearchResults()
adapter!!.setFilter(filter)
adapter!!.filterItems()
if (isUnifiedSearchAvailable(currentUser!!)) {
if (isUnifiedSearchAvailable(currentUser!!.capabilities!!.spreedCapability!!)) {
startMessageSearch(filter)
}
} else {
@ -1173,7 +1181,11 @@ class ConversationsListActivity :
private fun handleConversation(conversation: Conversation?) {
selectedConversation = conversation
if (selectedConversation != null) {
val hasChatPermission = ParticipantPermissions(currentUser!!, selectedConversation!!).hasChatPermission()
val hasChatPermission = ParticipantPermissions(
currentUser!!.capabilities!!.spreedCapability!!,
selectedConversation!!
)
.hasChatPermission()
if (showShareToScreen) {
if (hasChatPermission &&
!isReadOnlyConversation(selectedConversation!!) &&
@ -1197,7 +1209,10 @@ class ConversationsListActivity :
}
private fun shouldShowLobby(conversation: Conversation): Boolean {
val participantPermissions = ParticipantPermissions(currentUser!!, conversation)
val participantPermissions = ParticipantPermissions(
currentUser!!.capabilities?.spreedCapability!!,
conversation
)
return conversation.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
!conversation.canModerate(currentUser!!) &&
!participantPermissions.canIgnoreLobby()
@ -1511,7 +1526,7 @@ class ConversationsListActivity :
.setNegativeButton(R.string.nc_settings_reauthorize) { _, _ ->
val intent = Intent(context, WebViewLoginActivity::class.java)
val bundle = Bundle()
bundle.putString(BundleKeys.KEY_BASE_URL, currentUser!!.baseUrl)
bundle.putString(BundleKeys.KEY_BASE_URL, currentUser!!.baseUrl!!)
bundle.putBoolean(BundleKeys.KEY_REAUTHORIZE_ACCOUNT, true)
intent.putExtras(bundle)
startActivity(intent)

View file

@ -27,6 +27,7 @@ import com.nextcloud.talk.callnotification.viewmodel.CallNotificationViewModel
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
import com.nextcloud.talk.conversation.viewmodel.ConversationViewModel
import com.nextcloud.talk.conversation.viewmodel.RenameConversationViewModel
import com.nextcloud.talk.conversationinfo.viewmodel.ConversationInfoViewModel
import com.nextcloud.talk.conversationinfoedit.viewmodel.ConversationInfoEditViewModel
import com.nextcloud.talk.conversationlist.viewmodels.ConversationsListViewModel
import com.nextcloud.talk.invitation.viewmodels.InvitationsViewModel
@ -137,6 +138,11 @@ abstract class ViewModelModule {
@ViewModelKey(CallNotificationViewModel::class)
abstract fun callNotificationViewModel(viewModel: CallNotificationViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(ConversationInfoViewModel::class)
abstract fun conversationInfoViewModel(viewModel: ConversationInfoViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(ConversationInfoEditViewModel::class)

View file

@ -36,7 +36,7 @@ object UserMapper {
entity.id,
entity.userId,
entity.username,
entity.baseUrl,
entity.baseUrl!!,
entity.token,
entity.displayName,
entity.pushConfigurationState,
@ -52,8 +52,8 @@ object UserMapper {
fun toEntity(model: User): UserEntity {
val userEntity = when (val id = model.id) {
null -> UserEntity(userId = model.userId, username = model.username, baseUrl = model.baseUrl)
else -> UserEntity(id, model.userId, model.username, model.baseUrl)
null -> UserEntity(userId = model.userId, username = model.username, baseUrl = model.baseUrl!!)
else -> UserEntity(id, model.userId, model.username, model.baseUrl!!)
}
userEntity.apply {
token = model.token

View file

@ -45,7 +45,7 @@ data class User(
var scheduledForDeletion: Boolean = FALSE
) : Parcelable {
fun getCredentials(): String = ApiUtils.getCredentials(username, token)
fun getCredentials(): String = ApiUtils.getCredentials(username, token)!!
fun hasSpreedFeatureCapability(capabilityName: String): Boolean {
return capabilities?.spreedCapability?.features?.contains(capabilityName) ?: false

View file

@ -118,7 +118,7 @@ fun ImageView.loadUserAvatar(
ignoreCache: Boolean
): io.reactivex.disposables.Disposable {
val imageRequestUri = ApiUtils.getUrlForAvatar(
user.baseUrl,
user.baseUrl!!,
avatarId,
requestBigSize
)
@ -155,7 +155,7 @@ private fun ImageView.loadAvatarInternal(
user?.let {
addHeader(
"Authorization",
ApiUtils.getCredentials(user.username, user.token)
ApiUtils.getCredentials(user.username, user.token)!!
)
}
transformations(CircleCropTransformation())
@ -196,7 +196,7 @@ fun ImageView.loadThumbnail(url: String, user: User): io.reactivex.disposables.D
) {
requestBuilder.addHeader(
"Authorization",
ApiUtils.getCredentials(user.username, user.token)
ApiUtils.getCredentials(user.username, user.token)!!
)
}
@ -222,7 +222,7 @@ fun ImageView.loadImage(url: String, user: User, placeholder: Drawable? = null):
) {
requestBuilder.addHeader(
"Authorization",
ApiUtils.getCredentials(user.username, user.token)
ApiUtils.getCredentials(user.username, user.token)!!
)
}

View file

@ -29,29 +29,29 @@ class InvitationsRepositoryImpl(private val ncApi: NcApi) :
InvitationsRepository {
override fun fetchInvitations(user: User): Observable<InvitationsModel> {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)
val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
return ncApi.getInvitations(
credentials,
ApiUtils.getUrlForInvitation(user.baseUrl)
ApiUtils.getUrlForInvitation(user.baseUrl!!)
).map { mapToInvitationsModel(user, it.ocs?.data!!) }
}
override fun acceptInvitation(user: User, invitation: Invitation): Observable<InvitationActionModel> {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)
val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
return ncApi.acceptInvitation(
credentials,
ApiUtils.getUrlForInvitationAccept(user.baseUrl, invitation.id)
ApiUtils.getUrlForInvitationAccept(user.baseUrl!!, invitation.id)
).map { InvitationActionModel(ActionEnum.ACCEPT, it.ocs?.meta?.statusCode!!, invitation) }
}
override fun rejectInvitation(user: User, invitation: Invitation): Observable<InvitationActionModel> {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)
val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
return ncApi.rejectInvitation(
credentials,
ApiUtils.getUrlForInvitationReject(user.baseUrl, invitation.id)
ApiUtils.getUrlForInvitationReject(user.baseUrl!!, invitation.id)
).map { InvitationActionModel(ActionEnum.REJECT, it.ocs?.meta?.statusCode!!, invitation) }
}

View file

@ -70,7 +70,7 @@ public class AddParticipantsToConversation extends Worker {
data.getLong(BundleKeys.KEY_INTERNAL_USER_ID, -1))
.blockingGet();
int apiVersion = ApiUtils.getConversationApiVersion(user, new int[] {ApiUtils.APIv4, 1});
int apiVersion = ApiUtils.getConversationApiVersion(user, new int[] {ApiUtils.API_V4, 1});
String conversationToken = data.getString(BundleKeys.KEY_TOKEN);
String credentials = ApiUtils.getCredentials(user.getUsername(), user.getToken());

View file

@ -129,7 +129,7 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
ncApi.searchContactsByPhoneNumber(
ApiUtils.getCredentials(currentUser.username, currentUser.token),
ApiUtils.getUrlForSearchByNumber(currentUser.baseUrl),
ApiUtils.getUrlForSearchByNumber(currentUser.baseUrl!!),
json.toRequestBody("application/json".toMediaTypeOrNull())
)
.subscribeOn(Schedulers.io())

View file

@ -80,7 +80,7 @@ public class DeleteConversationWorker extends Worker {
User operationUser = userManager.getUserWithId(operationUserId).blockingGet();
if (operationUser != null) {
int apiVersion = ApiUtils.getConversationApiVersion(operationUser, new int[]{ApiUtils.APIv4, 1});
int apiVersion = ApiUtils.getConversationApiVersion(operationUser, new int[]{ApiUtils.API_V4, 1});
String credentials = ApiUtils.getCredentials(operationUser.getUsername(), operationUser.getToken());
ncApi = retrofit

View file

@ -92,7 +92,7 @@ public class LeaveConversationWorker extends Worker {
EventStatus.EventType.CONVERSATION_UPDATE,
true);
int apiVersion = ApiUtils.getConversationApiVersion(operationUser, new int[] {ApiUtils.APIv4, 1});
int apiVersion = ApiUtils.getConversationApiVersion(operationUser, new int[] {ApiUtils.API_V4, 1});
ncApi.removeSelfFromRoom(credentials, ApiUtils.getUrlForParticipantsSelf(apiVersion,
operationUser.getBaseUrl(),

View file

@ -248,7 +248,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
val soundUri = getCallRingtoneUri(applicationContext, appPreferences)
val notificationChannelId = NotificationUtils.NotificationChannels.NOTIFICATION_CHANNEL_CALLS_V4.name
val uri = Uri.parse(signatureVerification.user!!.baseUrl)
val uri = Uri.parse(signatureVerification.user!!.baseUrl!!)
val baseUrl = uri.host
val notification =
@ -279,7 +279,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
credentials = ApiUtils.getCredentials(
signatureVerification.user!!.username,
signatureVerification.user!!.token
)
)!!
ncApi = retrofit!!.newBuilder().client(
okHttpClient!!.newBuilder().cookieJar(
JavaNetCookieJar(
@ -338,7 +338,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
ncApi.getNcNotification(
credentials,
ApiUtils.getUrlForNcNotificationWithId(
user!!.baseUrl,
user!!.baseUrl!!,
(pushMessage.notificationId!!).toString()
)
)
@ -451,7 +451,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
0
}
val pendingIntent = PendingIntent.getActivity(context, requestCode, intent, intentFlag)
val uri = Uri.parse(signatureVerification.user!!.baseUrl)
val uri = Uri.parse(signatureVerification.user!!.baseUrl!!)
val baseUrl = uri.host
var contentTitle: CharSequence? = ""
@ -601,12 +601,12 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
val baseUrl = signatureVerification.user!!.baseUrl
val avatarUrl = if ("user" == userType) {
ApiUtils.getUrlForAvatar(
baseUrl,
baseUrl!!,
notificationUser.id,
false
)
} else {
ApiUtils.getUrlForGuestAvatar(baseUrl, notificationUser.name, false)
ApiUtils.getUrlForGuestAvatar(baseUrl!!, notificationUser.name, false)
}
person.setIcon(loadAvatarSync(avatarUrl, context!!))
}
@ -840,8 +840,8 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
var inCallOnDifferentDevice = false
val apiVersion = ApiUtils.getConversationApiVersion(
signatureVerification.user,
intArrayOf(ApiUtils.APIv4, 1)
signatureVerification.user!!,
intArrayOf(ApiUtils.API_V4, 1)
)
var isCallNotificationVisible = true
@ -850,8 +850,8 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
credentials,
ApiUtils.getUrlForCall(
apiVersion,
signatureVerification.user!!.baseUrl,
pushMessage.id
signatureVerification.user!!.baseUrl!!,
pushMessage.id!!
)
)
.repeatWhen { completed ->
@ -920,10 +920,10 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
if (isOngoingCallNotificationVisible) {
val apiVersion = ApiUtils.getConversationApiVersion(
signatureVerification.user,
signatureVerification.user!!,
intArrayOf(
ApiUtils.APIv4,
ApiUtils.APIv3,
ApiUtils.API_V4,
ApiUtils.API_V3,
1
)
)
@ -931,7 +931,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
credentials,
ApiUtils.getUrlForRoom(
apiVersion,
signatureVerification.user?.baseUrl,
signatureVerification.user?.baseUrl!!,
pushMessage.id
)
)

View file

@ -62,7 +62,7 @@ class ShareOperationWorker(context: Context, workerParams: WorkerParameters) : W
for (filePath in filesArray) {
ncApi.createRemoteShare(
credentials,
ApiUtils.getSharingUrl(baseUrl),
ApiUtils.getSharingUrl(baseUrl!!),
filePath,
roomToken,
"10",
@ -87,7 +87,7 @@ class ShareOperationWorker(context: Context, workerParams: WorkerParameters) : W
val operationsUser = userManager.getUserWithId(userId).blockingGet()
baseUrl = operationsUser.baseUrl
credentials = ApiUtils.getCredentials(operationsUser.username, operationsUser.token)
credentials = ApiUtils.getCredentials(operationsUser.username, operationsUser.token)!!
}
companion object {

View file

@ -85,7 +85,7 @@ public class SignalingSettingsWorker extends Worker {
for (User user : userEntityObjectList) {
int apiVersion = ApiUtils.getSignalingApiVersion(user, new int[] {ApiUtils.APIv3, 2, 1});
int apiVersion = ApiUtils.getSignalingApiVersion(user, new int[] {ApiUtils.API_V3, 2, 1});
ncApi.getSignalingSettings(
ApiUtils.getCredentials(user.getUsername(), user.getToken()),

View file

@ -56,7 +56,7 @@ import com.nextcloud.talk.utils.RemoteFileUtils
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FROM_NOTIFICATION_START_CALL
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.CapabilitiesUtil
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
import com.nextcloud.talk.utils.preferences.AppPreferences
import okhttp3.MediaType.Companion.toMediaTypeOrNull
@ -186,7 +186,9 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
}
private fun getRemotePath(currentUser: User): String {
var remotePath = CapabilitiesUtilNew.getAttachmentFolder(currentUser)!! + "/" + fileName
var remotePath = CapabilitiesUtil.getAttachmentFolder(
currentUser.capabilities!!.spreedCapability!!
) + "/" + fileName
remotePath = RemoteFileUtils.getNewPathIfFileExists(
ncApi,
currentUser,

View file

@ -64,6 +64,7 @@ class GeocodingActivity :
lateinit var okHttpClient: OkHttpClient
lateinit var roomToken: String
private var chatApiVersion: Int = 1
private var nominatimClient: TalkJsonNominatimClient? = null
private var searchItem: MenuItem? = null
@ -86,6 +87,7 @@ class GeocodingActivity :
Configuration.getInstance().load(context, PreferenceManager.getDefaultSharedPreferences(context))
roomToken = intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)!!
chatApiVersion = intent.getIntExtra(BundleKeys.KEY_CHAT_API_VERSION, 1)
recyclerView = findViewById(R.id.geocoding_results)
recyclerView.layoutManager = LinearLayoutManager(this)
@ -130,6 +132,7 @@ class GeocodingActivity :
val intent = Intent(this@GeocodingActivity, LocationPickerActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, roomToken)
intent.putExtra(BundleKeys.KEY_CHAT_API_VERSION, chatApiVersion)
intent.putExtra(BundleKeys.KEY_GEOCODING_RESULT, geocodingResult)
startActivity(intent)
}
@ -158,6 +161,7 @@ class GeocodingActivity :
val intent = Intent(this@GeocodingActivity, LocationPickerActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, roomToken)
intent.putExtra(BundleKeys.KEY_CHAT_API_VERSION, chatApiVersion)
intent.putExtra(BundleKeys.KEY_GEOCODING_RESULT, geocodingResult)
startActivity(intent)
}
@ -217,6 +221,7 @@ class GeocodingActivity :
val intent = Intent(context, LocationPickerActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, roomToken)
intent.putExtra(BundleKeys.KEY_CHAT_API_VERSION, chatApiVersion)
startActivity(intent)
return true
}

View file

@ -58,6 +58,7 @@ import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CHAT_API_VERSION
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_GEOCODING_RESULT
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import fr.dudie.nominatim.client.TalkJsonNominatimClient
@ -103,6 +104,7 @@ class LocationPickerActivity :
var nominatimClient: TalkJsonNominatimClient? = null
lateinit var roomToken: String
private var chatApiVersion: Int = 1
var geocodingResult: GeocodingResult? = null
var myLocation: GeoPoint = GeoPoint(COORDINATE_ZERO, COORDINATE_ZERO)
@ -130,6 +132,7 @@ class LocationPickerActivity :
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
roomToken = intent.getStringExtra(KEY_ROOM_TOKEN)!!
chatApiVersion = intent.getIntExtra(KEY_CHAT_API_VERSION, 1)
geocodingResult = intent.getParcelableExtra(KEY_GEOCODING_RESULT)
if (savedInstanceState != null) {
@ -244,6 +247,7 @@ class LocationPickerActivity :
val intent = Intent(this, GeocodingActivity::class.java)
intent.putExtra(BundleKeys.KEY_GEOCODING_QUERY, query)
intent.putExtra(KEY_ROOM_TOKEN, roomToken)
intent.putExtra(KEY_CHAT_API_VERSION, chatApiVersion)
startActivity(intent)
}
return true
@ -465,11 +469,10 @@ class LocationPickerActivity :
"\"longitude\":\"$selectedLon\",\"name\":\"$locationNameToShare\"}"
val currentUser = userManager.currentUser.blockingGet()
val apiVersion = ApiUtils.getChatApiVersion(currentUser, intArrayOf(1))
ncApi.sendLocation(
ApiUtils.getCredentials(currentUser.username, currentUser.token),
ApiUtils.getUrlToSendLocation(apiVersion, currentUser.baseUrl, roomToken),
ApiUtils.getUrlToSendLocation(chatApiVersion, currentUser.baseUrl!!, roomToken),
"geo-location",
objectId,
metaData

View file

@ -27,5 +27,5 @@ import kotlinx.parcelize.Parcelize
@Parcelize
data class RetrofitBucket(
var url: String? = null,
var queryMap: Map<String, String>? = null
var queryMap: MutableMap<String, String>? = null
) : Parcelable

View file

@ -3,7 +3,7 @@ package com.nextcloud.talk.models.domain
import com.nextcloud.talk.models.json.conversations.Conversation
class ConversationModel(
var roomId: String?,
var roomId: String? = null,
var token: String? = null,
var name: String? = null,
var displayName: String? = null,
@ -42,7 +42,11 @@ class ConversationModel(
var statusClearAt: Long? = 0,
var callRecording: Int = 0,
var avatarVersion: String? = null,
var hasCustomAvatar: Boolean? = null
var hasCustomAvatar: Boolean? = null,
var callStartTime: Long? = null,
var recordingConsentRequired: Int = 0,
var remoteServer: String? = null,
var remoteToken: String? = null
) {
companion object {
@ -95,7 +99,11 @@ class ConversationModel(
statusClearAt = conversation.statusClearAt,
callRecording = conversation.callRecording,
avatarVersion = conversation.avatarVersion,
hasCustomAvatar = conversation.hasCustomAvatar
hasCustomAvatar = conversation.hasCustomAvatar,
callStartTime = conversation.callStartTime,
recordingConsentRequired = conversation.recordingConsentRequired,
remoteServer = conversation.remoteServer,
remoteToken = conversation.remoteToken
)
}
}

View file

@ -0,0 +1,45 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* 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.models.domain.converters
import com.bluelinelabs.logansquare.typeconverters.IntBasedTypeConverter
import com.nextcloud.talk.models.domain.NotificationLevel
class DomainEnumNotificationLevelConverter : IntBasedTypeConverter<NotificationLevel>() {
override fun getFromInt(i: Int): NotificationLevel {
return when (i) {
0 -> NotificationLevel.DEFAULT
1 -> NotificationLevel.ALWAYS
2 -> NotificationLevel.MENTION
3 -> NotificationLevel.NEVER
else -> NotificationLevel.DEFAULT
}
}
override fun convertToInt(`object`: NotificationLevel): Int {
return when (`object`) {
NotificationLevel.DEFAULT -> 0
NotificationLevel.ALWAYS -> 1
NotificationLevel.MENTION -> 2
NotificationLevel.NEVER -> 3
else -> 0
}
}
}

View file

@ -0,0 +1,42 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* @author Tim Krüger
* @author Andy Scherzinger
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* 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.models.json.capabilities
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import com.nextcloud.talk.models.json.generic.GenericMeta
import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class RoomCapabilitiesOCS(
@JsonField(name = ["meta"])
var meta: GenericMeta?,
@JsonField(name = ["data"])
var data: SpreedCapability?
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
}

View file

@ -0,0 +1,37 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* @author Tim Krüger
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* 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.models.json.capabilities
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class RoomCapabilitiesOverall(
@JsonField(name = ["ocs"])
var ocs: RoomCapabilitiesOCS? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null)
}

View file

@ -37,7 +37,7 @@ import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.chat.ChatUtils.Companion.getParsedMessage
import com.nextcloud.talk.models.json.converters.EnumSystemMessageTypeConverter
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.CapabilitiesUtil
import com.stfalcon.chatkit.commons.models.IUser
import com.stfalcon.chatkit.commons.models.MessageContentType
import kotlinx.parcelize.Parcelize
@ -213,7 +213,7 @@ data class ChatMessage(
@Suppress("ReturnCount")
fun isLinkPreview(): Boolean {
if (CapabilitiesUtilNew.isLinkPreviewAvailable(activeUser!!)) {
if (CapabilitiesUtil.isLinkPreviewAvailable(activeUser!!)) {
val regexStringFromServer = activeUser?.capabilities?.coreCapability?.referenceRegex
val regexFromServer = regexStringFromServer?.toRegex(setOf(RegexOption.MULTILINE, RegexOption.IGNORE_CASE))
@ -249,8 +249,8 @@ data class ChatMessage(
if (!isVoiceMessage) {
if (activeUser != null && activeUser!!.baseUrl != null) {
return ApiUtils.getUrlForFilePreviewWithFileId(
activeUser!!.baseUrl,
individualHashMap["id"],
activeUser!!.baseUrl!!,
individualHashMap["id"]!!,
sharedApplication!!.resources.getDimensionPixelSize(R.dimen.maximum_file_preview_size)
)
} else {
@ -413,11 +413,11 @@ data class ChatMessage(
null
}
actorType == "users" -> {
ApiUtils.getUrlForAvatar(activeUser!!.baseUrl, actorId, true)
ApiUtils.getUrlForAvatar(activeUser!!.baseUrl!!, actorId, true)
}
actorType == "bridged" -> {
ApiUtils.getUrlForAvatar(
activeUser!!.baseUrl,
activeUser!!.baseUrl!!,
"bridge-bot",
true
)
@ -427,7 +427,7 @@ data class ChatMessage(
if (!TextUtils.isEmpty(actorDisplayName)) {
apiId = actorDisplayName
}
ApiUtils.getUrlForGuestAvatar(activeUser!!.baseUrl, apiId, true)
ApiUtils.getUrlForGuestAvatar(activeUser!!.baseUrl!!, apiId, true)
}
}
}

View file

@ -38,8 +38,9 @@ import com.nextcloud.talk.models.json.converters.EnumParticipantTypeConverter
import com.nextcloud.talk.models.json.converters.EnumReadOnlyConversationConverter
import com.nextcloud.talk.models.json.converters.EnumRoomTypeConverter
import com.nextcloud.talk.models.json.participants.Participant.ParticipantType
import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.ConversationUtils
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.CapabilitiesUtil
import kotlinx.parcelize.Parcelize
@Parcelize
@ -160,7 +161,13 @@ data class Conversation(
var callStartTime: Long? = null,
@JsonField(name = ["recordingConsent"])
var recordingConsentRequired: Int = 0
var recordingConsentRequired: Int = 0,
@JsonField(name = ["remoteServer"])
var remoteServer: String? = null,
@JsonField(name = ["remoteToken"])
var remoteToken: String? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
@ -185,13 +192,20 @@ data class Conversation(
@Deprecated("Use ConversationUtil")
private fun isLockedOneToOne(conversationUser: User): Boolean {
return type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL &&
CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "locked-one-to-one-rooms")
CapabilitiesUtil.hasSpreedFeatureCapability(
conversationUser.capabilities?.spreedCapability!!,
SpreedFeatures.LOCKED_ONE_TO_ONE_ROOMS
)
}
@Deprecated("Use ConversationUtil")
fun canModerate(conversationUser: User): Boolean {
return isParticipantOwnerOrModerator &&
!isLockedOneToOne(conversationUser) &&
ConversationUtils.isLockedOneToOne(
ConversationModel.mapToConversationModel(this),
conversationUser
.capabilities?.spreedCapability!!
) &&
type != ConversationType.FORMER_ONE_TO_ONE &&
!ConversationUtils.isNoteToSelfConversation(ConversationModel.mapToConversationModel(this))
}

View file

@ -31,14 +31,14 @@ class OpenConversationsRepositoryImpl(private val ncApi: NcApi, currentUserProvi
OpenConversationsRepository {
val currentUser: User = currentUserProvider.currentUser.blockingGet()
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv3, 1))
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))
override fun fetchConversations(): Observable<OpenConversationsModel> {
return ncApi.getOpenConversations(
credentials,
ApiUtils.getUrlForOpenConversations(apiVersion, currentUser.baseUrl)
ApiUtils.getUrlForOpenConversations(apiVersion, currentUser.baseUrl!!)
).map { mapToOpenConversationsModel(it.ocs?.data!!) }
}

View file

@ -37,7 +37,7 @@ class PollRepositoryImpl(private val ncApi: NcApi, private val currentUserProvid
PollRepository {
val currentUser: User = currentUserProvider.currentUser.blockingGet()
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
override fun createPoll(
roomToken: String,
@ -49,7 +49,7 @@ class PollRepositoryImpl(private val ncApi: NcApi, private val currentUserProvid
return ncApi.createPoll(
credentials,
ApiUtils.getUrlForPoll(
currentUser.baseUrl,
currentUser.baseUrl!!,
roomToken
),
question,
@ -63,7 +63,7 @@ class PollRepositoryImpl(private val ncApi: NcApi, private val currentUserProvid
return ncApi.getPoll(
credentials,
ApiUtils.getUrlForPoll(
currentUser.baseUrl,
currentUser.baseUrl!!,
roomToken,
pollId
)
@ -74,7 +74,7 @@ class PollRepositoryImpl(private val ncApi: NcApi, private val currentUserProvid
return ncApi.votePoll(
credentials,
ApiUtils.getUrlForPoll(
currentUser.baseUrl,
currentUser.baseUrl!!,
roomToken,
pollId
),
@ -86,7 +86,7 @@ class PollRepositoryImpl(private val ncApi: NcApi, private val currentUserProvid
return ncApi.closePoll(
credentials,
ApiUtils.getUrlForPoll(
currentUser.baseUrl,
currentUser.baseUrl!!,
roomToken,
pollId
)

View file

@ -77,6 +77,7 @@ public class MentionAutocompletePresenter extends RecyclerViewPresenter<Mention>
private Context context;
private String roomToken;
private int chatApiVersion;
private List<AbstractFlexibleItem> abstractFlexibleItemList = new ArrayList<>();
@ -87,10 +88,11 @@ public class MentionAutocompletePresenter extends RecyclerViewPresenter<Mention>
currentUser = userManager.getCurrentUser().blockingGet();
}
public MentionAutocompletePresenter(Context context, String roomToken) {
public MentionAutocompletePresenter(Context context, String roomToken, int chatApiVersion) {
super(context);
this.roomToken = roomToken;
this.context = context;
this.chatApiVersion = chatApiVersion;
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
currentUser = userManager.getCurrentUser().blockingGet();
}
@ -120,8 +122,6 @@ public class MentionAutocompletePresenter extends RecyclerViewPresenter<Mention>
queryString = "";
}
int apiVersion = ApiUtils.getChatApiVersion(currentUser, new int[] {1});
adapter.setFilter(queryString);
Map<String, String> queryMap = new HashMap<>();
@ -129,7 +129,7 @@ public class MentionAutocompletePresenter extends RecyclerViewPresenter<Mention>
ncApi.getMentionAutocompleteSuggestions(
ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken()),
ApiUtils.getUrlForMentionSuggestions(apiVersion, currentUser.getBaseUrl(), roomToken),
ApiUtils.getUrlForMentionSuggestions(chatApiVersion, currentUser.getBaseUrl(), roomToken),
queryString, 5, queryMap)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())

View file

@ -66,12 +66,13 @@ import com.nextcloud.talk.ui.dialog.ScopeDialog
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.Mimetype.IMAGE_JPG
import com.nextcloud.talk.utils.Mimetype.IMAGE_PREFIX_GENERIC
import com.nextcloud.talk.utils.PickImage
import com.nextcloud.talk.utils.PickImage.Companion.REQUEST_PERMISSION_CAMERA
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.CapabilitiesUtil
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
@ -127,7 +128,7 @@ class ProfileActivity : BaseActivity() {
binding.avatarDelete.setOnClickListener {
ncApi.deleteAvatar(
credentials,
ApiUtils.getUrlForTempAvatar(currentUser!!.baseUrl)
ApiUtils.getUrlForTempAvatar(currentUser!!.baseUrl!!)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@ -154,7 +155,7 @@ class ProfileActivity : BaseActivity() {
})
}
binding.avatarImage.let { ViewCompat.setTransitionName(it, "userAvatar.transitionTag") }
ncApi.getUserProfile(credentials, ApiUtils.getUrlForUserProfile(currentUser!!.baseUrl))
ncApi.getUserProfile(credentials, ApiUtils.getUrlForUserProfile(currentUser!!.baseUrl!!))
.retry(DEFAULT_RETRIES)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@ -226,13 +227,17 @@ class ProfileActivity : BaseActivity() {
item.icon = ContextCompat.getDrawable(this, R.drawable.ic_check)
binding.emptyList.root.visibility = View.GONE
binding.userinfoList.visibility = View.VISIBLE
if (CapabilitiesUtilNew.isAvatarEndpointAvailable(currentUser!!)) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(
currentUser!!.capabilities!!.spreedCapability!!,
SpreedFeatures.TEMP_USER_AVATAR_API
)
) {
// TODO later avatar can also be checked via user fields, for now it is in Talk capability
binding.avatarButtons.visibility = View.VISIBLE
}
ncApi.getEditableUserProfileFields(
ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
ApiUtils.getUrlForUserFields(currentUser!!.baseUrl)
ApiUtils.getUrlForUserFields(currentUser!!.baseUrl!!)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@ -292,7 +297,7 @@ class ProfileActivity : BaseActivity() {
private fun showUserProfile() {
if (currentUser!!.baseUrl != null) {
binding.userinfoBaseurl.text = Uri.parse(currentUser!!.baseUrl).host
binding.userinfoBaseurl.text = Uri.parse(currentUser!!.baseUrl!!).host
}
DisplayUtils.loadAvatarImage(currentUser, binding.avatarImage, false)
if (!TextUtils.isEmpty(userInfo?.displayName)) {
@ -327,10 +332,10 @@ class ProfileActivity : BaseActivity() {
}
// show edit button
if (CapabilitiesUtilNew.canEditScopes(currentUser!!)) {
if (CapabilitiesUtil.canEditScopes(currentUser!!)) {
ncApi.getEditableUserProfileFields(
ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
ApiUtils.getUrlForUserFields(currentUser!!.baseUrl)
ApiUtils.getUrlForUserFields(currentUser!!.baseUrl!!)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@ -438,7 +443,7 @@ class ProfileActivity : BaseActivity() {
val credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
ncApi.setUserData(
credentials,
ApiUtils.getUrlForUserData(currentUser!!.baseUrl, currentUser!!.userId),
ApiUtils.getUrlForUserData(currentUser!!.baseUrl!!, currentUser!!.userId!!),
item.field.fieldName,
item.text
)
@ -535,7 +540,7 @@ class ProfileActivity : BaseActivity() {
// upload file
ncApi.uploadAvatar(
ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
ApiUtils.getUrlForTempAvatar(currentUser!!.baseUrl),
ApiUtils.getUrlForTempAvatar(currentUser!!.baseUrl!!),
filePart
)
.subscribeOn(Schedulers.io())
@ -569,7 +574,7 @@ class ProfileActivity : BaseActivity() {
val credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
ncApi.setUserData(
credentials,
ApiUtils.getUrlForUserData(currentUser!!.baseUrl, currentUser!!.userId),
ApiUtils.getUrlForUserData(currentUser!!.baseUrl!!, currentUser!!.userId!!),
item.field.scopeName,
item.scope!!.name
)

View file

@ -31,7 +31,7 @@ class RequestAssistanceRepositoryImpl(private val ncApi: NcApi, currentUserProvi
RequestAssistanceRepository {
val currentUser: User = currentUserProvider.currentUser.blockingGet()
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
var apiVersion = 1

View file

@ -91,8 +91,8 @@ class DirectReplyReceiver : BroadcastReceiver() {
private fun sendDirectReply() {
val credentials = ApiUtils.getCredentials(currentUser.username, currentUser.token)
val apiVersion = ApiUtils.getChatApiVersion(currentUser, intArrayOf(1))
val url = ApiUtils.getUrlForChat(apiVersion, currentUser.baseUrl, roomToken)
val apiVersion = ApiUtils.getChatApiVersion(currentUser.capabilities!!.spreedCapability!!, intArrayOf(1))
val url = ApiUtils.getUrlForChat(apiVersion, currentUser.baseUrl!!, roomToken!!)
ncApi.sendChatMessage(credentials, url, replyMessage, currentUser.displayName, null, false)
?.subscribeOn(Schedulers.io())
@ -153,7 +153,7 @@ class DirectReplyReceiver : BroadcastReceiver() {
// Add reply
Single.fromCallable {
val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl, currentUser.userId, false)
val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl!!, currentUser.userId, false)
val me = Person.Builder()
.setName(currentUser.displayName)
.setIcon(NotificationUtils.loadAvatarSync(avatarUrl, context))

View file

@ -80,11 +80,11 @@ class MarkAsReadReceiver : BroadcastReceiver() {
private fun markAsRead() {
val credentials = ApiUtils.getCredentials(currentUser.username, currentUser.token)
val apiVersion = ApiUtils.getChatApiVersion(currentUser, intArrayOf(1))
val apiVersion = ApiUtils.getChatApiVersion(currentUser.capabilities!!.spreedCapability!!, intArrayOf(1))
val url = ApiUtils.getUrlForChatReadMarker(
apiVersion,
currentUser.baseUrl,
roomToken
currentUser.baseUrl!!,
roomToken!!
)
ncApi.setChatReadMarker(credentials, url, messageId)

View file

@ -94,7 +94,7 @@ class RemoteFileBrowserItemsListViewHolder(
if (item.hasPreview) {
val path = ApiUtils.getUrlForFilePreviewWithRemotePath(
currentUser.baseUrl,
currentUser.baseUrl!!,
item.path,
fileIcon.context.resources.getDimensionPixelSize(R.dimen.small_item_height)
)

View file

@ -33,7 +33,7 @@ class CallRecordingRepositoryImpl(private val ncApi: NcApi, currentUserProvider:
CallRecordingRepository {
val currentUser: User = currentUserProvider.currentUser.blockingGet()
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
var apiVersion = 1
@ -42,7 +42,7 @@ class CallRecordingRepositoryImpl(private val ncApi: NcApi, currentUserProvider:
credentials,
ApiUtils.getUrlForRecording(
apiVersion,
currentUser.baseUrl,
currentUser.baseUrl!!,
roomToken
),
1
@ -54,7 +54,7 @@ class CallRecordingRepositoryImpl(private val ncApi: NcApi, currentUserProvider:
credentials,
ApiUtils.getUrlForRecording(
apiVersion,
currentUser.baseUrl,
currentUser.baseUrl!!,
roomToken
)
).map { mapToStopCallRecordingModel(it.ocs?.meta!!) }

View file

@ -38,12 +38,12 @@ class ConversationsRepositoryImpl(private val api: NcApi, private val userProvid
get() = userProvider.currentUser.blockingGet()
private val credentials: String
get() = ApiUtils.getCredentials(user.username, user.token)
get() = ApiUtils.getCredentials(user.username, user.token)!!
override fun allowGuests(token: String, allow: Boolean): Observable<AllowGuestsResult> {
val url = ApiUtils.getUrlForRoomPublic(
apiVersion(),
user.baseUrl,
user.baseUrl!!,
token
)
@ -100,7 +100,7 @@ class ConversationsRepositoryImpl(private val api: NcApi, private val userProvid
}
private fun apiVersion(): Int {
return ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.APIv4))
return ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4))
}
companion object {

View file

@ -34,13 +34,13 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur
ReactionsRepository {
val currentUser: User = currentUserProvider.currentUser.blockingGet()
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
override fun addReaction(roomToken: String, message: ChatMessage, emoji: String): Observable<ReactionAddedModel> {
return ncApi.sendReaction(
credentials,
ApiUtils.getUrlForMessageReaction(
currentUser.baseUrl,
currentUser.baseUrl!!,
roomToken,
message.id
),
@ -56,7 +56,7 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur
return ncApi.deleteReaction(
credentials,
ApiUtils.getUrlForMessageReaction(
currentUser.baseUrl,
currentUser.baseUrl!!,
roomToken,
message.id
),

View file

@ -37,7 +37,7 @@ class UnifiedSearchRepositoryImpl(private val api: NcApi, private val userProvid
get() = userProvider.currentUser.blockingGet()
private val credentials: String
get() = ApiUtils.getCredentials(user.username, user.token)
get() = ApiUtils.getCredentials(user.username, user.token)!!
override fun searchMessages(
searchTerm: String,

View file

@ -90,6 +90,7 @@ import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
import com.nextcloud.talk.profile.ProfileActivity
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.ClosedInterfaceImpl
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.LoggingUtils.sendMailWithAttachment
@ -97,7 +98,7 @@ import com.nextcloud.talk.utils.NotificationUtils
import com.nextcloud.talk.utils.NotificationUtils.getCallRingtoneUri
import com.nextcloud.talk.utils.NotificationUtils.getMessageRingtoneUri
import com.nextcloud.talk.utils.SecurityUtils
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.CapabilitiesUtil
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
import com.nextcloud.talk.utils.power.PowerManagerUtils
@ -266,7 +267,11 @@ class SettingsActivity : BaseActivity() {
}
private fun setupPhoneBookIntegration() {
if (CapabilitiesUtilNew.isPhoneBookIntegrationAvailable(currentUser!!)) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(
currentUser?.capabilities?.spreedCapability!!,
SpreedFeatures.PHONEBOOK_SEARCH
)
) {
binding.settingsPhoneBookIntegration.visibility = View.VISIBLE
} else {
binding.settingsPhoneBookIntegration.visibility = View.GONE
@ -507,7 +512,7 @@ class SettingsActivity : BaseActivity() {
var port = -1
val uri: URI
try {
uri = URI(currentUser!!.baseUrl)
uri = URI(currentUser!!.baseUrl!!)
host = uri.host
port = uri.port
Log.d(TAG, "uri is $uri")
@ -823,7 +828,7 @@ class SettingsActivity : BaseActivity() {
private fun setupProfileQueryDisposable() {
profileQueryDisposable = ncApi.getUserProfile(
credentials,
ApiUtils.getUrlForUserProfile(currentUser!!.baseUrl)
ApiUtils.getUrlForUserProfile(currentUser!!.baseUrl!!)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@ -854,7 +859,7 @@ class SettingsActivity : BaseActivity() {
private fun setupServerAgeWarning() {
when {
CapabilitiesUtilNew.isServerEOL(currentUser!!.capabilities) -> {
CapabilitiesUtil.isServerEOL(currentUser!!.serverVersion!!.major) -> {
binding.serverAgeWarningText.setTextColor(ContextCompat.getColor((context), R.color.nc_darkRed))
binding.serverAgeWarningText.setText(R.string.nc_settings_server_eol)
binding.serverAgeWarningIcon.setColorFilter(
@ -863,7 +868,7 @@ class SettingsActivity : BaseActivity() {
)
}
CapabilitiesUtilNew.isServerAlmostEOL(currentUser!!) -> {
CapabilitiesUtil.isServerAlmostEOL(currentUser!!.serverVersion!!.major) -> {
binding.serverAgeWarningText.setTextColor(
ContextCompat.getColor((context), R.color.nc_darkYellow)
)
@ -889,8 +894,8 @@ class SettingsActivity : BaseActivity() {
binding.settingsIncognitoKeyboardSwitch.visibility = View.GONE
}
if (CapabilitiesUtilNew.isReadStatusAvailable(currentUser!!)) {
binding.settingsReadPrivacySwitch.isChecked = !CapabilitiesUtilNew.isReadStatusPrivate(currentUser!!)
if (CapabilitiesUtil.isReadStatusAvailable(currentUser!!.capabilities!!.spreedCapability!!)) {
binding.settingsReadPrivacySwitch.isChecked = !CapabilitiesUtil.isReadStatusPrivate(currentUser!!)
} else {
binding.settingsReadPrivacy.visibility = View.GONE
}
@ -954,10 +959,10 @@ class SettingsActivity : BaseActivity() {
private fun setupTypingStatusSetting() {
if (currentUser!!.externalSignalingServer?.externalSignalingServer?.isNotEmpty() == true) {
binding.settingsTypingStatusOnlyWithHpb.visibility = View.GONE
Log.i(TAG, "Typing Status Available: ${CapabilitiesUtilNew.isTypingStatusAvailable(currentUser!!)}")
Log.i(TAG, "Typing Status Available: ${CapabilitiesUtil.isTypingStatusAvailable(currentUser!!)}")
if (CapabilitiesUtilNew.isTypingStatusAvailable(currentUser!!)) {
binding.settingsTypingStatusSwitch.isChecked = !CapabilitiesUtilNew.isTypingStatusPrivate(currentUser!!)
if (CapabilitiesUtil.isTypingStatusAvailable(currentUser!!)) {
binding.settingsTypingStatusSwitch.isChecked = !CapabilitiesUtil.isTypingStatusPrivate(currentUser!!)
} else {
binding.settingsTypingStatus.visibility = View.GONE
}
@ -1209,7 +1214,7 @@ class SettingsActivity : BaseActivity() {
private fun checkForPhoneNumber() {
ncApi.getUserData(
ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
ApiUtils.getUrlForUserProfile(currentUser!!.baseUrl)
ApiUtils.getUrlForUserProfile(currentUser!!.baseUrl!!)
).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<UserProfileOverall> {
@ -1294,7 +1299,7 @@ class SettingsActivity : BaseActivity() {
val phoneNumber = textInputLayout.editText!!.text.toString()
ncApi.setUserData(
ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
ApiUtils.getUrlForUserData(currentUser!!.baseUrl, currentUser!!.userId),
ApiUtils.getUrlForUserData(currentUser!!.baseUrl!!, currentUser!!.userId!!),
"phone",
phoneNumber
).subscribeOn(Schedulers.io())
@ -1349,7 +1354,7 @@ class SettingsActivity : BaseActivity() {
val json = "{\"key\": \"read_status_privacy\", \"value\" : $booleanValue}"
ncApi.setReadStatusPrivacy(
ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
ApiUtils.getUrlForUserSettings(currentUser!!.baseUrl),
ApiUtils.getUrlForUserSettings(currentUser!!.baseUrl!!),
json.toRequestBody("application/json".toMediaTypeOrNull())
)
.subscribeOn(Schedulers.io())
@ -1387,7 +1392,7 @@ class SettingsActivity : BaseActivity() {
val json = "{\"key\": \"typing_privacy\", \"value\" : $booleanValue}"
ncApi.setTypingStatusPrivacy(
ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
ApiUtils.getUrlForUserSettings(currentUser!!.baseUrl),
ApiUtils.getUrlForUserSettings(currentUser!!.baseUrl!!),
json.toRequestBody("application/json".toMediaTypeOrNull())
)
.subscribeOn(Schedulers.io())

View file

@ -105,7 +105,7 @@ class SharedItemsRepositoryImpl @Inject constructor(private val ncApi: NcApi, pr
fileParameters["link"]!!,
fileParameters["mimetype"]!!,
previewAvailable,
previewLink(fileParameters["id"], parameters.baseUrl)
previewLink(fileParameters["id"], parameters.baseUrl!!)
)
} else if (it.value.messageParameters?.containsKey("object") == true) {
val objectParameters = it.value.messageParameters!!["object"]!!
@ -184,7 +184,7 @@ class SharedItemsRepositoryImpl @Inject constructor(private val ncApi: NcApi, pr
return ncApi.getSharedItemsOverview(
credentials,
ApiUtils.getUrlForChatSharedItemsOverview(1, parameters.baseUrl, parameters.roomToken),
ApiUtils.getUrlForChatSharedItemsOverview(1, parameters.baseUrl!!, parameters.roomToken),
1
).map {
val types = mutableSetOf<SharedItemType>()
@ -206,7 +206,7 @@ class SharedItemsRepositoryImpl @Inject constructor(private val ncApi: NcApi, pr
private fun previewLink(fileId: String?, baseUrl: String): String {
return ApiUtils.getUrlForFilePreviewWithFileId(
baseUrl,
fileId,
fileId!!,
sharedApplication!!.resources.getDimensionPixelSize(R.dimen.maximum_file_preview_size)
)
}

View file

@ -37,8 +37,8 @@ class TranslateViewModel @Inject constructor(
fun translateMessage(toLanguage: String, fromLanguage: String?, text: String) {
val currentUser: User = userManager.currentUser.blockingGet()
val authorization: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
val url: String = ApiUtils.getUrlForTranslation(currentUser.baseUrl)
val authorization: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
val url: String = ApiUtils.getUrlForTranslation(currentUser.baseUrl!!)
val calculatedFromLanguage =
if (fromLanguage == null || fromLanguage == "") {
null
@ -60,8 +60,8 @@ class TranslateViewModel @Inject constructor(
fun getLanguages() {
val currentUser: User = userManager.currentUser.blockingGet()
val authorization: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
val url: String = ApiUtils.getUrlForLanguages(currentUser.baseUrl)
val authorization: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
val url: String = ApiUtils.getUrlForLanguages(currentUser.baseUrl!!)
Log.d(TAG, "URL is: $url")
repository.getLanguages(authorization, url)
.subscribeOn(Schedulers.io())

View file

@ -57,7 +57,7 @@ class ProfileBottomSheet(val ncApi: NcApi, val userModel: User, val viewThemeUti
fun showFor(user: String, context: Context) {
ncApi.hoverCard(
ApiUtils.getCredentials(userModel.username, userModel.token),
ApiUtils.getUrlForHoverCard(userModel.baseUrl, user)
ApiUtils.getUrlForHoverCard(userModel.baseUrl!!, user)
).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<HoverCardOverall> {
override fun onSubscribe(d: Disposable) {
@ -121,10 +121,10 @@ class ProfileBottomSheet(val ncApi: NcApi, val userModel: User, val viewThemeUti
private fun talkTo(userId: String, context: Context) {
val apiVersion =
ApiUtils.getConversationApiVersion(userModel, intArrayOf(ApiUtils.APIv4, 1))
ApiUtils.getConversationApiVersion(userModel, intArrayOf(ApiUtils.API_V4, 1))
val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
apiVersion,
userModel.baseUrl,
userModel.baseUrl!!,
"1",
null,
userId,

View file

@ -35,7 +35,8 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.databinding.DialogAttachmentBinding
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.CapabilitiesUtil
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
@ -61,7 +62,7 @@ class AttachmentDialog(val activity: Activity, var chatActivity: ChatActivity) :
}
private fun initItemsStrings() {
var serverName = CapabilitiesUtilNew.getServerName(chatActivity.conversationUser)
var serverName = CapabilitiesUtil.getServerName(chatActivity.conversationUser)
dialogAttachmentBinding.txtAttachFileFromCloud.text = chatActivity.resources?.let {
if (serverName.isNullOrEmpty()) {
serverName = it.getString(R.string.nc_server_product_name)
@ -71,15 +72,15 @@ class AttachmentDialog(val activity: Activity, var chatActivity: ChatActivity) :
}
private fun initItemsVisibility() {
if (!CapabilitiesUtilNew.hasSpreedFeatureCapability(
chatActivity.conversationUser,
"geo-location-sharing"
if (!CapabilitiesUtil.hasSpreedFeatureCapability(
chatActivity.spreedCapabilities,
SpreedFeatures.GEO_LOCATION_SHARING
)
) {
dialogAttachmentBinding.menuShareLocation.visibility = View.GONE
}
if (!CapabilitiesUtilNew.hasSpreedFeatureCapability(chatActivity.conversationUser, "talk-polls") ||
if (!CapabilitiesUtil.hasSpreedFeatureCapability(chatActivity.spreedCapabilities, SpreedFeatures.TALK_POLLS) ||
chatActivity.isOneToOneConversation()
) {
dialogAttachmentBinding.menuAttachPoll.visibility = View.GONE

View file

@ -54,7 +54,7 @@ import com.nextcloud.talk.ui.theme.ViewThemeUtils;
import com.nextcloud.talk.users.UserManager;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.DisplayUtils;
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew;
import com.nextcloud.talk.utils.CapabilitiesUtil;
import java.net.CookieManager;
import java.util.ArrayList;
@ -262,7 +262,7 @@ public class ChooseAccountDialogFragment extends DialogFragment {
private void loadCurrentStatus(User user) {
String credentials = ApiUtils.getCredentials(user.getUsername(), user.getToken());
if (CapabilitiesUtilNew.isUserStatusAvailable(userManager.getCurrentUser().blockingGet())) {
if (CapabilitiesUtil.isUserStatusAvailable(userManager.getCurrentUser().blockingGet())) {
binding.statusView.setVisibility(View.VISIBLE);
ncApi.status(credentials, ApiUtils.getUrlForStatus(user.getBaseUrl())).

View file

@ -89,7 +89,7 @@ class ChooseAccountShareToDialogFragment : DialogFragment() {
if (user != null) {
binding!!.currentAccount.userName.text = user.displayName
binding!!.currentAccount.ticker.visibility = View.GONE
binding!!.currentAccount.account.text = Uri.parse(user.baseUrl).host
binding!!.currentAccount.account.text = Uri.parse(user.baseUrl!!).host
viewThemeUtils!!.platform.colorImageView(binding!!.currentAccount.accountMenu, ColorRole.PRIMARY)
if (user.baseUrl != null &&
(user.baseUrl!!.startsWith("http://") || user.baseUrl!!.startsWith("https://"))

View file

@ -46,7 +46,7 @@ import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
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.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.CapabilitiesUtil
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
@ -86,7 +86,7 @@ class ConversationsListBottomDialog(
initItemsVisibility()
initClickListeners()
credentials = ApiUtils.getCredentials(currentUser.username, currentUser.token)
credentials = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
}
override fun onStart() {
@ -105,7 +105,10 @@ class ConversationsListBottomDialog(
}
private fun initItemsVisibility() {
val hasFavoritesCapability = CapabilitiesUtilNew.hasSpreedFeatureCapability(currentUser, "favorites")
val hasFavoritesCapability = CapabilitiesUtil.hasSpreedFeatureCapability(
currentUser.capabilities?.spreedCapability!!,
"favorites"
)
val canModerate = conversation.canModerate(currentUser)
binding.conversationRemoveFromFavorites.visibility = setVisibleIf(
@ -116,11 +119,19 @@ class ConversationsListBottomDialog(
)
binding.conversationMarkAsRead.visibility = setVisibleIf(
conversation.unreadMessages > 0 && CapabilitiesUtilNew.canSetChatReadMarker(currentUser)
conversation.unreadMessages > 0 && CapabilitiesUtil.hasSpreedFeatureCapability(
currentUser
.capabilities?.spreedCapability!!,
"chat-read-marker"
)
)
binding.conversationMarkAsUnread.visibility = setVisibleIf(
conversation.unreadMessages <= 0 && CapabilitiesUtilNew.canMarkRoomAsUnread(currentUser)
conversation.unreadMessages <= 0 && CapabilitiesUtil.hasSpreedFeatureCapability(
currentUser
.capabilities?.spreedCapability!!,
"chat-unread"
)
)
binding.conversationOperationRename.visibility = setVisibleIf(
@ -178,12 +189,12 @@ class ConversationsListBottomDialog(
}
private fun addConversationToFavorites() {
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv1))
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
ncApi.addConversationToFavorites(
credentials,
ApiUtils.getUrlForRoomFavorite(
apiVersion,
currentUser.baseUrl,
currentUser.baseUrl!!,
conversation.token
)
)
@ -218,12 +229,12 @@ class ConversationsListBottomDialog(
}
private fun removeConversationFromFavorites() {
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv1))
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
ncApi.removeConversationFromFavorites(
credentials,
ApiUtils.getUrlForRoomFavorite(
apiVersion,
currentUser.baseUrl,
currentUser.baseUrl!!,
conversation.token
)
)
@ -262,8 +273,8 @@ class ConversationsListBottomDialog(
credentials,
ApiUtils.getUrlForChatReadMarker(
chatApiVersion(),
currentUser.baseUrl,
conversation.token
currentUser.baseUrl!!,
conversation.token!!
)
)
.subscribeOn(Schedulers.io())
@ -301,8 +312,8 @@ class ConversationsListBottomDialog(
credentials,
ApiUtils.getUrlForChatReadMarker(
chatApiVersion(),
currentUser.baseUrl,
conversation.token
currentUser.baseUrl!!,
conversation.token!!
),
conversation.lastMessage!!.jsonMessageId
)
@ -396,7 +407,7 @@ class ConversationsListBottomDialog(
}
private fun chatApiVersion(): Int {
return ApiUtils.getChatApiVersion(currentUser, intArrayOf(ApiUtils.APIv1))
return ApiUtils.getChatApiVersion(currentUser.capabilities!!.spreedCapability!!, intArrayOf(ApiUtils.API_V1))
}
companion object {

View file

@ -50,7 +50,8 @@ import javax.inject.Inject
class DateTimePickerFragment(
token: String,
id: String,
chatViewModel: ChatViewModel
chatViewModel: ChatViewModel,
private val chatApiVersion: Int
) : DialogFragment() {
lateinit var binding: DialogDateTimePickerBinding
private var dialogView: View? = null
@ -144,7 +145,7 @@ class DateTimePickerFragment(
}
private fun getReminder() {
viewModel.getReminder(userManager.currentUser.blockingGet(), roomToken, messageId)
viewModel.getReminder(userManager.currentUser.blockingGet(), roomToken, messageId, chatApiVersion)
}
private fun showDelete(value: Boolean) {
@ -221,12 +222,18 @@ class DateTimePickerFragment(
binding.buttonClose.setOnClickListener { dismiss() }
binding.buttonSet.setOnClickListener {
currentTimeStamp?.let { time ->
viewModel.setReminder(userManager.currentUser.blockingGet(), roomToken, messageId, time.toInt())
viewModel.setReminder(
userManager.currentUser.blockingGet(),
roomToken,
messageId,
time.toInt(),
chatApiVersion
)
}
dismiss()
}
binding.buttonDelete.setOnClickListener {
viewModel.deleteReminder(userManager.currentUser.blockingGet(), roomToken, messageId)
viewModel.deleteReminder(userManager.currentUser.blockingGet(), roomToken, messageId, chatApiVersion)
}
}
@ -299,11 +306,12 @@ class DateTimePickerFragment(
private const val HOUR_SIX_PM = 18
@JvmStatic
fun newInstance(token: String, id: String, chatViewModel: ChatViewModel) =
fun newInstance(token: String, id: String, chatViewModel: ChatViewModel, chatApiVersion: Int) =
DateTimePickerFragment(
token,
id,
chatViewModel
chatViewModel,
chatApiVersion
)
}
}

View file

@ -46,14 +46,17 @@ import com.nextcloud.talk.models.domain.ConversationReadOnlyState
import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.domain.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.repositories.reactions.ReactionsRepository
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.ConversationUtils
import com.nextcloud.talk.utils.DateConstants
import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.CapabilitiesUtil
import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
import com.vanniktech.emoji.EmojiPopup
import com.vanniktech.emoji.EmojiTextView
import com.vanniktech.emoji.installDisableKeyboardInput
@ -72,7 +75,8 @@ class MessageActionsDialog(
private val user: User?,
private val currentConversation: ConversationModel?,
private val showMessageDeletionButton: Boolean,
private val hasChatPermission: Boolean
private val hasChatPermission: Boolean,
private val spreedCapabilities: SpreedCapability
) : BottomSheetDialog(chatActivity) {
@Inject
@ -100,8 +104,8 @@ class MessageActionsDialog(
private val isUserAllowedToEdit = chatActivity.userAllowedByPrivilages(message)
private val isMessageEditable = CapabilitiesUtilNew.hasSpreedFeatureCapability(
user,
private val isMessageEditable = CapabilitiesUtil.hasSpreedFeatureCapability(
spreedCapabilities,
"edit-messages"
) && messageHasRegularText && !isOlderThanTwentyFourHours && isUserAllowedToEdit
@ -116,9 +120,9 @@ class MessageActionsDialog(
viewThemeUtils.platform.themeDialog(dialogMessageActionsBinding.root)
initEmojiBar(hasChatPermission)
initMenuItemCopy(!message.isDeleted)
val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv3, 1))
val apiVersion = ApiUtils.getConversationApiVersion(user!!, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))
chatActivity.chatViewModel.checkForNoteToSelf(
ApiUtils.getCredentials(user!!.username, user.token),
ApiUtils.getCredentials(user!!.username, user.token)!!,
ApiUtils.getUrlForRooms(
apiVersion,
user.baseUrl
@ -144,7 +148,7 @@ class MessageActionsDialog(
initMenuItemTranslate(
!message.isDeleted &&
ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() &&
CapabilitiesUtilNew.isTranslationsSupported(user)
CapabilitiesUtil.isTranslationsSupported(spreedCapabilities)
)
initMenuEditorDetails(message.lastEditTimestamp != 0L && !message.isDeleted)
initMenuReplyToMessage(message.replyable && hasChatPermission)
@ -160,7 +164,10 @@ class MessageActionsDialog(
ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() &&
!(message.isDeletedCommentMessage || message.isDeleted)
)
initMenuRemindMessage(!message.isDeleted && CapabilitiesUtilNew.isRemindSupported(user))
initMenuRemindMessage(
!message.isDeleted && CapabilitiesUtil.hasSpreedFeatureCapability
(spreedCapabilities, "remind-me-later")
)
initMenuMarkAsUnread(
message.previousMessageId > NO_PREVIOUS_MESSAGE_ID &&
ChatMessage.MessageType.SYSTEM_MESSAGE != message.getCalculateMessageType()
@ -242,7 +249,7 @@ class MessageActionsDialog(
}
private fun initEmojiBar(hasChatPermission: Boolean) {
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "reactions") &&
if (hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.REACTIONS) &&
isPermitted(hasChatPermission) &&
isReactableMessageType(message)
) {

View file

@ -35,7 +35,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.databinding.DialogMoreCallActionsBinding
import com.nextcloud.talk.raisehand.viewmodel.RaiseHandViewModel
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.CapabilitiesUtil
import com.nextcloud.talk.viewmodels.CallRecordingViewModel
import com.vanniktech.emoji.EmojiTextView
import javax.inject.Inject
@ -72,7 +72,7 @@ class MoreCallActionsDialog(private val callActivity: CallActivity) : BottomShee
}
private fun initItemsVisibility() {
if (CapabilitiesUtilNew.isCallReactionsSupported(callActivity.conversationUser)) {
if (CapabilitiesUtil.isCallReactionsSupported(callActivity.conversationUser)) {
binding.callEmojiBar.visibility = View.VISIBLE
} else {
binding.callEmojiBar.visibility = View.GONE
@ -102,7 +102,7 @@ class MoreCallActionsDialog(private val callActivity: CallActivity) : BottomShee
}
private fun initEmojiBar() {
if (CapabilitiesUtilNew.isCallReactionsSupported(callActivity.conversationUser)) {
if (CapabilitiesUtil.isCallReactionsSupported(callActivity.conversationUser)) {
binding.advancedCallOptionsTitle.visibility = View.GONE
val capabilities = callActivity.conversationUser?.capabilities

View file

@ -128,8 +128,8 @@ class SetStatusDialogFragment :
currentUser = currentUserProvider?.currentUser?.blockingGet()
currentStatus = it.getParcelable(ARG_CURRENT_STATUS_PARAM)
credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token)
ncApi.getPredefinedStatuses(credentials, ApiUtils.getUrlForPredefinedStatuses(currentUser?.baseUrl))
credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token)!!
ncApi.getPredefinedStatuses(credentials, ApiUtils.getUrlForPredefinedStatuses(currentUser?.baseUrl!!))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<ResponseBody> {
@ -369,7 +369,7 @@ class SetStatusDialogFragment :
private fun clearStatus() {
val credentials = ApiUtils.getCredentials(currentUser?.username, currentUser?.token)
ncApi.statusDeleteMessage(credentials, ApiUtils.getUrlForStatusMessage(currentUser?.baseUrl))
ncApi.statusDeleteMessage(credentials, ApiUtils.getUrlForStatusMessage(currentUser?.baseUrl!!))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(object : Observer<GenericOverall> {
override fun onSubscribe(d: Disposable) {
@ -393,7 +393,7 @@ class SetStatusDialogFragment :
private fun setStatus(statusType: StatusType) {
visualizeStatus(statusType)
ncApi.setStatusType(credentials, ApiUtils.getUrlForSetStatusType(currentUser?.baseUrl), statusType.string)
ncApi.setStatusType(credentials, ApiUtils.getUrlForSetStatusType(currentUser?.baseUrl!!), statusType.string)
.subscribeOn(
Schedulers
.io()
@ -468,7 +468,7 @@ class SetStatusDialogFragment :
) {
ncApi.setCustomStatusMessage(
credentials,
ApiUtils.getUrlForSetCustomStatus(currentUser?.baseUrl),
ApiUtils.getUrlForSetCustomStatus(currentUser?.baseUrl!!),
statusIcon,
inputText,
clearAt
@ -499,7 +499,7 @@ class SetStatusDialogFragment :
ncApi.setPredefinedStatusMessage(
credentials,
ApiUtils.getUrlForSetPredefinedStatus(currentUser?.baseUrl),
ApiUtils.getUrlForSetPredefinedStatus(currentUser?.baseUrl!!),
selectedPredefinedStatus!!.id,
if (clearAt == -1L) null else clearAt
)

View file

@ -154,7 +154,7 @@ class ShowReactionsDialog(
ncApi.getReactions(
credentials,
ApiUtils.getUrlForMessageReaction(
user?.baseUrl,
user?.baseUrl!!,
roomToken,
chatMessage.id
),
@ -209,7 +209,7 @@ class ShowReactionsDialog(
ncApi.deleteReaction(
credentials,
ApiUtils.getUrlForMessageReaction(
user?.baseUrl,
user?.baseUrl!!,
roomToken,
message.id
),

View file

@ -81,7 +81,7 @@ class ChunkedFileUploader(
init {
initHttpClient(okHttpClient, currentUser)
remoteChunkUrl = ApiUtils.getUrlForChunkedUpload(currentUser.baseUrl, currentUser.userId)
remoteChunkUrl = ApiUtils.getUrlForChunkedUpload(currentUser.baseUrl!!, currentUser.userId!!)
}
@Suppress("Detekt.TooGenericExceptionCaught")
@ -295,7 +295,7 @@ class ChunkedFileUploader(
ApiUtils.getCredentials(
currentUser.username,
currentUser.token
),
)!!,
"Authorization"
)
)
@ -304,8 +304,8 @@ class ChunkedFileUploader(
private fun assembleChunks(uploadFolderUri: String, targetPath: String) {
val destinationUri: String = ApiUtils.getUrlForFileUpload(
currentUser.baseUrl,
currentUser.userId,
currentUser.baseUrl!!,
currentUser.userId!!,
targetPath
)
val originUri = "$uploadFolderUri/.file"

View file

@ -24,7 +24,7 @@ class FileUploader(
fun upload(sourceFileUri: Uri, fileName: String, remotePath: String, metaData: String?): Observable<Boolean> {
return ncApi.uploadFile(
ApiUtils.getCredentials(currentUser.username, currentUser.token),
ApiUtils.getUrlForFileUpload(currentUser.baseUrl, currentUser.userId, remotePath),
ApiUtils.getUrlForFileUpload(currentUser.baseUrl!!, currentUser.userId!!, remotePath),
createRequestBody(sourceFileUri)
)
.subscribeOn(Schedulers.io())

View file

@ -69,7 +69,7 @@ object AccountUtils {
private fun matchAccounts(importAccount: ImportAccount, user: User): Boolean {
var accountFound = false
if (importAccount.token != null) {
if (UriUtils.hasHttpProtocolPrefixed(importAccount.baseUrl)) {
if (UriUtils.hasHttpProtocolPrefixed(importAccount.baseUrl!!)) {
if (
user.username == importAccount.username &&
user.baseUrl == importAccount.baseUrl

View file

@ -1,559 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* @author Marcel Hibbe
* @author Tim Krüger
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2021-2022 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* 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.utils;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import com.nextcloud.talk.BuildConfig;
import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.models.RetrofitBucket;
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import okhttp3.Credentials;
public class ApiUtils {
public static final int APIv1 = 1;
public static final int APIv2 = 2;
public static final int APIv3 = 3;
public static final int APIv4 = 4;
public static final int AVATAR_SIZE_BIG = 512;
public static final int AVATAR_SIZE_SMALL = 64;
private static final String TAG = "ApiUtils";
private static final String ocsApiVersion = "/ocs/v2.php";
private static final String spreedApiVersion = "/apps/spreed/api/v1";
private static final String spreedApiBase = ocsApiVersion + "/apps/spreed/api/v";
private static final String userAgent = "Mozilla/5.0 (Android) Nextcloud-Talk v";
public static String getUserAgent() {
return userAgent + BuildConfig.VERSION_NAME;
}
/**
* @deprecated This is only supported on API v1-3, in API v4+ please use
* {@link ApiUtils#getUrlForAttendees(int, String, String)} instead.
*/
@Deprecated
public static String getUrlForRemovingParticipantFromConversation(String baseUrl, String roomToken, boolean isGuest) {
String url = getUrlForParticipants(APIv1, baseUrl, roomToken);
if (isGuest) {
url += "/guests";
}
return url;
}
public static RetrofitBucket getRetrofitBucketForContactsSearch(String baseUrl, @Nullable String searchQuery) {
RetrofitBucket retrofitBucket = new RetrofitBucket();
retrofitBucket.setUrl(baseUrl + ocsApiVersion + "/apps/files_sharing/api/v1/sharees");
Map<String, String> queryMap = new HashMap<>();
if (searchQuery == null) {
searchQuery = "";
}
queryMap.put("format", "json");
queryMap.put("search", searchQuery);
queryMap.put("itemType", "call");
retrofitBucket.setQueryMap(queryMap);
return retrofitBucket;
}
public static String getUrlForFilePreviewWithRemotePath(String baseUrl, String remotePath, int px) {
return baseUrl + "/index.php/core/preview.png?file="
+ Uri.encode(remotePath, "UTF-8")
+ "&x=" + px + "&y=" + px + "&a=1&mode=cover&forceIcon=1";
}
public static String getUrlForFilePreviewWithFileId(String baseUrl, String fileId, int px) {
return baseUrl + "/index.php/core/preview?fileId="
+ fileId + "&x=" + px + "&y=" + px + "&a=1&mode=cover&forceIcon=1";
}
public static String getSharingUrl(String baseUrl) {
return baseUrl + ocsApiVersion + "/apps/files_sharing/api/v1/shares";
}
public static RetrofitBucket getRetrofitBucketForContactsSearchFor14(String baseUrl, @Nullable String searchQuery) {
RetrofitBucket retrofitBucket = getRetrofitBucketForContactsSearch(baseUrl, searchQuery);
retrofitBucket.setUrl(baseUrl + ocsApiVersion + "/core/autocomplete/get");
retrofitBucket.getQueryMap().put("itemId", "new");
return retrofitBucket;
}
public static String getUrlForCapabilities(String baseUrl) {
return baseUrl + ocsApiVersion + "/cloud/capabilities";
}
public static int getCallApiVersion(User capabilities, int[] versions) throws NoSupportedApiException {
return getConversationApiVersion(capabilities, versions);
}
public static int getConversationApiVersion(User user, int[] versions) throws NoSupportedApiException {
boolean hasApiV4 = false;
for (int version : versions) {
hasApiV4 |= version == APIv4;
}
if (!hasApiV4) {
Exception e = new Exception("Api call did not try conversation-v4 api");
Log.d(TAG, e.getMessage(), e);
}
for (int version : versions) {
if (user.hasSpreedFeatureCapability("conversation-v" + version)) {
return version;
}
// Fallback for old API versions
if ((version == APIv1 || version == APIv2)) {
if (user.hasSpreedFeatureCapability("conversation-v2")) {
return version;
}
if (version == APIv1 &&
user.hasSpreedFeatureCapability("mention-flag") &&
!user.hasSpreedFeatureCapability("conversation-v4")) {
return version;
}
}
}
throw new NoSupportedApiException();
}
public static int getSignalingApiVersion(User user, int[] versions) throws NoSupportedApiException {
for (int version : versions) {
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "signaling-v" + version)) {
return version;
}
if (version == APIv2 &&
CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "sip-support") &&
!CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "signaling-v3")) {
return version;
}
if (version == APIv1 &&
!CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "signaling-v3")) {
// Has no capability, we just assume it is always there when there is no v3 or later
return version;
}
}
throw new NoSupportedApiException();
}
public static int getChatApiVersion(User user, int[] versions) throws NoSupportedApiException {
for (int version : versions) {
if (version == APIv1 && CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "chat-v2")) {
// Do not question that chat-v2 capability shows the availability of api/v1/ endpoint *see no evil*
return version;
}
}
throw new NoSupportedApiException();
}
protected static String getUrlForApi(int version, String baseUrl) {
return baseUrl + spreedApiBase + version;
}
public static String getUrlForRooms(int version, String baseUrl) {
return getUrlForApi(version, baseUrl) + "/room";
}
public static String getUrlForRoom(int version, String baseUrl, String token) {
return getUrlForRooms(version, baseUrl) + "/" + token;
}
public static String getUrlForAttendees(int version, String baseUrl, String token) {
return getUrlForRoom(version, baseUrl, token) + "/attendees";
}
public static String getUrlForParticipants(int version, String baseUrl, String token) {
if (token == null || token.isEmpty()) {
Log.e(TAG, "token was null or empty");
}
return getUrlForRoom(version, baseUrl, token) + "/participants";
}
public static String getUrlForParticipantsActive(int version, String baseUrl, String token) {
return getUrlForParticipants(version, baseUrl, token) + "/active";
}
public static String getUrlForParticipantsSelf(int version, String baseUrl, String token) {
return getUrlForParticipants(version, baseUrl, token) + "/self";
}
public static String getUrlForParticipantsResendInvitations(int version, String baseUrl, String token) {
return getUrlForParticipants(version, baseUrl, token) + "/resend-invitations";
}
public static String getUrlForRoomFavorite(int version, String baseUrl, String token) {
return getUrlForRoom(version, baseUrl, token) + "/favorite";
}
public static String getUrlForRoomModerators(int version, String baseUrl, String token) {
return getUrlForRoom(version, baseUrl, token) + "/moderators";
}
public static String getUrlForRoomNotificationLevel(int version, String baseUrl, String token) {
return getUrlForRoom(version, baseUrl, token) + "/notify";
}
public static String getUrlForRoomPublic(int version, String baseUrl, String token) {
return getUrlForRoom(version, baseUrl, token) + "/public";
}
public static String getUrlForRoomPassword(int version, String baseUrl, String token) {
return getUrlForRoom(version, baseUrl, token) + "/password";
}
public static String getUrlForRoomReadOnlyState(int version, String baseUrl, String token) {
return getUrlForRoom(version, baseUrl, token) + "/read-only";
}
public static String getUrlForRoomWebinaryLobby(int version, String baseUrl, String token) {
return getUrlForRoom(version, baseUrl, token) + "/webinar/lobby";
}
public static String getUrlForRoomNotificationCalls(int version, String baseUrl, String token) {
return getUrlForRoom(version, baseUrl, token) + "/notify-calls";
}
public static String getUrlForCall(int version, String baseUrl, String token) {
return getUrlForApi(version, baseUrl) + "/call/" + token;
}
public static String getUrlForChat(int version, String baseUrl, String token) {
return getUrlForApi(version, baseUrl) + "/chat/" + token;
}
public static String getUrlForMentionSuggestions(int version, String baseUrl, String token) {
return getUrlForChat(version, baseUrl, token) + "/mentions";
}
public static String getUrlForChatMessage(int version, String baseUrl, String token, String messageId) {
return getUrlForChat(version, baseUrl, token) + "/" + messageId;
}
public static String getUrlForChatSharedItems(int version, String baseUrl, String token) {
return getUrlForChat(version, baseUrl, token) + "/share";
}
public static String getUrlForChatSharedItemsOverview(int version, String baseUrl, String token) {
return getUrlForChatSharedItems(version, baseUrl, token) + "/overview";
}
public static String getUrlForSignaling(int version, String baseUrl) {
return getUrlForApi(version, baseUrl) + "/signaling";
}
public static String getUrlForSignalingBackend(int version, String baseUrl) {
return getUrlForSignaling(version, baseUrl) + "/backend";
}
public static String getUrlForSignalingSettings(int version, String baseUrl) {
return getUrlForSignaling(version, baseUrl) + "/settings";
}
public static String getUrlForSignaling(int version, String baseUrl, String token) {
return getUrlForSignaling(version, baseUrl) + "/" + token;
}
public static String getUrlForOpenConversations(int version, String baseUrl) {
return getUrlForApi(version, baseUrl) + "/listed-room";
}
public static RetrofitBucket getRetrofitBucketForCreateRoom(int version, String baseUrl, String roomType,
@Nullable String source,
@Nullable String invite,
@Nullable String conversationName) {
RetrofitBucket retrofitBucket = new RetrofitBucket();
retrofitBucket.setUrl(getUrlForRooms(version, baseUrl));
Map<String, String> queryMap = new HashMap<>();
queryMap.put("roomType", roomType);
if (invite != null) {
queryMap.put("invite", invite);
}
if (source != null) {
queryMap.put("source", source);
}
if (conversationName != null) {
queryMap.put("roomName", conversationName);
}
retrofitBucket.setQueryMap(queryMap);
return retrofitBucket;
}
public static RetrofitBucket getRetrofitBucketForAddParticipant(int version, String baseUrl, String token, String user) {
RetrofitBucket retrofitBucket = new RetrofitBucket();
retrofitBucket.setUrl(getUrlForParticipants(version, baseUrl, token));
Map<String, String> queryMap = new HashMap<>();
queryMap.put("newParticipant", user);
retrofitBucket.setQueryMap(queryMap);
return retrofitBucket;
}
public static RetrofitBucket getRetrofitBucketForAddParticipantWithSource(
int version,
String baseUrl,
String token,
String source,
String id
) {
RetrofitBucket retrofitBucket = getRetrofitBucketForAddParticipant(version, baseUrl, token, id);
retrofitBucket.getQueryMap().put("source", source);
return retrofitBucket;
}
public static String getUrlForUserProfile(String baseUrl) {
return baseUrl + ocsApiVersion + "/cloud/user";
}
public static String getUrlForUserData(String baseUrl, String userId) {
return baseUrl + ocsApiVersion + "/cloud/users/" + userId;
}
public static String getUrlForUserSettings(String baseUrl) {
// FIXME Introduce API version
return baseUrl + ocsApiVersion + spreedApiVersion + "/settings/user";
}
public static String getUrlPostfixForStatus() {
return "/status.php";
}
public static String getUrlForAvatar(String baseUrl, String name, boolean requestBigSize) {
int avatarSize = requestBigSize ? AVATAR_SIZE_BIG : AVATAR_SIZE_SMALL;
return baseUrl + "/index.php/avatar/" + Uri.encode(name) + "/" + avatarSize;
}
public static String getUrlForGuestAvatar(String baseUrl, String name, boolean requestBigSize) {
int avatarSize = requestBigSize ? AVATAR_SIZE_BIG : AVATAR_SIZE_SMALL;
return baseUrl + "/index.php/avatar/guest/" + Uri.encode(name) + "/" + avatarSize;
}
public static String getUrlForConversationAvatar(int version, String baseUrl, String token) {
return getUrlForRoom(version, baseUrl, token) + "/avatar";
}
public static String getUrlForConversationAvatarWithVersion(int version, String baseUrl, String token,
boolean isDark,
String avatarVersion) {
String isDarkString = "";
if (isDark) {
isDarkString = "/dark";
}
String avatarVersionString = "";
if (avatarVersion != null) {
avatarVersionString = "?avatarVersion=" + avatarVersion;
}
return getUrlForRoom(version, baseUrl, token) + "/avatar" + isDarkString + avatarVersionString;
}
public static String getCredentials(String username, String token) {
if (TextUtils.isEmpty(username) && TextUtils.isEmpty(token)) {
return null;
}
return Credentials.basic(username, token, StandardCharsets.UTF_8);
}
public static String getUrlNextcloudPush(String baseUrl) {
return baseUrl + ocsApiVersion + "/apps/notifications/api/v2/push";
}
public static String getUrlPushProxy() {
return NextcloudTalkApplication.Companion.getSharedApplication().
getApplicationContext().getResources().getString(R.string.nc_push_server_url) + "/devices";
}
// see https://github.com/nextcloud/notifications/blob/master/docs/ocs-endpoint-v2.md
public static String getUrlForNcNotificationWithId(String baseUrl, String notificationId) {
return baseUrl + ocsApiVersion + "/apps/notifications/api/v2/notifications/" + notificationId;
}
public static String getUrlForSearchByNumber(String baseUrl) {
return baseUrl + ocsApiVersion + "/cloud/users/search/by-phone";
}
public static String getUrlForFileUpload(String baseUrl, String user, String remotePath) {
return baseUrl + "/remote.php/dav/files/" + user + remotePath;
}
public static String getUrlForChunkedUpload(String baseUrl, String user) {
return baseUrl + "/remote.php/dav/uploads/" + user;
}
public static String getUrlForFileDownload(String baseUrl, String user, String remotePath) {
return baseUrl + "/remote.php/dav/files/" + user + "/" + remotePath;
}
public static String getUrlForTempAvatar(String baseUrl) {
return baseUrl + ocsApiVersion + "/apps/spreed/temp-user-avatar";
}
public static String getUrlForUserFields(String baseUrl) {
return baseUrl + ocsApiVersion + "/cloud/user/fields";
}
public static String getUrlToSendLocation(int version, String baseUrl, String roomToken) {
return getUrlForChat(version, baseUrl, roomToken) + "/share";
}
public static String getUrlForHoverCard(String baseUrl, String userId) {
return baseUrl + ocsApiVersion +
"/hovercard/v1/" + userId;
}
public static String getUrlForChatReadMarker(int version, String baseUrl, String roomToken) {
return getUrlForChat(version, baseUrl, roomToken) + "/read";
}
/*
* OCS Status API
*/
public static String getUrlForStatus(String baseUrl) {
return baseUrl + ocsApiVersion + "/apps/user_status/api/v1/user_status";
}
public static String getUrlForSetStatusType(String baseUrl) {
return getUrlForStatus(baseUrl) + "/status";
}
public static String getUrlForPredefinedStatuses(String baseUrl) {
return baseUrl + ocsApiVersion + "/apps/user_status/api/v1/predefined_statuses";
}
public static String getUrlForStatusMessage(String baseUrl) {
return getUrlForStatus(baseUrl) + "/message";
}
public static String getUrlForSetCustomStatus(String baseUrl) {
return baseUrl + ocsApiVersion + "/apps/user_status/api/v1/user_status/message/custom";
}
public static String getUrlForSetPredefinedStatus(String baseUrl) {
return baseUrl + ocsApiVersion + "/apps/user_status/api/v1/user_status/message/predefined";
}
public static String getUrlForUserStatuses(String baseUrl) {
return baseUrl + ocsApiVersion + "/apps/user_status/api/v1/statuses";
}
public static String getUrlForMessageReaction(String baseUrl,
String roomToken,
String messageId) {
return baseUrl + ocsApiVersion + spreedApiVersion + "/reaction/" + roomToken + "/" + messageId;
}
@NonNull
public static String getUrlForUnifiedSearch(@NonNull String baseUrl, @NonNull String providerId) {
return baseUrl + ocsApiVersion + "/search/providers/" + providerId + "/search";
}
public static String getUrlForPoll(String baseUrl,
String roomToken,
String pollId) {
return getUrlForPoll(baseUrl, roomToken) + "/" + pollId;
}
public static String getUrlForPoll(String baseUrl,
String roomToken) {
return baseUrl + ocsApiVersion + spreedApiVersion + "/poll/" + roomToken;
}
public static String getUrlForMessageExpiration(int version, String baseUrl, String token) {
return getUrlForRoom(version, baseUrl, token) + "/message-expiration";
}
public static String getUrlForOpenGraph(String baseUrl) {
return baseUrl + ocsApiVersion + "/references/resolve";
}
public static String getUrlForRecording(int version, String baseUrl, String token) {
return getUrlForApi(version, baseUrl) + "/recording/" + token;
}
public static String getUrlForRequestAssistance(int version, String baseUrl, String token) {
return getUrlForApi(version, baseUrl) + "/breakout-rooms/" + token + "/request-assistance";
}
public static String getUrlForConversationDescription(int version, String baseUrl, String token) {
return getUrlForRoom(version, baseUrl, token) + "/description";
}
public static String getUrlForTranslation(String baseUrl) {
return baseUrl + ocsApiVersion + "/translation/translate";
}
public static String getUrlForLanguages(String baseUrl) {
return baseUrl + ocsApiVersion + "/translation/languages";
}
public static String getUrlForReminder(User user, String roomToken, String messageId, int version) {
String url = ApiUtils.getUrlForChatMessage(version, user.getBaseUrl(), roomToken, messageId);
return url + "/reminder";
}
public static String getUrlForRecordingConsent(int version, String baseUrl, String token) {
return getUrlForRoom(version, baseUrl, token) + "/recording-consent";
}
public static String getUrlForInvitation(String baseUrl) {
return baseUrl + ocsApiVersion + spreedApiVersion + "/federation/invitation";
}
public static String getUrlForInvitationAccept(String baseUrl, int id) {
return getUrlForInvitation(baseUrl) + "/" + id;
}
public static String getUrlForInvitationReject(String baseUrl, int id) {
return getUrlForInvitation(baseUrl) + "/" + id;
}
}

View file

@ -0,0 +1,577 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* @author Marcel Hibbe
* @author Tim Krüger
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2021-2022 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* 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.utils
import android.net.Uri
import android.text.TextUtils
import android.util.Log
import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.RetrofitBucket
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
import okhttp3.Credentials.basic
import java.nio.charset.StandardCharsets
@Suppress("TooManyFunctions")
object ApiUtils {
private val TAG = ApiUtils::class.java.simpleName
const val API_V1 = 1
private const val API_V2 = 2
const val API_V3 = 3
const val API_V4 = 4
private const val AVATAR_SIZE_BIG = 512
private const val AVATAR_SIZE_SMALL = 64
private const val OCS_API_VERSION = "/ocs/v2.php"
private const val SPREED_API_VERSION = "/apps/spreed/api/v1"
private const val SPREED_API_BASE = "$OCS_API_VERSION/apps/spreed/api/v"
@JvmStatic
val userAgent = "Mozilla/5.0 (Android) Nextcloud-Talk v"
get() = field + BuildConfig.VERSION_NAME
@Deprecated(
"This is only supported on API v1-3, in API v4+ please use " +
"{@link ApiUtils#getUrlForAttendees(int, String, String)} instead."
)
fun getUrlForRemovingParticipantFromConversation(baseUrl: String?, roomToken: String?, isGuest: Boolean): String {
var url = getUrlForParticipants(API_V1, baseUrl, roomToken)
if (isGuest) {
url += "/guests"
}
return url
}
private fun getRetrofitBucketForContactsSearch(baseUrl: String, searchQuery: String?): RetrofitBucket {
var query = searchQuery
val retrofitBucket = RetrofitBucket()
retrofitBucket.url = "$baseUrl$OCS_API_VERSION/apps/files_sharing/api/v1/sharees"
val queryMap: MutableMap<String, String> = HashMap()
if (query == null) {
query = ""
}
queryMap["format"] = "json"
queryMap["search"] = query
queryMap["itemType"] = "call"
retrofitBucket.queryMap = queryMap
return retrofitBucket
}
fun getUrlForFilePreviewWithRemotePath(baseUrl: String, remotePath: String?, px: Int): String {
return (
baseUrl + "/index.php/core/preview.png?file=" +
Uri.encode(remotePath, "UTF-8") +
"&x=" + px + "&y=" + px + "&a=1&mode=cover&forceIcon=1"
)
}
fun getUrlForFilePreviewWithFileId(baseUrl: String, fileId: String, px: Int): String {
return (
baseUrl + "/index.php/core/preview?fileId=" +
fileId + "&x=" + px + "&y=" + px + "&a=1&mode=cover&forceIcon=1"
)
}
fun getSharingUrl(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/apps/files_sharing/api/v1/shares"
}
fun getRetrofitBucketForContactsSearchFor14(baseUrl: String, searchQuery: String?): RetrofitBucket {
val retrofitBucket = getRetrofitBucketForContactsSearch(baseUrl, searchQuery)
retrofitBucket.url = "$baseUrl$OCS_API_VERSION/core/autocomplete/get"
retrofitBucket.queryMap?.put("itemId", "new")
return retrofitBucket
}
@JvmStatic
fun getUrlForCapabilities(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/cloud/capabilities"
}
@Throws(NoSupportedApiException::class)
fun getCallApiVersion(capabilities: User, versions: IntArray): Int {
return getConversationApiVersion(capabilities, versions)
}
@JvmStatic
@Throws(NoSupportedApiException::class)
@Suppress("ReturnCount")
fun getConversationApiVersion(user: User, versions: IntArray): Int {
var hasApiV4 = false
for (version in versions) {
hasApiV4 = hasApiV4 or (version == API_V4)
}
if (!hasApiV4) {
val e = Exception("Api call did not try conversation-v4 api")
Log.d(TAG, e.message, e)
}
for (version in versions) {
if (user.hasSpreedFeatureCapability("conversation-v$version")) {
return version
}
// Fallback for old API versions
if (version == API_V1 || version == API_V2) {
if (user.hasSpreedFeatureCapability("conversation-v2")) {
return version
}
if (version == API_V1 &&
user.hasSpreedFeatureCapability("mention-flag") &&
!user.hasSpreedFeatureCapability("conversation-v4")
) {
return version
}
}
}
throw NoSupportedApiException()
}
@JvmStatic
@Throws(NoSupportedApiException::class)
@Suppress("ReturnCount")
fun getSignalingApiVersion(user: User, versions: IntArray): Int {
val spreedCapabilities = user.capabilities!!.spreedCapability
for (version in versions) {
if (spreedCapabilities != null) {
if (hasSpreedFeatureCapability(spreedCapabilities, "signaling-v$version")) {
return version
}
if (version == API_V2 &&
hasSpreedFeatureCapability(spreedCapabilities, "sip-support") &&
!hasSpreedFeatureCapability(spreedCapabilities, "signaling-v3")
) {
return version
}
if (version == API_V1 &&
!hasSpreedFeatureCapability(spreedCapabilities, "signaling-v3")
) {
// Has no capability, we just assume it is always there when there is no v3 or later
return version
}
}
}
throw NoSupportedApiException()
}
@JvmStatic
@Throws(NoSupportedApiException::class)
fun getChatApiVersion(spreedCapabilities: SpreedCapability, versions: IntArray): Int {
for (version in versions) {
if (version == API_V1 && hasSpreedFeatureCapability(spreedCapabilities, "chat-v2")) {
// Do not question that chat-v2 capability shows the availability of api/v1/ endpoint *see no evil*
return version
}
}
throw NoSupportedApiException()
}
private fun getUrlForApi(version: Int, baseUrl: String?): String {
return baseUrl + SPREED_API_BASE + version
}
fun getUrlForRooms(version: Int, baseUrl: String?): String {
return getUrlForApi(version, baseUrl) + "/room"
}
@JvmStatic
fun getUrlForRoom(version: Int, baseUrl: String?, token: String?): String {
return getUrlForRooms(version, baseUrl) + "/" + token
}
fun getUrlForAttendees(version: Int, baseUrl: String?, token: String?): String {
return getUrlForRoom(version, baseUrl, token) + "/attendees"
}
fun getUrlForParticipants(version: Int, baseUrl: String?, token: String?): String {
if (token.isNullOrEmpty()) {
Log.e(TAG, "token was null or empty")
}
return getUrlForRoom(version, baseUrl, token) + "/participants"
}
fun getUrlForParticipantsActive(version: Int, baseUrl: String?, token: String?): String {
return getUrlForParticipants(version, baseUrl, token) + "/active"
}
@JvmStatic
fun getUrlForParticipantsSelf(version: Int, baseUrl: String?, token: String?): String {
return getUrlForParticipants(version, baseUrl, token) + "/self"
}
fun getUrlForParticipantsResendInvitations(version: Int, baseUrl: String?, token: String?): String {
return getUrlForParticipants(version, baseUrl, token) + "/resend-invitations"
}
fun getUrlForRoomFavorite(version: Int, baseUrl: String?, token: String?): String {
return getUrlForRoom(version, baseUrl, token) + "/favorite"
}
fun getUrlForRoomModerators(version: Int, baseUrl: String?, token: String?): String {
return getUrlForRoom(version, baseUrl, token) + "/moderators"
}
@JvmStatic
fun getUrlForRoomNotificationLevel(version: Int, baseUrl: String?, token: String?): String {
return getUrlForRoom(version, baseUrl, token) + "/notify"
}
fun getUrlForRoomPublic(version: Int, baseUrl: String?, token: String?): String {
return getUrlForRoom(version, baseUrl, token) + "/public"
}
fun getUrlForRoomPassword(version: Int, baseUrl: String?, token: String?): String {
return getUrlForRoom(version, baseUrl, token) + "/password"
}
fun getUrlForRoomReadOnlyState(version: Int, baseUrl: String?, token: String?): String {
return getUrlForRoom(version, baseUrl, token) + "/read-only"
}
fun getUrlForRoomWebinaryLobby(version: Int, baseUrl: String?, token: String?): String {
return getUrlForRoom(version, baseUrl, token) + "/webinar/lobby"
}
@JvmStatic
fun getUrlForRoomNotificationCalls(version: Int, baseUrl: String?, token: String?): String {
return getUrlForRoom(version, baseUrl, token) + "/notify-calls"
}
fun getUrlForCall(version: Int, baseUrl: String?, token: String): String {
return getUrlForApi(version, baseUrl) + "/call/" + token
}
fun getUrlForChat(version: Int, baseUrl: String?, token: String): String {
return getUrlForApi(version, baseUrl) + "/chat/" + token
}
@JvmStatic
fun getUrlForMentionSuggestions(version: Int, baseUrl: String?, token: String): String {
return getUrlForChat(version, baseUrl, token) + "/mentions"
}
fun getUrlForChatMessage(version: Int, baseUrl: String?, token: String, messageId: String): String {
return getUrlForChat(version, baseUrl, token) + "/" + messageId
}
fun getUrlForChatSharedItems(version: Int, baseUrl: String?, token: String): String {
return getUrlForChat(version, baseUrl, token) + "/share"
}
fun getUrlForChatSharedItemsOverview(version: Int, baseUrl: String?, token: String): String {
return getUrlForChatSharedItems(version, baseUrl, token) + "/overview"
}
fun getUrlForSignaling(version: Int, baseUrl: String?): String {
return getUrlForApi(version, baseUrl) + "/signaling"
}
@JvmStatic
fun getUrlForSignalingBackend(version: Int, baseUrl: String?): String {
return getUrlForSignaling(version, baseUrl) + "/backend"
}
@JvmStatic
fun getUrlForSignalingSettings(version: Int, baseUrl: String?): String {
return getUrlForSignaling(version, baseUrl) + "/settings"
}
fun getUrlForSignaling(version: Int, baseUrl: String?, token: String): String {
return getUrlForSignaling(version, baseUrl) + "/" + token
}
fun getUrlForOpenConversations(version: Int, baseUrl: String?): String {
return getUrlForApi(version, baseUrl) + "/listed-room"
}
@Suppress("LongParameterList")
fun getRetrofitBucketForCreateRoom(
version: Int,
baseUrl: String?,
roomType: String,
source: String?,
invite: String?,
conversationName: String?
): RetrofitBucket {
val retrofitBucket = RetrofitBucket()
retrofitBucket.url = getUrlForRooms(version, baseUrl)
val queryMap: MutableMap<String, String> = HashMap()
queryMap["roomType"] = roomType
if (invite != null) {
queryMap["invite"] = invite
}
if (source != null) {
queryMap["source"] = source
}
if (conversationName != null) {
queryMap["roomName"] = conversationName
}
retrofitBucket.queryMap = queryMap
return retrofitBucket
}
@JvmStatic
fun getRetrofitBucketForAddParticipant(
version: Int,
baseUrl: String?,
token: String?,
user: String
): RetrofitBucket {
val retrofitBucket = RetrofitBucket()
retrofitBucket.url = getUrlForParticipants(version, baseUrl, token)
val queryMap: MutableMap<String, String> = HashMap()
queryMap["newParticipant"] = user
retrofitBucket.queryMap = queryMap
return retrofitBucket
}
@JvmStatic
fun getRetrofitBucketForAddParticipantWithSource(
version: Int,
baseUrl: String?,
token: String?,
source: String,
id: String
): RetrofitBucket {
val retrofitBucket = getRetrofitBucketForAddParticipant(version, baseUrl, token, id)
retrofitBucket.queryMap?.put("source", source)
return retrofitBucket
}
fun getUrlForUserProfile(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/cloud/user"
}
fun getUrlForUserData(baseUrl: String, userId: String): String {
return "$baseUrl$OCS_API_VERSION/cloud/users/$userId"
}
fun getUrlForUserSettings(baseUrl: String): String {
// FIXME Introduce API version
return "$baseUrl$OCS_API_VERSION$SPREED_API_VERSION/settings/user"
}
fun getUrlPostfixForStatus(): String {
return "/status.php"
}
@JvmStatic
fun getUrlForAvatar(baseUrl: String?, name: String?, requestBigSize: Boolean): String {
val avatarSize = if (requestBigSize) AVATAR_SIZE_BIG else AVATAR_SIZE_SMALL
return baseUrl + "/index.php/avatar/" + Uri.encode(name) + "/" + avatarSize
}
@JvmStatic
fun getUrlForGuestAvatar(baseUrl: String?, name: String?, requestBigSize: Boolean): String {
val avatarSize = if (requestBigSize) AVATAR_SIZE_BIG else AVATAR_SIZE_SMALL
return baseUrl + "/index.php/avatar/guest/" + Uri.encode(name) + "/" + avatarSize
}
fun getUrlForConversationAvatar(version: Int, baseUrl: String?, token: String?): String {
return getUrlForRoom(version, baseUrl, token) + "/avatar"
}
fun getUrlForConversationAvatarWithVersion(
version: Int,
baseUrl: String?,
token: String?,
isDark: Boolean,
avatarVersion: String?
): String {
var isDarkString = ""
if (isDark) {
isDarkString = "/dark"
}
var avatarVersionString = ""
if (avatarVersion != null) {
avatarVersionString = "?avatarVersion=$avatarVersion"
}
return getUrlForRoom(version, baseUrl, token) + "/avatar" + isDarkString + avatarVersionString
}
@JvmStatic
fun getCredentials(username: String?, token: String?): String? {
return if (TextUtils.isEmpty(username) && TextUtils.isEmpty(token)) {
null
} else {
basic(username!!, token!!, StandardCharsets.UTF_8)
}
}
@JvmStatic
fun getUrlNextcloudPush(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/apps/notifications/api/v2/push"
}
@JvmStatic
fun getUrlPushProxy(): String {
return sharedApplication!!.applicationContext.resources.getString(R.string.nc_push_server_url) + "/devices"
}
// see https://github.com/nextcloud/notifications/blob/master/docs/ocs-endpoint-v2.md
fun getUrlForNcNotificationWithId(baseUrl: String, notificationId: String): String {
return "$baseUrl$OCS_API_VERSION/apps/notifications/api/v2/notifications/$notificationId"
}
fun getUrlForSearchByNumber(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/cloud/users/search/by-phone"
}
fun getUrlForFileUpload(baseUrl: String, user: String, remotePath: String): String {
return "$baseUrl/remote.php/dav/files/$user$remotePath"
}
fun getUrlForChunkedUpload(baseUrl: String, user: String): String {
return "$baseUrl/remote.php/dav/uploads/$user"
}
fun getUrlForFileDownload(baseUrl: String, user: String, remotePath: String): String {
return "$baseUrl/remote.php/dav/files/$user/$remotePath"
}
fun getUrlForTempAvatar(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/apps/spreed/temp-user-avatar"
}
fun getUrlForUserFields(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/cloud/user/fields"
}
fun getUrlToSendLocation(version: Int, baseUrl: String?, roomToken: String): String {
return getUrlForChat(version, baseUrl, roomToken) + "/share"
}
fun getUrlForHoverCard(baseUrl: String, userId: String): String {
return baseUrl + OCS_API_VERSION +
"/hovercard/v1/" + userId
}
fun getUrlForChatReadMarker(version: Int, baseUrl: String?, roomToken: String): String {
return getUrlForChat(version, baseUrl, roomToken) + "/read"
}
/*
* OCS Status API
*/
@JvmStatic
fun getUrlForStatus(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/apps/user_status/api/v1/user_status"
}
fun getUrlForSetStatusType(baseUrl: String): String {
return getUrlForStatus(baseUrl) + "/status"
}
fun getUrlForPredefinedStatuses(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/apps/user_status/api/v1/predefined_statuses"
}
fun getUrlForStatusMessage(baseUrl: String): String {
return getUrlForStatus(baseUrl) + "/message"
}
fun getUrlForSetCustomStatus(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/apps/user_status/api/v1/user_status/message/custom"
}
fun getUrlForSetPredefinedStatus(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/apps/user_status/api/v1/user_status/message/predefined"
}
fun getUrlForUserStatuses(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/apps/user_status/api/v1/statuses"
}
fun getUrlForMessageReaction(baseUrl: String, roomToken: String, messageId: String): String {
return "$baseUrl$OCS_API_VERSION$SPREED_API_VERSION/reaction/$roomToken/$messageId"
}
fun getUrlForUnifiedSearch(baseUrl: String, providerId: String): String {
return "$baseUrl$OCS_API_VERSION/search/providers/$providerId/search"
}
fun getUrlForPoll(baseUrl: String, roomToken: String, pollId: String): String {
return getUrlForPoll(baseUrl, roomToken) + "/" + pollId
}
fun getUrlForPoll(baseUrl: String, roomToken: String): String {
return "$baseUrl$OCS_API_VERSION$SPREED_API_VERSION/poll/$roomToken"
}
@JvmStatic
fun getUrlForMessageExpiration(version: Int, baseUrl: String?, token: String?): String {
return getUrlForRoom(version, baseUrl, token) + "/message-expiration"
}
fun getUrlForOpenGraph(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/references/resolve"
}
fun getUrlForRecording(version: Int, baseUrl: String?, token: String): String {
return getUrlForApi(version, baseUrl) + "/recording/" + token
}
fun getUrlForRequestAssistance(version: Int, baseUrl: String?, token: String): String {
return getUrlForApi(version, baseUrl) + "/breakout-rooms/" + token + "/request-assistance"
}
fun getUrlForConversationDescription(version: Int, baseUrl: String?, token: String?): String {
return getUrlForRoom(version, baseUrl, token) + "/description"
}
fun getUrlForTranslation(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/translation/translate"
}
fun getUrlForLanguages(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/translation/languages"
}
fun getUrlForReminder(user: User, roomToken: String, messageId: String, version: Int): String {
val url = getUrlForChatMessage(version, user.baseUrl!!, roomToken, messageId)
return "$url/reminder"
}
fun getUrlForRecordingConsent(version: Int, baseUrl: String?, token: String?): String {
return getUrlForRoom(version, baseUrl, token) + "/recording-consent"
}
fun getUrlForInvitation(baseUrl: String): String {
return baseUrl + OCS_API_VERSION + SPREED_API_VERSION + "/federation/invitation"
}
fun getUrlForInvitationAccept(baseUrl: String, id: Int): String {
return getUrlForInvitation(baseUrl) + "/" + id
}
fun getUrlForInvitationReject(baseUrl: String, id: Int): String {
return getUrlForInvitation(baseUrl) + "/" + id
}
@JvmStatic
fun getUrlForRoomCapabilities(version: Int, baseUrl: String?, token: String?): String {
return getUrlForRooms(version, baseUrl) + "/" + token + "/capabilities"
}
}

View file

@ -0,0 +1,275 @@
/*
* Nextcloud Talk application
*
* @author Andy Scherzinger
* @author Mario Danic
* @author Marcel Hibbe
* Copyright (C) 2023-2024 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2021 Andy Scherzinger (info@andy-scherzinger.de)
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* 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.utils
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
enum class SpreedFeatures(val value: String) {
RECORDING_V1("recording-v1"),
REACTIONS("reactions"),
RAISE_HAND("raise-hand"),
DIRECT_MENTION_FLAG("direct-mention-flag"),
CONVERSATION_CALL_FLAGS("conversation-call-flags"),
SILENT_SEND("silent-send"),
MENTION_FLAG("mention-flag"),
DELETE_MESSAGES("delete-messages"),
READ_ONLY_ROOMS("read-only-rooms"),
RICH_OBJECT_LIST_MEDIA("rich-object-list-media"),
SILENT_CALL("silent-call"),
MESSAGE_EXPIRATION("message-expiration"),
WEBINARY_LOBBY("webinary-lobby"),
VOICE_MESSAGE_SHARING("voice-message-sharing"),
INVITE_GROUPS_AND_MAILS("invite-groups-and-mails"),
CIRCLES_SUPPORT("circles-support"),
LAST_ROOM_ACTIVITY("last-room-activity"),
NOTIFICATION_LEVELS("notification-levels"),
CLEAR_HISTORY("clear-history"),
AVATAR("avatar"),
LISTABLE_ROOMS("listable-rooms"),
LOCKED_ONE_TO_ONE_ROOMS("locked-one-to-one-rooms"),
TEMP_USER_AVATAR_API("temp-user-avatar-api"),
PHONEBOOK_SEARCH("phonebook-search"),
GEO_LOCATION_SHARING("geo-location-sharing"),
TALK_POLLS("talk-polls")
}
@Suppress("TooManyFunctions")
object CapabilitiesUtil {
//region Version checks
fun isServerEOL(serverVersion: Int): Boolean {
return (serverVersion < SERVER_VERSION_MIN_SUPPORTED)
}
fun isServerAlmostEOL(serverVersion: Int): Boolean {
return (serverVersion < SERVER_VERSION_SUPPORT_WARNING)
}
// endregion
//region CoreCapabilities
@JvmStatic
fun isLinkPreviewAvailable(user: User): Boolean {
return user.capabilities?.coreCapability?.referenceApi != null &&
user.capabilities?.coreCapability?.referenceApi == "true"
}
// endregion
//region SpreedCapabilities
@JvmStatic
fun hasSpreedFeatureCapability(spreedCapabilities: SpreedCapability, spreedFeatures: SpreedFeatures): Boolean {
if (spreedCapabilities.features != null) {
return spreedCapabilities.features!!.contains(spreedFeatures.value)
}
return false
}
@JvmStatic
@Deprecated("Add your capability to Capability enums and use hasSpreedFeatureCapability with enum.")
fun hasSpreedFeatureCapability(spreedCapabilities: SpreedCapability, capabilityName: String): Boolean {
if (spreedCapabilities.features != null) {
return spreedCapabilities.features!!.contains(capabilityName)
}
return false
}
fun getMessageMaxLength(spreedCapabilities: SpreedCapability): Int {
if (spreedCapabilities.config?.containsKey("chat") == true) {
val chatConfigHashMap = spreedCapabilities.config!!["chat"]
if (chatConfigHashMap?.containsKey("max-length") == true) {
val chatSize = (chatConfigHashMap["max-length"]!!.toString()).toInt()
return if (chatSize > 0) {
chatSize
} else {
DEFAULT_CHAT_SIZE
}
}
}
return DEFAULT_CHAT_SIZE
}
fun isReadStatusAvailable(spreedCapabilities: SpreedCapability): Boolean {
if (spreedCapabilities.config?.containsKey("chat") == true) {
val map: Map<String, Any>? = spreedCapabilities.config!!["chat"]
return map != null && map.containsKey("read-privacy")
}
return false
}
@JvmStatic
fun isCallRecordingAvailable(spreedCapabilities: SpreedCapability): Boolean {
if (hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.RECORDING_V1) &&
spreedCapabilities.config?.containsKey("call") == true
) {
val map: Map<String, Any>? = spreedCapabilities.config!!["call"]
if (map != null && map.containsKey("recording")) {
return (map["recording"].toString()).toBoolean()
}
}
return false
}
@JvmStatic
fun getAttachmentFolder(spreedCapabilities: SpreedCapability): String {
if (spreedCapabilities.config?.containsKey("attachments") == true) {
val map = spreedCapabilities.config!!["attachments"]
if (map?.containsKey("folder") == true) {
return map["folder"].toString()
}
}
return "/Talk"
}
fun isConversationDescriptionEndpointAvailable(spreedCapabilities: SpreedCapability): Boolean {
return hasSpreedFeatureCapability(spreedCapabilities, "room-description")
}
fun isUnifiedSearchAvailable(spreedCapabilities: SpreedCapability): Boolean {
return hasSpreedFeatureCapability(spreedCapabilities, "unified-search")
}
fun isAbleToCall(spreedCapabilities: SpreedCapability): Boolean {
return if (
spreedCapabilities.config?.containsKey("call") == true &&
spreedCapabilities.config!!["call"] != null &&
spreedCapabilities.config!!["call"]!!.containsKey("enabled")
) {
java.lang.Boolean.parseBoolean(spreedCapabilities.config!!["call"]!!["enabled"].toString())
} else {
// older nextcloud versions without the capability can't disable the calls
true
}
}
fun isCallReactionsSupported(user: User?): Boolean {
if (user?.capabilities != null) {
val capabilities = user.capabilities
return capabilities?.spreedCapability?.config?.containsKey("call") == true &&
capabilities.spreedCapability!!.config!!["call"] != null &&
capabilities.spreedCapability!!.config!!["call"]!!.containsKey("supported-reactions")
}
return false
}
fun isTranslationsSupported(spreedCapabilities: SpreedCapability): Boolean {
return spreedCapabilities.config?.containsKey("chat") == true &&
spreedCapabilities.config!!["chat"] != null &&
spreedCapabilities.config!!["chat"]!!.containsKey("has-translation-providers") &&
spreedCapabilities.config!!["chat"]!!["has-translation-providers"] == true
}
fun getRecordingConsentType(spreedCapabilities: SpreedCapability): Int {
if (
spreedCapabilities.config?.containsKey("call") == true &&
spreedCapabilities.config!!["call"] != null &&
spreedCapabilities.config!!["call"]!!.containsKey("recording-consent")
) {
return when (
spreedCapabilities.config!!["call"]!!["recording-consent"].toString()
.toInt()
) {
1 -> RECORDING_CONSENT_REQUIRED
2 -> RECORDING_CONSENT_DEPEND_ON_CONVERSATION
else -> RECORDING_CONSENT_NOT_REQUIRED
}
}
return RECORDING_CONSENT_NOT_REQUIRED
}
// endregion
//region SpreedCapabilities that can't be used with federation as the settings for them are global
fun isReadStatusPrivate(user: User): Boolean {
if (user.capabilities?.spreedCapability?.config?.containsKey("chat") == true) {
val map = user.capabilities!!.spreedCapability!!.config!!["chat"]
if (map?.containsKey("read-privacy") == true) {
return (map["read-privacy"]!!.toString()).toInt() == 1
}
}
return false
}
fun isTypingStatusAvailable(user: User): Boolean {
if (user.capabilities?.spreedCapability?.config?.containsKey("chat") == true) {
val map = user.capabilities!!.spreedCapability!!.config!!["chat"]
return map != null && map.containsKey("typing-privacy")
}
return false
}
fun isTypingStatusPrivate(user: User): Boolean {
if (user.capabilities?.spreedCapability?.config?.containsKey("chat") == true) {
val map = user.capabilities!!.spreedCapability!!.config!!["chat"]
if (map?.containsKey("typing-privacy") == true) {
return (map["typing-privacy"]!!.toString()).toInt() == 1
}
}
return false
}
// endregion
//region ThemingCapabilities
fun getServerName(user: User?): String? {
if (user?.capabilities?.themingCapability != null) {
return user.capabilities!!.themingCapability!!.name
}
return ""
}
// endregion
//region ProvisioningCapabilities
fun canEditScopes(user: User): Boolean {
return user.capabilities?.provisioningCapability?.accountPropertyScopesVersion != null &&
user.capabilities!!.provisioningCapability!!.accountPropertyScopesVersion!! > 1
}
// endregion
//region UserStatusCapabilities
@JvmStatic
fun isUserStatusAvailable(user: User): Boolean {
return user.capabilities?.userStatusCapability?.enabled == true &&
user.capabilities?.userStatusCapability?.supportsEmoji == true
}
// endregion
const val DEFAULT_CHAT_SIZE = 1000
const val RECORDING_CONSENT_NOT_REQUIRED = 0
const val RECORDING_CONSENT_REQUIRED = 1
const val RECORDING_CONSENT_DEPEND_ON_CONVERSATION = 2
private const val SERVER_VERSION_MIN_SUPPORTED = 14
private const val SERVER_VERSION_SUPPORT_WARNING = 18
}

View file

@ -1,10 +1,9 @@
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
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
/*
* Nextcloud Talk application
@ -45,28 +44,28 @@ object ConversationUtils {
ParticipantType.MODERATOR == conversation.participantType
}
private fun isLockedOneToOne(conversation: ConversationModel, conversationUser: User): Boolean {
fun isLockedOneToOne(conversation: ConversationModel, spreedCapabilities: SpreedCapability): Boolean {
return conversation.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL &&
CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "locked-one-to-one-rooms")
CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, "locked-one-to-one-rooms")
}
fun canModerate(conversation: ConversationModel, conversationUser: User): Boolean {
fun canModerate(conversation: ConversationModel, spreedCapabilities: SpreedCapability): Boolean {
return isParticipantOwnerOrModerator(conversation) &&
!isLockedOneToOne(conversation, conversationUser) &&
!isLockedOneToOne(conversation, spreedCapabilities) &&
conversation.type != ConversationType.FORMER_ONE_TO_ONE &&
!isNoteToSelfConversation(conversation)
}
fun isLobbyViewApplicable(conversation: ConversationModel, conversationUser: User): Boolean {
return !canModerate(conversation, conversationUser) &&
fun isLobbyViewApplicable(conversation: ConversationModel, spreedCapabilities: SpreedCapability): Boolean {
return !canModerate(conversation, spreedCapabilities) &&
(
conversation.type == ConversationType.ROOM_GROUP_CALL ||
conversation.type == ConversationType.ROOM_PUBLIC_CALL
)
}
fun isNameEditable(conversation: ConversationModel, conversationUser: User): Boolean {
return canModerate(conversation, conversationUser) &&
fun isNameEditable(conversation: ConversationModel, spreedCapabilities: SpreedCapability): Boolean {
return canModerate(conversation, spreedCapabilities) &&
ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL != conversation.type
}
@ -79,12 +78,12 @@ object ConversationUtils {
}
}
fun canDelete(conversation: ConversationModel, conversationUser: User): Boolean {
fun canDelete(conversation: ConversationModel, spreedCapability: SpreedCapability): Boolean {
return if (conversation.canDeleteConversation != null) {
// Available since APIv2
conversation.canDeleteConversation!!
} else {
canModerate(conversation, conversationUser)
canModerate(conversation, spreedCapability)
// Fallback for APIv1
}
}

View file

@ -61,7 +61,6 @@ import com.nextcloud.talk.utils.MimetypeUtils.isGif
import com.nextcloud.talk.utils.MimetypeUtils.isMarkdown
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACCOUNT
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FILE_ID
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import java.io.File
import java.util.concurrent.ExecutionException
@ -308,7 +307,7 @@ class FileViewerUtils(private val context: Context, private val user: User) {
.putString(DownloadFileToCacheWorker.KEY_USER_ID, user.userId)
.putString(
DownloadFileToCacheWorker.KEY_ATTACHMENT_FOLDER,
CapabilitiesUtilNew.getAttachmentFolder(user)
CapabilitiesUtil.getAttachmentFolder(user.capabilities!!.spreedCapability!!)
)
.putString(DownloadFileToCacheWorker.KEY_FILE_NAME, fileInfo.fileName)
.putString(DownloadFileToCacheWorker.KEY_FILE_PATH, path)

View file

@ -22,22 +22,21 @@
package com.nextcloud.talk.utils
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
/**
* see https://nextcloud-talk.readthedocs.io/en/latest/constants/#attendee-permissions
*/
class ParticipantPermissions(
private val user: User,
private val spreedCapabilities: SpreedCapability,
private val conversation: ConversationModel
) {
@Deprecated("Use ChatRepository.ConversationModel")
constructor(user: User, conversation: Conversation) : this(
user,
constructor(spreedCapabilities: SpreedCapability, conversation: Conversation) : this(
spreedCapabilities,
ConversationModel.mapToConversationModel(conversation)
)
@ -52,8 +51,8 @@ class ParticipantPermissions(
private val hasChatPermission = (conversation.permissions and CHAT) == CHAT
private fun hasConversationPermissions(): Boolean {
return CapabilitiesUtilNew.hasSpreedFeatureCapability(
user,
return CapabilitiesUtil.hasSpreedFeatureCapability(
spreedCapabilities,
"conversation-permissions"
)
}
@ -91,7 +90,7 @@ class ParticipantPermissions(
}
fun hasChatPermission(): Boolean {
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "chat-permission")) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, "chat-permission")) {
return hasChatPermission
}
// if capability is not available then the spreed version doesn't support to restrict this

View file

@ -238,7 +238,7 @@ class PushUtils {
val credentials = ApiUtils.getCredentials(user.username, user.token)
ncApi.registerDeviceForNotificationsWithNextcloud(
credentials,
ApiUtils.getUrlNextcloudPush(user.baseUrl),
ApiUtils.getUrlNextcloudPush(user.baseUrl!!),
nextcloudRegisterPushMap
)
.subscribe(object : Observer<PushRegistrationOverall> {

View file

@ -53,8 +53,8 @@ object RemoteFileUtils {
return ncApi.checkIfFileExists(
ApiUtils.getCredentials(currentUser.username, currentUser.token),
ApiUtils.getUrlForFileUpload(
currentUser.baseUrl,
currentUser.userId,
currentUser.baseUrl!!,
currentUser.userId!!,
remotePath
)
)

View file

@ -22,10 +22,10 @@ package com.nextcloud.talk.utils
import android.content.Context
import com.nextcloud.talk.R
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.domain.ConversationModel
object ShareUtils {
fun getStringForIntent(context: Context, user: User, conversation: Conversation?): String {
fun getStringForIntent(context: Context, user: User, conversation: ConversationModel?): String {
return String.format(
context.resources.getString(R.string.nc_share_text),
user.baseUrl,

View file

@ -89,4 +89,5 @@ object BundleKeys {
const val KEY_REAUTHORIZE_ACCOUNT = "KEY_REAUTHORIZE_ACCOUNT"
const val KEY_PASSWORD = "KEY_PASSWORD"
const val KEY_REMOTE_TALK_SHARE = "KEY_REMOTE_TALK_SHARE"
const val KEY_CHAT_API_VERSION = "KEY_CHAT_API_VERSION"
}

View file

@ -1,267 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Andy Scherzinger
* @author Mario Danic
* Copyright (C) 2021 Andy Scherzinger (info@andy-scherzinger.de)
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* 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.utils.database.user
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.capabilities.Capabilities
@Suppress("TooManyFunctions")
object CapabilitiesUtilNew {
fun hasNotificationsCapability(user: User, capabilityName: String): Boolean {
return user.capabilities?.spreedCapability?.features?.contains(capabilityName) == true
}
fun hasExternalCapability(user: User, capabilityName: String?): Boolean {
if (user.capabilities?.externalCapability?.containsKey("v1") == true) {
return user.capabilities!!.externalCapability!!["v1"]?.contains(capabilityName!!) == true
}
return false
}
@JvmStatic
fun isServerEOL(capabilities: Capabilities?): Boolean {
// Capability is available since Talk 4 => Nextcloud 14 => Autmn 2018
return !hasSpreedFeatureCapability(capabilities, "no-ping")
}
fun isServerAlmostEOL(user: User): Boolean {
// Capability is available since Talk 8 => Nextcloud 18 => January 2020
return !hasSpreedFeatureCapability(user, "chat-replies")
}
fun canSetChatReadMarker(user: User): Boolean {
return hasSpreedFeatureCapability(user, "chat-read-marker")
}
fun canMarkRoomAsUnread(user: User): Boolean {
return hasSpreedFeatureCapability(user, "chat-unread")
}
@JvmStatic
fun hasSpreedFeatureCapability(user: User?, capabilityName: String): Boolean {
return hasSpreedFeatureCapability(user?.capabilities, capabilityName)
}
@JvmStatic
fun hasSpreedFeatureCapability(capabilities: Capabilities?, capabilityName: String): Boolean {
if (capabilities?.spreedCapability?.features != null) {
return capabilities.spreedCapability!!.features!!.contains(capabilityName)
}
return false
}
fun getMessageMaxLength(user: User?): Int {
if (user?.capabilities?.spreedCapability?.config?.containsKey("chat") == true) {
val chatConfigHashMap = user.capabilities!!.spreedCapability!!.config!!["chat"]
if (chatConfigHashMap?.containsKey("max-length") == true) {
val chatSize = (chatConfigHashMap["max-length"]!!.toString()).toInt()
return if (chatSize > 0) {
chatSize
} else {
DEFAULT_CHAT_SIZE
}
}
}
return DEFAULT_CHAT_SIZE
}
fun isPhoneBookIntegrationAvailable(user: User): Boolean {
return user.capabilities?.spreedCapability?.features?.contains("phonebook-search") == true
}
fun isReadStatusAvailable(user: User): Boolean {
if (user.capabilities?.spreedCapability?.config?.containsKey("chat") == true) {
val map: Map<String, Any>? = user.capabilities!!.spreedCapability!!.config!!["chat"]
return map != null && map.containsKey("read-privacy")
}
return false
}
fun isReadStatusPrivate(user: User): Boolean {
if (user.capabilities?.spreedCapability?.config?.containsKey("chat") == true) {
val map = user.capabilities!!.spreedCapability!!.config!!["chat"]
if (map?.containsKey("read-privacy") == true) {
return (map["read-privacy"]!!.toString()).toInt() == 1
}
}
return false
}
fun isTypingStatusAvailable(user: User): Boolean {
if (user.capabilities?.spreedCapability?.config?.containsKey("chat") == true) {
val map = user.capabilities!!.spreedCapability!!.config!!["chat"]
return map != null && map.containsKey("typing-privacy")
}
return false
}
fun isTypingStatusPrivate(user: User): Boolean {
if (user.capabilities?.spreedCapability?.config?.containsKey("chat") == true) {
val map = user.capabilities!!.spreedCapability!!.config!!["chat"]
if (map?.containsKey("typing-privacy") == true) {
return (map["typing-privacy"]!!.toString()).toInt() == 1
}
}
return false
}
@JvmStatic
fun isCallRecordingAvailable(user: User): Boolean {
if (hasSpreedFeatureCapability(user, "recording-v1") &&
user.capabilities?.spreedCapability?.config?.containsKey("call") == true
) {
val map: Map<String, Any>? = user.capabilities!!.spreedCapability!!.config!!["call"]
if (map != null && map.containsKey("recording")) {
return (map["recording"].toString()).toBoolean()
}
}
return false
}
@JvmStatic
fun isUserStatusAvailable(user: User): Boolean {
return user.capabilities?.userStatusCapability?.enabled == true &&
user.capabilities?.userStatusCapability?.supportsEmoji == true
}
@JvmStatic
fun getAttachmentFolder(user: User): String {
if (user.capabilities?.spreedCapability?.config?.containsKey("attachments") == true) {
val map = user.capabilities!!.spreedCapability!!.config!!["attachments"]
if (map?.containsKey("folder") == true) {
return map["folder"].toString()
}
}
return "/Talk"
}
fun getServerName(user: User?): String? {
if (user?.capabilities?.themingCapability != null) {
return user.capabilities!!.themingCapability!!.name
}
return ""
}
// TODO later avatar can also be checked via user fields, for now it is in Talk capability
fun isAvatarEndpointAvailable(user: User): Boolean {
return user.capabilities?.spreedCapability?.features?.contains("temp-user-avatar-api") == true
}
fun isConversationAvatarEndpointAvailable(user: User): Boolean {
return user.capabilities?.spreedCapability?.features?.contains("avatar") == true
}
fun isConversationDescriptionEndpointAvailable(user: User): Boolean {
return user.capabilities?.spreedCapability?.features?.contains("room-description") == true
}
fun canEditScopes(user: User): Boolean {
return user.capabilities?.provisioningCapability?.accountPropertyScopesVersion != null &&
user.capabilities!!.provisioningCapability!!.accountPropertyScopesVersion!! > 1
}
fun isAbleToCall(user: User?): Boolean {
if (user?.capabilities != null) {
val capabilities = user.capabilities
return if (
capabilities?.spreedCapability?.config?.containsKey("call") == true &&
capabilities.spreedCapability!!.config!!["call"] != null &&
capabilities.spreedCapability!!.config!!["call"]!!.containsKey("enabled")
) {
java.lang.Boolean.parseBoolean(capabilities.spreedCapability!!.config!!["call"]!!["enabled"].toString())
} else {
// older nextcloud versions without the capability can't disable the calls
true
}
}
return false
}
fun isCallReactionsSupported(user: User?): Boolean {
if (user?.capabilities != null) {
val capabilities = user.capabilities
return capabilities?.spreedCapability?.config?.containsKey("call") == true &&
capabilities.spreedCapability!!.config!!["call"] != null &&
capabilities.spreedCapability!!.config!!["call"]!!.containsKey("supported-reactions")
}
return false
}
@JvmStatic
fun isUnifiedSearchAvailable(user: User): Boolean {
return hasSpreedFeatureCapability(user, "unified-search")
}
@JvmStatic
fun isLinkPreviewAvailable(user: User): Boolean {
return user.capabilities?.coreCapability?.referenceApi != null &&
user.capabilities?.coreCapability?.referenceApi == "true"
}
fun isTranslationsSupported(user: User?): Boolean {
if (user?.capabilities != null) {
val capabilities = user.capabilities
return capabilities?.spreedCapability?.config?.containsKey("chat") == true &&
capabilities.spreedCapability!!.config!!["chat"] != null &&
capabilities.spreedCapability!!.config!!["chat"]!!.containsKey("has-translation-providers") &&
capabilities.spreedCapability!!.config!!["chat"]!!["has-translation-providers"] == true
}
return false
}
fun isRemindSupported(user: User?): Boolean {
if (user?.capabilities != null) {
val capabilities = user.capabilities
return capabilities?.spreedCapability?.features?.contains("remind-me-later") == true
}
return false
}
fun getRecordingConsentType(user: User?): Int {
if (user?.capabilities != null) {
val capabilities = user.capabilities
if (
capabilities?.spreedCapability?.config?.containsKey("call") == true &&
capabilities.spreedCapability!!.config!!["call"] != null &&
capabilities.spreedCapability!!.config!!["call"]!!.containsKey("recording-consent")
) {
return when (
capabilities.spreedCapability!!.config!!["call"]!!["recording-consent"].toString()
.toInt()
) {
1 -> RECORDING_CONSENT_REQUIRED
2 -> RECORDING_CONSENT_DEPEND_ON_CONVERSATION
else -> RECORDING_CONSENT_NOT_REQUIRED
}
}
}
return RECORDING_CONSENT_NOT_REQUIRED
}
const val DEFAULT_CHAT_SIZE = 1000
const val RECORDING_CONSENT_NOT_REQUIRED = 0
const val RECORDING_CONSENT_REQUIRED = 1
const val RECORDING_CONSENT_DEPEND_ON_CONVERSATION = 2
}

View file

@ -35,7 +35,7 @@ import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.models.json.generic.GenericOverall;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.UserIdUtils;
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew;
import com.nextcloud.talk.utils.CapabilitiesUtil;
import javax.inject.Inject;
@ -158,7 +158,8 @@ public class DatabaseStorageModule {
});
} else if ("conversation_info_message_notifications_dropdown".equals(key)) {
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "notification-levels")) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(conversationUser.getCapabilities().getSpreedCapability(), "notification" +
"-levels")) {
if (TextUtils.isEmpty(messageNotificationLevel) || !messageNotificationLevel.equals(value)) {
int intValue;
switch (value) {
@ -175,7 +176,7 @@ public class DatabaseStorageModule {
intValue = 0;
}
int apiVersion = ApiUtils.getConversationApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1});
int apiVersion = ApiUtils.getConversationApiVersion(conversationUser, new int[]{ApiUtils.API_V4, 1});
ncApi.setNotificationLevel(ApiUtils.getCredentials(conversationUser.getUsername(),
conversationUser.getToken()),

View file

@ -25,7 +25,6 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.nextcloud.talk.activities.CallActivity.Companion.TAG
import com.nextcloud.talk.location.GeocodingActivity
import fr.dudie.nominatim.client.TalkJsonNominatimClient
import fr.dudie.nominatim.model.Address
import kotlinx.coroutines.CoroutineScope
@ -70,9 +69,9 @@ class GeoCodingViewModel : ViewModel() {
try {
val results = nominatimClient.search(query) as ArrayList<Address>
for (address in results) {
Log.d(GeocodingActivity.TAG, address.displayName)
Log.d(GeocodingActivity.TAG, address.latitude.toString())
Log.d(GeocodingActivity.TAG, address.longitude.toString())
Log.d(TAG, address.displayName)
Log.d(TAG, address.latitude.toString())
Log.d(TAG, address.longitude.toString())
}
geocodingResults = results
geocodingResultsLiveData.postValue(results)

View file

@ -113,7 +113,7 @@ public class WebSocketConnectionHelper {
}
HelloOverallWebSocketMessage getAssembledHelloModel(User user, String ticket) {
int apiVersion = ApiUtils.getSignalingApiVersion(user, new int[]{ApiUtils.APIv3, 2, 1});
int apiVersion = ApiUtils.getSignalingApiVersion(user, new int[]{ApiUtils.API_V3, 2, 1});
HelloOverallWebSocketMessage helloOverallWebSocketMessage = new HelloOverallWebSocketMessage();
helloOverallWebSocketMessage.setType("hello");

View file

@ -22,7 +22,7 @@
package com.nextcloud.talk.utils
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.conversations.Conversation
import junit.framework.TestCase
import org.junit.Test
@ -31,7 +31,7 @@ class ParticipantPermissionsTest : TestCase() {
@Test
fun test_areFlagsSet() {
val user = User()
val spreedCapability = SpreedCapability()
val conversation = Conversation()
conversation.permissions = ParticipantPermissions.PUBLISH_SCREEN or
ParticipantPermissions.JOIN_CALL or
@ -39,7 +39,7 @@ class ParticipantPermissionsTest : TestCase() {
val attendeePermissions =
ParticipantPermissions(
user,
spreedCapability,
conversation
)

View file

@ -23,7 +23,7 @@ import android.content.Context
import android.content.res.Resources
import com.nextcloud.talk.R
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.users.UserManager
import io.reactivex.Maybe
import org.junit.Assert
@ -49,19 +49,19 @@ class ShareUtilsTest {
private val baseUrl = "https://my.nextcloud.com"
private val token = "2aotbrjr"
private lateinit var conversation: Conversation
private lateinit var conversation: ConversationModel
@Before
fun setUp() {
MockitoAnnotations.openMocks(this)
Mockito.`when`(userManager!!.currentUser).thenReturn(Maybe.just(user))
Mockito.`when`(user!!.baseUrl).thenReturn(baseUrl)
Mockito.`when`(user!!.baseUrl!!).thenReturn(baseUrl)
Mockito.`when`(context!!.resources).thenReturn(resources)
Mockito.`when`(resources!!.getString(R.string.nc_share_text))
.thenReturn("Join the conversation at %1\$s/index.php/call/%2\$s")
Mockito.`when`(resources.getString(R.string.nc_share_text_pass)).thenReturn("\nPassword: %1\$s")
conversation = Conversation(token = token)
conversation = ConversationModel(token = token)
}
@Test

View file

@ -1,5 +1,5 @@
build:
maxIssues: 116
maxIssues: 122
weights:
# complexity: 2
# LongParameterList: 1

View file

@ -1313,8 +1313,6 @@ lQyC8nl8P5PgkEZ5CHcGymZlpzihR3ECrPJTk39Sb7D3SxCW4WrChV3kVfmLgvc=
-----END PGP PUBLIC KEY BLOCK-----
pub C21CE653B639E41A
uid Eric Kuck <eric@bluelinelabs.com>
sub 4F80368F9034B8D0
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: BCPG v1.68
@ -1324,21 +1322,20 @@ aBF7dud1bzw7voZo5ieGK923wUB+R9vQYd5DYfNLBHj9/TrTVCfKfUIeeEQRZYBz
ufYcDwi4uVx9VPj2wRhkK+lzxphvosJCNFK8Vn82oY7eHQ1RA4AEhCeE/hz8maq6
NPoOPjpEN0DVnPIYdjPsdqd4UKQzkX/wMOxghz8SdcVROzUoL+9pZzx968OFuGrV
lUD0su37S6To1IUn6WNEuy1uJTzT3Zqi0hfm31AqPxlLWDOwnuKvUJl3RObyli2k
CBtDa5xPE6GU6ZUEFUZ7qbk7iV5p8uTchKVbABEBAAG0IUVyaWMgS3VjayA8ZXJp
Y0BibHVlbGluZWxhYnMuY29tPrkBDQRU5ChoAQgAxC44rEZjgnJevvrzdL5vCVqC
1WI9cZ7L8DwF/pvm7NbRKC5GgXigul18UET80q4E3WIi0tTMG+pVWO+1v0dEu/Re
B/l+hc76iwJjOlwSiQ1jvq6/q0Nhne0/0khSYNWyd0AwJ2VZktcD93dJV4EqTm1O
Ck1gigAd/GN5wslQkMST/nUYUGm4cA/RZVSA8PSFZDZ2CxHyRyHgaOQNBUmWG2gf
ExUrrJA26iKowkNqZXWegnzYwlf8ZRE6MZM0JPLOUw/+r4ybI8ny+/U55s2sm0XZ
CcJvNda5N3SoaC/OgGWZFx1s9UksN7MmvhznaSUMeaeVFbGC3nu9dsQhV9RxMwAR
AQABiQEfBBgBAgAJBQJU5ChoAhsMAAoJEMIc5lO2OeQadSwH/07x1foZKkFRGMlj
wCmofKGSqZ9fu6ueOIV6fwHjrhlfkSyKN+96xbjhwIvWhKdSmWP/AsUqRDD/mTHw
ZMdlgmdXkGEvvCuJDL5FlQzl9OWeeplfVhLysx6dzj/G8AUXlfEIGBvb8Q56d5dK
MpId4H4vt+YIzS8x/ry+QTTDJAOu1cVJfwoX34yMcZ+IHTzly2XKi4zQ41DyfrgY
lCodWna10RtBdPZY41Jf4xSezX2q7KZBXXRgyVNYu3dDuNzhJAJ6jy7eMcb6urK6
n73cz5uZPmWIbp4cAecZB0BfMj/PW37dK0oYdWKLxaDwpxvIV7T45Y64Md2FCC+d
nC2Xh7c=
=kzY9
CBtDa5xPE6GU6ZUEFUZ7qbk7iV5p8uTchKVbABEBAAG5AQ0EVOQoaAEIAMQuOKxG
Y4JyXr7683S+bwlagtViPXGey/A8Bf6b5uzW0SguRoF4oLpdfFBE/NKuBN1iItLU
zBvqVVjvtb9HRLv0Xgf5foXO+osCYzpcEokNY76uv6tDYZ3tP9JIUmDVsndAMCdl
WZLXA/d3SVeBKk5tTgpNYIoAHfxjecLJUJDEk/51GFBpuHAP0WVUgPD0hWQ2dgsR
8kch4GjkDQVJlhtoHxMVK6yQNuoiqMJDamV1noJ82MJX/GUROjGTNCTyzlMP/q+M
myPJ8vv1OebNrJtF2QnCbzXWuTd0qGgvzoBlmRcdbPVJLDezJr4c52klDHmnlRWx
gt57vXbEIVfUcTMAEQEAAYkBHwQYAQIACQUCVOQoaAIbDAAKCRDCHOZTtjnkGnUs
B/9O8dX6GSpBURjJY8ApqHyhkqmfX7urnjiFen8B464ZX5EsijfvesW44cCL1oSn
Uplj/wLFKkQw/5kx8GTHZYJnV5BhL7wriQy+RZUM5fTlnnqZX1YS8rMenc4/xvAF
F5XxCBgb2/EOeneXSjKSHeB+L7fmCM0vMf68vkE0wyQDrtXFSX8KF9+MjHGfiB08
5ctlyouM0ONQ8n64GJQqHVp2tdEbQXT2WONSX+MUns19quymQV10YMlTWLt3Q7jc
4SQCeo8u3jHG+rqyup+93M+bmT5liG6eHAHnGQdAXzI/z1t+3StKGHVii8Wg8Kcb
yFe0+OWOuDHdhQgvnZwtl4e3
=DwNF
-----END PGP PUBLIC KEY BLOCK-----
pub C488A74FCAE540C6
@ -1674,43 +1671,6 @@ fW1AkBVEk6siyL8PXfxmj9ev3H9xiQVLyJ6HpdHTLVjHjFkgNOLd
=R7zg
-----END PGP PUBLIC KEY BLOCK-----
pub D041CAD2E452550F
uid Deanna <deannagarcia@google.com>
sub 5199F3DAE89C332D
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: BCPG v1.68
mQGNBGCtdhoBDADdopjDt4eUNEqLJSw1ZICSR0oq09SOVtJSaSYdF8UiXjBfL1Ds
fhTDqSv5pT2a2gLj0OU3tFhWHvINLaKKCjQnHVcFXi2LTxt+XBOjRYkFjHVisbaZ
PZ6HnTMStPrvs+hQ168vU3VfYOsOLN22j53I/Ba+FA7E0G0bqkratuT5L7BTR1mC
fqDaeisWSCllfe6EEysaFF+/1RcRy+Yt+8ZWV0FZEF7UwQvqKHcYmlkqPIn3v/8y
J/yvmzIEtCQ1F+bvJbzaROmeJf254G2Uh7IfMYEm9WlqnGwNdbIhil7bdxq8Y/0H
XbQPaESxkki7yL5JTfH/+UzdklMe+Dga273L/cgzfjV3zJJ9vR94W5ABAbGYh4ZW
aKvNnT1m4vTbEMfo4r3NF2zc+K9Ly/JNaHqkR5M4SVElvN2lsC5KNUiRvExhg+h0
mKyx61mu3gUIrC1UOmqhtx7RzQQf7ESMdzmNHY0P93lR0Ic10fyli0wfl7A6q7+q
zV2a1V2k9Yg6B9sAEQEAAbQgRGVhbm5hIDxkZWFubmFnYXJjaWFAZ29vZ2xlLmNv
bT65AY0EYK12GgEMAMgP3//QeBsTS3IrfSp3m44el96X6BWona2yo4DvVyuwqfUL
ZE+Nhj7I+kEZLrA29AOySOD/6quJ4MIJZfq/Do920Di8/10WQ00OdCM1wH7bMz2U
vcSqsr0iOgQtycuUf7JOHSTME9vqk+C3Lhn0r59AVaRdXEe6zBgNZyzZJeCr5F8w
RhglPlwvhOGs2aLEqlCxFnY4pLayQFoQyw1lDjHIXHg5JtfOHvqiNXVDcGpyKLG8
SzImp62iL4sfuA0weVIQeS9kZiQabSYKvSf3TvNXYTgmFz/vjPbYhv9LTkBroTlV
g3l+UmAxLrHVuXMx0zX3jfNNHAqUjVhPYZhnifMkmGJgLeMIVqr5Q/tx8pzyYiiO
cqQ1zDg8ubJDGRue1JjlUGdw19OvhFDs+lydukt8Mmhb0gPkBLi2syZHgYHtEooX
PLwEsJ+SynZCFhZiWj8BsWNFJpaDd8ynNeWhMAcwi3B5ZeQiZaAlV0sItxsrzvbu
4ZYZtkjAkQdsaaTWSwARAQABiQG8BBgBCgAmFiEEaWthmaKp2MKc54zA0EHK0uRS
VQ8FAmCtdhoCGwwFCQPCZwAACgkQ0EHK0uRSVQ+G7wwAvaVPDgnM+i2pGQPwq6Mk
SzhKEG4H1pvBWyYR8H9D3p/dE33IjVu3EEy1h37Nzdyp46KtASGNe3KBodSsh6gv
PlV5pNGxMNbX6fo8ZGtS83C+6uTF1cYmuO1nmi8P4+7qtcNZg4xv/ujAZIC20kem
YKDth3FvPxEXsoxY+Ns7sxgd3SqoyLhjcyoczI8uyhim5nfvvbnEd6WrdiBPBtb/
F1h/nfqdFj2TcZkAlnzGnlVlgU8J60u6zE+9VvBm0lJR73Ar55mQEwarGFPL1a3/
A7ZEeNa0Dc3Oa5sKMYtxMlGKZ0WGUoGcDWiaDEsv5YyRnaSOaXKM1NkJCR013QAr
RcHrRBPo+0/RIZVE+b8oEcmGzdL8HNwnm7e06ruZryF9LQA5YBmCKE0urigmgEvC
zZsj/fMJ+OIZcAhE7UVae48GpW2kLATxmK01oSzvizIlmN3rVz2EnjOun2iuuEpF
/lmDbjK5n1r3f8npB1l1fT5cozzQJkPVYzhBWH1KXP5X
=nh9O
-----END PGP PUBLIC KEY BLOCK-----
pub D364ABAA39A47320
sub 3F606403DCA455C8
-----BEGIN PGP PUBLIC KEY BLOCK-----

Some files were not shown because too many files have changed in this diff Show more