浏览代码

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>
Tim Krüger 2 年之前
父节点
当前提交
84116e4cb2
共有 21 个文件被更改,包括 754 次插入418 次删除
  1. 9 0
      app/src/main/java/com/nextcloud/talk/api/NcApi.java
  2. 43 25
      app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt
  3. 0 6
      app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/ConversationOperationEnum.kt
  4. 3 35
      app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.kt
  5. 0 55
      app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.kt
  6. 254 0
      app/src/main/java/com/nextcloud/talk/conversation/info/GuestAccessHelper.kt
  7. 8 0
      app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt
  8. 36 0
      app/src/main/java/com/nextcloud/talk/models/json/conversations/password/PasswordData.kt
  9. 40 0
      app/src/main/java/com/nextcloud/talk/models/json/conversations/password/PasswordOCS.kt
  10. 36 0
      app/src/main/java/com/nextcloud/talk/models/json/conversations/password/PasswordOverall.kt
  11. 46 0
      app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepository.kt
  12. 113 0
      app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepositoryImpl.kt
  13. 0 76
      app/src/main/java/com/nextcloud/talk/ui/dialog/ConversationsListBottomDialog.kt
  14. 4 0
      app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java
  15. 9 17
      app/src/main/java/com/nextcloud/talk/utils/ShareUtils.kt
  16. 14 4
      app/src/main/res/layout/controller_conversation_info.xml
  17. 0 180
      app/src/main/res/layout/dialog_conversation_operations.xml
  18. 41 0
      app/src/main/res/layout/dialog_password.xml
  19. 81 0
      app/src/main/res/layout/guest_access_settings_item.xml
  20. 15 6
      app/src/main/res/values/strings.xml
  21. 2 14
      app/src/test/java/com/nextcloud/talk/utils/ShareUtilsTest.kt

+ 9 - 0
app/src/main/java/com/nextcloud/talk/api/NcApi.java

@@ -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);
 

+ 43 - 25
app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt

@@ -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

+ 0 - 6
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/ConversationOperationEnum.kt

@@ -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,

+ 3 - 35
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.kt

@@ -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

+ 0 - 55
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.kt

@@ -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,

+ 254 - 0
app/src/main/java/com/nextcloud/talk/conversation/info/GuestAccessHelper.kt

@@ -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
+    }
+}

+ 8 - 0
app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt

@@ -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)

+ 36 - 0
app/src/main/java/com/nextcloud/talk/models/json/conversations/password/PasswordData.kt

@@ -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)
+}

+ 40 - 0
app/src/main/java/com/nextcloud/talk/models/json/conversations/password/PasswordOCS.kt

@@ -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)
+}

+ 36 - 0
app/src/main/java/com/nextcloud/talk/models/json/conversations/password/PasswordOverall.kt

@@ -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)
+}

+ 46 - 0
app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepository.kt

@@ -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>
+}

+ 113 - 0
app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepositoryImpl.kt

@@ -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
+    }
+}

+ 0 - 76
app/src/main/java/com/nextcloud/talk/ui/dialog/ConversationsListBottomDialog.kt

@@ -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) {

+ 4 - 0
app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java

@@ -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";
     }

+ 9 - 17
app/src/main/java/com/nextcloud/talk/utils/ShareUtils.kt

@@ -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
+        )
     }
 }

+ 14 - 4
app/src/main/res/layout/controller_conversation_info.xml

@@ -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

+ 0 - 180
app/src/main/res/layout/dialog_conversation_operations.xml

@@ -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"

+ 41 - 0
app/src/main/res/layout/dialog_password.xml

@@ -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>
+

+ 81 - 0
app/src/main/res/layout/guest_access_settings_item.xml

@@ -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>

+ 15 - 6
app/src/main/res/values/strings.xml

@@ -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>
 

+ 2 - 14
app/src/test/java/com/nextcloud/talk/utils/ShareUtilsTest.kt

@@ -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)
         )
     }
 }