diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt index 45dc36750..ed42b9ac4 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt +++ b/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> = ArrayList() private val searchableConversationItems: MutableList> = 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>) { + 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) diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java index dc205feb0..f225a680e 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java +++ b/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; diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountShareToDialogFragment.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountShareToDialogFragment.kt new file mode 100644 index 000000000..3340f7cb3 --- /dev/null +++ b/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 + * Copyright (C) 2017 Mario Danic + * 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 . + * + * 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? = null + private val userItems: MutableList = 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() + } + } +} diff --git a/app/src/main/res/layout/dialog_choose_account_share_to.xml b/app/src/main/res/layout/dialog_choose_account_share_to.xml new file mode 100644 index 000000000..652f8acf1 --- /dev/null +++ b/app/src/main/res/layout/dialog_choose_account_share_to.xml @@ -0,0 +1,51 @@ + + + + + + + + diff --git a/app/src/main/res/menu/menu_conversation_plus_filter.xml b/app/src/main/res/menu/menu_conversation_plus_filter.xml index 828b7202a..b46bf45df 100644 --- a/app/src/main/res/menu/menu_conversation_plus_filter.xml +++ b/app/src/main/res/menu/menu_conversation_plus_filter.xml @@ -1,8 +1,9 @@ - - + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> - - + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d19c8db9e..6d59a36a7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -455,6 +455,9 @@ Location Deck card + + Choose account + Talk recording from %1$s (%2$s) Hold to record, release to send. @@ -599,5 +602,4 @@ 1 hour 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. -