Rework conversation menu & bug fixes and improvements

Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
Mario Danic 2019-10-18 13:23:51 +02:00
parent 978be1afe1
commit d66134c663
26 changed files with 569 additions and 1131 deletions

View File

@ -28,6 +28,7 @@ import android.text.TextUtils;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import androidx.emoji.widget.EmojiTextView; import androidx.emoji.widget.EmojiTextView;
import butterknife.BindView; import butterknife.BindView;
@ -103,6 +104,12 @@ public class ConversationItem
holder.dialogAvatar.setController(null); holder.dialogAvatar.setController(null);
if (conversation.isUpdating()) {
holder.progressBar.setVisibility(View.VISIBLE);
} else {
holder.progressBar.setVisibility(View.GONE);
}
if (adapter.hasFilter()) { if (adapter.hasFilter()) {
FlexibleUtils.highlightText(holder.dialogName, conversation.getDisplayName(), FlexibleUtils.highlightText(holder.dialogName, conversation.getDisplayName(),
String.valueOf(adapter.getFilter(String.class)), String.valueOf(adapter.getFilter(String.class)),
@ -274,6 +281,8 @@ public class ConversationItem
ImageView passwordProtectedRoomImageView; ImageView passwordProtectedRoomImageView;
@BindView(R.id.favoriteConversationImageView) @BindView(R.id.favoriteConversationImageView)
ImageView pinnedConversationImageView; ImageView pinnedConversationImageView;
@BindView(R.id.actionProgressBar)
ProgressBar progressBar;
ConversationItemViewHolder(View view, FlexibleAdapter adapter) { ConversationItemViewHolder(View view, FlexibleAdapter adapter) {
super(view, adapter); super(view, adapter);

View File

@ -323,13 +323,13 @@ public interface NcApi {
@Url String url, @Query("search") String query, @Url String url, @Query("search") String query,
@Nullable @Query("limit") Integer limit); @Nullable @Query("limit") Integer limit);
// Url is: /api/{apiVersion}/room/{token}/pin // Url is: /api/{apiVersion}/room/{token}/favorite
@POST @POST
Observable<GenericOverall> addConversationToFavorites( Observable<GenericOverall> addConversationToFavorites(
@Header("Authorization") String authorization, @Header("Authorization") String authorization,
@Url String url); @Url String url);
// Url is: /api/{apiVersion}/room/{token}/favorites // Url is: /api/{apiVersion}/room/{token}/favorite
@DELETE @DELETE
Observable<GenericOverall> removeConversationFromFavorites( Observable<GenericOverall> removeConversationFromFavorites(
@Header("Authorization") String authorization, @Header("Authorization") String authorization,

View File

@ -1,728 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* 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.controllers;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.text.InputType;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import androidx.core.view.MenuItemCompat;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.work.Data;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import autodagger.AutoInjector;
import butterknife.BindView;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.changehandler.TransitionChangeHandlerCompat;
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler;
import com.bluelinelabs.conductor.internal.NoOpControllerChangeHandler;
import com.facebook.common.executors.UiThreadImmediateExecutorService;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.kennyc.bottomsheet.BottomSheet;
import com.nextcloud.talk.R;
import com.nextcloud.talk.activities.MagicCallActivity;
import com.nextcloud.talk.adapters.items.CallItem;
import com.nextcloud.talk.adapters.items.ConversationItem;
import com.nextcloud.talk.api.NcApi;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.controllers.base.BaseController;
import com.nextcloud.talk.controllers.bottomsheet.CallMenuController;
import com.nextcloud.talk.controllers.bottomsheet.EntryMenuController;
import com.nextcloud.talk.events.BottomSheetLockEvent;
import com.nextcloud.talk.events.EventStatus;
import com.nextcloud.talk.events.MoreMenuClickEvent;
import com.nextcloud.talk.interfaces.ConversationMenuInterface;
import com.nextcloud.talk.jobs.DeleteConversationWorker;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.json.conversations.Conversation;
import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.ConductorRemapping;
import com.nextcloud.talk.utils.DisplayUtils;
import com.nextcloud.talk.utils.KeyboardUtils;
import com.nextcloud.talk.utils.animations.SharedElementTransition;
import com.nextcloud.talk.utils.bundle.BundleKeys;
import com.nextcloud.talk.utils.database.user.UserUtils;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import com.uber.autodispose.AutoDispose;
import com.yarolegovich.lovelydialog.LovelySaveStateHandler;
import com.yarolegovich.lovelydialog.LovelyStandardDialog;
import eu.davidea.fastscroller.FastScroller;
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager;
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.parceler.Parcels;
import retrofit2.HttpException;
@AutoInjector(NextcloudTalkApplication.class)
public class ConversationsListController extends BaseController
implements SearchView.OnQueryTextListener,
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, FastScroller
.OnScrollStateChangeListener, ConversationMenuInterface {
public static final String TAG = "ConversationsListController";
public static final int ID_DELETE_CONVERSATION_DIALOG = 0;
private static final String KEY_SEARCH_QUERY = "ContactsController.searchQuery";
@Inject
UserUtils userUtils;
@Inject
EventBus eventBus;
@Inject
NcApi ncApi;
@Inject
Context context;
@Inject
AppPreferences appPreferences;
@BindView(R.id.recyclerView)
RecyclerView recyclerView;
@BindView(R.id.swipeRefreshLayoutView)
SwipeRefreshLayout swipeRefreshLayout;
@BindView(R.id.fast_scroller)
FastScroller fastScroller;
@BindView(R.id.floatingActionButton)
FloatingActionButton floatingActionButton;
private UserEntity currentUser;
private FlexibleAdapter<AbstractFlexibleItem> adapter;
private List<AbstractFlexibleItem> callItems = new ArrayList<>();
private BottomSheet bottomSheet;
private MenuItem searchItem;
private SearchView searchView;
private String searchQuery;
private View view;
private boolean shouldUseLastMessageLayout;
private String credentials;
private boolean adapterWasNull = true;
private boolean isRefreshing;
private LovelySaveStateHandler saveStateHandler;
private Bundle conversationMenuBundle = null;
public ConversationsListController() {
super();
setHasOptionsMenu(true);
}
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_conversations_rv, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
if (getActionBar() != null) {
getActionBar().show();
}
if (saveStateHandler == null) {
saveStateHandler = new LovelySaveStateHandler();
}
if (adapter == null) {
adapter = new FlexibleAdapter<>(callItems, getActivity(), true);
} else {
//progressBarView.setVisibility(View.GONE);
}
adapter.addListener(this);
prepareViews();
}
private void loadUserAvatar(MenuItem menuItem) {
if (getActivity() != null) {
int avatarSize = (int) DisplayUtils.convertDpToPixel(menuItem.getIcon().getIntrinsicHeight(),
getActivity());
ImageRequest imageRequest = DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatarWithNameAndPixels(currentUser.getBaseUrl(),
currentUser.getUserId(), avatarSize), null);
ImagePipeline imagePipeline = Fresco.getImagePipeline();
DataSource<CloseableReference<CloseableImage>> dataSource =
imagePipeline.fetchDecodedImage(imageRequest, null);
dataSource.subscribe(new BaseBitmapDataSubscriber() {
@Override
protected void onNewResultImpl(Bitmap bitmap) {
if (bitmap != null && getResources() != null) {
RoundedBitmapDrawable roundedBitmapDrawable =
RoundedBitmapDrawableFactory.create(getResources(), bitmap);
roundedBitmapDrawable.setCircular(true);
roundedBitmapDrawable.setAntiAlias(true);
menuItem.setIcon(roundedBitmapDrawable);
}
}
@Override
protected void onFailureImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
menuItem.setIcon(R.drawable.ic_settings_white_24dp);
}
}, UiThreadImmediateExecutorService.getInstance());
}
}
@Override
protected void onAttach(@NonNull View view) {
super.onAttach(view);
currentUser = userUtils.getCurrentUser();
if (currentUser != null) {
credentials = ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken());
shouldUseLastMessageLayout = currentUser.hasSpreedFeatureCapability("last-room-activity");
fetchData(false);
}
}
private void initSearchView() {
if (getActivity() != null) {
SearchManager searchManager =
(SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE);
if (searchItem != null) {
searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
searchView.setMaxWidth(Integer.MAX_VALUE);
searchView.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER);
int imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_FULLSCREEN;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
&& appPreferences.getIsKeyboardIncognito()) {
imeOptions |= EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING;
}
searchView.setImeOptions(imeOptions);
searchView.setQueryHint(getResources().getString(R.string.nc_search));
if (searchManager != null) {
searchView.setSearchableInfo(
searchManager.getSearchableInfo(getActivity().getComponentName()));
}
searchView.setOnQueryTextListener(this);
}
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
ArrayList<String> names = new ArrayList<>();
names.add("userAvatar.transitionTag");
getRouter().pushController((RouterTransaction.with(new SettingsController())
.pushChangeHandler(new TransitionChangeHandlerCompat(new SharedElementTransition(names),
new VerticalChangeHandler()))
.popChangeHandler(new TransitionChangeHandlerCompat(new SharedElementTransition(names),
new VerticalChangeHandler()))));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.menu_conversation_plus_filter, menu);
searchItem = menu.findItem(R.id.action_search);
initSearchView();
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
searchItem.setVisible(callItems.size() > 0);
if (adapter.hasFilter()) {
searchItem.expandActionView();
searchView.setQuery(adapter.getFilter(String.class), false);
}
MenuItem menuItem = menu.findItem(R.id.action_settings);
loadUserAvatar(menuItem);
}
private void fetchData(boolean fromBottomSheet) {
isRefreshing = true;
callItems = new ArrayList<>();
ncApi.getRooms(credentials, ApiUtils.getUrlForGetRooms(currentUser.getBaseUrl()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(roomsOverall -> {
if (adapterWasNull) {
adapterWasNull = false;
//progressBarView.setVisibility(View.GONE);
}
/*if (roomsOverall.getOcs().getData().size() > 0) {
if (emptyLayoutView.getVisibility() != View.GONE) {
emptyLayoutView.setVisibility(View.GONE);
}
if (swipeRefreshLayout.getVisibility() != View.VISIBLE) {
swipeRefreshLayout.setVisibility(View.VISIBLE);
}
} else {
if (emptyLayoutView.getVisibility() != View.VISIBLE) {
emptyLayoutView.setVisibility(View.VISIBLE);
}
if (swipeRefreshLayout.getVisibility() != View.GONE) {
swipeRefreshLayout.setVisibility(View.GONE);
}
}*/
Conversation conversation;
for (int i = 0; i < roomsOverall.getOcs().getData().size(); i++) {
conversation = roomsOverall.getOcs().getData().get(i);
if (shouldUseLastMessageLayout) {
if (getActivity() != null) {
ConversationItem conversationItem = new ConversationItem(conversation
, currentUser, getActivity());
callItems.add(conversationItem);
}
} else {
CallItem callItem = new CallItem(conversation, currentUser);
callItems.add(callItem);
}
}
if (currentUser.hasSpreedFeatureCapability("last-room-activity")) {
Collections.sort(callItems, (o1, o2) -> {
Conversation conversation1 = ((ConversationItem) o1).getModel();
Conversation conversation2 = ((ConversationItem) o2).getModel();
return new CompareToBuilder()
.append(conversation2.isFavorite(), conversation1.isFavorite())
.append(conversation2.getLastActivity(), conversation1.getLastActivity())
.toComparison();
});
} else {
Collections.sort(callItems, (callItem, t1) ->
Long.compare(((CallItem) t1).getModel().getLastPing(),
((CallItem) callItem).getModel().getLastPing()));
}
adapter.updateDataSet(callItems, false);
if (searchItem != null) {
searchItem.setVisible(callItems.size() > 0);
}
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(false);
}
}, throwable -> {
if (searchItem != null) {
searchItem.setVisible(false);
}
if (throwable instanceof HttpException) {
HttpException exception = (HttpException) throwable;
switch (exception.code()) {
case 401:
/*if (getParentController() != null &&
getParentController().getRouter() != null) {
getParentController().getRouter().pushController((RouterTransaction.with
(new WebViewLoginController(currentUser.getBaseUrl(),
true))
.pushChangeHandler(new VerticalChangeHandler())
.popChangeHandler(new VerticalChangeHandler())));
}*/
break;
default:
break;
}
}
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(false);
}
}, () -> {
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(false);
}
if (fromBottomSheet) {
new Handler().postDelayed(() -> {
bottomSheet.setCancelable(true);
if (bottomSheet.isShowing()) {
bottomSheet.cancel();
}
}, 2500);
}
isRefreshing = false;
});
}
private void prepareViews() {
SmoothScrollLinearLayoutManager layoutManager =
new SmoothScrollLinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
recyclerView.setAdapter(adapter);
swipeRefreshLayout.setOnRefreshListener(() -> fetchData(false));
swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
//emptyLayoutView.setOnClickListener(v -> showNewConversationsScreen());
floatingActionButton.setOnClickListener(v -> {
showNewConversationsScreen();
});
fastScroller.addOnScrollStateChangeListener(this);
adapter.setFastScroller(fastScroller);
fastScroller.setBubbleTextCreator(position -> {
String displayName;
if (shouldUseLastMessageLayout) {
displayName = ((ConversationItem) adapter.getItem(position)).getModel().getDisplayName();
} else {
displayName = ((CallItem) adapter.getItem(position)).getModel().getDisplayName();
}
if (displayName.length() > 8) {
displayName = displayName.substring(0, 4) + "...";
}
return displayName;
});
}
private void showNewConversationsScreen() {
Bundle bundle = new Bundle();
bundle.putBoolean(BundleKeys.INSTANCE.getKEY_NEW_CONVERSATION(), true);
getRouter().pushController((RouterTransaction.with(new ContactsController(bundle))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler())));
}
@Override
public void onSaveViewState(@NonNull View view, @NonNull Bundle outState) {
saveStateHandler.saveInstanceState(outState);
if (searchView != null && !TextUtils.isEmpty(searchView.getQuery())) {
outState.putString(KEY_SEARCH_QUERY, searchView.getQuery().toString());
}
super.onSaveViewState(view, outState);
}
@Override
public void onRestoreViewState(@NonNull View view, @NonNull Bundle savedViewState) {
super.onRestoreViewState(view, savedViewState);
searchQuery = savedViewState.getString(KEY_SEARCH_QUERY, "");
if (LovelySaveStateHandler.wasDialogOnScreen(savedViewState)) {
//Dialog won't be restarted automatically, so we need to call this method.
//Each dialog knows how to restore its viewState
showLovelyDialog(LovelySaveStateHandler.getSavedDialogId(savedViewState), savedViewState);
}
}
@Override
public boolean onQueryTextChange(String newText) {
if (adapter.hasNewFilter(newText) || !TextUtils.isEmpty(searchQuery)) {
if (!TextUtils.isEmpty(searchQuery)) {
adapter.setFilter(searchQuery);
searchQuery = "";
adapter.filterItems();
} else {
adapter.setFilter(newText);
adapter.filterItems(300);
}
}
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setEnabled(!adapter.hasFilter());
}
return true;
}
@Override
public boolean onQueryTextSubmit(String query) {
return onQueryTextChange(query);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(BottomSheetLockEvent bottomSheetLockEvent) {
if (bottomSheet != null) {
if (!bottomSheetLockEvent.isCancelable()) {
bottomSheet.setCancelable(bottomSheetLockEvent.isCancelable());
} else {
if (bottomSheetLockEvent.getDelay() != 0 && bottomSheetLockEvent.isShouldRefreshData()) {
fetchData(true);
} else {
bottomSheet.setCancelable(bottomSheetLockEvent.isCancelable());
if (bottomSheet.isShowing() && bottomSheetLockEvent.isCancel()) {
bottomSheet.cancel();
}
}
}
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MoreMenuClickEvent moreMenuClickEvent) {
Bundle bundle = new Bundle();
Conversation conversation = moreMenuClickEvent.getConversation();
bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ROOM(), Parcels.wrap(conversation));
bundle.putParcelable(BundleKeys.INSTANCE.getKEY_MENU_TYPE(),
Parcels.wrap(CallMenuController.MenuType.REGULAR));
prepareAndShowBottomSheetWithBundle(bundle, true);
}
private void prepareAndShowBottomSheetWithBundle(Bundle bundle,
boolean shouldShowCallMenuController) {
if (view == null) {
view = getActivity().getLayoutInflater().inflate(R.layout.bottom_sheet, null, false);
}
if (shouldShowCallMenuController) {
getChildRouter((ViewGroup) view).setRoot(
RouterTransaction.with(new CallMenuController(bundle, this))
.popChangeHandler(new VerticalChangeHandler())
.pushChangeHandler(new VerticalChangeHandler()));
} else {
getChildRouter((ViewGroup) view).setRoot(
RouterTransaction.with(new EntryMenuController(bundle))
.popChangeHandler(new VerticalChangeHandler())
.pushChangeHandler(new VerticalChangeHandler()));
}
if (bottomSheet == null) {
bottomSheet = new BottomSheet.Builder(getActivity()).setView(view).create();
}
bottomSheet.setOnShowListener(
dialog -> new KeyboardUtils(getActivity(), bottomSheet.getLayout(), true));
bottomSheet.setOnDismissListener(
dialog -> getActionBar().setDisplayHomeAsUpEnabled(getRouter().getBackstackSize() > 1));
bottomSheet.show();
}
@Override
public String getTitle() {
return getResources().getString(R.string.nc_app_name);
}
@Override
public void onFastScrollerStateChange(boolean scrolling) {
swipeRefreshLayout.setEnabled(!scrolling);
}
@Override
public boolean onItemClick(View view, int position) {
Object clickedItem = adapter.getItem(position);
if (clickedItem != null && getActivity() != null) {
Conversation conversation;
if (shouldUseLastMessageLayout) {
conversation = ((ConversationItem) clickedItem).getModel();
} else {
conversation = ((CallItem) clickedItem).getModel();
}
Bundle bundle = new Bundle();
bundle.putParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY(), currentUser);
bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), conversation.getToken());
bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), conversation.getRoomId());
if (conversation.hasPassword && (conversation.getParticipantType()
.equals(Participant.ParticipantType.GUEST) ||
conversation.getParticipantType()
.equals(Participant.ParticipantType.USER_FOLLOWING_LINK))) {
bundle.putInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), 99);
prepareAndShowBottomSheetWithBundle(bundle, false);
} else {
currentUser = userUtils.getCurrentUser();
if (currentUser.hasSpreedFeatureCapability("chat-v2")) {
bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ACTIVE_CONVERSATION(),
Parcels.wrap(conversation));
ConductorRemapping.INSTANCE.remapChatController(getRouter(), currentUser.getId(),
conversation.getToken(), bundle, false);
} else {
overridePushHandler(new NoOpControllerChangeHandler());
overridePopHandler(new NoOpControllerChangeHandler());
Intent callIntent = new Intent(getActivity(), MagicCallActivity.class);
callIntent.putExtras(bundle);
startActivity(callIntent);
}
}
}
return true;
}
@Override
public void onItemLongClick(int position) {
if (currentUser.hasSpreedFeatureCapability("last-room-activity")) {
Object clickedItem = adapter.getItem(position);
if (clickedItem != null) {
Conversation conversation;
if (shouldUseLastMessageLayout) {
conversation = ((ConversationItem) clickedItem).getModel();
} else {
conversation = ((CallItem) clickedItem).getModel();
}
MoreMenuClickEvent moreMenuClickEvent = new MoreMenuClickEvent(conversation);
onMessageEvent(moreMenuClickEvent);
}
}
}
@Subscribe(sticky = true, threadMode = ThreadMode.BACKGROUND)
public void onMessageEvent(EventStatus eventStatus) {
if (currentUser != null && eventStatus.getUserId() == currentUser.getId()) {
switch (eventStatus.getEventType()) {
case CONVERSATION_UPDATE:
if (eventStatus.isAllGood() && !isRefreshing) {
fetchData(false);
}
break;
default:
break;
}
}
}
private void showDeleteConversationDialog(Bundle savedInstanceState) {
if (getActivity() != null
&& conversationMenuBundle != null
&& currentUser != null
&& conversationMenuBundle.getLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID())
== currentUser.getId()) {
Conversation conversation =
Parcels.unwrap(conversationMenuBundle.getParcelable(BundleKeys.INSTANCE.getKEY_ROOM()));
if (conversation != null) {
new LovelyStandardDialog(getActivity(), LovelyStandardDialog.ButtonLayout.HORIZONTAL)
.setTopColorRes(R.color.nc_darkRed)
.setIcon(DisplayUtils.getTintedDrawable(context.getResources(),
R.drawable.ic_delete_black_24dp, R.color.bg_default))
.setPositiveButtonColor(context.getResources().getColor(R.color.nc_darkRed))
.setTitle(R.string.nc_delete_call)
.setMessage(conversation.getDeleteWarningMessage())
.setPositiveButton(R.string.nc_delete, new View.OnClickListener() {
@Override
public void onClick(View v) {
Data.Builder data = new Data.Builder();
data.putLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(),
conversationMenuBundle.getLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID()));
data.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), conversation.getToken());
conversationMenuBundle = null;
deleteConversation(data.build());
}
})
.setNegativeButton(R.string.nc_cancel, new View.OnClickListener() {
@Override
public void onClick(View v) {
conversationMenuBundle = null;
}
})
.setInstanceStateHandler(ID_DELETE_CONVERSATION_DIALOG, saveStateHandler)
.setSavedInstanceState(savedInstanceState)
.show();
}
}
}
private void deleteConversation(Data data) {
OneTimeWorkRequest deleteConversationWorker =
new OneTimeWorkRequest.Builder(DeleteConversationWorker.class).setInputData(data).build();
WorkManager.getInstance().enqueue(deleteConversationWorker);
}
private void showLovelyDialog(int dialogId, Bundle savedInstanceState) {
switch (dialogId) {
case ID_DELETE_CONVERSATION_DIALOG:
showDeleteConversationDialog(savedInstanceState);
break;
default:
break;
}
}
@Override
public void openLovelyDialogWithIdAndBundle(int dialogId, Bundle bundle) {
conversationMenuBundle = bundle;
switch (dialogId) {
case ID_DELETE_CONVERSATION_DIALOG:
showLovelyDialog(dialogId, null);
break;
default:
break;
}
}
}

