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

add account switcher for "share to"
This commit is contained in:
Tim Krüger 2022-09-23 12:53:18 +02:00 committed by GitHub
commit dc6083334b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 345 additions and 21 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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