Add guests access preferences to conversation info

Currently a conversation can be made public via the bottom sheet menu in
the conversation list.

With this commit this is added to the conversation info to align with Talk web
and iOS. The functionality is removed from the bottom sheet menu in the
conversation list.

Resolves: #2134

Signed-off-by: Tim Krüger <t@timkrueger.me>
This commit is contained in:
Tim Krüger 2022-08-29 11:02:09 +02:00
parent 68eaa311b1
commit 84116e4cb2
No known key found for this signature in database
GPG Key ID: FECE3A7222C52A4E
21 changed files with 754 additions and 418 deletions

View File

@ -142,6 +142,9 @@ public interface NcApi {
Observable<AddParticipantOverall> addParticipant(@Header("Authorization") String authorization, @Url String url,
@QueryMap Map<String, String> options);
@POST
Observable<GenericOverall> resendParticipantInvitations(@Header("Authorization") String authorization,
@Url String url);
// also used for removing a guest from a conversation
@Deprecated
@ -313,6 +316,12 @@ public interface NcApi {
Observable<GenericOverall> setPassword(@Header("Authorization") String authorization, @Url String url,
@Field("password") String password);
@FormUrlEncoded
@PUT
Observable<Response<GenericOverall>> setPassword2(@Header("Authorization") String authorization,
@Url String url,
@Field("password") String password);
@GET
Observable<CapabilitiesOverall> getCapabilities(@Header("Authorization") String authorization, @Url String url);

View File

@ -6,7 +6,7 @@
* @author Tim Krüger
* @author Marcel Hibbe
* Copyright (C) 2022 Marcel Hibbe (dev@mhibbe.de)
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2021-2022 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2021 Andy Scherzinger (info@andy-scherzinger.de)
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
@ -37,6 +37,8 @@ import android.text.TextUtils
import android.util.Log
import android.view.MenuItem
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SwitchCompat
@ -61,6 +63,7 @@ import com.nextcloud.talk.controllers.base.BaseController
import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage
import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage
import com.nextcloud.talk.controllers.util.viewBinding
import com.nextcloud.talk.conversation.info.GuestAccessHelper
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ControllerConversationInfoBinding
import com.nextcloud.talk.events.EventStatus
@ -75,6 +78,7 @@ import com.nextcloud.talk.models.json.participants.Participant.ActorType.CIRCLES
import com.nextcloud.talk.models.json.participants.Participant.ActorType.GROUPS
import com.nextcloud.talk.models.json.participants.Participant.ActorType.USERS
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.DateConstants
@ -110,6 +114,9 @@ class ConversationInfoController(args: Bundle) :
@Inject
lateinit var ncApi: NcApi
@Inject
lateinit var conversationsRepository: ConversationsRepository
@Inject
lateinit var eventBus: EventBus
@ -167,6 +174,7 @@ class ConversationInfoController(args: Bundle) :
binding.notificationSettingsView.notificationSettings.setStorageModule(databaseStorageModule)
binding.webinarInfoView.webinarSettings.setStorageModule(databaseStorageModule)
binding.guestAccessView.guestAccessSettings.setStorageModule(databaseStorageModule)
binding.deleteConversationAction.setOnClickListener { showDeleteConversationDialog() }
binding.leaveConversationAction.setOnClickListener { leaveConversation() }
@ -176,7 +184,7 @@ class ConversationInfoController(args: Bundle) :
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "rich-object-list-media")) {
binding.showSharedItemsAction.setOnClickListener { showSharedItems() }
} else {
binding.categorySharedItems.visibility = View.GONE
binding.categorySharedItems.visibility = GONE
}
fetchRoomInfo()
@ -190,7 +198,9 @@ class ConversationInfoController(args: Bundle) :
listOf(
binding.webinarInfoView.conversationInfoLobby,
binding.notificationSettingsView.callNotifications,
binding.notificationSettingsView.conversationInfoPriorityConversation
binding.notificationSettingsView.conversationInfoPriorityConversation,
binding.guestAccessView.guestAccessAllowSwitch,
binding.guestAccessView.guestAccessPasswordSwitch
).forEach(viewThemeUtils::colorSwitchPreference)
}
}
@ -205,6 +215,7 @@ class ConversationInfoController(args: Bundle) :
ownOptions,
categorySharedItems,
categoryConversationSettings,
binding.guestAccessView.guestAccessCategory,
binding.webinarInfoView.conversationInfoWebinar,
binding.notificationSettingsView.notificationSettingsCategory
).forEach(viewThemeUtils::colorPreferenceCategory)
@ -225,7 +236,7 @@ class ConversationInfoController(args: Bundle) :
override fun onViewBound(view: View) {
super.onViewBound(view)
binding.addParticipantsAction.visibility = View.GONE
binding.addParticipantsAction.visibility = GONE
viewThemeUtils.colorCircularProgressBar(binding.progressBar)
}
@ -235,7 +246,7 @@ class ConversationInfoController(args: Bundle) :
webinaryRoomType(conversation!!) &&
conversation!!.canModerate(conversationUser!!)
) {
binding.webinarInfoView.webinarSettings.visibility = View.VISIBLE
binding.webinarInfoView.webinarSettings.visibility = VISIBLE
val isLobbyOpenToModeratorsOnly =
conversation!!.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY
@ -271,7 +282,7 @@ class ConversationInfoController(args: Bundle) :
submitLobbyChanges()
}
} else {
binding.webinarInfoView.webinarSettings.visibility = View.GONE
binding.webinarInfoView.webinarSettings.visibility = GONE
}
}
@ -311,9 +322,9 @@ class ConversationInfoController(args: Bundle) :
}
if (isChecked) {
binding.webinarInfoView.startTimePreferences.visibility = View.VISIBLE
binding.webinarInfoView.startTimePreferences.visibility = VISIBLE
} else {
binding.webinarInfoView.startTimePreferences.visibility = View.GONE
binding.webinarInfoView.startTimePreferences.visibility = GONE
}
}
@ -433,7 +444,7 @@ class ConversationInfoController(args: Bundle) :
setupAdapter()
binding.participantsListCategory.visibility = View.VISIBLE
binding.participantsListCategory.visibility = VISIBLE
adapter!!.updateDataSet(userItems)
}
@ -624,40 +635,40 @@ class ConversationInfoController(args: Bundle) :
val conversationCopy = conversation
if (conversationCopy!!.canModerate(conversationUser)) {
binding.addParticipantsAction.visibility = View.VISIBLE
binding.addParticipantsAction.visibility = VISIBLE
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "clear-history")) {
binding.clearConversationHistory.visibility = View.VISIBLE
binding.clearConversationHistory.visibility = VISIBLE
} else {
binding.clearConversationHistory.visibility = View.GONE
binding.clearConversationHistory.visibility = GONE
}
} else {
binding.addParticipantsAction.visibility = View.GONE
binding.clearConversationHistory.visibility = View.GONE
binding.addParticipantsAction.visibility = GONE
binding.clearConversationHistory.visibility = GONE
}
if (isAttached && (!isBeingDestroyed || !isDestroyed)) {
binding.ownOptions.visibility = View.VISIBLE
binding.ownOptions.visibility = VISIBLE
setupWebinaryView()
if (!conversation!!.canLeave()) {
binding.leaveConversationAction.visibility = View.GONE
binding.leaveConversationAction.visibility = GONE
} else {
binding.leaveConversationAction.visibility = View.VISIBLE
binding.leaveConversationAction.visibility = VISIBLE
}
if (!conversation!!.canDelete(conversationUser)) {
binding.deleteConversationAction.visibility = View.GONE
binding.deleteConversationAction.visibility = GONE
} else {
binding.deleteConversationAction.visibility = View.VISIBLE
binding.deleteConversationAction.visibility = VISIBLE
}
if (Conversation.ConversationType.ROOM_SYSTEM == conversation!!.type) {
binding.notificationSettingsView.callNotifications.visibility = View.GONE
binding.notificationSettingsView.callNotifications.visibility = GONE
}
if (conversation!!.notificationCalls === null) {
binding.notificationSettingsView.callNotifications.visibility = View.GONE
binding.notificationSettingsView.callNotifications.visibility = GONE
} else {
binding.notificationSettingsView.callNotifications.value =
conversationCopy.notificationCalls == 1
@ -665,22 +676,29 @@ class ConversationInfoController(args: Bundle) :
getListOfParticipants()
binding.progressBar.visibility = View.GONE
binding.progressBar.visibility = GONE
binding.conversationInfoName.visibility = View.VISIBLE
binding.conversationInfoName.visibility = VISIBLE
binding.displayNameText.text = conversation!!.displayName
if (conversation!!.description != null && !conversation!!.description!!.isEmpty()) {
binding.descriptionText.text = conversation!!.description
binding.conversationDescription.visibility = View.VISIBLE
binding.conversationDescription.visibility = VISIBLE
}
loadConversationAvatar()
adjustNotificationLevelUI()
initExpiringMessageOption()
binding.notificationSettingsView.notificationSettings.visibility = View.VISIBLE
GuestAccessHelper(
this@ConversationInfoController,
binding,
conversation!!,
conversationUser
).setupGuestAccess()
binding.notificationSettingsView.notificationSettings.visibility = VISIBLE
}
} catch (npe: NullPointerException) {
// view binding can be null

View File

@ -22,12 +22,6 @@ package com.nextcloud.talk.controllers.bottomsheet
enum class ConversationOperationEnum {
OPS_CODE_RENAME_ROOM,
OPS_CODE_MAKE_PUBLIC,
OPS_CODE_CHANGE_PASSWORD,
OPS_CODE_CLEAR_PASSWORD,
OPS_CODE_SET_PASSWORD,
OPS_CODE_SHARE_LINK,
OPS_CODE_MAKE_PRIVATE,
OPS_CODE_GET_AND_JOIN_ROOM,
OPS_CODE_INVITE_USERS,
OPS_CODE_MARK_AS_READ,

View File

@ -23,7 +23,6 @@
*/
package com.nextcloud.talk.controllers.bottomsheet
import android.content.ComponentName
import android.content.Intent
import android.content.res.ColorStateList
import android.os.Bundle
@ -47,7 +46,6 @@ import com.nextcloud.talk.controllers.util.viewBinding
import com.nextcloud.talk.databinding.ControllerEntryMenuBinding
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ShareUtils
import com.nextcloud.talk.utils.UriUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
@ -139,13 +137,6 @@ class EntryMenuController(args: Bundle) :
)
}
ConversationOperationEnum.OPS_CODE_CHANGE_PASSWORD -> {
labelText = resources!!.getString(R.string.nc_new_password)
binding.textEdit.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
}
ConversationOperationEnum.OPS_CODE_SET_PASSWORD,
ConversationOperationEnum.OPS_CODE_SHARE_LINK,
ConversationOperationEnum.OPS_CODE_JOIN_ROOM -> {
// 99 is joining a conversation via password
labelText = resources!!.getString(R.string.nc_password)
@ -242,18 +233,12 @@ class EntryMenuController(args: Bundle) :
private fun onOkButtonClick() {
if (operation === ConversationOperationEnum.OPS_CODE_JOIN_ROOM) {
joinRoom()
} else if (operation !== ConversationOperationEnum.OPS_CODE_SHARE_LINK &&
} else if (
operation !== ConversationOperationEnum.OPS_CODE_GET_AND_JOIN_ROOM &&
operation !== ConversationOperationEnum.OPS_CODE_INVITE_USERS
) {
val bundle = Bundle()
if (operation === ConversationOperationEnum.OPS_CODE_CHANGE_PASSWORD ||
operation === ConversationOperationEnum.OPS_CODE_SET_PASSWORD
) {
conversation!!.password = binding.textEdit.text.toString()
} else {
conversation!!.name = binding.textEdit.text.toString()
}
conversation!!.name = binding.textEdit.text.toString()
bundle.putParcelable(BundleKeys.KEY_ROOM, Parcels.wrap<Any>(conversation))
bundle.putSerializable(BundleKeys.KEY_OPERATION_CODE, operation)
router.pushController(
@ -261,20 +246,6 @@ class EntryMenuController(args: Bundle) :
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
} else if (operation === ConversationOperationEnum.OPS_CODE_SHARE_LINK && activity != null) {
shareIntent?.putExtra(
Intent.EXTRA_TEXT,
ShareUtils.getStringForIntent(
activity,
binding.textEdit.text.toString(),
userManager,
conversation
)
)
val intent = Intent(shareIntent)
intent.component = ComponentName(packageName, name)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
activity?.startActivity(intent)
} else if (operation !== ConversationOperationEnum.OPS_CODE_INVITE_USERS) {
val bundle = Bundle()
bundle.putSerializable(BundleKeys.KEY_OPERATION_CODE, operation)
@ -336,10 +307,7 @@ class EntryMenuController(args: Bundle) :
companion object {
private val PASSWORD_ENTRY_OPERATIONS: List<ConversationOperationEnum> =
immutableListOf(
ConversationOperationEnum.OPS_CODE_JOIN_ROOM,
ConversationOperationEnum.OPS_CODE_CHANGE_PASSWORD,
ConversationOperationEnum.OPS_CODE_SET_PASSWORD,
ConversationOperationEnum.OPS_CODE_SHARE_LINK
ConversationOperationEnum.OPS_CODE_JOIN_ROOM
)
const val OPACITY_DISABLED = 0.38f
const val OPACITY_BUTTON_DISABLED = 0.7f

View File

@ -211,11 +211,6 @@ class OperationsMenuController(args: Bundle) : BaseController(
credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
when (operation) {
ConversationOperationEnum.OPS_CODE_RENAME_ROOM -> operationRenameRoom()
ConversationOperationEnum.OPS_CODE_MAKE_PUBLIC -> operationMakePublic()
ConversationOperationEnum.OPS_CODE_CHANGE_PASSWORD,
ConversationOperationEnum.OPS_CODE_CLEAR_PASSWORD,
ConversationOperationEnum.OPS_CODE_SET_PASSWORD -> operationChangePassword()
ConversationOperationEnum.OPS_CODE_MAKE_PRIVATE -> operationMakePrivate()
ConversationOperationEnum.OPS_CODE_GET_AND_JOIN_ROOM -> operationGetAndJoinRoom()
ConversationOperationEnum.OPS_CODE_INVITE_USERS -> operationInviteUsers()
ConversationOperationEnum.OPS_CODE_MARK_AS_READ -> operationMarkAsRead()
@ -267,56 +262,6 @@ class OperationsMenuController(args: Bundle) : BaseController(
.subscribe(GenericOperationsObserver())
}
private fun operationMakePrivate() {
ncApi.makeRoomPrivate(
credentials,
ApiUtils.getUrlForRoomPublic(
apiVersion(),
currentUser!!.baseUrl,
conversation!!.token
)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry(1)
.subscribe(GenericOperationsObserver())
}
private fun operationChangePassword() {
var pass: String? = ""
if (conversation!!.password != null) {
pass = conversation!!.password
}
ncApi.setPassword(
credentials,
ApiUtils.getUrlForRoomPassword(
apiVersion(),
currentUser!!.baseUrl,
conversation!!.token
),
pass
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry(1)
.subscribe(GenericOperationsObserver())
}
private fun operationMakePublic() {
ncApi.makeRoomPublic(
credentials,
ApiUtils.getUrlForRoomPublic(
apiVersion(),
currentUser!!.baseUrl,
conversation!!.token
)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry(1)
.subscribe(GenericOperationsObserver())
}
private fun operationRenameRoom() {
ncApi.renameRoom(
credentials,

View File

@ -0,0 +1,254 @@
package com.nextcloud.talk.conversation.info
import android.annotation.SuppressLint
import android.content.Intent
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SwitchCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.talk.R
import com.nextcloud.talk.controllers.ConversationInfoController
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ControllerConversationInfoBinding
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.repositories.conversations.ConversationsRepository
import com.nextcloud.talk.utils.Mimetype
import com.nextcloud.talk.utils.ShareUtils
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
class GuestAccessHelper(
controller: ConversationInfoController,
private val binding: ControllerConversationInfoBinding,
private val conversation: Conversation,
private val conversationUser: User
) {
private val activity = controller.activity!!
private val conversationsRepository = controller.conversationsRepository
private val viewThemeUtils = controller.viewThemeUtils
private val context = controller.context
fun setupGuestAccess() {
val guestAccessAllowSwitch = (
binding.guestAccessView.guestAccessAllowSwitch.findViewById<View>(R.id.mp_checkable)
as SwitchCompat
)
val guestAccessPasswordSwitch = (
binding.guestAccessView.guestAccessPasswordSwitch.findViewById<View>(R.id.mp_checkable)
as SwitchCompat
)
if (conversation.canModerate(conversationUser)) {
binding.guestAccessView.guestAccessSettings.visibility = View.VISIBLE
} else {
return
}
if (conversation.type == Conversation.ConversationType.ROOM_PUBLIC_CALL) {
guestAccessAllowSwitch.isChecked = true
showAllOptions()
if (conversation.hasPassword) {
guestAccessPasswordSwitch.isChecked = true
}
}
binding.guestAccessView.guestAccessAllowSwitch.setOnClickListener {
conversationsRepository.allowGuests(
conversation.token!!,
!guestAccessAllowSwitch.isChecked
).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(AllowGuestsResultObserver())
}
binding.guestAccessView.guestAccessPasswordSwitch.setOnClickListener {
if (guestAccessPasswordSwitch.isChecked) {
conversationsRepository.password("", conversation.token!!).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(PasswordResultObserver(false))
} else {
showPasswordDialog(guestAccessPasswordSwitch)
}
}
binding.guestAccessView.guestAccessCopyUrl.setOnClickListener {
shareUrl()
}
binding.guestAccessView.guestAccessResendInvitations.setOnClickListener {
conversationsRepository.resendInvitations(conversation.token!!).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(ResendInvitationsObserver())
}
}
@SuppressLint("InflateParams")
private fun showPasswordDialog(guestAccessPasswordSwitch: SwitchCompat) {
val builder = MaterialAlertDialogBuilder(activity)
builder.apply {
val dialogPassword = LayoutInflater.from(context).inflate(R.layout.dialog_password, null)
setView(dialogPassword)
setTitle("Guest access password")
setPositiveButton(
"OK"
) { _, _ ->
val password = dialogPassword.findViewById<EditText>(R.id.password).text.toString()
conversationsRepository.password(password, conversation.token!!)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(PasswordResultObserver(true))
}
setNegativeButton(
"Cancel"
) { _, _ ->
guestAccessPasswordSwitch.isChecked = false
}
}
createDialog(builder)
}
private fun createDialog(builder: MaterialAlertDialogBuilder) {
builder.create()
viewThemeUtils.colorMaterialAlertDialogBackground(binding.conversationInfoName.context, builder)
val dialog = builder.show()
viewThemeUtils.colorTextButtons(
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
)
}
private fun shareUrl() {
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
type = Mimetype.TEXT_PLAIN
putExtra(
Intent.EXTRA_SUBJECT,
String.format(
activity.resources.getString(R.string.nc_share_subject),
activity.resources.getString(R.string.nc_app_product_name)
)
)
putExtra(
Intent.EXTRA_TEXT,
ShareUtils.getStringForIntent(activity, conversationUser, conversation)
)
}
val shareIntent = Intent.createChooser(sendIntent, null)
activity.startActivity(shareIntent)
}
inner class ResendInvitationsObserver : Observer<ConversationsRepository.ResendInvitationsResult> {
private lateinit var resendInvitationsResult: ConversationsRepository.ResendInvitationsResult
override fun onSubscribe(d: Disposable) = Unit
override fun onNext(t: ConversationsRepository.ResendInvitationsResult) {
resendInvitationsResult = t
}
override fun onError(e: Throwable) {
val message = context.getString(R.string.nc_guest_access_resend_invitations_failed)
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
Log.e(TAG, message, e)
}
override fun onComplete() {
if (resendInvitationsResult.successful) {
Toast.makeText(context, R.string.nc_guest_access_resend_invitations_successful, Toast.LENGTH_SHORT)
.show()
}
}
}
inner class AllowGuestsResultObserver : Observer<ConversationsRepository.AllowGuestsResult> {
private lateinit var allowGuestsResult: ConversationsRepository.AllowGuestsResult
override fun onNext(t: ConversationsRepository.AllowGuestsResult) {
allowGuestsResult = t
}
override fun onError(e: Throwable) {
val message = context.getString(R.string.nc_guest_access_allow_failed)
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
Log.e(TAG, message, e)
}
override fun onComplete() {
(
binding.guestAccessView.guestAccessAllowSwitch.findViewById<View>(R.id.mp_checkable)
as SwitchCompat
).isChecked = allowGuestsResult.allow
if (allowGuestsResult.allow) {
showAllOptions()
} else {
hideAllOptions()
}
}
override fun onSubscribe(d: Disposable) = Unit
}
private fun showAllOptions() {
binding.guestAccessView.guestAccessPasswordSwitch.visibility = View.VISIBLE
binding.guestAccessView.guestAccessCopyUrl.visibility = View.VISIBLE
if (conversationUser.capabilities?.spreedCapability?.features?.contains("sip-support") == true) {
binding.guestAccessView.guestAccessResendInvitations.visibility = View.VISIBLE
}
}
private fun hideAllOptions() {
binding.guestAccessView.guestAccessPasswordSwitch.visibility = View.GONE
binding.guestAccessView.guestAccessCopyUrl.visibility = View.GONE
binding.guestAccessView.guestAccessResendInvitations.visibility = View.GONE
}
inner class PasswordResultObserver(private val setPassword: Boolean) :
Observer<ConversationsRepository.PasswordResult> {
private lateinit var passwordResult: ConversationsRepository.PasswordResult
override fun onSubscribe(d: Disposable) = Unit
override fun onNext(t: ConversationsRepository.PasswordResult) {
passwordResult = t
}
override fun onError(e: Throwable) {
val message = context.getString(R.string.nc_guest_access_password_failed)
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
Log.e(TAG, message, e)
}
override fun onComplete() {
val guestAccessPasswordSwitch = (
binding.guestAccessView.guestAccessPasswordSwitch.findViewById<View>(R.id.mp_checkable)
as SwitchCompat
)
guestAccessPasswordSwitch.isChecked = passwordResult.passwordSet && setPassword
if (passwordResult.passwordIsWeak) {
val builder = MaterialAlertDialogBuilder(activity)
builder.apply {
setTitle(R.string.nc_guest_access_password_weak_alert_title)
setMessage(passwordResult.message)
setPositiveButton("OK") { _, _ -> }
}
createDialog(builder)
}
}
}
companion object {
private val TAG = GuestAccessHelper::class.simpleName
}
}

View File

@ -33,6 +33,8 @@ import com.nextcloud.talk.polls.repositories.PollRepository
import com.nextcloud.talk.polls.repositories.PollRepositoryImpl
import com.nextcloud.talk.remotefilebrowser.repositories.RemoteFileBrowserItemsRepository
import com.nextcloud.talk.remotefilebrowser.repositories.RemoteFileBrowserItemsRepositoryImpl
import com.nextcloud.talk.repositories.conversations.ConversationsRepository
import com.nextcloud.talk.repositories.conversations.ConversationsRepositoryImpl
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepositoryImpl
import com.nextcloud.talk.shareditems.repositories.SharedItemsRepository
@ -44,6 +46,12 @@ import okhttp3.OkHttpClient
@Module
class RepositoryModule {
@Provides
fun provideConversationsRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): ConversationsRepository {
return ConversationsRepositoryImpl(ncApi, userProvider)
}
@Provides
fun provideSharedItemsRepository(ncApi: NcApi): SharedItemsRepository {
return SharedItemsRepositoryImpl(ncApi)

View File

@ -0,0 +1,36 @@
/*
* Nextcloud Talk application
*
* @author Tim Krüger
* Copyright (C) 2022 Tim Krüger
* Copyright (C) 2022 Nextcloud GmbH
*
* 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.conversations.password
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.android.parcel.Parcelize
@JsonObject
@Parcelize
data class PasswordData(
@JsonField(name = ["message"])
var message: String? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null)
}

View File

@ -0,0 +1,40 @@
/*
* Nextcloud Talk application
*
* @author Tim Krüger
* Copyright (C) 2022 Tim Krüger
* Copyright (C) 2022 Nextcloud GmbH
*
* 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.conversations.password
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.android.parcel.Parcelize
@Parcelize
@JsonObject
data class PasswordOCS(
@JsonField(name = ["meta"])
var meta: GenericMeta? = null,
@JsonField(name = ["data"])
var data: PasswordData? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
}

View File

@ -0,0 +1,36 @@
/*
* Nextcloud Talk application
*
* @author Tim Krüger
* Copyright (C) 2022 Tim Krüger
* Copyright (C) 2022 Nextcloud GmbH
*
* 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.conversations.password
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.android.parcel.Parcelize
@Parcelize
@JsonObject
data class PasswordOverall(
@JsonField(name = ["ocs"])
var ocs: PasswordOCS? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null)
}

View File

@ -0,0 +1,46 @@
/*
* Nextcloud Talk application
*
* @author Tim Krüger
* Copyright (C) 2022 Tim Krüger
* Copyright (C) 2022 Nextcloud GmbH
*
* 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.repositories.conversations
import io.reactivex.Observable
interface ConversationsRepository {
data class AllowGuestsResult(
val allow: Boolean
)
fun allowGuests(token: String, allow: Boolean): Observable<AllowGuestsResult>
data class PasswordResult(
val passwordSet: Boolean,
val passwordIsWeak: Boolean,
val message: String
)
fun password(password: String, token: String): Observable<PasswordResult>
data class ResendInvitationsResult(
val successful: Boolean
)
fun resendInvitations(token: String): Observable<ResendInvitationsResult>
}

View File

@ -0,0 +1,113 @@
/*
* Nextcloud Talk application
*
* @author Tim Krüger
* Copyright (C) 2022 Tim Krüger
* Copyright (C) 2022 Nextcloud GmbH
*
* 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.repositories.conversations
import com.bluelinelabs.logansquare.LoganSquare
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.conversations.password.PasswordOverall
import com.nextcloud.talk.repositories.conversations.ConversationsRepository.AllowGuestsResult
import com.nextcloud.talk.repositories.conversations.ConversationsRepository.PasswordResult
import com.nextcloud.talk.repositories.conversations.ConversationsRepository.ResendInvitationsResult
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import io.reactivex.Observable
class ConversationsRepositoryImpl(private val api: NcApi, private val userProvider: CurrentUserProviderNew) :
ConversationsRepository {
private val user: User
get() = userProvider.currentUser.blockingGet()
private val credentials: String
get() = ApiUtils.getCredentials(user.username, user.token)
override fun allowGuests(token: String, allow: Boolean): Observable<AllowGuestsResult> {
val url = ApiUtils.getUrlForRoomPublic(
apiVersion(),
user.baseUrl,
token
)
val apiObservable = if (allow) {
api.makeRoomPublic(
credentials,
url
)
} else {
api.makeRoomPrivate(
credentials,
url
)
}
return apiObservable.map { AllowGuestsResult(it.ocs!!.meta!!.statusCode == STATUS_CODE_OK && allow) }
}
override fun password(password: String, token: String): Observable<PasswordResult> {
val apiObservable = api.setPassword2(
credentials,
ApiUtils.getUrlForRoomPassword(
apiVersion(),
user.baseUrl!!,
token
),
password
)
return apiObservable.map {
val passwordPolicyMessage = if (it.code() == STATUS_CODE_BAD_REQUEST) {
LoganSquare.parse(it.errorBody()!!.string(), PasswordOverall::class.java).ocs!!.data!!
.message!!
} else {
""
}
PasswordResult(it.isSuccessful, passwordPolicyMessage.isNotEmpty(), passwordPolicyMessage)
}
}
override fun resendInvitations(token: String): Observable<ResendInvitationsResult> {
val apiObservable = api.resendParticipantInvitations(
credentials,
ApiUtils.getUrlForParticipantsResendInvitations(
apiVersion(),
user.baseUrl!!,
token
)
)
return apiObservable.map {
ResendInvitationsResult(true)
}
}
private fun apiVersion(): Int {
return ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.APIv4))
}
companion object {
const val STATUS_CODE_OK = 200
const val STATUS_CODE_BAD_REQUEST = 400
}
}

View File

@ -21,7 +21,6 @@
package com.nextcloud.talk.ui.dialog
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.text.TextUtils
import android.view.View
@ -42,14 +41,9 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.controllers.ConversationsListController
import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum
import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_ADD_FAVORITE
import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_CHANGE_PASSWORD
import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_CLEAR_PASSWORD
import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_MAKE_PRIVATE
import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_MAKE_PUBLIC
import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_MARK_AS_READ
import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_REMOVE_FAVORITE
import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_RENAME_ROOM
import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum.OPS_CODE_SET_PASSWORD
import com.nextcloud.talk.controllers.bottomsheet.EntryMenuController
import com.nextcloud.talk.controllers.bottomsheet.OperationsMenuController
import com.nextcloud.talk.data.user.model.User
@ -58,8 +52,6 @@ import com.nextcloud.talk.jobs.LeaveConversationWorker
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.Mimetype.TEXT_PLAIN
import com.nextcloud.talk.utils.ShareUtils
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_OPERATION_CODE
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM
@ -130,34 +122,10 @@ class ConversationsListBottomDialog(
conversation.isNameEditable(currentUser)
)
binding.conversationOperationMakePublic.visibility = setVisibleIf(
canModerate && !conversation.isPublic
)
binding.conversationOperationChangePassword.visibility = setVisibleIf(
canModerate && conversation.hasPassword && conversation.isPublic
)
binding.conversationOperationClearPassword.visibility = setVisibleIf(
canModerate && conversation.hasPassword && conversation.isPublic
)
binding.conversationOperationSetPassword.visibility = setVisibleIf(
canModerate && !conversation.hasPassword && conversation.isPublic
)
binding.conversationOperationDelete.visibility = setVisibleIf(
canModerate
)
binding.conversationOperationShareLink.visibility = setVisibleIf(
conversation.isPublic
)
binding.conversationOperationMakePrivate.visibility = setVisibleIf(
conversation.isPublic && canModerate
)
binding.conversationOperationLeave.visibility = setVisibleIf(
conversation.canLeave() &&
// leaving is by api not possible for the last user with moderator permissions.
@ -210,26 +178,6 @@ class ConversationsListBottomDialog(
dismiss()
}
binding.conversationOperationMakePublic.setOnClickListener {
executeOperationsMenuController(OPS_CODE_MAKE_PUBLIC)
}
binding.conversationOperationMakePrivate.setOnClickListener {
executeOperationsMenuController(OPS_CODE_MAKE_PRIVATE)
}
binding.conversationOperationChangePassword.setOnClickListener {
executeEntryMenuController(OPS_CODE_CHANGE_PASSWORD)
}
binding.conversationOperationClearPassword.setOnClickListener {
executeOperationsMenuController(OPS_CODE_CLEAR_PASSWORD)
}
binding.conversationOperationSetPassword.setOnClickListener {
executeEntryMenuController(OPS_CODE_SET_PASSWORD)
}
binding.conversationOperationRename.setOnClickListener {
executeEntryMenuController(OPS_CODE_RENAME_ROOM)
}
@ -237,30 +185,6 @@ class ConversationsListBottomDialog(
binding.conversationOperationMarkAsRead.setOnClickListener {
executeOperationsMenuController(OPS_CODE_MARK_AS_READ)
}
binding.conversationOperationShareLink.setOnClickListener {
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
type = TEXT_PLAIN
putExtra(
Intent.EXTRA_SUBJECT,
String.format(
activity.resources.getString(R.string.nc_share_subject),
activity.resources.getString(R.string.nc_app_product_name)
)
)
// password should not be shared!!
putExtra(
Intent.EXTRA_TEXT,
ShareUtils.getStringForIntent(activity, null, userManager, conversation)
)
}
val shareIntent = Intent.createChooser(sendIntent, null)
activity.startActivity(shareIntent)
dismiss()
}
}
private fun executeOperationsMenuController(operation: ConversationOperationEnum) {

View File

@ -218,6 +218,10 @@ public class ApiUtils {
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";
}

View File

@ -21,28 +21,20 @@ 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.users.UserManager
object ShareUtils {
fun getStringForIntent(
context: Context?,
password: String?,
userManager: UserManager,
context: Context,
user: User,
conversation: Conversation?
): String {
val userEntity = userManager.currentUser.blockingGet()
var shareString = ""
if (userEntity != null && context != null) {
shareString = String.format(
context.resources.getString(R.string.nc_share_text),
userEntity.baseUrl,
conversation?.token
)
if (!password.isNullOrEmpty()) {
shareString += String.format(context.resources.getString(R.string.nc_share_text_pass), password)
}
}
return shareString
return String.format(
context.resources.getString(R.string.nc_share_text),
user.baseUrl,
conversation?.token
)
}
}

View File

@ -4,6 +4,8 @@
~ @author Mario Danic
~ @author Andy Scherzinger
~ @author Marcel Hibbe
~ @author Tim Krüger
~ Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
~ Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
@ -25,10 +27,10 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:apc="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:background="@color/white"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:orientation="vertical"
tools:background="@color/white">
<ProgressBar
android:id="@+id/progressBar"
@ -80,8 +82,8 @@
android:layout_width="@dimen/avatar_size_big"
android:layout_height="@dimen/avatar_size_big"
android:layout_centerHorizontal="true"
tools:background="@color/hwSecurityRed"
apc:roundAsCircle="true" />
apc:roundAsCircle="true"
tools:background="@color/hwSecurityRed" />
</RelativeLayout>
</com.yarolegovich.mp.MaterialPreferenceCategory>
@ -150,6 +152,14 @@
android:visibility="gone"
tools:visibility="visible" />
<include
android:id="@+id/guest_access_view"
layout="@layout/guest_access_settings_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>
<com.yarolegovich.mp.MaterialPreferenceCategory

View File

@ -166,126 +166,6 @@
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_make_public"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="@dimen/standard_padding"
android:paddingEnd="@dimen/standard_padding"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_link_grey600_24px"
app:tint="@color/high_emphasis_menu_icon" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_make_call_public"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_change_password"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="@dimen/standard_padding"
android:paddingEnd="@dimen/standard_padding"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_lock_grey600_24px"
app:tint="@color/high_emphasis_menu_icon" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_change_password"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_clear_password"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="@dimen/standard_padding"
android:paddingEnd="@dimen/standard_padding"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_lock_open_grey600_24dp"
app:tint="@color/high_emphasis_menu_icon" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_clear_password"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_set_password"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="@dimen/standard_padding"
android:paddingEnd="@dimen/standard_padding"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_lock_plus_grey600_24dp"
app:tint="@color/high_emphasis_menu_icon" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_set_password"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_delete"
android:layout_width="match_parent"
@ -316,66 +196,6 @@
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_share_link"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="@dimen/standard_padding"
android:paddingEnd="@dimen/standard_padding"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_link_grey600_24px"
app:tint="@color/high_emphasis_menu_icon" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_share_link"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_make_private"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="@dimen/standard_padding"
android:paddingEnd="@dimen/standard_padding"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_group_grey600_24px"
app:tint="@color/high_emphasis_menu_icon" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_make_call_private"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_leave"
android:layout_width="match_parent"

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Nextcloud Talk application
@author Tim Krüger
Copyright (C) 2022 Tim Krüger
Copyright (C) 2022 Nextcloud GmbH
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/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:background="@color/white">
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/standard_margin"
android:layout_marginTop="@dimen/standard_margin"
android:layout_marginEnd="@dimen/standard_margin"
android:layout_marginBottom="@dimen/standard_margin"
android:hint="@string/nc_guest_access_password_hint"
android:inputType="textPassword"
android:importantForAutofill="no" />
</LinearLayout>

View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?><!--
Nextcloud Talk application
@author Tim Krüger
Copyright (C) 2022 Tim Krüger
Copyright (C) 2022 Nextcloud GmbH
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/>.
-->
<com.yarolegovich.mp.MaterialPreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:apc="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/guest_access_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.yarolegovich.mp.MaterialPreferenceCategory
android:id="@+id/guest_access_category"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
apc:cardBackgroundColor="@color/bg_default"
apc:cardElevation="0dp"
apc:mpc_title="@string/nc_guest_access">
<com.yarolegovich.mp.MaterialSwitchPreference
android:id="@+id/guest_access_allow_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
apc:mp_default_value="false"
apc:mp_key="guest_access_allowed"
apc:mp_summary="@string/nc_guest_access_allow_summary"
apc:mp_title="@string/nc_guest_access_allow_title" />
<com.yarolegovich.mp.MaterialSwitchPreference
android:id="@+id/guest_access_password_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
apc:mp_default_value="false"
apc:mp_key="guest_access_password"
apc:mp_summary="@string/nc_guest_access_password_summary"
apc:mp_title="@string/nc_guest_access_password_title"
tools:visibility="visible" />
<com.yarolegovich.mp.MaterialStandardPreference
android:id="@+id/guest_access_copy_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
apc:mp_icon="@drawable/ic_share_variant"
apc:mp_icon_tint="@color/grey_600"
apc:mp_title="@string/nc_guest_access_share_link"
tools:visibility="visible" >
</com.yarolegovich.mp.MaterialStandardPreference>
<com.yarolegovich.mp.MaterialStandardPreference
android:id="@+id/guest_access_resend_invitations"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
apc:mp_icon="@drawable/ic_email"
apc:mp_icon_tint="@color/grey_600"
apc:mp_title="@string/nc_guest_access_resend_invitations"
tools:visibility="visible" />
</com.yarolegovich.mp.MaterialPreferenceCategory>
</com.yarolegovich.mp.MaterialPreferenceScreen>

View File

@ -168,12 +168,6 @@
<string name="nc_clear_history_warning">Do you really want to delete all messages in this conversation?</string>
<string name="nc_clear_history_success">All messages were deleted</string>
<string name="nc_rename">Rename conversation</string>
<string name="nc_set_password">Set a password</string>
<string name="nc_change_password">Change password</string>
<string name="nc_clear_password">Clear password</string>
<string name="nc_share_link">Share link</string>
<string name="nc_make_call_public">Make conversation public</string>
<string name="nc_make_call_private">Make conversation private</string>
<string name="nc_delete_call">Delete conversation</string>
<string name="nc_delete">Delete</string>
<string name="nc_delete_all">Delete all</string>
@ -337,6 +331,21 @@
<string name="emoji_backspace">Backspace</string>
<string name="emoji_search">Search emoji</string>
<!-- Conversation info guest access -->
<string name="nc_guest_access">Guest access</string>
<string name="nc_guest_access_allow_title">Allow guests</string>
<string name="nc_guest_access_allow_summary">Allow guests to share a public link to join this conversation.</string>
<string name="nc_guest_access_allow_failed">Can\'t en-/disable guest access.</string>
<string name="nc_guest_access_password_title">Password protection</string>
<string name="nc_guest_access_password_summary">Set a password to restrict who can use the public link.</string>
<string name="nc_guest_access_password_hint">Enter a password</string>
<string name="nc_guest_access_password_failed">Error during setting/disabling the password.</string>
<string name="nc_guest_access_password_weak_alert_title">Weak password</string>
<string name="nc_guest_access_share_link">Share conversation link</string>
<string name="nc_guest_access_resend_invitations">Resend invitations</string>
<string name="nc_guest_access_resend_invitations_successful">Invitations were sent out again.</string>
<string name="nc_guest_access_resend_invitations_failed">Invitations were not send due to an error.</string>
<!-- Content descriptions -->
<string name="nc_description_send_message_button">Send message</string>

View File

@ -72,20 +72,8 @@ class ShareUtilsTest {
)
Assert.assertEquals(
"Intent string was not as expected",
expectedResult, ShareUtils.getStringForIntent(context, "", userManager!!, conversation)
)
}
@Test
fun stringForIntent_passwordGiven_correctStringWithPasswordReturned() {
val password = "superSecret"
val expectedResult = String.format(
"Join the conversation at %s/index.php/call/%s\nPassword: %s",
baseUrl, token, password
)
Assert.assertEquals(
"Intent string was not as expected",
expectedResult, ShareUtils.getStringForIntent(context, password, userManager!!, conversation)
expectedResult,
ShareUtils.getStringForIntent(context!!, userManager!!.currentUser.blockingGet(), conversation)
)
}
}