View File

@ -1,347 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* 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.controllers.bottomsheet;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.work.Data;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import autodagger.AutoInjector;
import butterknife.BindView;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.kennyc.bottomsheet.adapters.AppAdapter;
import com.nextcloud.talk.R;
import com.nextcloud.talk.adapters.items.AppItem;
import com.nextcloud.talk.adapters.items.MenuItem;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.controllers.ConversationsListController;
import com.nextcloud.talk.controllers.base.BaseController;
import com.nextcloud.talk.events.BottomSheetLockEvent;
import com.nextcloud.talk.interfaces.ConversationMenuInterface;
import com.nextcloud.talk.jobs.LeaveConversationWorker;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.json.conversations.Conversation;
import com.nextcloud.talk.utils.DisplayUtils;
import com.nextcloud.talk.utils.ShareUtils;
import com.nextcloud.talk.utils.bundle.BundleKeys;
import com.nextcloud.talk.utils.database.user.UserUtils;
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager;
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.greenrobot.eventbus.EventBus;
import org.parceler.Parcel;
import org.parceler.Parcels;
@AutoInjector(NextcloudTalkApplication.class)
public class CallMenuController extends BaseController
implements FlexibleAdapter.OnItemClickListener {
@BindView(R.id.recyclerView)
RecyclerView recyclerView;
@Inject
EventBus eventBus;
@Inject
UserUtils userUtils;
private Conversation conversation;
private List<AbstractFlexibleItem> menuItems;
private FlexibleAdapter<AbstractFlexibleItem> adapter;
private MenuType menuType;
private Intent shareIntent;
private UserEntity currentUser;
private ConversationMenuInterface conversationMenuInterface;
public CallMenuController(Bundle args) {
super();
this.conversation = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_ROOM()));
if (args.containsKey(BundleKeys.INSTANCE.getKEY_MENU_TYPE())) {
this.menuType = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_MENU_TYPE()));
}
}
public CallMenuController(Bundle args,
@Nullable ConversationMenuInterface conversationMenuInterface) {
super();
this.conversation = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_ROOM()));
if (args.containsKey(BundleKeys.INSTANCE.getKEY_MENU_TYPE())) {
this.menuType = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_MENU_TYPE()));
}
this.conversationMenuInterface = conversationMenuInterface;
}
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_call_menu, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
prepareViews();
}
private void prepareViews() {
LinearLayoutManager layoutManager = new SmoothScrollLinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
prepareMenu();
if (adapter == null) {
adapter = new FlexibleAdapter<>(menuItems, getActivity(), false);
}
adapter.addListener(this);
recyclerView.setAdapter(adapter);
}
private void prepareIntent() {
shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
String.format(getResources().getString(R.string.nc_share_subject),
getResources().getString(R.string.nc_app_name)));
}
private void prepareMenu() {
menuItems = new ArrayList<>();
if (menuType.equals(MenuType.REGULAR)) {
if (!TextUtils.isEmpty(conversation.getDisplayName())) {
menuItems.add(new MenuItem(conversation.getDisplayName(), 0, null));
} else if (!TextUtils.isEmpty(conversation.getName())) {
menuItems.add(new MenuItem(conversation.getName(), 0, null));
} else {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_configure_room), 0, null));
}
currentUser = userUtils.getCurrentUser();
if (conversation.isFavorite()) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_remove_from_favorites), 97,
DisplayUtils.getTintedDrawable(getResources(), R.drawable.ic_star_border_black_24dp,
R.color.grey_600)));
} else if (currentUser.hasSpreedFeatureCapability("favorites")) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_add_to_favorites)
, 98, DisplayUtils.getTintedDrawable(getResources(), R.drawable.ic_star_black_24dp,
R.color.grey_600)));
}
if (conversation.isNameEditable(currentUser)) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_rename), 2,
getResources().getDrawable(R.drawable
.ic_pencil_grey600_24dp)));
}
if (conversation.canModerate(currentUser)) {
if (!conversation.isPublic()) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_make_call_public), 3,
getResources().getDrawable(R.drawable
.ic_link_grey600_24px)));
} else {
if (conversation.isHasPassword()) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_change_password), 4,
getResources().getDrawable(R.drawable
.ic_lock_grey600_24px)));
menuItems.add(new MenuItem(getResources().getString(R.string.nc_clear_password), 5,
getResources().getDrawable(R.drawable
.ic_lock_open_grey600_24dp)));
} else {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_set_password), 6,
getResources().getDrawable(R.drawable
.ic_lock_plus_grey600_24dp)));
}
}
menuItems.add(new MenuItem(getResources().getString(R.string.nc_delete_call), 9,
getResources().getDrawable(R.drawable
.ic_delete_grey600_24dp)));
}
if (conversation.isPublic()) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_share_link), 7,
getResources().getDrawable(R.drawable
.ic_link_grey600_24px)));
if (conversation.canModerate(currentUser)) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_make_call_private), 8,
getResources().getDrawable(R.drawable
.ic_group_grey600_24px)));
}
}
if (conversation.canLeave(currentUser)) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_leave), 1,
DisplayUtils.getTintedDrawable(getResources(),
R.drawable.ic_exit_to_app_black_24dp, R.color.grey_600)
));
}
} else if (menuType.equals(MenuType.SHARE)) {
prepareIntent();
List<AppAdapter.AppInfo> appInfoList =
ShareUtils.getShareApps(getActivity(), shareIntent, null,
null);
menuItems.add(new AppItem(getResources().getString(R.string.nc_share_link_via), "", "",
getResources().getDrawable(R.drawable.ic_link_grey600_24px)));
if (appInfoList != null) {
for (AppAdapter.AppInfo appInfo : appInfoList) {
menuItems.add(
new AppItem(appInfo.title, appInfo.packageName, appInfo.name, appInfo.drawable));
}
}
} else {
menuItems.add(
new MenuItem(getResources().getString(R.string.nc_start_conversation), 0, null));
menuItems.add(new MenuItem(getResources().getString(R.string.nc_new_conversation), 1,
getResources().getDrawable(R.drawable.ic_add_grey600_24px)));
menuItems.add(new MenuItem(getResources().getString(R.string.nc_join_via_link), 2,
getResources().getDrawable(R.drawable.ic_link_grey600_24px)));
}
}
@Override
public boolean onItemClick(View view, int position) {
Bundle bundle = new Bundle();
bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ROOM(), Parcels.wrap(conversation));
if (menuType.equals(MenuType.REGULAR)) {
MenuItem menuItem = (MenuItem) adapter.getItem(position);
if (menuItem != null) {
int tag = menuItem.getTag();
if (tag == 5) {
conversation.setPassword("");
}
if (tag > 0) {
if (tag == 1 || tag == 9) {
if (tag == 1) {
Data data;
if ((data = getWorkerData()) != null) {
OneTimeWorkRequest leaveConversationWorker =
new OneTimeWorkRequest.Builder(LeaveConversationWorker.class).setInputData(data)
.build();
WorkManager.getInstance().enqueue(leaveConversationWorker);
}
} else {
Bundle deleteConversationBundle;
if ((deleteConversationBundle = getDeleteConversationBundle()) != null) {
conversationMenuInterface.openLovelyDialogWithIdAndBundle(
ConversationsListController.ID_DELETE_CONVERSATION_DIALOG,
deleteConversationBundle);
}
}
eventBus.post(new BottomSheetLockEvent(true, 0, false, true));
} else {
bundle.putInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), tag);
if (tag != 2 && tag != 4 && tag != 6 && tag != 7) {
eventBus.post(new BottomSheetLockEvent(false, 0, false, false));
getRouter().pushController(
RouterTransaction.with(new OperationsMenuController(bundle))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
} else if (tag != 7) {
getRouter().pushController(RouterTransaction.with(new EntryMenuController(bundle))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
} else {
bundle.putParcelable(BundleKeys.INSTANCE.getKEY_MENU_TYPE(),
Parcels.wrap(MenuType.SHARE));
getRouter().pushController(
RouterTransaction.with(new CallMenuController(bundle, null))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
}
}
}
}
} else if (menuType.equals(MenuType.SHARE) && position != 0) {
AppItem appItem = (AppItem) adapter.getItem(position);
if (appItem != null && getActivity() != null) {
if (!conversation.hasPassword) {
shareIntent.putExtra(Intent.EXTRA_TEXT, ShareUtils.getStringForIntent(getActivity(), null,
userUtils, conversation));
Intent intent = new Intent(shareIntent);
intent.setComponent(new ComponentName(appItem.getPackageName(), appItem.getName()));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
eventBus.post(new BottomSheetLockEvent(true, 0, false, true));
getActivity().startActivity(intent);
} else {
bundle.putInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), 7);
bundle.putParcelable(BundleKeys.INSTANCE.getKEY_SHARE_INTENT(),
Parcels.wrap(shareIntent));
bundle.putString(BundleKeys.INSTANCE.getKEY_APP_ITEM_PACKAGE_NAME(),
appItem.getPackageName());
bundle.putString(BundleKeys.INSTANCE.getKEY_APP_ITEM_NAME(), appItem.getName());
getRouter().pushController(RouterTransaction.with(new EntryMenuController(bundle))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
}
}
}
return true;
}
private Data getWorkerData() {
if (!TextUtils.isEmpty(conversation.getToken())) {
Data.Builder data = new Data.Builder();
data.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), conversation.getToken());
data.putLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), currentUser.getId());
return data.build();
}
return null;
}
private Bundle getDeleteConversationBundle() {
if (!TextUtils.isEmpty(conversation.getToken())) {
Bundle bundle = new Bundle();
bundle.putLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), currentUser.getId());
bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ROOM(), Parcels.wrap(conversation));
return bundle;
}
return null;
}
@Parcel
public enum MenuType {
REGULAR, SHARE
}
}

