diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ba4e7ddcb..78ee53876 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -261,6 +261,10 @@
android:name=".openconversations.ListOpenConversationsActivity"
android:theme="@style/AppTheme" />
+
+
diff --git a/app/src/main/java/com/nextcloud/talk/account/SwitchAccountActivity.kt b/app/src/main/java/com/nextcloud/talk/account/SwitchAccountActivity.kt
index c19c3223f..c00d23ab4 100644
--- a/app/src/main/java/com/nextcloud/talk/account/SwitchAccountActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/account/SwitchAccountActivity.kt
@@ -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)
diff --git a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt
index ce631df93..81d918448 100644
--- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt
@@ -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) }
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.java b/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.java
deleted file mode 100644
index 494918d39..000000000
--- a/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * @author Andy Scherzinger
- * Copyright (C) 2022 Andy Scherzinger
- * Copyright (C) 2017 Mario Danic
- *
- * 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 .
- */
-
-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 implements
- IFilterable {
-
- 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);
- }
- }
-}
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.kt b/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.kt
new file mode 100644
index 000000000..5d70920af
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.kt
@@ -0,0 +1,129 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * @author Andy Scherzinger
+ * Copyright (C) 2022 Andy Scherzinger
+ * Copyright (C) 2017 Mario Danic
+ *
+ * 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 .
+ */
+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(), IFilterable {
+ 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>?
+ ): UserItemViewHolder {
+ return UserItemViewHolder(view, adapter)
+ }
+
+ override fun bindViewHolder(
+ adapter: FlexibleAdapter>,
+ holder: UserItemViewHolder,
+ position: Int,
+ payloads: MutableList
+ ) {
+ 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!!)
+ }
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java
index 493067de2..cbc446eb1 100644
--- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java
+++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java
@@ -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 setRecordingConsent(@Header("Authorization") String authorization,
@Url String url,
@Field("recordingConsent") int recordingConsent);
+
+ @GET
+ Observable getInvitations(@Header("Authorization") String authorization,
+ @Url String url);
+
+ @POST
+ Observable acceptInvitation(@Header("Authorization") String authorization,
+ @Url String url);
+
+ @DELETE
+ Observable rejectInvitation(@Header("Authorization") String authorization,
+ @Url String url);
}
\ No newline at end of file
diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt
index 38a81eebd..e2309f977 100644
--- a/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt
@@ -8,7 +8,7 @@
* @author Ezhil Shanmugham
* Copyright (C) 2022 Álvaro Brey
* 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
*
@@ -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
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/data/ConversationsListRepository.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/data/ConversationsListRepository.kt
new file mode 100644
index 000000000..3e1bb83d0
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/conversationlist/data/ConversationsListRepository.kt
@@ -0,0 +1,23 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe
+ *
+ * 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 .
+ */
+
+package com.nextcloud.talk.conversationlist.data
+
+interface ConversationsListRepository
diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/data/ConversationsListRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/data/ConversationsListRepositoryImpl.kt
new file mode 100644
index 000000000..7cf0fca08
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/conversationlist/data/ConversationsListRepositoryImpl.kt
@@ -0,0 +1,25 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe
+ *
+ * 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 .
+ */
+
+package com.nextcloud.talk.conversationlist.data
+
+import com.nextcloud.talk.api.NcApi
+
+class ConversationsListRepositoryImpl(private val ncApi: NcApi) : ConversationsListRepository
diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModel.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModel.kt
new file mode 100644
index 000000000..7d95f4754
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModel.kt
@@ -0,0 +1,112 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe
+ *
+ * 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 .
+ */
+
+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 =
+ MutableLiveData(GetFederationInvitationsStartState)
+ val getFederationInvitationsViewState: LiveData
+ get() = _getFederationInvitationsViewState
+
+ object ShowBadgeStartState : ViewState
+ object ShowBadgeErrorState : ViewState
+ open class ShowBadgeSuccessState(val showBadge: Boolean) : ViewState
+
+ private val _showBadgeViewState: MutableLiveData = MutableLiveData(ShowBadgeStartState)
+ val showBadgeViewState: LiveData
+ 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 {
+ 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
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt
index 19694599a..d1e891216 100644
--- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt
+++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt
@@ -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)
+ }
}
diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java b/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java
index d1a241fd9..6da4de8c3 100644
--- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java
+++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java
@@ -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();
diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt
index 596fb30d2..e9ddd400b 100644
--- a/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt
+++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt
@@ -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
}
diff --git a/app/src/main/java/com/nextcloud/talk/invitation/InvitationsActivity.kt b/app/src/main/java/com/nextcloud/talk/invitation/InvitationsActivity.kt
new file mode 100644
index 000000000..71f1519e3
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/invitation/InvitationsActivity.kt
@@ -0,0 +1,195 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe
+ *
+ * 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 .
+ */
+
+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)
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/invitation/adapters/InvitationsAdapter.kt b/app/src/main/java/com/nextcloud/talk/invitation/adapters/InvitationsAdapter.kt
new file mode 100644
index 000000000..6631e8ecb
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/invitation/adapters/InvitationsAdapter.kt
@@ -0,0 +1,104 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe
+ *
+ * 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 .
+ */
+
+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(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() {
+ override fun areItemsTheSame(oldItem: Invitation, newItem: Invitation): Boolean {
+ return oldItem == newItem
+ }
+
+ override fun areContentsTheSame(oldItem: Invitation, newItem: Invitation): Boolean {
+ return oldItem.id == newItem.id
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/invitation/data/Invitation.kt b/app/src/main/java/com/nextcloud/talk/invitation/data/Invitation.kt
new file mode 100644
index 000000000..6a4150112
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/invitation/data/Invitation.kt
@@ -0,0 +1,35 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe
+ *
+ * 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 .
+ */
+
+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
+)
diff --git a/app/src/main/java/com/nextcloud/talk/invitation/data/InvitationActionModel.kt b/app/src/main/java/com/nextcloud/talk/invitation/data/InvitationActionModel.kt
new file mode 100644
index 000000000..dbe2c1162
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/invitation/data/InvitationActionModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe
+ *
+ * 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 .
+ */
+
+package com.nextcloud.talk.invitation.data
+enum class ActionEnum { ACCEPT, REJECT }
+data class InvitationActionModel(
+ var action: ActionEnum,
+ var statusCode: Int,
+ var invitation: Invitation
+)
diff --git a/app/src/main/java/com/nextcloud/talk/invitation/data/InvitationsModel.kt b/app/src/main/java/com/nextcloud/talk/invitation/data/InvitationsModel.kt
new file mode 100644
index 000000000..c39a95140
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/invitation/data/InvitationsModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe
+ *
+ * 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 .
+ */
+
+package com.nextcloud.talk.invitation.data
+
+import com.nextcloud.talk.data.user.model.User
+
+data class InvitationsModel(
+ var user: User,
+ var invitations: List
+)
diff --git a/app/src/main/java/com/nextcloud/talk/invitation/data/InvitationsRepository.kt b/app/src/main/java/com/nextcloud/talk/invitation/data/InvitationsRepository.kt
new file mode 100644
index 000000000..2343ce69c
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/invitation/data/InvitationsRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe
+ *
+ * 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 .
+ */
+
+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
+ fun acceptInvitation(user: User, invitation: Invitation): Observable
+ fun rejectInvitation(user: User, invitation: Invitation): Observable
+}
diff --git a/app/src/main/java/com/nextcloud/talk/invitation/data/InvitationsRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/invitation/data/InvitationsRepositoryImpl.kt
new file mode 100644
index 000000000..6429db880
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/invitation/data/InvitationsRepositoryImpl.kt
@@ -0,0 +1,87 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe
+ *
+ * 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 .
+ */
+
+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 {
+ 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 {
+ 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 {
+ 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
+ ): 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
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/invitation/viewmodels/InvitationsViewModel.kt b/app/src/main/java/com/nextcloud/talk/invitation/viewmodels/InvitationsViewModel.kt
new file mode 100644
index 000000000..6b36a98aa
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/invitation/viewmodels/InvitationsViewModel.kt
@@ -0,0 +1,136 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2023 Marcel Hibbe
+ *
+ * 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 .
+ */
+
+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) : ViewState
+
+ private val _fetchInvitationsViewState: MutableLiveData = MutableLiveData(FetchInvitationsStartState)
+ val fetchInvitationsViewState: LiveData
+ get() = _fetchInvitationsViewState
+
+ object InvitationActionStartState : ViewState
+ object InvitationActionErrorState : ViewState
+
+ private val _invitationActionViewState: MutableLiveData = MutableLiveData(InvitationActionStartState)
+
+ open class InvitationActionSuccessState(val action: ActionEnum, val invitation: Invitation) : ViewState
+
+ val invitationActionViewState: LiveData
+ 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 {
+ 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 {
+ 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
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt
index cb4c3871f..6a31a389b 100644
--- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt
+++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt
@@ -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
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/invitation/Invitation.kt b/app/src/main/java/com/nextcloud/talk/models/json/invitation/Invitation.kt
new file mode 100644
index 000000000..56fbc2373
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/models/json/invitation/Invitation.kt
@@ -0,0 +1,55 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2024 Marcel Hibbe
+ *
+ * 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 .
+ */
+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)
+}
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/invitation/InvitationOCS.kt b/app/src/main/java/com/nextcloud/talk/models/json/invitation/InvitationOCS.kt
new file mode 100644
index 000000000..1bc86ea02
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/models/json/invitation/InvitationOCS.kt
@@ -0,0 +1,38 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2024 Marcel Hibbe
+ *
+ * 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 .
+ */
+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?
+) : Parcelable {
+ // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
+ constructor() : this(null, null)
+}
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/invitation/InvitationOverall.kt b/app/src/main/java/com/nextcloud/talk/models/json/invitation/InvitationOverall.kt
new file mode 100644
index 000000000..9c133c064
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/models/json/invitation/InvitationOverall.kt
@@ -0,0 +1,35 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Marcel Hibbe
+ * Copyright (C) 2024 Marcel Hibbe
+ *
+ * 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 .
+ */
+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)
+}
diff --git a/app/src/main/java/com/nextcloud/talk/openconversations/ListOpenConversationsActivity.kt b/app/src/main/java/com/nextcloud/talk/openconversations/ListOpenConversationsActivity.kt
index aa1b19c97..51b4c1a7a 100644
--- a/app/src/main/java/com/nextcloud/talk/openconversations/ListOpenConversationsActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/openconversations/ListOpenConversationsActivity.kt
@@ -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
diff --git a/app/src/main/java/com/nextcloud/talk/openconversations/adapters/OpenConversationsAdapter.kt b/app/src/main/java/com/nextcloud/talk/openconversations/adapters/OpenConversationsAdapter.kt
index 79087c68b..96f608377 100644
--- a/app/src/main/java/com/nextcloud/talk/openconversations/adapters/OpenConversationsAdapter.kt
+++ b/app/src/main/java/com/nextcloud/talk/openconversations/adapters/OpenConversationsAdapter.kt
@@ -18,7 +18,7 @@
* along with this program. If not, see .
*/
-package com.nextcloud.talk.openconversations
+package com.nextcloud.talk.openconversations.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java
index 4dfb482bb..261df9edc 100644
--- a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java
+++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountDialogFragment.java
@@ -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();
diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountShareToDialogFragment.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountShareToDialogFragment.kt
index 3fe01b131..7e024a1a9 100644
--- a/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountShareToDialogFragment.kt
+++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountShareToDialogFragment.kt
@@ -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? = 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()
diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java
index 298ced754..c36d882f1 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java
+++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java
@@ -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;
+ }
}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt b/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
index 4e2331e31..a8cb1e405 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
+++ b/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
@@ -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"
}
diff --git a/app/src/main/res/drawable/baseline_notifications_24.xml b/app/src/main/res/drawable/baseline_notifications_24.xml
new file mode 100644
index 000000000..87fc0e0eb
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_notifications_24.xml
@@ -0,0 +1,26 @@
+
+
+
+
diff --git a/app/src/main/res/layout/account_item.xml b/app/src/main/res/layout/account_item.xml
index a5322ef05..21adc51b9 100644
--- a/app/src/main/res/layout/account_item.xml
+++ b/app/src/main/res/layout/account_item.xml
@@ -99,5 +99,18 @@
+
+
diff --git a/app/src/main/res/layout/activity_conversations.xml b/app/src/main/res/layout/activity_conversations.xml
index 7128158ad..bbf0a5d32 100644
--- a/app/src/main/res/layout/activity_conversations.xml
+++ b/app/src/main/res/layout/activity_conversations.xml
@@ -2,6 +2,8 @@
~ Nextcloud Talk application
~
~ @author Mario Danic
+ ~ @author Marcel Hibbe
+ ~ Copyright (C) 2023-2024 Marcel Hibbe
~ Copyright (C) 2017-2018 Mario Danic
~
~ 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">
-
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+
-
+
diff --git a/app/src/main/res/layout/activity_invitations.xml b/app/src/main/res/layout/activity_invitations.xml
new file mode 100644
index 000000000..91a8980e6
--- /dev/null
+++ b/app/src/main/res/layout/activity_invitations.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/federated_invitation_hint.xml b/app/src/main/res/layout/federated_invitation_hint.xml
new file mode 100644
index 000000000..5f1867c9d
--- /dev/null
+++ b/app/src/main/res/layout/federated_invitation_hint.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/rv_item_invitation.xml b/app/src/main/res/layout/rv_item_invitation.xml
new file mode 100644
index 000000000..9cc2b096c
--- /dev/null
+++ b/app/src/main/res/layout/rv_item_invitation.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 982641554..dab4ba2a4 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -104,5 +104,6 @@
#FFFFFF
#99000000
+ #EF3B02
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 89956198c..3cdc2baa7 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -93,4 +93,9 @@
30dp
16dp
+ 24dp
+ 24dp
+ 21dp
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9de2c754b..4c6e8b6a4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -498,10 +498,6 @@ How to translate with transifex:
The meeting will start soon
Not set
- Allow guests
- Could not leave conversation
- You need to promote a new moderator before you can leave %1$s.
-
Copy
Forward
@@ -593,7 +589,6 @@ How to translate with transifex:
Encrypted
Avatar
- Account icon
No personal info set
Add name, picture and contact details on your profile page.
Failed to retrieve personal user information.
@@ -643,7 +638,6 @@ How to translate with transifex:
Switch account
Maintenance mode
Server is currently in maintenance mode.
- Close app
Take a photo
@@ -727,8 +721,6 @@ How to translate with transifex:
Private poll
Multiple answers
- Attachments
-
All
Send without notification
Call without notification
@@ -750,6 +742,14 @@ How to translate with transifex:
Switch to main room
Switch to breakout room
+
+ Invitations
+ from %1$s at %2$s
+ Accept
+ Reject
+ You have pending invitations
+ No pending invitations
+
You are not allowed to activate audio!
You are not allowed to activate video!
Scroll to bottom
diff --git a/scripts/analysis/lint-results.txt b/scripts/analysis/lint-results.txt
index 992f68d81..19f1c33c2 100644
--- a/scripts/analysis/lint-results.txt
+++ b/scripts/analysis/lint-results.txt
@@ -1,2 +1,2 @@
DO NOT TOUCH; GENERATED BY DRONE
- Lint Report: 8 errors and 80 warnings
+ Lint Report: 8 errors and 79 warnings