Browse Source

Merge pull request #2413 from nextcloud/feature/1353/shareToChooseAccount

add account switcher for "share to"
Tim Krüger 2 years ago
parent
commit
dc6083334b

+ 74 - 8
app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt

@@ -100,6 +100,7 @@ import com.nextcloud.talk.models.json.status.Status
 import com.nextcloud.talk.models.json.statuses.StatusesOverall
 import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
 import com.nextcloud.talk.ui.dialog.ChooseAccountDialogFragment
+import com.nextcloud.talk.ui.dialog.ChooseAccountShareToDialogFragment
 import com.nextcloud.talk.ui.dialog.ConversationsListBottomDialog
 import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.utils.ApiUtils
@@ -180,6 +181,7 @@ class ConversationsListController(bundle: Bundle) :
     private var conversationItemsWithHeader: MutableList<AbstractFlexibleItem<*>> = ArrayList()
     private val searchableConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
     private var searchItem: MenuItem? = null
+    private var chooseAccountItem: MenuItem? = null
     private var searchView: SearchView? = null
     private var searchQuery: String? = null
     private var credentials: String? = null
@@ -250,6 +252,43 @@ class ConversationsListController(bundle: Bundle) :
         }
     }
 
+    private fun loadUserAvatar(menuItem: MenuItem) {
+        if (activity != null) {
+            val imageRequest = DisplayUtils.getImageRequestForUrl(
+                ApiUtils.getUrlForAvatar(
+                    currentUser!!.baseUrl,
+                    currentUser!!.userId,
+                    true
+                ),
+                currentUser
+            )
+            val imagePipeline = Fresco.getImagePipeline()
+            val dataSource = imagePipeline.fetchDecodedImage(imageRequest, null)
+            dataSource.subscribe(
+                object : BaseBitmapDataSubscriber() {
+                    override fun onNewResultImpl(bitmap: Bitmap?) {
+                        if (bitmap != null && resources != null) {
+                            val roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(
+                                resources!!,
+                                bitmap
+                            )
+                            roundedBitmapDrawable.isCircular = true
+                            roundedBitmapDrawable.setAntiAlias(true)
+                            menuItem.icon = roundedBitmapDrawable
+                        }
+                    }
+
+                    override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage?>>) {
+                        if (resources != null) {
+                            menuItem.icon = ResourcesCompat.getDrawable(resources!!, R.drawable.ic_user, null)
+                        }
+                    }
+                },
+                UiThreadImmediateExecutorService.getInstance()
+            )
+        }
+    }
+
     override fun onAttach(view: View) {
         Log.d(
             TAG,
@@ -257,6 +296,9 @@ class ConversationsListController(bundle: Bundle) :
                 " Activity: " + System.identityHashCode(activity)
         )
         super.onAttach(view)
+
+        showShareToScreen = hasActivityActionSendIntent()
+
         ClosedInterfaceImpl().setUpPushTokenRegistration()
         if (!eventBus.isRegistered(this)) {
             eventBus.register(this)
@@ -328,15 +370,32 @@ class ConversationsListController(bundle: Bundle) :
 
     override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
         super.onCreateOptionsMenu(menu, inflater)
+
         inflater.inflate(R.menu.menu_conversation_plus_filter, menu)
         searchItem = menu.findItem(R.id.action_search)
+        chooseAccountItem = menu.findItem(R.id.action_choose_account)
+        loadUserAvatar(chooseAccountItem!!)
+
+        chooseAccountItem?.setOnMenuItemClickListener {
+            if (resources != null && resources!!.getBoolean(R.bool.multiaccount_support)) {
+                val newFragment: DialogFragment = ChooseAccountShareToDialogFragment.newInstance()
+                newFragment.show(
+                    (activity as MainActivity?)!!.supportFragmentManager,
+                    ChooseAccountShareToDialogFragment.TAG
+                )
+            }
+            true
+        }
         initSearchView()
     }
 
     override fun onPrepareOptionsMenu(menu: Menu) {
         super.onPrepareOptionsMenu(menu)
         searchView = MenuItemCompat.getActionView(searchItem) as SearchView
-        showShareToScreen = !showShareToScreen && hasActivityActionSendIntent()
+
+        val moreAccountsAvailable = userManager.users.blockingGet().size > 1
+        menu.findItem(R.id.action_choose_account).isVisible = showShareToScreen && moreAccountsAvailable
+
         if (showShareToScreen) {
             hideSearchBar()
             actionBar?.setTitle(R.string.send_to_three_dots)
@@ -679,7 +738,7 @@ class ConversationsListController(bundle: Bundle) :
                     val newFragment: DialogFragment = ChooseAccountDialogFragment.newInstance()
                     newFragment.show(
                         (getActivity() as MainActivity?)!!.supportFragmentManager,
-                        "ChooseAccountDialogFragment"
+                        ChooseAccountDialogFragment.TAG
                     )
                 } else {
                     router.pushController(
@@ -858,7 +917,7 @@ class ConversationsListController(bundle: Bundle) :
                     loadMoreMessages()
                 }
                 ConversationItem.VIEW_TYPE -> {
-                    showConversation((Objects.requireNonNull(item) as ConversationItem).model)
+                    handleConversation((Objects.requireNonNull(item) as ConversationItem).model)
                 }
             }
         }
@@ -870,21 +929,24 @@ class ConversationsListController(bundle: Bundle) :
             val conversationItem = absItem as ConversationItem
             if (conversationItem.model.token == conversationToken) {
                 val conversation = conversationItem.model
-                showConversation(conversation)
+                handleConversation(conversation)
             }
         }
     }
 