View File

@ -22,6 +22,7 @@ package com.nextcloud.talk.models.json.conversations;
import android.content.res.Resources; import android.content.res.Resources;
import com.bluelinelabs.logansquare.annotation.JsonField; import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonIgnore;
import com.bluelinelabs.logansquare.annotation.JsonObject; import com.bluelinelabs.logansquare.annotation.JsonObject;
import com.nextcloud.talk.R; import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.application.NextcloudTalkApplication;
@ -90,6 +91,7 @@ public class Conversation {
public Long lobbyTimer; public Long lobbyTimer;
@JsonField(name = "lastReadMessage") @JsonField(name = "lastReadMessage")
public int lastReadMessage; public int lastReadMessage;
public boolean updating;
public boolean isPublic() { public boolean isPublic() {
return (ConversationType.ROOM_PUBLIC_CALL.equals(type)); return (ConversationType.ROOM_PUBLIC_CALL.equals(type));

View File

@ -22,16 +22,50 @@ package com.nextcloud.talk.newarch.data.repository
import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.newarch.data.source.remote.ApiService import com.nextcloud.talk.newarch.data.source.remote.ApiService
import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkRepository import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkRepository
import com.nextcloud.talk.newarch.utils.getCredentials
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
class NextcloudTalkRepositoryImpl(private val apiService: ApiService) : NextcloudTalkRepository { class NextcloudTalkRepositoryImpl(private val apiService: ApiService) : NextcloudTalkRepository {
override suspend fun deleteConversationForUser(
user: UserEntity,
conversation: Conversation
): GenericOverall {
return apiService.deleteConversation(user.getCredentials(), ApiUtils.getRoom(user.baseUrl, conversation.token))
}
override suspend fun leaveConversationForUser(
user: UserEntity,
conversation: Conversation
): GenericOverall {
return apiService.leaveConversation(user.getCredentials(), ApiUtils.getUrlForRemoveSelfFromRoom(user
.baseUrl, conversation.token))
}
override suspend fun setFavoriteValueForConversation(
user: UserEntity,
conversation: Conversation,
favorite: Boolean
): GenericOverall {
if (favorite) {
return apiService.addConversationToFavorites(
user.getCredentials(),
ApiUtils.getUrlForConversationFavorites(user.baseUrl, conversation.token)
)
} else {
return apiService.removeConversationFromFavorites(
user.getCredentials(),
ApiUtils.getUrlForConversationFavorites(user.baseUrl, conversation.token)
)
}
}
override suspend fun getConversationsForUser(user: UserEntity): List<Conversation> { override suspend fun getConversationsForUser(user: UserEntity): List<Conversation> {
return apiService.getConversations( return apiService.getConversations(
ApiUtils.getCredentials(user.username, user.token), user.getCredentials(),
ApiUtils.getUrlForGetRooms(user.baseUrl) ApiUtils.getUrlForGetRooms(user.baseUrl)
) ).ocs.data
.ocs.data
} }
} }

View File

@ -21,8 +21,12 @@
package com.nextcloud.talk.newarch.data.source.remote package com.nextcloud.talk.newarch.data.source.remote
import com.nextcloud.talk.models.json.conversations.RoomsOverall import com.nextcloud.talk.models.json.conversations.RoomsOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import io.reactivex.Observable
import retrofit2.http.DELETE
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Header import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.Url import retrofit2.http.Url
interface ApiService { interface ApiService {
@ -36,4 +40,29 @@ interface ApiService {
"Authorization" "Authorization"
) authorization: String, @Url url: String ) authorization: String, @Url url: String
): RoomsOverall ): RoomsOverall
@POST
suspend fun addConversationToFavorites(
@Header("Authorization") authorization: String,
@Url url: String
): GenericOverall
@DELETE
suspend fun removeConversationFromFavorites(
@Header("Authorization") authorization: String,
@Url url: String
): GenericOverall
@DELETE
suspend fun leaveConversation(
@Header("Authorization") authorization: String,
@Url url: String
): GenericOverall
@DELETE
suspend fun deleteConversation(
@Header("Authorization") authorization: String,
@Url url: String
): GenericOverall
} }

