handle federation invitations
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Родитель
2c911d998a
Коммит
c13f2589ff
|
@ -261,6 +261,10 @@
|
|||
android:name=".openconversations.ListOpenConversationsActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".invitation.InvitationsActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".lock.LockedActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
|
|
@ -84,7 +84,7 @@ class SwitchAccountActivity : BaseActivity() {
|
|||
if (userItems.size > position) {
|
||||
val user = (userItems[position] as AdvancedUserItem).user
|
||||
|
||||
if (userManager.setUserAsActive(user).blockingGet()) {
|
||||
if (userManager.setUserAsActive(user!!).blockingGet()) {
|
||||
cookieManager.cookieStore.removeAll()
|
||||
finish()
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ class SwitchAccountActivity : BaseActivity() {
|
|||
participant.actorType = Participant.ActorType.USERS
|
||||
participant.actorId = userId
|
||||
participant.displayName = user.displayName
|
||||
userItems.add(AdvancedUserItem(participant, user, null, viewThemeUtils))
|
||||
userItems.add(AdvancedUserItem(participant, user, null, viewThemeUtils, 0))
|
||||
}
|
||||
}
|
||||
adapter!!.addListener(onSwitchItemClickListener)
|
||||
|
@ -164,7 +164,7 @@ class SwitchAccountActivity : BaseActivity() {
|
|||
participant.displayName = importAccount.getUsername()
|
||||
user = User()
|
||||
user.baseUrl = importAccount.getBaseUrl()
|
||||
userItems.add(AdvancedUserItem(participant, user, account, viewThemeUtils))
|
||||
userItems.add(AdvancedUserItem(participant, user, account, viewThemeUtils, 0))
|
||||
}
|
||||
adapter!!.addListener(onImportItemClickListener)
|
||||
adapter!!.updateDataSet(userItems, false)
|
||||
|
|
|
@ -49,6 +49,7 @@ import com.nextcloud.talk.chat.ChatActivity
|
|||
import com.nextcloud.talk.conversationlist.ConversationsListActivity
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.databinding.ActivityMainBinding
|
||||
import com.nextcloud.talk.invitation.InvitationsActivity
|
||||
import com.nextcloud.talk.lock.LockedActivity
|
||||
import com.nextcloud.talk.models.json.conversations.RoomOverall
|
||||
import com.nextcloud.talk.users.UserManager
|
||||
|
@ -258,7 +259,12 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
|||
}
|
||||
|
||||
if (user != null && userManager.setUserAsActive(user).blockingGet()) {
|
||||
if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
|
||||
if (intent.hasExtra(BundleKeys.KEY_REMOTE_TALK_SHARE)) {
|
||||
if (intent.getBooleanExtra(BundleKeys.KEY_REMOTE_TALK_SHARE, false)) {
|
||||
val intent = Intent(this, InvitationsActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
} else if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
|
||||
if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) {
|
||||
val callNotificationIntent = Intent(this, CallNotificationActivity::class.java)
|
||||
intent.extras?.let { callNotificationIntent.putExtras(it) }
|
||||
|
|
|
@ -1,154 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* @author Andy Scherzinger
|
||||
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
* 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.adapters.items;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
|
||||
import com.nextcloud.talk.R;
|
||||
import com.nextcloud.talk.data.user.model.User;
|
||||
import com.nextcloud.talk.databinding.AccountItemBinding;
|
||||
import com.nextcloud.talk.extensions.ImageViewExtensionsKt;
|
||||
import com.nextcloud.talk.models.json.participants.Participant;
|
||||
import com.nextcloud.talk.ui.theme.ViewThemeUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter;
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
|
||||
import eu.davidea.flexibleadapter.items.IFilterable;
|
||||
import eu.davidea.viewholders.FlexibleViewHolder;
|
||||
|
||||
public class AdvancedUserItem extends AbstractFlexibleItem<AdvancedUserItem.UserItemViewHolder> implements
|
||||
IFilterable<String> {
|
||||
|
||||
private final Participant participant;
|
||||
private final User user;
|
||||
@Nullable
|
||||
private final Account account;
|
||||
private final ViewThemeUtils viewThemeUtils;
|
||||
|
||||
public AdvancedUserItem(Participant participant,
|
||||
User user,
|
||||
@Nullable Account account,
|
||||
ViewThemeUtils viewThemeUtils) {
|
||||
this.participant = participant;
|
||||
this.user = user;
|
||||
this.account = account;
|
||||
this.viewThemeUtils = viewThemeUtils;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof AdvancedUserItem inItem) {
|
||||
return participant.equals(inItem.getModel());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return participant.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the model object
|
||||
*/
|
||||
public Participant getModel() {
|
||||
return participant;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLayoutRes() {
|
||||
return R.layout.account_item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserItemViewHolder createViewHolder(View view, FlexibleAdapter adapter) {
|
||||
return new UserItemViewHolder(view, adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindViewHolder(FlexibleAdapter adapter, UserItemViewHolder holder, int position, List payloads) {
|
||||
|
||||
if (adapter.hasFilter()) {
|
||||
viewThemeUtils.talk.themeAndHighlightText(
|
||||
holder.binding.userName,
|
||||
participant.getDisplayName(),
|
||||
String.valueOf(adapter.getFilter(String.class)));
|
||||
} else {
|
||||
holder.binding.userName.setText(participant.getDisplayName());
|
||||
}
|
||||
|
||||
if (user != null && !TextUtils.isEmpty(user.getBaseUrl())) {
|
||||
String host = Uri.parse(user.getBaseUrl()).getHost();
|
||||
if (!TextUtils.isEmpty(host)) {
|
||||
holder.binding.account.setText(Uri.parse(user.getBaseUrl()).getHost());
|
||||
} else {
|
||||
holder.binding.account.setText(user.getBaseUrl());
|
||||
}
|
||||
}
|
||||
|
||||
if (user != null &&
|
||||
user.getBaseUrl() != null &&
|
||||
(user.getBaseUrl().startsWith("http://") || user.getBaseUrl().startsWith("https://"))) {
|
||||
ImageViewExtensionsKt.loadUserAvatar(holder.binding.userIcon, user, participant.getCalculatedActorId(),
|
||||
true, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean filter(String constraint) {
|
||||
return participant.getDisplayName() != null &&
|
||||
Pattern
|
||||
.compile(constraint, Pattern.CASE_INSENSITIVE | Pattern.LITERAL)
|
||||
.matcher(participant.getDisplayName().trim())
|
||||
.find();
|
||||
}
|
||||
|
||||
static class UserItemViewHolder extends FlexibleViewHolder {
|
||||
|
||||
public AccountItemBinding binding;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
UserItemViewHolder(View view, FlexibleAdapter adapter) {
|
||||
super(view, adapter);
|
||||
binding = AccountItemBinding.bind(view);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* @author Andy Scherzinger
|
||||
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
* 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.adapters.items
|
||||
|
||||
import android.accounts.Account
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.adapters.items.AdvancedUserItem.UserItemViewHolder
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.databinding.AccountItemBinding
|
||||
import com.nextcloud.talk.extensions.loadUserAvatar
|
||||
import com.nextcloud.talk.models.json.participants.Participant
|
||||
import com.nextcloud.talk.ui.theme.ViewThemeUtils
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFilterable
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class AdvancedUserItem(
|
||||
/**
|
||||
* @return the model object
|
||||
*/
|
||||
val model: Participant,
|
||||
@JvmField val user: User?,
|
||||
val account: Account?,
|
||||
private val viewThemeUtils: ViewThemeUtils,
|
||||
private val actionRequiredCount: Int
|
||||
) : AbstractFlexibleItem<UserItemViewHolder>(), IFilterable<String?> {
|
||||
override fun equals(o: Any?): Boolean {
|
||||
return if (o is AdvancedUserItem) {
|
||||
model == o.model
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return model.hashCode()
|
||||
}
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.account_item
|
||||
}
|
||||
|
||||
override fun createViewHolder(
|
||||
view: View?,
|
||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>?
|
||||
): UserItemViewHolder {
|
||||
return UserItemViewHolder(view, adapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(
|
||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
||||
holder: UserItemViewHolder,
|
||||
position: Int,
|
||||
payloads: MutableList<Any>
|
||||
) {
|
||||
if (adapter.hasFilter()) {
|
||||
viewThemeUtils.talk.themeAndHighlightText(
|
||||
holder.binding.userName,
|
||||
model.displayName,
|
||||
adapter.getFilter(String::class.java).toString()
|
||||
)
|
||||
} else {
|
||||
holder.binding.userName.text = model.displayName
|
||||
}
|
||||
if (user != null && !TextUtils.isEmpty(user.baseUrl)) {
|
||||
val host = Uri.parse(user.baseUrl).host
|
||||
if (!TextUtils.isEmpty(host)) {
|
||||
holder.binding.account.text = Uri.parse(user.baseUrl).host
|
||||
} else {
|
||||
holder.binding.account.text = user.baseUrl
|
||||
}
|
||||
}
|
||||
if (user?.baseUrl != null &&
|
||||
(user.baseUrl!!.startsWith("http://") || user.baseUrl!!.startsWith("https://"))
|
||||
) {
|
||||
holder.binding.userIcon.loadUserAvatar(user, model.calculatedActorId!!, true, false)
|
||||
}
|
||||
if (actionRequiredCount > 0) {
|
||||
holder.binding.actionRequired.visibility = View.VISIBLE
|
||||
} else {
|
||||
holder.binding.actionRequired.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
override fun filter(constraint: String?): Boolean {
|
||||
return model.displayName != null &&
|
||||
Pattern
|
||||
.compile(constraint, Pattern.CASE_INSENSITIVE or Pattern.LITERAL)
|
||||
.matcher(model.displayName!!.trim { it <= ' ' })
|
||||
.find()
|
||||
}
|
||||
|
||||
class UserItemViewHolder(view: View?, adapter: FlexibleAdapter<*>?) : FlexibleViewHolder(view, adapter) {
|
||||
var binding: AccountItemBinding
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
init {
|
||||
binding = AccountItemBinding.bind(view!!)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,6 +33,7 @@ import com.nextcloud.talk.models.json.conversations.RoomsOverall;
|
|||
import com.nextcloud.talk.models.json.generic.GenericOverall;
|
||||
import com.nextcloud.talk.models.json.generic.Status;
|
||||
import com.nextcloud.talk.models.json.hovercard.HoverCardOverall;
|
||||
import com.nextcloud.talk.models.json.invitation.InvitationOverall;
|
||||
import com.nextcloud.talk.models.json.mention.MentionOverall;
|
||||
import com.nextcloud.talk.models.json.notifications.NotificationOverall;
|
||||
import com.nextcloud.talk.models.json.opengraph.OpenGraphOverall;
|
||||
|
@ -706,4 +707,16 @@ public interface NcApi {
|
|||
Observable<GenericOverall> setRecordingConsent(@Header("Authorization") String authorization,
|
||||
@Url String url,
|
||||
@Field("recordingConsent") int recordingConsent);
|
||||
|
||||
@GET
|
||||
Observable<InvitationOverall> getInvitations(@Header("Authorization") String authorization,
|
||||
@Url String url);
|
||||
|
||||
@POST
|
||||
Observable<GenericOverall> acceptInvitation(@Header("Authorization") String authorization,
|
||||
@Url String url);
|
||||
|
||||
@DELETE
|
||||
Observable<GenericOverall> rejectInvitation(@Header("Authorization") String authorization,
|
||||
@Url String url);
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
* @author Ezhil Shanmugham
|
||||
* Copyright (C) 2022 Álvaro Brey <alvaro.brey@nextcloud.com>
|
||||
* Copyright (C) 2022 Andy Scherzinger (info@andy-scherzinger.de)
|
||||
* Copyright (C) 2022 Marcel Hibbe (dev@mhibbe.de)
|
||||
* Copyright (C) 2022-2024 Marcel Hibbe (dev@mhibbe.de)
|
||||
* Copyright (C) 2017-2020 Mario Danic (mario@lovelyhq.com)
|
||||
* Copyright (C) 2023 Ezhil Shanmugham <ezhil56x.contact@gmail.com>
|
||||
*
|
||||
|
@ -53,10 +53,12 @@ import android.view.inputmethod.EditorInfo
|
|||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.view.MenuItemCompat
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
|
@ -68,6 +70,9 @@ import coil.request.ImageRequest
|
|||
import coil.target.Target
|
||||
import coil.transform.CircleCropTransformation
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.badge.BadgeDrawable
|
||||
import com.google.android.material.badge.BadgeUtils
|
||||
import com.google.android.material.badge.ExperimentalBadgeUtils
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
|
@ -88,10 +93,12 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
|
|||
import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
|
||||
import com.nextcloud.talk.chat.ChatActivity
|
||||
import com.nextcloud.talk.contacts.ContactsActivity
|
||||
import com.nextcloud.talk.conversationlist.viewmodels.ConversationsListViewModel
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.databinding.ActivityConversationsBinding
|
||||
import com.nextcloud.talk.events.ConversationsListFetchDataEvent
|
||||
import com.nextcloud.talk.events.EventStatus
|
||||
import com.nextcloud.talk.invitation.InvitationsActivity
|
||||
import com.nextcloud.talk.jobs.AccountRemovalWorker
|
||||
import com.nextcloud.talk.jobs.ContactAddressBookWorker.Companion.run
|
||||
import com.nextcloud.talk.jobs.DeleteConversationWorker
|
||||
|
@ -170,6 +177,11 @@ class ConversationsListActivity :
|
|||
@Inject
|
||||
lateinit var arbitraryStorageManager: ArbitraryStorageManager
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelProvider.Factory
|
||||
|
||||
lateinit var conversationsListViewModel: ConversationsListViewModel
|
||||
|
||||
override val appBarLayoutType: AppBarLayoutType
|
||||
get() = AppBarLayoutType.SEARCH_BAR
|
||||
|
||||
|
@ -206,6 +218,7 @@ class ConversationsListActivity :
|
|||
FilterConversationFragment.UNREAD to false
|
||||
)
|
||||
val searchBehaviorSubject = BehaviorSubject.createDefault(false)
|
||||
private lateinit var accountIconBadge: BadgeDrawable
|
||||
|
||||
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
|
@ -221,6 +234,8 @@ class ConversationsListActivity :
|
|||
super.onCreate(savedInstanceState)
|
||||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
|
||||
conversationsListViewModel = ViewModelProvider(this, viewModelFactory)[ConversationsListViewModel::class.java]
|
||||
|
||||
binding = ActivityConversationsBinding.inflate(layoutInflater)
|
||||
setupActionBar()
|
||||
setContentView(binding.root)
|
||||
|
@ -230,6 +245,8 @@ class ConversationsListActivity :
|
|||
|
||||
forwardMessage = intent.getBooleanExtra(KEY_FORWARD_MSG_FLAG, false)
|
||||
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
|
||||
|
||||
initObservers()
|
||||
}
|
||||
|
||||
override fun onPostCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -279,6 +296,7 @@ class ConversationsListActivity :
|
|||
viewThemeUtils.material.colorMaterialTextButton(binding.switchAccountButton)
|
||||
searchBehaviorSubject.onNext(false)
|
||||
fetchRooms()
|
||||
fetchPendingInvitations()
|
||||
} else {
|
||||
Log.e(TAG, "userManager.currentUser.blockingGet() returned null")
|
||||
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
|
||||
|
@ -287,6 +305,48 @@ class ConversationsListActivity :
|
|||
showSearchOrToolbar()
|
||||
}
|
||||
|
||||
private fun initObservers() {
|
||||
conversationsListViewModel.getFederationInvitationsViewState.observe(this) { state ->
|
||||
when (state) {
|
||||
is ConversationsListViewModel.GetFederationInvitationsStartState -> {
|
||||
binding.conversationListHintInclude.conversationListHintLayout.visibility = View.GONE
|
||||
}
|
||||
|
||||
is ConversationsListViewModel.GetFederationInvitationsSuccessState -> {
|
||||
if (state.showInvitationsHint) {
|
||||
binding.conversationListHintInclude.conversationListHintLayout.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.conversationListHintInclude.conversationListHintLayout.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
is ConversationsListViewModel.GetFederationInvitationsErrorState -> {
|
||||
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
conversationsListViewModel.showBadgeViewState.observe(this) { state ->
|
||||
when (state) {
|
||||
is ConversationsListViewModel.ShowBadgeStartState -> {
|
||||
showAccountIconBadge(false)
|
||||
}
|
||||
|
||||
is ConversationsListViewModel.ShowBadgeSuccessState -> {
|
||||
showAccountIconBadge(state.showBadge)
|
||||
}
|
||||
|
||||
is ConversationsListViewModel.ShowBadgeErrorState -> {
|
||||
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun filterConversation() {
|
||||
val accountId = UserIdUtils.getIdForUser(userManager.currentUser.blockingGet())
|
||||
filterState[FilterConversationFragment.UNREAD] = (
|
||||
|
@ -469,6 +529,22 @@ class ConversationsListActivity :
|
|||
return true
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalBadgeUtils::class)
|
||||
fun showAccountIconBadge(showBadge: Boolean) {
|
||||
if (!::accountIconBadge.isInitialized) {
|
||||
accountIconBadge = BadgeDrawable.create(binding.switchAccountButton.context)
|
||||
accountIconBadge.verticalOffset = BADGE_OFFSET
|
||||
accountIconBadge.horizontalOffset = BADGE_OFFSET
|
||||
accountIconBadge.backgroundColor = resources.getColor(R.color.badge_color, null)
|
||||
}
|
||||
|
||||
if (showBadge) {
|
||||
BadgeUtils.attachBadgeDrawable(accountIconBadge, binding.switchAccountButton)
|
||||
} else {
|
||||
BadgeUtils.detachBadgeDrawable(accountIconBadge, binding.switchAccountButton)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
|
||||
|
@ -673,6 +749,18 @@ class ConversationsListActivity :
|
|||
}
|
||||
}
|
||||
|
||||
private fun fetchPendingInvitations() {
|
||||
binding.conversationListHintInclude.conversationListHintLayout.setOnClickListener {
|
||||
val intent = Intent(this, InvitationsActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
// TODO create mvvm, fetch pending invitations for all users and store in database for users, if current user
|
||||
// has invitation -> show hint, if one or more other users have invitations -> show badge
|
||||
|
||||
conversationsListViewModel.getFederationInvitations()
|
||||
}
|
||||
|
||||
private fun initOverallLayout(isConversationListNotEmpty: Boolean) {
|
||||
if (isConversationListNotEmpty) {
|
||||
if (binding?.emptyLayout?.visibility != View.GONE) {
|
||||
|
@ -857,7 +945,10 @@ class ConversationsListActivity :
|
|||
}
|
||||
false
|
||||
}
|
||||
binding?.swipeRefreshLayoutView?.setOnRefreshListener { fetchRooms() }
|
||||
binding?.swipeRefreshLayoutView?.setOnRefreshListener {
|
||||
fetchRooms()
|
||||
fetchPendingInvitations()
|
||||
}
|
||||
binding?.swipeRefreshLayoutView?.let { viewThemeUtils.androidx.themeSwipeRefreshLayout(it) }
|
||||
binding?.emptyLayout?.setOnClickListener { showNewConversationsScreen() }
|
||||
binding?.floatingActionButton?.setOnClickListener {
|
||||
|
@ -1716,5 +1807,6 @@ class ConversationsListActivity :
|
|||
const val HTTP_SERVICE_UNAVAILABLE = 503
|
||||
const val MAINTENANCE_MODE_HEADER_KEY = "X-Nextcloud-Maintenance-Mode"
|
||||
const val REQUEST_POST_NOTIFICATIONS_PERMISSION = 111
|
||||
const val BADGE_OFFSET = 35
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2023 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/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.conversationlist.data
|
||||
|
||||
interface ConversationsListRepository
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2023 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/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.conversationlist.data
|
||||
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
|
||||
class ConversationsListRepositoryImpl(private val ncApi: NcApi) : ConversationsListRepository
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2023 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/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.conversationlist.viewmodels
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.nextcloud.talk.conversationlist.data.ConversationsListRepository
|
||||
import com.nextcloud.talk.invitation.data.InvitationsModel
|
||||
import com.nextcloud.talk.invitation.data.InvitationsRepository
|
||||
import com.nextcloud.talk.users.UserManager
|
||||
import io.reactivex.Observer
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import javax.inject.Inject
|
||||
|
||||
class ConversationsListViewModel @Inject constructor(
|
||||
private val conversationsListRepository: ConversationsListRepository
|
||||
) :
|
||||
ViewModel() {
|
||||
|
||||
@Inject
|
||||
lateinit var invitationsRepository: InvitationsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var userManager: UserManager
|
||||
|
||||
sealed interface ViewState
|
||||
|
||||
object GetFederationInvitationsStartState : ViewState
|
||||
object GetFederationInvitationsErrorState : ViewState
|
||||
|
||||
open class GetFederationInvitationsSuccessState(val showInvitationsHint: Boolean) : ViewState
|
||||
|
||||
private val _getFederationInvitationsViewState: MutableLiveData<ViewState> =
|
||||
MutableLiveData(GetFederationInvitationsStartState)
|
||||
val getFederationInvitationsViewState: LiveData<ViewState>
|
||||
get() = _getFederationInvitationsViewState
|
||||
|
||||
object ShowBadgeStartState : ViewState
|
||||
object ShowBadgeErrorState : ViewState
|
||||
open class ShowBadgeSuccessState(val showBadge: Boolean) : ViewState
|
||||
|
||||
private val _showBadgeViewState: MutableLiveData<ViewState> = MutableLiveData(ShowBadgeStartState)
|
||||
val showBadgeViewState: LiveData<ViewState>
|
||||
get() = _showBadgeViewState
|
||||
|
||||
fun getFederationInvitations() {
|
||||
_getFederationInvitationsViewState.value = GetFederationInvitationsStartState
|
||||
_showBadgeViewState.value = ShowBadgeStartState
|
||||
|
||||
userManager.users.blockingGet()?.forEach {
|
||||
invitationsRepository.fetchInvitations(it)
|
||||
.subscribeOn(Schedulers.io())
|
||||
?.observeOn(AndroidSchedulers.mainThread())
|
||||
?.subscribe(FederatedInvitationsObserver())
|
||||
}
|
||||
}
|
||||
|
||||
inner class FederatedInvitationsObserver : Observer<InvitationsModel> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun onNext(invitationsModel: InvitationsModel) {
|
||||
if (invitationsModel.user.userId?.equals(userManager.currentUser.blockingGet().userId) == true) {
|
||||
if (invitationsModel.invitations.isNotEmpty()) {
|
||||
_getFederationInvitationsViewState.value = GetFederationInvitationsSuccessState(true)
|
||||
} else {
|
||||
_getFederationInvitationsViewState.value = GetFederationInvitationsSuccessState(false)
|
||||
}
|
||||
} else {
|
||||
if (invitationsModel.invitations.isNotEmpty()) {
|
||||
_showBadgeViewState.value = ShowBadgeSuccessState(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
_getFederationInvitationsViewState.value = GetFederationInvitationsErrorState
|
||||
Log.e(TAG, "Failed to fetch pending invitations", e)
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
// unused atm
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = ConversationsListViewModel::class.simpleName
|
||||
}
|
||||
}
|
|
@ -32,11 +32,15 @@ import com.nextcloud.talk.conversation.repository.ConversationRepository
|
|||
import com.nextcloud.talk.conversation.repository.ConversationRepositoryImpl
|
||||
import com.nextcloud.talk.conversationinfoedit.data.ConversationInfoEditRepository
|
||||
import com.nextcloud.talk.conversationinfoedit.data.ConversationInfoEditRepositoryImpl
|
||||
import com.nextcloud.talk.conversationlist.data.ConversationsListRepository
|
||||
import com.nextcloud.talk.conversationlist.data.ConversationsListRepositoryImpl
|
||||
import com.nextcloud.talk.data.source.local.TalkDatabase
|
||||
import com.nextcloud.talk.data.storage.ArbitraryStoragesRepository
|
||||
import com.nextcloud.talk.data.storage.ArbitraryStoragesRepositoryImpl
|
||||
import com.nextcloud.talk.data.user.UsersRepository
|
||||
import com.nextcloud.talk.data.user.UsersRepositoryImpl
|
||||
import com.nextcloud.talk.invitation.data.InvitationsRepository
|
||||
import com.nextcloud.talk.invitation.data.InvitationsRepositoryImpl
|
||||
import com.nextcloud.talk.openconversations.data.OpenConversationsRepository
|
||||
import com.nextcloud.talk.openconversations.data.OpenConversationsRepositoryImpl
|
||||
import com.nextcloud.talk.polls.repositories.PollRepository
|
||||
|
@ -135,6 +139,11 @@ class RepositoryModule {
|
|||
return TranslateRepositoryImpl(ncApi)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideConversationsListRepository(ncApi: NcApi): ConversationsListRepository {
|
||||
return ConversationsListRepositoryImpl(ncApi)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideChatRepository(ncApi: NcApi): ChatRepository {
|
||||
return ChatRepositoryImpl(ncApi)
|
||||
|
@ -152,4 +161,9 @@ class RepositoryModule {
|
|||
fun provideConversationRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): ConversationRepository {
|
||||
return ConversationRepositoryImpl(ncApi, userProvider)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideInvitationsRepository(ncApi: NcApi): InvitationsRepository {
|
||||
return InvitationsRepositoryImpl(ncApi)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -250,6 +250,7 @@ public class RestModule {
|
|||
.header("User-Agent", ApiUtils.getUserAgent())
|
||||
.header("Accept", "application/json")
|
||||
.header("OCS-APIRequest", "true")
|
||||
.header("ngrok-skip-browser-warning", "true")
|
||||
.method(original.method(), original.body())
|
||||
.build();
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@ import com.nextcloud.talk.chat.viewmodels.ChatViewModel
|
|||
import com.nextcloud.talk.conversation.viewmodel.ConversationViewModel
|
||||
import com.nextcloud.talk.conversation.viewmodel.RenameConversationViewModel
|
||||
import com.nextcloud.talk.conversationinfoedit.viewmodel.ConversationInfoEditViewModel
|
||||
import com.nextcloud.talk.conversationlist.viewmodels.ConversationsListViewModel
|
||||
import com.nextcloud.talk.invitation.viewmodels.InvitationsViewModel
|
||||
import com.nextcloud.talk.messagesearch.MessageSearchViewModel
|
||||
import com.nextcloud.talk.openconversations.viewmodels.OpenConversationsViewModel
|
||||
import com.nextcloud.talk.polls.viewmodels.PollCreateViewModel
|
||||
|
@ -120,6 +122,11 @@ abstract class ViewModelModule {
|
|||
@ViewModelKey(OpenConversationsViewModel::class)
|
||||
abstract fun openConversationsViewModel(viewModel: OpenConversationsViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(ConversationsListViewModel::class)
|
||||
abstract fun conversationsListViewModel(viewModel: ConversationsListViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(ChatViewModel::class)
|
||||
|
@ -144,4 +151,9 @@ abstract class ViewModelModule {
|
|||
@IntoMap
|
||||
@ViewModelKey(ConversationViewModel::class)
|
||||
abstract fun conversationViewModel(viewModel: ConversationViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(InvitationsViewModel::class)
|
||||
abstract fun invitationsViewModel(viewModel: InvitationsViewModel): ViewModel
|
||||
}
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2023 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/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.invitation
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import autodagger.AutoInjector
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.BaseActivity
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.conversationlist.ConversationsListActivity
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.databinding.ActivityInvitationsBinding
|
||||
import com.nextcloud.talk.invitation.adapters.InvitationsAdapter
|
||||
import com.nextcloud.talk.invitation.data.ActionEnum
|
||||
import com.nextcloud.talk.invitation.data.Invitation
|
||||
import com.nextcloud.talk.invitation.viewmodels.InvitationsViewModel
|
||||
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class InvitationsActivity : BaseActivity() {
|
||||
|
||||
private lateinit var binding: ActivityInvitationsBinding
|
||||
|
||||
@Inject
|
||||
lateinit var ncApi: NcApi
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelProvider.Factory
|
||||
|
||||
@Inject
|
||||
lateinit var userProvider: CurrentUserProviderNew
|
||||
|
||||
lateinit var invitationsViewModel: InvitationsViewModel
|
||||
|
||||
lateinit var adapter: InvitationsAdapter
|
||||
|
||||
private lateinit var currentUser: User
|
||||
|
||||
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
val intent = Intent(this@InvitationsActivity, ConversationsListActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
|
||||
invitationsViewModel = ViewModelProvider(this, viewModelFactory)[InvitationsViewModel::class.java]
|
||||
|
||||
currentUser = userProvider.currentUser.blockingGet()
|
||||
invitationsViewModel.fetchInvitations(currentUser)
|
||||
|
||||
binding = ActivityInvitationsBinding.inflate(layoutInflater)
|
||||
setupActionBar()
|
||||
setContentView(binding.root)
|
||||
setupSystemColors()
|
||||
|
||||
adapter = InvitationsAdapter(currentUser) { invitation, action ->
|
||||
handleInvitation(invitation, action)
|
||||
}
|
||||
|
||||
binding.invitationsRecyclerView.adapter = adapter
|
||||
|
||||
initObservers()
|
||||
|
||||
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
|
||||
}
|
||||
|
||||
enum class InvitationAction {
|
||||
ACCEPT,
|
||||
REJECT
|
||||
}
|
||||
|
||||
private fun handleInvitation(invitation: Invitation, action: InvitationAction) {
|
||||
when (action) {
|
||||
InvitationAction.ACCEPT -> {
|
||||
invitationsViewModel.acceptInvitation(currentUser, invitation)
|
||||
}
|
||||
|
||||
InvitationAction.REJECT -> {
|
||||
invitationsViewModel.rejectInvitation(currentUser, invitation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initObservers() {
|
||||
invitationsViewModel.fetchInvitationsViewState.observe(this) { state ->
|
||||
when (state) {
|
||||
is InvitationsViewModel.FetchInvitationsStartState -> {
|
||||
binding.invitationsRecyclerView.visibility = View.GONE
|
||||
binding.progressBarWrapper.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
is InvitationsViewModel.FetchInvitationsSuccessState -> {
|
||||
binding.invitationsRecyclerView.visibility = View.VISIBLE
|
||||
binding.progressBarWrapper.visibility = View.GONE
|
||||
adapter.submitList(state.invitations)
|
||||
}
|
||||
|
||||
is InvitationsViewModel.FetchInvitationsEmptyState -> {
|
||||
binding.invitationsRecyclerView.visibility = View.GONE
|
||||
binding.progressBarWrapper.visibility = View.GONE
|
||||
|
||||
binding.emptyList.emptyListView.visibility = View.VISIBLE
|
||||
binding.emptyList.emptyListViewHeadline.text = getString(R.string.nc_federation_no_invitations)
|
||||
binding.emptyList.emptyListIcon.setImageResource(R.drawable.baseline_info_24)
|
||||
binding.emptyList.emptyListIcon.visibility = View.VISIBLE
|
||||
binding.emptyList.emptyListViewText.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
is InvitationsViewModel.FetchInvitationsErrorState -> {
|
||||
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
invitationsViewModel.invitationActionViewState.observe(this) { state ->
|
||||
when (state) {
|
||||
is InvitationsViewModel.InvitationActionStartState -> {
|
||||
}
|
||||
|
||||
is InvitationsViewModel.InvitationActionSuccessState -> {
|
||||
if (state.action == ActionEnum.ACCEPT) {
|
||||
// val bundle = Bundle()
|
||||
// bundle.putString(BundleKeys.KEY_ROOM_TOKEN, ????) // ???
|
||||
//
|
||||
// val chatIntent = Intent(context, ChatActivity::class.java)
|
||||
// chatIntent.putExtras(bundle)
|
||||
// chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
// startActivity(chatIntent)
|
||||
|
||||
val intent = Intent(this, ConversationsListActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
startActivity(intent)
|
||||
} else {
|
||||
// adapter.currentList.remove(state.invitation)
|
||||
// adapter.notifyDataSetChanged() // leads to UnsupportedOperationException ?!
|
||||
|
||||
// Just reload activity as lazy workaround to not deal with adapter for now.
|
||||
// Might be fine until switching to jetpack compose.
|
||||
finish()
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
is InvitationsViewModel.InvitationActionErrorState -> {
|
||||
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupActionBar() {
|
||||
setSupportActionBar(binding.invitationsToolbar)
|
||||
binding.invitationsToolbar.setNavigationOnClickListener {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
supportActionBar?.setIcon(ColorDrawable(resources!!.getColor(R.color.transparent, null)))
|
||||
viewThemeUtils.material.themeToolbar(binding.invitationsToolbar)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2023 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/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.invitation.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import autodagger.AutoInjector
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.databinding.RvItemInvitationBinding
|
||||
import com.nextcloud.talk.invitation.InvitationsActivity
|
||||
import com.nextcloud.talk.invitation.data.Invitation
|
||||
import com.nextcloud.talk.ui.theme.ViewThemeUtils
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class InvitationsAdapter(
|
||||
val user: User,
|
||||
private val handleInvitation: (Invitation, InvitationsActivity.InvitationAction) -> Unit
|
||||
) : ListAdapter<Invitation, InvitationsAdapter.InvitationsViewHolder>(InvitationsCallback) {
|
||||
|
||||
@Inject
|
||||
lateinit var viewThemeUtils: ViewThemeUtils
|
||||
|
||||
inner class InvitationsViewHolder(private val itemBinding: RvItemInvitationBinding) :
|
||||
RecyclerView.ViewHolder(itemBinding.root) {
|
||||
|
||||
private var currentInvitation: Invitation? = null
|
||||
|
||||
fun bindItem(invitation: Invitation) {
|
||||
currentInvitation = invitation
|
||||
|
||||
itemBinding.title.text = invitation.roomName
|
||||
itemBinding.subject.text = String.format(
|
||||
itemBinding.root.context.resources.getString(R.string.nc_federation_invited_to_room),
|
||||
invitation.inviterDisplayName,
|
||||
invitation.remoteServerUrl
|
||||
)
|
||||
|
||||
itemBinding.acceptInvitation.setOnClickListener {
|
||||
currentInvitation?.let {
|
||||
handleInvitation(it, InvitationsActivity.InvitationAction.ACCEPT)
|
||||
}
|
||||
}
|
||||
|
||||
itemBinding.rejectInvitation.setOnClickListener {
|
||||
currentInvitation?.let {
|
||||
handleInvitation(it, InvitationsActivity.InvitationAction.REJECT)
|
||||
}
|
||||
}
|
||||
|
||||
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(itemBinding.rejectInvitation)
|
||||
viewThemeUtils.material.colorMaterialButtonPrimaryTonal(itemBinding.acceptInvitation)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InvitationsViewHolder {
|
||||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
return InvitationsViewHolder(
|
||||
RvItemInvitationBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: InvitationsViewHolder, position: Int) {
|
||||
val invitation = getItem(position)
|
||||
holder.bindItem(invitation)
|
||||
}
|
||||
}
|
||||
|
||||
object InvitationsCallback : DiffUtil.ItemCallback<Invitation>() {
|
||||
override fun areItemsTheSame(oldItem: Invitation, newItem: Invitation): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: Invitation, newItem: Invitation): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2023 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/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.invitation.data
|
||||
|
||||
data class Invitation(
|
||||
var id: Int,
|
||||
var userId: String,
|
||||
var state: Int,
|
||||
var localRoomId: Int,
|
||||
var accessToken: String?,
|
||||
var remoteServerUrl: String,
|
||||
var remoteToken: String,
|
||||
var remoteAttendeeId: Int,
|
||||
var inviterCloudId: String,
|
||||
var inviterDisplayName: String,
|
||||
var roomName: String
|
||||
)
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2023 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/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.invitation.data
|
||||
enum class ActionEnum { ACCEPT, REJECT }
|
||||
data class InvitationActionModel(
|
||||
var action: ActionEnum,
|
||||
var statusCode: Int,
|
||||
var invitation: Invitation
|
||||
)
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2023 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/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.invitation.data
|
||||
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
|
||||
data class InvitationsModel(
|
||||
var user: User,
|
||||
var invitations: List<Invitation>
|
||||
)
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2023 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/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.invitation.data
|
||||
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import io.reactivex.Observable
|
||||
|
||||
interface InvitationsRepository {
|
||||
fun fetchInvitations(user: User): Observable<InvitationsModel>
|
||||
fun acceptInvitation(user: User, invitation: Invitation): Observable<InvitationActionModel>
|
||||
fun rejectInvitation(user: User, invitation: Invitation): Observable<InvitationActionModel>
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2023 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/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.invitation.data
|
||||
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
import io.reactivex.Observable
|
||||
|
||||
class InvitationsRepositoryImpl(private val ncApi: NcApi) :
|
||||
InvitationsRepository {
|
||||
|
||||
override fun fetchInvitations(user: User): Observable<InvitationsModel> {
|
||||
val credentials: String = ApiUtils.getCredentials(user.username, user.token)
|
||||
|
||||
return ncApi.getInvitations(
|
||||
credentials,
|
||||
ApiUtils.getUrlForInvitation(user.baseUrl)
|
||||
).map { mapToInvitationsModel(user, it.ocs?.data!!) }
|
||||
}
|
||||
|
||||
override fun acceptInvitation(user: User, invitation: Invitation): Observable<InvitationActionModel> {
|
||||
val credentials: String = ApiUtils.getCredentials(user.username, user.token)
|
||||
|
||||
return ncApi.acceptInvitation(
|
||||
credentials,
|
||||
ApiUtils.getUrlForInvitationAccept(user.baseUrl, invitation.id)
|
||||
).map { InvitationActionModel(ActionEnum.ACCEPT, it.ocs?.meta?.statusCode!!, invitation) }
|
||||
}
|
||||
|
||||
override fun rejectInvitation(user: User, invitation: Invitation): Observable<InvitationActionModel> {
|
||||
val credentials: String = ApiUtils.getCredentials(user.username, user.token)
|
||||
|
||||
return ncApi.rejectInvitation(
|
||||
credentials,
|
||||
ApiUtils.getUrlForInvitationReject(user.baseUrl, invitation.id)
|
||||
).map { InvitationActionModel(ActionEnum.REJECT, it.ocs?.meta?.statusCode!!, invitation) }
|
||||
}
|
||||
|
||||
private fun mapToInvitationsModel(
|
||||
user: User,
|
||||
invitations: List<com.nextcloud.talk.models.json.invitation.Invitation>
|
||||
): InvitationsModel {
|
||||
val filteredInvitations = invitations.filter { it.state == OPEN_PENDING_INVITATION }
|
||||
|
||||
return InvitationsModel(
|
||||
user,
|
||||
filteredInvitations.map { invitation ->
|
||||
Invitation(
|
||||
invitation.id,
|
||||
invitation.userId!!,
|
||||
invitation.state,
|
||||
invitation.localRoomId,
|
||||
invitation.accessToken!!,
|
||||
invitation.remoteServerUrl!!,
|
||||
invitation.remoteToken!!,
|
||||
invitation.remoteAttendeeId,
|
||||
invitation.inviterCloudId!!,
|
||||
invitation.inviterDisplayName!!,
|
||||
invitation.roomName!!
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val OPEN_PENDING_INVITATION = 0
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2023 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/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.invitation.viewmodels
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.invitation.data.ActionEnum
|
||||
import com.nextcloud.talk.invitation.data.Invitation
|
||||
import com.nextcloud.talk.invitation.data.InvitationActionModel
|
||||
import com.nextcloud.talk.invitation.data.InvitationsModel
|
||||
import com.nextcloud.talk.invitation.data.InvitationsRepository
|
||||
import io.reactivex.Observer
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import javax.inject.Inject
|
||||
|
||||
class InvitationsViewModel @Inject constructor(private val repository: InvitationsRepository) :
|
||||
ViewModel() {
|
||||
|
||||
sealed interface ViewState
|
||||
|
||||
object FetchInvitationsStartState : ViewState
|
||||
object FetchInvitationsEmptyState : ViewState
|
||||
object FetchInvitationsErrorState : ViewState
|
||||
open class FetchInvitationsSuccessState(val invitations: List<Invitation>) : ViewState
|
||||
|
||||
private val _fetchInvitationsViewState: MutableLiveData<ViewState> = MutableLiveData(FetchInvitationsStartState)
|
||||
val fetchInvitationsViewState: LiveData<ViewState>
|
||||
get() = _fetchInvitationsViewState
|
||||
|
||||
object InvitationActionStartState : ViewState
|
||||
object InvitationActionErrorState : ViewState
|
||||
|
||||
private val _invitationActionViewState: MutableLiveData<ViewState> = MutableLiveData(InvitationActionStartState)
|
||||
|
||||
open class InvitationActionSuccessState(val action: ActionEnum, val invitation: Invitation) : ViewState
|
||||
|
||||
val invitationActionViewState: LiveData<ViewState>
|
||||
get() = _invitationActionViewState
|
||||
|
||||
fun fetchInvitations(user: User) {
|
||||
_fetchInvitationsViewState.value = FetchInvitationsStartState
|
||||
repository.fetchInvitations(user)
|
||||
.subscribeOn(Schedulers.io())
|
||||
?.observeOn(AndroidSchedulers.mainThread())
|
||||
?.subscribe(FetchInvitationsObserver())
|
||||
}
|
||||
|
||||
fun acceptInvitation(user: User, invitation: Invitation) {
|
||||
repository.acceptInvitation(user, invitation)
|
||||
.subscribeOn(Schedulers.io())
|
||||
?.observeOn(AndroidSchedulers.mainThread())
|
||||
?.subscribe(InvitationActionObserver())
|
||||
}
|
||||
|
||||
fun rejectInvitation(user: User, invitation: Invitation) {
|
||||
repository.rejectInvitation(user, invitation)
|
||||
.subscribeOn(Schedulers.io())
|
||||
?.observeOn(AndroidSchedulers.mainThread())
|
||||
?.subscribe(InvitationActionObserver())
|
||||
}
|
||||
|
||||
inner class FetchInvitationsObserver : Observer<InvitationsModel> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun onNext(model: InvitationsModel) {
|
||||
if (model.invitations.isEmpty()) {
|
||||
_fetchInvitationsViewState.value = FetchInvitationsEmptyState
|
||||
} else {
|
||||
_fetchInvitationsViewState.value = FetchInvitationsSuccessState(model.invitations)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Log.e(TAG, "Error when fetching invitations")
|
||||
_fetchInvitationsViewState.value = FetchInvitationsErrorState
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
// unused atm
|
||||
}
|
||||
}
|
||||
|
||||
inner class InvitationActionObserver : Observer<InvitationActionModel> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun onNext(model: InvitationActionModel) {
|
||||
if (model.statusCode == HTTP_OK) {
|
||||
_invitationActionViewState.value = InvitationActionSuccessState(model.action, model.invitation)
|
||||
} else {
|
||||
_invitationActionViewState.value = InvitationActionErrorState
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Log.e(TAG, "Error when handling invitation")
|
||||
_invitationActionViewState.value = InvitationActionErrorState
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
// unused atm
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = InvitationsViewModel::class.simpleName
|
||||
private const val OPEN_PENDING_INVITATION = "0"
|
||||
private const val HTTP_OK = 200
|
||||
}
|
||||
}
|
|
@ -94,6 +94,7 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_MESSAGE_ID
|
|||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NOTIFICATION_ID
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NOTIFICATION_RESTRICT_DELETION
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NOTIFICATION_TIMESTAMP
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_REMOTE_TALK_SHARE
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SHARE_RECORDING_TO_CHAT_URL
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SYSTEM_NOTIFICATION_ID
|
||||
|
@ -175,6 +176,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
Log.d(TAG, "pushMessage.type: " + pushMessage.type)
|
||||
when (pushMessage.type) {
|
||||
TYPE_CHAT, TYPE_ROOM, TYPE_RECORDING, TYPE_REMINDER -> handleNonCallPushMessage()
|
||||
TYPE_REMOTE_TALK_SHARE -> handleRemoteTalkSharePushMessage()
|
||||
TYPE_CALL -> handleCallPushMessage()
|
||||
else -> Log.e(TAG, "unknown pushMessage.type")
|
||||
}
|
||||
|
@ -194,6 +196,21 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleRemoteTalkSharePushMessage() {
|
||||
val mainActivityIntent = Intent(context, MainActivity::class.java)
|
||||
mainActivityIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
val bundle = Bundle()
|
||||
bundle.putLong(KEY_INTERNAL_USER_ID, signatureVerification.user!!.id!!)
|
||||
bundle.putBoolean(KEY_REMOTE_TALK_SHARE, true)
|
||||
mainActivityIntent.putExtras(bundle)
|
||||
|
||||
if (pushMessage.notificationId != Long.MIN_VALUE) {
|
||||
getNcDataAndShowNotification(mainActivityIntent)
|
||||
} else {
|
||||
showNotification(mainActivityIntent, null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCallPushMessage() {
|
||||
val fullScreenIntent = Intent(context, CallNotificationActivity::class.java)
|
||||
val bundle = Bundle()
|
||||
|
@ -402,7 +419,10 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
) {
|
||||
var category = ""
|
||||
when (pushMessage.type) {
|
||||
TYPE_CHAT, TYPE_ROOM, TYPE_RECORDING, TYPE_REMINDER -> category = Notification.CATEGORY_MESSAGE
|
||||
TYPE_CHAT, TYPE_ROOM, TYPE_RECORDING, TYPE_REMINDER, TYPE_REMOTE_TALK_SHARE -> {
|
||||
category = Notification.CATEGORY_MESSAGE
|
||||
}
|
||||
|
||||
TYPE_CALL -> category = Notification.CATEGORY_CALL
|
||||
else -> Log.e(TAG, "unknown pushMessage.type")
|
||||
}
|
||||
|
@ -459,7 +479,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
when (pushMessage.type) {
|
||||
TYPE_CHAT, TYPE_ROOM, TYPE_RECORDING, TYPE_REMINDER -> {
|
||||
TYPE_CHAT, TYPE_ROOM, TYPE_RECORDING, TYPE_REMINDER, TYPE_REMOTE_TALK_SHARE -> {
|
||||
notificationBuilder.setChannelId(
|
||||
NotificationUtils.NotificationChannels.NOTIFICATION_CHANNEL_MESSAGES_V4.name
|
||||
)
|
||||
|
@ -510,12 +530,15 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
largeIcon =
|
||||
ContextCompat.getDrawable(context!!, R.drawable.ic_people_group_black_24px)?.toBitmap()!!
|
||||
}
|
||||
|
||||
"group" ->
|
||||
largeIcon =
|
||||
ContextCompat.getDrawable(context!!, R.drawable.ic_people_group_black_24px)?.toBitmap()!!
|
||||
|
||||
"public" ->
|
||||
largeIcon =
|
||||
ContextCompat.getDrawable(context!!, R.drawable.ic_link_black_24px)?.toBitmap()!!
|
||||
|
||||
else -> // assuming one2one
|
||||
largeIcon = if (TYPE_CHAT == pushMessage.type || TYPE_ROOM == pushMessage.type) {
|
||||
ContextCompat.getDrawable(context!!, R.drawable.ic_comment)?.toBitmap()!!
|
||||
|
@ -987,6 +1010,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
private const val TYPE_ROOM = "room"
|
||||
private const val TYPE_CALL = "call"
|
||||
private const val TYPE_RECORDING = "recording"
|
||||
private const val TYPE_REMOTE_TALK_SHARE = "remote_talk_share"
|
||||
private const val TYPE_REMINDER = "reminder"
|
||||
private const val SPREED_APP = "spreed"
|
||||
private const val TIMER_START = 1
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2024 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/>.
|
||||
*/
|
||||
package com.nextcloud.talk.models.json.invitation
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@JsonObject
|
||||
data class Invitation(
|
||||
@JsonField(name = ["id"])
|
||||
var id: Int = 0,
|
||||
@JsonField(name = ["userId"])
|
||||
var userId: String? = null,
|
||||
@JsonField(name = ["state"])
|
||||
var state: Int = 0,
|
||||
@JsonField(name = ["localRoomId"])
|
||||
var localRoomId: Int = 0,
|
||||
@JsonField(name = ["accessToken"])
|
||||
var accessToken: String? = null,
|
||||
@JsonField(name = ["remoteServerUrl"])
|
||||
var remoteServerUrl: String? = null,
|
||||
@JsonField(name = ["remoteToken"])
|
||||
var remoteToken: String? = null,
|
||||
@JsonField(name = ["remoteAttendeeId"])
|
||||
var remoteAttendeeId: Int = 0,
|
||||
@JsonField(name = ["inviterCloudId"])
|
||||
var inviterCloudId: String? = null,
|
||||
@JsonField(name = ["inviterDisplayName"])
|
||||
var inviterDisplayName: String? = null,
|
||||
@JsonField(name = ["roomName"])
|
||||
var roomName: String? = null
|
||||
) : Parcelable {
|
||||
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||
constructor() : this(0, null, 0, 0, null, null, null, 0, null, null, null)
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2024 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/>.
|
||||
*/
|
||||
package com.nextcloud.talk.models.json.invitation
|
||||
|
||||
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.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@JsonObject
|
||||
data class InvitationOCS(
|
||||
@JsonField(name = ["meta"])
|
||||
var meta: GenericMeta?,
|
||||
@JsonField(name = ["data"])
|
||||
var data: List<Invitation>?
|
||||
) : Parcelable {
|
||||
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||
constructor() : this(null, null)
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2024 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/>.
|
||||
*/
|
||||
package com.nextcloud.talk.models.json.invitation
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@JsonObject
|
||||
data class InvitationOverall(
|
||||
@JsonField(name = ["ocs"])
|
||||
var ocs: InvitationOCS?
|
||||
) : Parcelable {
|
||||
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||
constructor() : this(null)
|
||||
}
|
|
@ -33,6 +33,7 @@ import com.nextcloud.talk.api.NcApi
|
|||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.chat.ChatActivity
|
||||
import com.nextcloud.talk.databinding.ActivityOpenConversationsBinding
|
||||
import com.nextcloud.talk.openconversations.adapters.OpenConversationsAdapter
|
||||
import com.nextcloud.talk.openconversations.data.OpenConversation
|
||||
import com.nextcloud.talk.openconversations.viewmodels.OpenConversationsViewModel
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.openconversations
|
||||
package com.nextcloud.talk.openconversations.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
|
|
|
@ -43,6 +43,8 @@ import com.nextcloud.talk.conversationlist.ConversationsListActivity;
|
|||
import com.nextcloud.talk.data.user.model.User;
|
||||
import com.nextcloud.talk.databinding.DialogChooseAccountBinding;
|
||||
import com.nextcloud.talk.extensions.ImageViewExtensionsKt;
|
||||
import com.nextcloud.talk.invitation.data.InvitationsModel;
|
||||
import com.nextcloud.talk.invitation.data.InvitationsRepository;
|
||||
import com.nextcloud.talk.models.json.participants.Participant;
|
||||
import com.nextcloud.talk.models.json.status.Status;
|
||||
import com.nextcloud.talk.models.json.status.StatusOverall;
|
||||
|
@ -79,6 +81,8 @@ public class ChooseAccountDialogFragment extends DialogFragment {
|
|||
|
||||
private static final float STATUS_SIZE_IN_DP = 9f;
|
||||
|
||||
Disposable disposable;
|
||||
|
||||
@Inject
|
||||
UserManager userManager;
|
||||
|
||||
|
@ -91,6 +95,9 @@ public class ChooseAccountDialogFragment extends DialogFragment {
|
|||
@Inject
|
||||
ViewThemeUtils viewThemeUtils;
|
||||
|
||||
@Inject
|
||||
InvitationsRepository invitationsRepository;
|
||||
|
||||
private DialogChooseAccountBinding binding;
|
||||
private View dialogView;
|
||||
|
||||
|
@ -150,7 +157,6 @@ public class ChooseAccountDialogFragment extends DialogFragment {
|
|||
adapter = new FlexibleAdapter<>(userItems, getActivity(), false);
|
||||
|
||||
User userEntity;
|
||||
Participant participant;
|
||||
|
||||
for (User userItem : userManager.getUsers().blockingGet()) {
|
||||
userEntity = userItem;
|
||||
|
@ -167,17 +173,48 @@ public class ChooseAccountDialogFragment extends DialogFragment {
|
|||
userId = userEntity.getUsername();
|
||||
}
|
||||
|
||||
participant = new Participant();
|
||||
participant.setActorType(Participant.ActorType.USERS);
|
||||
participant.setActorId(userId);
|
||||
participant.setDisplayName(userEntity.getDisplayName());
|
||||
userItems.add(new AdvancedUserItem(participant, userEntity, null, viewThemeUtils));
|
||||
User finalUserEntity = userEntity;
|
||||
invitationsRepository.fetchInvitations(userItem)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Observer<>() {
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) {
|
||||
disposable = d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(InvitationsModel invitationsModel) {
|
||||
Participant participant;
|
||||
participant = new Participant();
|
||||
participant.setActorType(Participant.ActorType.USERS);
|
||||
participant.setActorId(userId);
|
||||
participant.setDisplayName(finalUserEntity.getDisplayName());
|
||||
userItems.add(
|
||||
new AdvancedUserItem(
|
||||
participant,
|
||||
finalUserEntity,
|
||||
null,
|
||||
viewThemeUtils,
|
||||
invitationsModel.getInvitations().size()
|
||||
));
|
||||
adapter.addListener(onSwitchItemClickListener);
|
||||
adapter.addListener(onSwitchItemLongClickListener);
|
||||
adapter.updateDataSet(userItems, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.annotations.NonNull Throwable e) {
|
||||
Log.e(TAG, "Failed to fetch invitations", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
// no actions atm
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
adapter.addListener(onSwitchItemClickListener);
|
||||
adapter.addListener(onSwitchItemLongClickListener);
|
||||
adapter.updateDataSet(userItems, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,6 +328,9 @@ public class ChooseAccountDialogFragment extends DialogFragment {
|
|||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
if (disposable != null && !disposable.isDisposed()) {
|
||||
disposable.dispose();
|
||||
}
|
||||
binding = null;
|
||||
}
|
||||
|
||||
|
@ -299,7 +339,7 @@ public class ChooseAccountDialogFragment extends DialogFragment {
|
|||
@Override
|
||||
public boolean onItemClick(View view, int position) {
|
||||
if (userItems.size() > position) {
|
||||
User user = (userItems.get(position)).getUser();
|
||||
User user = (userItems.get(position)).user;
|
||||
|
||||
if (userManager.setUserAsActive(user).blockingGet()) {
|
||||
cookieManager.getCookieStore().removeAll();
|
||||
|
|
|
@ -59,9 +59,8 @@ class ChooseAccountShareToDialogFragment : DialogFragment() {
|
|||
@Inject
|
||||
var cookieManager: CookieManager? = null
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
var viewThemeUtils: ViewThemeUtils? = null
|
||||
lateinit var viewThemeUtils: ViewThemeUtils
|
||||
private var binding: DialogChooseAccountShareToBinding? = null
|
||||
private var dialogView: View? = null
|
||||
private var adapter: FlexibleAdapter<AdvancedUserItem>? = null
|
||||
|
@ -121,7 +120,7 @@ class ChooseAccountShareToDialogFragment : DialogFragment() {
|
|||
participant.actorType = Participant.ActorType.USERS
|
||||
participant.actorId = userId
|
||||
participant.displayName = userEntity.displayName
|
||||
userItems.add(AdvancedUserItem(participant, userEntity, null, viewThemeUtils))
|
||||
userItems.add(AdvancedUserItem(participant, userEntity, null, viewThemeUtils, 0))
|
||||
}
|
||||
}
|
||||
adapter!!.addListener(onSwitchItemClickListener)
|
||||
|
@ -158,7 +157,7 @@ class ChooseAccountShareToDialogFragment : DialogFragment() {
|
|||
private val onSwitchItemClickListener = FlexibleAdapter.OnItemClickListener { view, position ->
|
||||
if (userItems.size > position) {
|
||||
val user = userItems[position].user
|
||||
if (userManager!!.setUserAsActive(user).blockingGet()) {
|
||||
if (userManager!!.setUserAsActive(user!!).blockingGet()) {
|
||||
cookieManager!!.cookieStore.removeAll()
|
||||
activity?.recreate()
|
||||
dismiss()
|
||||
|
|
|
@ -544,4 +544,16 @@ public class ApiUtils {
|
|||
public static String getUrlForRecordingConsent(int version, String baseUrl, String token) {
|
||||
return getUrlForRoom(version, baseUrl, token) + "/recording-consent";
|
||||
}
|
||||
|
||||
public static String getUrlForInvitation(String baseUrl) {
|
||||
return baseUrl + ocsApiVersion + spreedApiVersion + "/federation/invitation";
|
||||
}
|
||||
|
||||
public static String getUrlForInvitationAccept(String baseUrl, int id) {
|
||||
return getUrlForInvitation(baseUrl) + "/" + id;
|
||||
}
|
||||
|
||||
public static String getUrlForInvitationReject(String baseUrl, int id) {
|
||||
return getUrlForInvitation(baseUrl) + "/" + id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,4 +88,5 @@ object BundleKeys {
|
|||
const val SAVED_TRANSLATED_MESSAGE = "SAVED_TRANSLATED_MESSAGE"
|
||||
const val KEY_REAUTHORIZE_ACCOUNT = "KEY_REAUTHORIZE_ACCOUNT"
|
||||
const val KEY_PASSWORD = "KEY_PASSWORD"
|
||||
const val KEY_REMOTE_TALK_SHARE = "KEY_REMOTE_TALK_SHARE"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<!--
|
||||
@author Google LLC
|
||||
Copyright (C) 2021 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z"/>
|
||||
</vector>
|
|
@ -99,5 +99,18 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/action_required"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@null"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/standard_padding"
|
||||
android:src="@drawable/accent_circle"
|
||||
app:tint="@color/badge_color" />
|
||||
|
||||
</RelativeLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Mario Danic
|
||||
~ @author Marcel Hibbe
|
||||
~ Copyright (C) 2023-2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
|
@ -108,7 +110,8 @@
|
|||
android:layout_centerVertical="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:theme="@style/Theme.MaterialComponents.DayNight.Bridge">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/switch_account_button"
|
||||
|
@ -215,16 +218,21 @@
|
|||
android:visibility="gone"
|
||||
app:layout_behavior="com.nextcloud.talk.utils.FABAwareScrollingViewBehavior">
|
||||
|
||||
<FrameLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/conversation_list_hint_include"
|
||||
layout="@layout/federated_invitation_hint" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Marcel Hibbe
|
||||
~ Copyright (C) 2023 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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:animateLayoutChanges="true">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/invitations_appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/invitations_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/appbar"
|
||||
android:theme="?attr/actionBarPopupTheme"
|
||||
app:title="@string/nc_invitations"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
app:navigationIconTint="@color/fontAppbar"
|
||||
app:popupTheme="@style/appActionBarPopupMenu"
|
||||
app:titleTextColor="@color/fontAppbar" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/invitations_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layoutManager="LinearLayoutManager"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/progress_bar_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:visibility="gone">
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"/>
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/emptyList"
|
||||
layout="@layout/empty_list" />
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Marcel Hibbe
|
||||
~ Copyright (C) 2024 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:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/conversation_list_hint_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center_vertical|center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="@dimen/standard_half_margin"
|
||||
android:layout_marginStart="@dimen/standard_margin"
|
||||
android:layout_marginEnd="@dimen/standard_margin"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="2dp"
|
||||
app:strokeWidth="0dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/standard_padding"
|
||||
android:paddingEnd="@dimen/standard_padding"
|
||||
android:textAlignment="center"
|
||||
android:padding="@dimen/standard_padding"
|
||||
android:text="@string/nc_federation_pending_invitation_hint" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</LinearLayout>
|
|
@ -0,0 +1,105 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Nextcloud Android client application
|
||||
|
||||
@author Tobias Kaminsky
|
||||
@author TSI-mc
|
||||
@author Marcel Hibbe
|
||||
Copyright (C) 2023 Andy Scherzinger
|
||||
Copyright (C) 2023 TSI-mc
|
||||
Copyright (C) 2018 Tobias Kaminsky
|
||||
Copyright (C) 2018 Nextcloud GmbH
|
||||
Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<RelativeLayout 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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/standard_padding"
|
||||
android:paddingTop="@dimen/standard_padding"
|
||||
android:paddingEnd="@dimen/standard_padding"
|
||||
android:paddingBottom="@dimen/standard_padding"
|
||||
tools:ignore="UseCompoundDrawables">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="@dimen/notification_icon_width"
|
||||
android:layout_height="@dimen/notification_icon_height"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginEnd="@dimen/notification_icon_layout_right_end_margin"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_email" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_alignTop="@id/icon"
|
||||
android:layout_toEndOf="@id/icon">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:paddingBottom="@dimen/standard_half_padding"
|
||||
tools:text="Ghostbusters" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subject"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:paddingBottom="@dimen/standard_half_padding"
|
||||
tools:text="from Bill Murray at 127.0.0.123" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/standard_half_margin"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/reject_invitation"
|
||||
style="@style/Button.Borderless"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/nc_federation_invitation_reject"
|
||||
android:theme="@style/Button.Primary"
|
||||
app:cornerRadius="@dimen/button_corner_radius" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/accept_invitation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="@dimen/standard_half_margin"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/nc_federation_invitation_accept"
|
||||
android:textColor="@color/high_emphasis_text"
|
||||
android:theme="@style/Button.Primary"
|
||||
app:cornerRadius="@dimen/button_corner_radius" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
|
@ -104,5 +104,6 @@
|
|||
<color name="dialog_background">#FFFFFF</color>
|
||||
|
||||
<color name="icon_on_bg_default">#99000000</color>
|
||||
<color name="badge_color">#EF3B02</color>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -93,4 +93,9 @@
|
|||
<dimen name="sm_icon_height">30dp</dimen>
|
||||
<dimen name="side_margin">16dp</dimen>
|
||||
|
||||
<dimen name="notification_icon_width">24dp</dimen>
|
||||
<dimen name="notification_icon_height">24dp</dimen>
|
||||
<dimen name="notification_icon_layout_right_end_margin">21dp</dimen>
|
||||
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -498,10 +498,6 @@ How to translate with transifex:
|
|||
<string name="nc_lobby_start_soon">The meeting will start soon</string>
|
||||
<string name="nc_manual">Not set</string>
|
||||
|
||||
<string name="nc_allow_guests">Allow guests</string>
|
||||
<string name="nc_last_moderator_title">Could not leave conversation</string>
|
||||
<string name="nc_last_moderator">You need to promote a new moderator before you can leave %1$s.</string>
|
||||
|
||||
<!-- Chat -->
|
||||
<string name="nc_copy_message">Copy</string>
|
||||
<string name="nc_forward_message">Forward</string>
|
||||
|
@ -593,7 +589,6 @@ How to translate with transifex:
|
|||
<string name="encrypted">Encrypted</string>
|
||||
|
||||
<string name="avatar">Avatar</string>
|
||||
<string name="account_icon">Account icon</string>
|
||||
<string name="userinfo_no_info_headline">No personal info set</string>
|
||||
<string name="userinfo_no_info_text">Add name, picture and contact details on your profile page.</string>
|
||||
<string name="userinfo_error_text">Failed to retrieve personal user information.</string>
|
||||
|
@ -643,7 +638,6 @@ How to translate with transifex:
|
|||
<string name="nc_switch_account">Switch account</string>
|
||||
<string name="nc_dialog_maintenance_mode">Maintenance mode</string>
|
||||
<string name="nc_dialog_maintenance_mode_description">Server is currently in maintenance mode.</string>
|
||||
<string name="nc_close_app">Close app</string>
|
||||
|
||||
<!-- Take photo -->
|
||||
<string name="take_photo">Take a photo</string>
|
||||
|
@ -727,8 +721,6 @@ How to translate with transifex:
|
|||
<string name="polls_private_poll">Private poll</string>
|
||||
<string name="polls_multiple_answers">Multiple answers</string>
|
||||
|
||||
<string name="title_attachments">Attachments</string>
|
||||
|
||||
<string name="reactions_tab_all">All</string>
|
||||
<string name="send_without_notification">Send without notification</string>
|
||||
<string name="call_without_notification">Call without notification</string>
|
||||
|
@ -750,6 +742,14 @@ How to translate with transifex:
|
|||
<string name="switch_to_main_room">Switch to main room</string>
|
||||
<string name="switch_to_breakout_room">Switch to breakout room</string>
|
||||
|
||||
<!-- Invitations -->
|
||||
<string name="nc_invitations">Invitations</string>
|
||||
<string name="nc_federation_invited_to_room">from %1$s at %2$s</string>
|
||||
<string name="nc_federation_invitation_accept">Accept</string>
|
||||
<string name="nc_federation_invitation_reject">Reject</string>
|
||||
<string name="nc_federation_pending_invitation_hint">You have pending invitations</string>
|
||||
<string name="nc_federation_no_invitations">No pending invitations</string>
|
||||
|
||||
<string name="nc_not_allowed_to_activate_audio">You are not allowed to activate audio!</string>
|
||||
<string name="nc_not_allowed_to_activate_video">You are not allowed to activate video!</string>
|
||||
<string name="scroll_to_bottom">Scroll to bottom</string>
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
DO NOT TOUCH; GENERATED BY DRONE
|
||||
<span class="mdl-layout-title">Lint Report: 8 errors and 80 warnings</span>
|
||||
<span class="mdl-layout-title">Lint Report: 8 errors and 79 warnings</span>
|
||||
|
|
Загрузка…
Ссылка в новой задаче