-    private fun showConversation(conversation: Conversation?) {
+    @Suppress("Detekt.ComplexMethod")
+    private fun handleConversation(conversation: Conversation?) {
         selectedConversation = conversation
         if (selectedConversation != null && activity != null) {
             val hasChatPermission = AttendeePermissionsUtil(selectedConversation!!.permissions).hasChatPermission(
                 currentUser!!
             )
             if (showShareToScreen) {
-                if (hasChatPermission && !isReadOnlyConversation(selectedConversation!!)) {
+                if (hasChatPermission &&
+                    !isReadOnlyConversation(selectedConversation!!) &&
+                    !selectedConversation!!.shouldShowLobby(currentUser!!)
+                ) {
                     handleSharedData()
-                    showShareToScreen = false
                 } else {
                     Toast.makeText(context, R.string.send_to_forbidden, Toast.LENGTH_LONG).show()
                 }
@@ -947,7 +1009,6 @@ class ConversationsListController(bundle: Bundle) :
                 }
                 .setNegativeButton(R.string.nc_no) { _, _ ->
                     Log.d(TAG, "sharing files aborted, going back to share-to screen")
-                    showShareToScreen = true
                 }
             viewThemeUtils.dialog
                 .colorMaterialAlertDialogBackground(binding.floatingActionButton.context, dialogBuilder)
@@ -961,6 +1022,10 @@ class ConversationsListController(bundle: Bundle) :
         }
     }
 
+    private fun clearIntentAction() {
+        activity!!.intent.action = ""
+    }
+
     override fun onItemLongClick(position: Int) {
         if (showShareToScreen) {
             Log.d(TAG, "sharing to multiple rooms not yet implemented. onItemLongClick is ignored.")
@@ -1085,6 +1150,7 @@ class ConversationsListController(bundle: Bundle) :
             bundle,
             false
         )
+        clearIntentAction()
     }
 
     @Subscribe(sticky = true, threadMode = ThreadMode.BACKGROUND)

+ 2 - 2
app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java

@@ -49,8 +49,8 @@ import com.nextcloud.talk.models.json.participants.Participant;
 import com.nextcloud.talk.models.json.status.Status;
 import com.nextcloud.talk.models.json.status.StatusOverall;
 import com.nextcloud.talk.ui.StatusDrawable;
-import com.nextcloud.talk.users.UserManager;
 import com.nextcloud.talk.ui.theme.ViewThemeUtils;
+import com.nextcloud.talk.users.UserManager;
 import com.nextcloud.talk.utils.ApiUtils;
 import com.nextcloud.talk.utils.DisplayUtils;
 import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew;
@@ -74,7 +74,7 @@ import io.reactivex.schedulers.Schedulers;
 
 @AutoInjector(NextcloudTalkApplication.class)
 public class ChooseAccountDialogFragment extends DialogFragment {
-    private static final String TAG = ChooseAccountDialogFragment.class.getSimpleName();
+    public static final String TAG = ChooseAccountDialogFragment.class.getSimpleName();
 
     private static final float STATUS_SIZE_IN_DP = 9f;
 

+ 195 - 0
app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountShareToDialogFragment.kt

@@ -0,0 +1,195 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Andy Scherzinger
+ * @author Mario Danic
+ * @author Marcel Hibbe
+ * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
+ * Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
+ * Copyright (C) 2022 Marcel Hibbe (dev@mhibbe.de)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Parts related to account import were either copied from or inspired by the great work done by David Luhmer at:
+ * https://github.com/nextcloud/ownCloud-Account-Importer
+ */
+package com.nextcloud.talk.ui.dialog
+
+import android.annotation.SuppressLint
+import android.app.Dialog
+import android.net.Uri
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.DialogFragment
+import androidx.recyclerview.widget.LinearLayoutManager
+import autodagger.AutoInjector
+import com.facebook.drawee.backends.pipeline.Fresco
+import com.facebook.drawee.interfaces.DraweeController
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.nextcloud.talk.activities.MainActivity
+import com.nextcloud.talk.adapters.items.AdvancedUserItem
+import com.nextcloud.talk.application.NextcloudTalkApplication
+import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
+import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.databinding.DialogChooseAccountShareToBinding
+import com.nextcloud.talk.models.json.participants.Participant
+import com.nextcloud.talk.ui.theme.ViewThemeUtils
+import com.nextcloud.talk.users.UserManager
+import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.DisplayUtils
+import eu.davidea.flexibleadapter.FlexibleAdapter
+import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
+import java.net.CookieManager
+import javax.inject.Inject
+
+@AutoInjector(NextcloudTalkApplication::class)
+class ChooseAccountShareToDialogFragment : DialogFragment() {
+    @JvmField
+    @Inject
+    var userManager: UserManager? = null
+
+    @JvmField
+    @Inject
+    var cookieManager: CookieManager? = null
+
+    @JvmField
+    @Inject
+    var viewThemeUtils: ViewThemeUtils? = null
+    private var binding: DialogChooseAccountShareToBinding? = null
+    private var dialogView: View? = null
+    private var adapter: FlexibleAdapter<AdvancedUserItem>? = null
+    private val userItems: MutableList<AdvancedUserItem> = ArrayList()
+    @SuppressLint("InflateParams")
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        binding = DialogChooseAccountShareToBinding.inflate(LayoutInflater.from(requireContext()))
+        dialogView = binding!!.root
+        return MaterialAlertDialogBuilder(requireContext()).setView(dialogView).create()
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        sharedApplication!!.componentApplication.inject(this)
+        val user = userManager!!.currentUser.blockingGet()
+        themeViews()
+        setupCurrentUser(user)
+        setupListeners(user)
+        setupAdapter()
+        prepareViews()
+    }
+
+    private fun setupCurrentUser(user: User?) {
+        binding!!.currentAccount.userIcon.tag = ""
+        if (user != null) {
+            binding!!.currentAccount.userName.text = user.displayName
+            binding!!.currentAccount.ticker.visibility = View.GONE
+            binding!!.currentAccount.account.text = Uri.parse(user.baseUrl).host
+            viewThemeUtils!!.platform.colorImageView(binding!!.currentAccount.accountMenu)
+            if (user.baseUrl != null &&
+                (user.baseUrl!!.startsWith("http://") || user.baseUrl!!.startsWith("https://"))
+            ) {
+                binding!!.currentAccount.userIcon.visibility = View.VISIBLE
+                val draweeController: DraweeController = Fresco.newDraweeControllerBuilder()
+                    .setOldController(binding!!.currentAccount.userIcon.controller)
+                    .setAutoPlayAnimations(true)
+                    .setImageRequest(
+                        DisplayUtils.getImageRequestForUrl(
+                            ApiUtils.getUrlForAvatar(
+                                user.baseUrl,
+                                user.userId,
+                                false
+                            )
+                        )
+                    )
+                    .build()
+                binding!!.currentAccount.userIcon.controller = draweeController
+            } else {
+                binding!!.currentAccount.userIcon.visibility = View.INVISIBLE
+            }
+        }
+    }
+
+    @Suppress("Detekt.NestedBlockDepth")
+    private fun setupAdapter() {
+        if (adapter == null) {
+            adapter = FlexibleAdapter(userItems, activity, false)
+            var userEntity: User
+            var participant: Participant
+            for (userItem in userManager!!.users.blockingGet()) {
+                userEntity = userItem
+                if (!userEntity.current) {
+                    var userId: String?
+                    userId = if (userEntity.userId != null) {
+                        userEntity.userId
+                    } else {
+                        userEntity.username
+                    }
+                    participant = Participant()
+                    participant.actorType = Participant.ActorType.USERS
+                    participant.actorId = userId
+                    participant.displayName = userEntity.displayName
+                    userItems.add(AdvancedUserItem(participant, userEntity, null, viewThemeUtils))
+                }
+            }
+            adapter!!.addListener(onSwitchItemClickListener)
+            adapter!!.updateDataSet(userItems, false)
+        }
+    }
+
+    private fun setupListeners(user: User) {
+        binding!!.currentAccount.root.setOnClickListener { v: View? -> dismiss() }
+    }
+
+    private fun themeViews() {
+        viewThemeUtils!!.platform.themeDialog(binding!!.root)
+    }
+
+    private fun prepareViews() {
+        if (activity != null) {
+            val layoutManager: LinearLayoutManager = SmoothScrollLinearLayoutManager(activity)
+            binding!!.accountsList.layoutManager = layoutManager
+        }
+        binding!!.accountsList.setHasFixedSize(true)
+        binding!!.accountsList.adapter = adapter
+    }
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+        return dialogView
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        binding = null
+    }
+
+    private val onSwitchItemClickListener = FlexibleAdapter.OnItemClickListener { view, position ->
+        if (userItems.size > position) {
+            val user = userItems[position].user
+            if (userManager!!.setUserAsActive(user).blockingGet()) {
+                cookieManager!!.cookieStore.removeAll()
+                activity?.runOnUiThread { (activity as MainActivity?)!!.resetConversationsList() }
+                dismiss()
+            }
+        }
+        true
+    }
+
+    companion object {
+        val TAG = ChooseAccountShareToDialogFragment::class.java.simpleName
+        fun newInstance(): ChooseAccountShareToDialogFragment {
+            return ChooseAccountShareToDialogFragment()
+        }
+    }
+}

+ 51 - 0
app/src/main/res/layout/dialog_choose_account_share_to.xml

@@ -0,0 +1,51 @@
+<!--
+  ~ Nextcloud Talk application
+  ~
+  ~ @author Marcel Hibbe
+  ~ Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
+  ~
+  ~ This program is free software: you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="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">
+
+    <include
+        android:id="@+id/current_account"
+        layout="@layout/current_account_item"
+        android:layout_width="match_parent"
+        android:layout_height="72dp"
+        android:layout_margin="4dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/accounts_list"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="10dp"
+        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+        app:layout_constrainedHeight="true"
+        app:layout_constraintBottom_toTopOf="@+id/add_account"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/divider"
+        tools:listitem="@layout/account_item" />
+
+</LinearLayout>

+ 20 - 10
app/src/main/res/menu/menu_conversation_plus_filter.xml

@@ -1,8 +1,9 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   ~ Nextcloud Talk application
   ~
   ~ @author Mario Danic
+  ~ @author Marcel Hibbe
+  ~ Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
   ~ Copyright (C) 2017 Mario Danic
   ~
   ~ This program is free software: you can redistribute it and/or modify
@@ -20,14 +21,23 @@
   -->
 
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
-      xmlns:app="http://schemas.android.com/apk/res-auto">
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
 
-	<!-- Search, should appear as action button -->
-	<item android:id="@+id/action_search"
-	      android:title="@string/nc_search"
-	      android:icon="@drawable/ic_search_white_24dp"
-	      app:showAsAction="collapseActionView|always"
-	      android:animateLayoutChanges="true"
-	      app:actionViewClass="androidx.appcompat.widget.SearchView" />
+    <!-- Search, should appear as action button -->
+    <item
+        android:id="@+id/action_search"
+        android:animateLayoutChanges="true"
+        android:icon="@drawable/ic_search_white_24dp"
+        android:title="@string/nc_search"
+        app:actionViewClass="androidx.appcompat.widget.SearchView"
+        app:showAsAction="collapseActionView|always" />
+
+    <item
+        android:id="@+id/action_choose_account"
+        android:animateLayoutChanges="true"
+        android:title="@string/nc_share_to_choose_account"
+        app:showAsAction="collapseActionView|ifRoom"
+        tools:icon="@drawable/account_circle_48dp" />
 
 </menu>

+ 3 - 1
app/src/main/res/values/strings.xml

@@ -455,6 +455,9 @@
     <string name="nc_shared_items_location">Location</string>
     <string name="nc_shared_items_deck_card">Deck card</string>
 
+    <!-- share to screen -->
+    <string name="nc_share_to_choose_account">Choose account</string>
+
     <!-- voice messages -->
     <string name="nc_voice_message_filename">Talk recording from %1$s (%2$s)</string>
     <string name="nc_voice_message_hold_to_record_info">Hold to record, release to send.</string>
@@ -599,5 +602,4 @@
     <string name="nc_expire_message_one_hour">1 hour</string>
     <string name="nc_expire_messages_explanation">Chat messages can be expired after a certain time. Note: Files shared in chat will not be deleted for the owner, but will no longer be shared in the conversation.</string>
 
-
 </resources>