View File

@ -79,7 +79,7 @@ val NetworkModule = module {
single { createSslSocketFactory(get(), get()) } single { createSslSocketFactory(get(), get()) }
single { createCache(androidApplication() as NextcloudTalkApplication) } single { createCache(androidApplication() as NextcloudTalkApplication) }
single { createOkHttpClient(androidContext(), get(), get(), get(), get(), get(), get(), get()) } single { createOkHttpClient(androidContext(), get(), get(), get(), get(), get(), get(), get()) }
factory { createApiErrorHandler() }
single { createNextcloudTalkRepository(get()) } single { createNextcloudTalkRepository(get()) }
} }

View File

@ -22,7 +22,15 @@ package com.nextcloud.talk.newarch.domain.repository
import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.generic.GenericOverall
interface NextcloudTalkRepository { interface NextcloudTalkRepository {
suspend fun getConversationsForUser(user: UserEntity): List<Conversation> suspend fun getConversationsForUser(user: UserEntity): List<Conversation>
suspend fun setFavoriteValueForConversation(
user: UserEntity,
conversation: Conversation,
favorite: Boolean
) : GenericOverall
suspend fun deleteConversationForUser(user: UserEntity, conversation: Conversation) : GenericOverall
suspend fun leaveConversationForUser(userEntity: UserEntity, conversation: Conversation) : GenericOverall
} }

View File

@ -0,0 +1,38 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 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.newarch.domain.usecases
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler
import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkRepository
import com.nextcloud.talk.newarch.domain.usecases.base.UseCase
import org.koin.core.parameter.DefinitionParameters
class DeleteConversationUseCase constructor(
private val nextcloudTalkRepository: NextcloudTalkRepository,
apiErrorHandler: ApiErrorHandler?
) : UseCase<GenericOverall, Any?>(apiErrorHandler) {
override suspend fun run(params: Any?): GenericOverall {
val definitionParameters = params as DefinitionParameters
return nextcloudTalkRepository.deleteConversationForUser(user, definitionParameters.get(0))
}
}

View File

@ -0,0 +1,38 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 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.newarch.domain.usecases
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler
import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkRepository
import com.nextcloud.talk.newarch.domain.usecases.base.UseCase
import org.koin.core.parameter.DefinitionParameters
class LeaveConversationUseCase constructor(
private val nextcloudTalkRepository: NextcloudTalkRepository,
apiErrorHandler: ApiErrorHandler?
) : UseCase<GenericOverall, Any?>(apiErrorHandler) {
override suspend fun run(params: Any?): GenericOverall {
val definitionParameters = params as DefinitionParameters
return nextcloudTalkRepository.leaveConversationForUser(user, definitionParameters.get(0))
}
}

View File

@ -0,0 +1,38 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 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.newarch.domain.usecases
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler
import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkRepository
import com.nextcloud.talk.newarch.domain.usecases.base.UseCase
import org.koin.core.parameter.DefinitionParameters
class SetConversationFavoriteValueUseCase constructor(
private val nextcloudTalkRepository: NextcloudTalkRepository,
apiErrorHandler: ApiErrorHandler?
) : UseCase<GenericOverall, Any?>(apiErrorHandler) {
override suspend fun run(params: Any?): GenericOverall {
val definitionParameters = params as DefinitionParameters
return nextcloudTalkRepository.setFavoriteValueForConversation(user, definitionParameters.get(0), definitionParameters.get(1))
}
}

View File

@ -21,6 +21,7 @@
package com.nextcloud.talk.newarch.domain.usecases.base package com.nextcloud.talk.newarch.domain.usecases.base
import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async import kotlinx.coroutines.async

View File

@ -23,16 +23,24 @@ package com.nextcloud.talk.newarch.features.conversationsList
import android.app.Application import android.app.Application
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.nextcloud.talk.newarch.domain.usecases.DeleteConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.SetConversationFavoriteValueUseCase
import com.nextcloud.talk.utils.database.user.UserUtils import com.nextcloud.talk.utils.database.user.UserUtils
class ConversationListViewModelFactory constructor( class ConversationListViewModelFactory constructor(
private val application: Application, private val application: Application,
private val conversationsUseCase: GetConversationsUseCase, private val conversationsUseCase: GetConversationsUseCase,
private val setConversationFavoriteValueUseCase: SetConversationFavoriteValueUseCase,
private val leaveConversationUseCase: LeaveConversationUseCase,
private val deleteConversationUseCase: DeleteConversationUseCase,
private val userUtils: UserUtils private val userUtils: UserUtils
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T { override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return ConversationsListViewModel(application, conversationsUseCase, userUtils) as T return ConversationsListViewModel(application, conversationsUseCase,
setConversationFavoriteValueUseCase, leaveConversationUseCase, deleteConversationUseCase,
userUtils) as T
} }
} }

View File

@ -38,23 +38,25 @@ import androidx.core.view.MenuItemCompat
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.recyclerview.widget.RecyclerView.ViewHolder
import butterknife.OnClick import butterknife.OnClick
import com.afollestad.materialdialogs.LayoutMode.WRAP_CONTENT
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.conductor.changehandler.TransitionChangeHandlerCompat import com.bluelinelabs.conductor.changehandler.TransitionChangeHandlerCompat
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.R.drawable
import com.nextcloud.talk.adapters.items.ConversationItem import com.nextcloud.talk.adapters.items.ConversationItem
import com.nextcloud.talk.controllers.ContactsController import com.nextcloud.talk.controllers.ContactsController
import com.nextcloud.talk.controllers.SettingsController import com.nextcloud.talk.controllers.SettingsController
import com.nextcloud.talk.controllers.bottomsheet.CallMenuController.MenuType import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage
import com.nextcloud.talk.controllers.bottomsheet.CallMenuController.MenuType.REGULAR
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseView import com.nextcloud.talk.newarch.conversationsList.mvp.BaseView
import com.nextcloud.talk.newarch.mvvm.ViewState.FAILED
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADED
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADED_EMPTY
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADING
import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView
import com.nextcloud.talk.newarch.utils.ViewState.FAILED
import com.nextcloud.talk.newarch.utils.ViewState.LOADED
import com.nextcloud.talk.newarch.utils.ViewState.LOADED_EMPTY
import com.nextcloud.talk.newarch.utils.ViewState.LOADING
import com.nextcloud.talk.utils.ConductorRemapping import com.nextcloud.talk.utils.ConductorRemapping
import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.animations.SharedElementTransition import com.nextcloud.talk.utils.animations.SharedElementTransition
@ -213,12 +215,17 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
if (value.equals(FAILED)) { if (value.equals(FAILED)) {
view?.stateWithMessageView?.errorStateTextView?.text = messageData view?.stateWithMessageView?.errorStateTextView?.text = messageData
if (messageData.equals(context.resources.getString(R.string.nc_no_connection_error))) { if (messageData.equals(
context.resources.getString(R.string.nc_no_connection_error)
)
) {
view?.stateWithMessageView?.errorStateImageView?.setImageResource( view?.stateWithMessageView?.errorStateImageView?.setImageResource(
R.drawable.ic_cloud_off_white_24dp) R.drawable.ic_cloud_off_white_24dp
)
} else { } else {
view?.stateWithMessageView?.errorStateImageView?.setImageResource( view?.stateWithMessageView?.errorStateImageView?.setImageResource(
R.drawable.ic_announcement_white_24dp) R.drawable.ic_announcement_white_24dp
)
} }
view?.floatingActionButton?.visibility = View.GONE view?.floatingActionButton?.visibility = View.GONE
} else { } else {
@ -236,7 +243,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
} }
}) })
conversationsListData.observe(this@ConversationsListView, Observer { conversationsLiveListData.observe(this@ConversationsListView, Observer {
val newConversations = mutableListOf<ConversationItem>() val newConversations = mutableListOf<ConversationItem>()
for (conversation in it) { for (conversation in it) {
newConversations.add(ConversationItem(conversation, viewModel.currentUser, activity)) newConversations.add(ConversationItem(conversation, viewModel.currentUser, activity))
@ -318,12 +325,64 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
override fun onItemLongClick(position: Int) { override fun onItemLongClick(position: Int) {
val clickedItem = recyclerViewAdapter.getItem(position) val clickedItem = recyclerViewAdapter.getItem(position)
if (clickedItem != null) { clickedItem?.let {
val conversation = (clickedItem as ConversationItem).model val conversationItem = it as ConversationItem
val bundle = Bundle() val conversation = conversationItem.model
bundle.putParcelable(BundleKeys.KEY_ROOM, Parcels.wrap<Conversation>(conversation))
bundle.putParcelable(BundleKeys.KEY_MENU_TYPE, Parcels.wrap<MenuType>(REGULAR)) activity?.let { activity ->
//prepareAndShowBottomSheetWithBundle(bundle, true) MaterialDialog(activity, BottomSheet(WRAP_CONTENT)).show {
cornerRadius(res = R.dimen.corner_radius)
title(text = conversation.displayName)
listItemsWithImage(
viewModel.getConversationMenuItemsForConversation(conversation)
) { dialog,
index, item ->
when (item.iconRes) {
drawable.ic_star_border_black_24dp -> {
viewModel.changeFavoriteValueForConversation(conversation, false)
}
drawable.ic_star_black_24dp -> {
viewModel.changeFavoriteValueForConversation(conversation, true)
}
drawable.ic_link_grey600_24px -> {
startActivity(viewModel.getShareIntentForConversation(conversation))
}
drawable.ic_exit_to_app_black_24dp -> {
MaterialDialog(activity).show {
title(R.string.nc_leave)
message(R.string.nc_leave_message)
positiveButton(R.string.nc_simple_leave) { dialog ->
viewModel.leaveConversation(conversation)
}
negativeButton(R.string.nc_cancel)
icon(drawable.ic_exit_to_app_black_24dp)
}
}
drawable.ic_delete_grey600_24dp -> {
MaterialDialog(activity).show {
title(R.string.nc_delete)
message(text = conversation.deleteWarningMessage)
positiveButton(R.string.nc_delete_call) { dialog ->
viewModel.deleteConversation(conversation)
}
negativeButton(R.string.nc_cancel)
icon(
drawable = DisplayUtils.getTintedDrawable(
resources, drawable
.ic_delete_grey600_24dp, R.color.nc_darkRed
)
)
}
}
else -> {
}
}
}
}
}
} }
} }

View File

@ -21,6 +21,7 @@
package com.nextcloud.talk.newarch.features.conversationsList package com.nextcloud.talk.newarch.features.conversationsList
import android.app.Application import android.app.Application
import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
@ -33,29 +34,42 @@ import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber
import com.facebook.imagepipeline.image.CloseableImage import com.facebook.imagepipeline.image.CloseableImage
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.R.drawable
import com.nextcloud.talk.R.string
import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage
import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel
import com.nextcloud.talk.newarch.data.model.ErrorModel import com.nextcloud.talk.newarch.data.model.ErrorModel
import com.nextcloud.talk.newarch.domain.usecases.DeleteConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.SetConversationFavoriteValueUseCase
import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse
import com.nextcloud.talk.newarch.mvvm.ViewState import com.nextcloud.talk.newarch.utils.ViewState
import com.nextcloud.talk.newarch.mvvm.ViewState.FAILED import com.nextcloud.talk.newarch.utils.ViewState.FAILED
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADED import com.nextcloud.talk.newarch.utils.ViewState.LOADED
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADED_EMPTY import com.nextcloud.talk.newarch.utils.ViewState.LOADED_EMPTY
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADING import com.nextcloud.talk.newarch.utils.ViewState.LOADING
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.ShareUtils
import com.nextcloud.talk.utils.database.user.UserUtils import com.nextcloud.talk.utils.database.user.UserUtils
import org.apache.commons.lang3.builder.CompareToBuilder import org.apache.commons.lang3.builder.CompareToBuilder
import org.koin.core.parameter.parametersOf
class ConversationsListViewModel constructor( class ConversationsListViewModel constructor(
application: Application, application: Application,
private val conversationsUseCase: GetConversationsUseCase, private val getConversationsUseCase: GetConversationsUseCase,
private val setConversationFavoriteValueUseCase: SetConversationFavoriteValueUseCase,
private val leaveConversationUseCase: LeaveConversationUseCase,
private val deleteConversationUseCase: DeleteConversationUseCase,
private val userUtils: UserUtils private val userUtils: UserUtils
) : BaseViewModel<ConversationsListView>(application) { ) : BaseViewModel<ConversationsListView>(application) {
val conversationsListData = MutableLiveData<List<Conversation>>() private var conversations: MutableList<Conversation> = mutableListOf()
val conversationsLiveListData = MutableLiveData<List<Conversation>>()
val viewState = MutableLiveData<ViewState>(LOADING) val viewState = MutableLiveData<ViewState>(LOADING)
var messageData: String? = null var messageData: String? = null
val searchQuery = MutableLiveData<String>() val searchQuery = MutableLiveData<String>()
@ -63,23 +77,91 @@ class ConversationsListViewModel constructor(
var currentUserAvatar: MutableLiveData<Drawable> = MutableLiveData() var currentUserAvatar: MutableLiveData<Drawable> = MutableLiveData()
get() { get() {
if (field.value == null) { if (field.value == null) {
field.value = context.resources.getDrawable(R.drawable.ic_settings_white_24dp) field.value = context.resources.getDrawable(drawable.ic_settings_white_24dp)
} }
return field return field
} }
fun leaveConversation(conversation: Conversation) {
leaveConversationUseCase.user = currentUser
setConversationUpdateStatus(conversation, true)
leaveConversationUseCase.invoke(viewModelScope, parametersOf(conversation),
object : UseCaseResponse<GenericOverall> {
override fun onSuccess(result: GenericOverall) {
// TODO: Use binary search to find the right room
conversations.find { it.roomId == conversation.roomId }?.let {
conversations.remove(it)
conversationsLiveListData.value = conversations
}
}
override fun onError(errorModel: ErrorModel?) {
setConversationUpdateStatus(conversation, false)
}
})
}
fun deleteConversation(conversation: Conversation) {
deleteConversationUseCase.user = currentUser
setConversationUpdateStatus(conversation, true)
deleteConversationUseCase.invoke(viewModelScope, parametersOf(conversation),
object : UseCaseResponse<GenericOverall> {
override fun onSuccess(result: GenericOverall) {
// TODO: Use binary search to find the right room
conversations.find { it.roomId == conversation.roomId }?.let {
conversations.remove(it)
conversationsLiveListData.value = conversations
}
}
override fun onError(errorModel: ErrorModel?) {
setConversationUpdateStatus(conversation, false)
}
})
}
fun changeFavoriteValueForConversation(
conversation: Conversation,
favorite: Boolean
) {
setConversationFavoriteValueUseCase.user = currentUser
setConversationUpdateStatus(conversation, true)
setConversationFavoriteValueUseCase.invoke(viewModelScope, parametersOf(conversation, favorite),
object : UseCaseResponse<GenericOverall> {
override fun onSuccess(result: GenericOverall) {
// TODO: Use binary search to find the right room
conversations.find { it.roomId == conversation.roomId }?.apply {
updating = false
isFavorite = favorite
conversationsLiveListData.value = conversations
}
}
override fun onError(errorModel: ErrorModel?) {
setConversationUpdateStatus(conversation, false)
}
})
}
fun loadConversations() { fun loadConversations() {
currentUser = userUtils.currentUser currentUser = userUtils.currentUser
if (viewState.value?.equals(FAILED)!! || !conversationsUseCase.isUserInitialized() || if (viewState.value?.equals(FAILED)!! || !getConversationsUseCase.isUserInitialized() ||
conversationsUseCase.user != currentUser getConversationsUseCase.user != currentUser
) { ) {
conversationsUseCase.user = currentUser getConversationsUseCase.user = currentUser
viewState.value = LOADING viewState.value = LOADING
} }
conversationsUseCase.invoke( getConversationsUseCase.invoke(
viewModelScope, null, object : UseCaseResponse<List<Conversation>> { viewModelScope, null, object : UseCaseResponse<List<Conversation>> {
override fun onSuccess(result: List<Conversation>) { override fun onSuccess(result: List<Conversation>) {
val newConversations = result.toMutableList() val newConversations = result.toMutableList()
@ -91,7 +173,8 @@ class ConversationsListViewModel constructor(
.toComparison() .toComparison()
}) })
conversationsListData.value = newConversations conversations = newConversations
conversationsLiveListData.value = conversations
viewState.value = if (newConversations.isNotEmpty()) LOADED else LOADED_EMPTY viewState.value = if (newConversations.isNotEmpty()) LOADED else LOADED_EMPTY
messageData = "" messageData = ""
} }
@ -133,4 +216,88 @@ class ConversationsListViewModel constructor(
}, UiThreadImmediateExecutorService.getInstance()) }, UiThreadImmediateExecutorService.getInstance())
} }
fun getShareIntentForConversation(conversation: Conversation): Intent {
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(
Intent.EXTRA_SUBJECT,
String.format(
context.getString(R.string.nc_share_subject),
context.getString(R.string.nc_app_name)
)
)
// TODO, make sure we ask for password if needed
putExtra(
Intent.EXTRA_TEXT, ShareUtils.getStringForIntent(
context, null,
userUtils, conversation
)
)
type = "text/plain"
}
val intent = Intent.createChooser(sendIntent, context.getString(string.nc_share_link))
// TODO filter our own app once we're there
return intent
}
fun getConversationMenuItemsForConversation(conversation: Conversation): MutableList<BasicListItemWithImage> {
val items = mutableListOf<BasicListItemWithImage>()
if (conversation.isFavorite) {
items.add(
BasicListItemWithImage(
drawable.ic_star_border_black_24dp,
context.getString(string.nc_remove_from_favorites)
)
)
} else {
items.add(
BasicListItemWithImage(
drawable.ic_star_black_24dp,
context.getString(string.nc_add_to_favorites)
)
)
}
if (conversation.isPublic) {
items.add(
(BasicListItemWithImage(
drawable
.ic_link_grey600_24px, context.getString(string.nc_share_link)
))
)
}
if (conversation.canLeave(currentUser)) {
items.add(
BasicListItemWithImage(
drawable.ic_exit_to_app_black_24dp, context.getString
(string.nc_leave)
)
)
}
if (conversation.canModerate(currentUser)) {
items.add(
BasicListItemWithImage(
drawable.ic_delete_grey600_24dp, context.getString(
string.nc_delete_call
)
)
)
}
return items
}
private fun setConversationUpdateStatus(conversation: Conversation, value: Boolean) {
conversations.find { it.roomId == conversation.roomId }?.apply {
updating = value
conversationsLiveListData.value = conversations
}
}
} }

View File

@ -22,18 +22,35 @@ package com.nextcloud.talk.newarch.features.conversationsList.di.module
import android.app.Application import android.app.Application
import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler
import com.nextcloud.talk.newarch.di.module.createApiErrorHandler
import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkRepository import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkRepository
import com.nextcloud.talk.newarch.domain.usecases.DeleteConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.SetConversationFavoriteValueUseCase
import com.nextcloud.talk.newarch.features.conversationsList.ConversationListViewModelFactory import com.nextcloud.talk.newarch.features.conversationsList.ConversationListViewModelFactory
import com.nextcloud.talk.utils.database.user.UserUtils import com.nextcloud.talk.utils.database.user.UserUtils
import org.koin.android.ext.koin.androidApplication import org.koin.android.ext.koin.androidApplication
import org.koin.dsl.module import org.koin.dsl.module
val ConversationsListModule = module { val ConversationsListModule = module {
single { createGetConversationsUseCase(get(), createApiErrorHandler()) } single { createGetConversationsUseCase(get(), get()) }
single { createSetConversationFavoriteValueUseCase(get(), get()) }
single { createLeaveConversationUseCase(get(), get()) }
single { createDeleteConversationuseCase(get(), get()) }
//viewModel { ConversationsListViewModel(get(), get()) } //viewModel { ConversationsListViewModel(get(), get()) }
factory { createConversationListViewModelFactory(androidApplication(), get(), get()) } factory {
createConversationListViewModelFactory(
androidApplication(), get(), get(), get(), get
(), get()
)
}
}
fun createSetConversationFavoriteValueUseCase(
nextcloudTalkRepository: NextcloudTalkRepository,
apiErrorHandler: ApiErrorHandler
): SetConversationFavoriteValueUseCase {
return SetConversationFavoriteValueUseCase(nextcloudTalkRepository, apiErrorHandler)
} }
fun createGetConversationsUseCase( fun createGetConversationsUseCase(
@ -43,11 +60,32 @@ fun createGetConversationsUseCase(
return GetConversationsUseCase(nextcloudTalkRepository, apiErrorHandler) return GetConversationsUseCase(nextcloudTalkRepository, apiErrorHandler)
} }
fun createLeaveConversationUseCase(
nextcloudTalkRepository: NextcloudTalkRepository,
apiErrorHandler: ApiErrorHandler
): LeaveConversationUseCase {
return LeaveConversationUseCase(nextcloudTalkRepository, apiErrorHandler)
}
fun createDeleteConversationuseCase(
nextcloudTalkRepository: NextcloudTalkRepository,
apiErrorHandler: ApiErrorHandler
): DeleteConversationUseCase {
return DeleteConversationUseCase(nextcloudTalkRepository, apiErrorHandler)
}
fun createConversationListViewModelFactory( fun createConversationListViewModelFactory(
application: Application, application: Application,
conversationsUseCase: getConversationsUseCase:
GetConversationsUseCase, GetConversationsUseCase,
setConversationFavoriteValueUseCase: SetConversationFavoriteValueUseCase,
leaveConversationUseCase: LeaveConversationUseCase,
deleteConversationUseCase: DeleteConversationUseCase,
userUtils: UserUtils userUtils: UserUtils
): ConversationListViewModelFactory { ): ConversationListViewModelFactory {
return ConversationListViewModelFactory(application, conversationsUseCase, userUtils) return ConversationListViewModelFactory(
application, getConversationsUseCase,
setConversationFavoriteValueUseCase, leaveConversationUseCase, deleteConversationUseCase,
userUtils
)
} }

View File

@ -0,0 +1,26 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 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.newarch.utils
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.utils.ApiUtils
fun UserEntity.getCredentials() = ApiUtils.getCredentials(username, token)

View File

@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.nextcloud.talk.newarch.mvvm package com.nextcloud.talk.newarch.utils
enum class ViewState { enum class ViewState {
LOADING, LOADING,

View File

@ -246,9 +246,10 @@ public class DisplayUtils {
public static Drawable getTintedDrawable(Resources res, @DrawableRes int drawableResId, public static Drawable getTintedDrawable(Resources res, @DrawableRes int drawableResId,
@ColorRes int colorResId) { @ColorRes int colorResId) {
Drawable drawable = res.getDrawable(drawableResId); Drawable drawable = res.getDrawable(drawableResId);
Drawable mutableDrawable = drawable.mutate();
int color = res.getColor(colorResId); int color = res.getColor(colorResId);
drawable.setTint(color); mutableDrawable.setTint(color);
return drawable; return mutableDrawable;
} }
public static Drawable getDrawableForMentionChipSpan(Context context, String id, public static Drawable getDrawableForMentionChipSpan(Context context, String id,

View File

@ -42,8 +42,7 @@ import java.util.Set;
public class ShareUtils { public class ShareUtils {
public static String getStringForIntent(Context context, @Nullable String password, public static String getStringForIntent(Context context, @Nullable String password,
UserUtils userUtils, Conversation UserUtils userUtils, Conversation conversation) {
conversation) {
UserEntity userEntity = userUtils.getCurrentUser(); UserEntity userEntity = userUtils.getCurrentUser();
String shareString = ""; String shareString = "";

View File

@ -21,5 +21,6 @@
<vector android:autoMirrored="true" android:height="24dp" <vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0" android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M10.09,15.59L11.5,17l5,-5 -5,-5 -1.41,1.41L12.67,11H3v2h9.67l-2.58,2.59zM19,3H5c-1.11,0 -2,0.9 -2,2v4h2V5h14v14H5v-4H3v4c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/> <path android:fillColor="@color/grey_600"
android:pathData="M10.09,15.59L11.5,17l5,-5 -5,-5 -1.41,1.41L12.67,11H3v2h9.67l-2.58,2.59zM19,3H5c-1.11,0 -2,0.9 -2,2v4h2V5h14v14H5v-4H3v4c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
</vector> </vector>

View File

@ -21,5 +21,6 @@
<vector android:autoMirrored="true" android:height="24dp" <vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0" android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/> <path android:fillColor="@color/grey_600"
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
</vector> </vector>

View File

@ -21,5 +21,5 @@
<vector android:autoMirrored="true" android:height="24dp" <vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0" android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/> <path android:fillColor="@color/grey_600" android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
</vector> </vector>

View File

@ -27,7 +27,8 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/rv_item_view_height" android:layout_height="@dimen/rv_item_view_height"
android:layout_margin="@dimen/double_margin_between_elements"> android:layout_margin="@dimen/double_margin_between_elements"
android:animateLayoutChanges="true">
<RelativeLayout <RelativeLayout
@ -78,7 +79,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_toStartOf="@id/dialogUnreadBubble" android:layout_toStartOf="@id/dialogUnreadBubble"
android:layout_toEndOf="@id/dialogLastMessageUserAvatar"
android:ellipsize="end" android:ellipsize="end"
android:gravity="top" android:gravity="top"
android:lines="1" android:lines="1"
@ -112,13 +112,25 @@
android:maxLines="1" android:maxLines="1"
android:textColor="@color/conversation_date" /> android:textColor="@color/conversation_date" />
<ProgressBar
android:layout_width="16sp"
android:layout_height="16sp"
android:id="@+id/actionProgressBar"
android:layout_alignBottom="@id/dialogName"
android:layout_marginEnd="8dp"
android:layout_toEndOf="@+id/dialogAvatarFrameLayout"
android:indeterminateTint="@color/colorPrimary"
android:visibility="gone"
/>
<androidx.emoji.widget.EmojiTextView <androidx.emoji.widget.EmojiTextView
android:id="@id/dialogName" android:id="@id/dialogName"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignTop="@id/dialogAvatarFrameLayout" android:layout_alignTop="@id/dialogAvatarFrameLayout"
android:layout_toStartOf="@id/dialogDate" android:layout_toStartOf="@id/dialogDate"
android:layout_toEndOf="@id/dialogAvatarFrameLayout" android:layout_toEndOf="@id/actionProgressBar"
android:ellipsize="end" android:ellipsize="end"
android:includeFontPadding="false" android:includeFontPadding="false"
android:maxLines="1" android:maxLines="1"

View File

@ -147,6 +147,8 @@
<string name="nc_start_conversation">Start a conversation</string> <string name="nc_start_conversation">Start a conversation</string>
<string name="nc_configure_room">Configure conversation</string> <string name="nc_configure_room">Configure conversation</string>
<string name="nc_leave">Leave conversation</string> <string name="nc_leave">Leave conversation</string>
<string name="nc_leave_message">Please confirm your intent to leave this conversation.</string>
<string name="nc_simple_leave">Leave</string>
<string name="nc_rename">Rename conversation</string> <string name="nc_rename">Rename conversation</string>
<string name="nc_set_password">Set a password</string> <string name="nc_set_password">Set a password</string>
<string name="nc_change_password">Change password</string> <string name="nc_change_password">Change password</string>
@ -157,10 +159,12 @@
<string name="nc_make_call_private">Make conversation private</string> <string name="nc_make_call_private">Make conversation private</string>
<string name="nc_delete_call">Delete conversation</string> <string name="nc_delete_call">Delete conversation</string>
<string name="nc_delete">Delete</string> <string name="nc_delete">Delete</string>
<string name="nc_delete_conversation_default">Please confirm your intent to remove the conversation.</string> <string name="nc_delete_conversation_default">Please confirm your intent to delete this
<string name="nc_delete_conversation_one2one">If you delete the conversation, it will also be conversation.</string>
<string name="nc_delete_conversation_one2one">If you delete the conversation it will also be
deleted for %1$s.</string> deleted for %1$s.</string>
<string name="nc_delete_conversation_more">If you delete the conversation, it will also be deleted for all other participants.</string> <string name="nc_delete_conversation_more">If you delete this conversation it will also be
deleted for all other participants.</string>
<string name="nc_new_conversation">New conversation</string> <string name="nc_new_conversation">New conversation</string>
<string name="nc_join_via_link">Join with a link</string> <string name="nc_join_via_link">Join with a link</string>