Browse Source

Merge pull request #2504 from nextcloud/2474-feature-mvp-activities

[MVP] Refactored the Activities activity
Andy Scherzinger 7 years ago
parent
commit
217d3c3bc8
21 changed files with 1422 additions and 475 deletions
  1. 2 2
      src/main/AndroidManifest.xml
  2. 324 0
      src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java
  3. 44 0
      src/main/java/com/owncloud/android/ui/activities/ActivitiesContract.java
  4. 92 0
      src/main/java/com/owncloud/android/ui/activities/ActivitiesPresenter.java
  5. 37 0
      src/main/java/com/owncloud/android/ui/activities/data/Injection.java
  6. 38 0
      src/main/java/com/owncloud/android/ui/activities/data/activities/ActivitiesRepository.java
  7. 40 0
      src/main/java/com/owncloud/android/ui/activities/data/activities/ActivitiesServiceApi.java
  8. 130 0
      src/main/java/com/owncloud/android/ui/activities/data/activities/ActivitiesServiceApiImpl.java
  9. 34 0
      src/main/java/com/owncloud/android/ui/activities/data/activities/ActivityRepositories.java
  10. 51 0
      src/main/java/com/owncloud/android/ui/activities/data/activities/RemoteActivitiesRepository.java
  11. 32 0
      src/main/java/com/owncloud/android/ui/activities/data/files/FileRepositories.java
  12. 38 0
      src/main/java/com/owncloud/android/ui/activities/data/files/FilesRepository.java
  13. 37 0
      src/main/java/com/owncloud/android/ui/activities/data/files/FilesServiceApi.java
  14. 142 0
      src/main/java/com/owncloud/android/ui/activities/data/files/FilesServiceApiImpl.java
  15. 50 0
      src/main/java/com/owncloud/android/ui/activities/data/files/RemoteFilesRepository.java
  16. 0 452
      src/main/java/com/owncloud/android/ui/activity/ActivitiesListActivity.java
  17. 2 1
      src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
  18. 11 20
      src/main/res/layout/activity_list_layout.xml
  19. 164 0
      src/test/java/com/owncloud/android/ui/activities/ActivitiesPresenterTest.java
  20. 77 0
      src/test/java/com/owncloud/android/ui/activities/data/activities/RemoteActivitiesRepositoryTest.java
  21. 77 0
      src/test/java/com/owncloud/android/ui/activities/data/files/RemoteFilesRepositoryTest.java

+ 2 - 2
src/main/AndroidManifest.xml

@@ -93,8 +93,8 @@
         <activity android:name=".ui.activity.UserInfoActivity" />
         <activity android:name=".ui.activity.NotificationsActivity"/>
         <activity android:name=".ui.activity.ParticipateActivity" />
-        <activity android:name=".ui.activity.ActivitiesListActivity"
-                  android:configChanges="orientation|screenSize|keyboardHidden" />
+        <activity android:name=".ui.activities.ActivitiesActivity"
+            android:configChanges="orientation|screenSize|keyboardHidden" />
         <activity android:name=".ui.activity.SyncedFoldersActivity"/>
         <activity android:name=".ui.activity.UploadFilesActivity" />
         <activity android:name=".ui.activity.ExternalSiteWebView"

+ 324 - 0
src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java

@@ -0,0 +1,324 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   Copyright (C) 2018 Edvard Holst
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.activities;
+
+import android.content.Intent;
+import android.graphics.PorterDuff;
+import android.os.Bundle;
+import android.support.design.widget.BottomNavigationView;
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.support.v7.app.ActionBar;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.owncloud.android.R;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.activities.models.RichObject;
+import com.owncloud.android.lib.resources.files.FileUtils;
+import com.owncloud.android.ui.activities.data.Injection;
+import com.owncloud.android.ui.activity.FileActivity;
+import com.owncloud.android.ui.activity.FileDisplayActivity;
+import com.owncloud.android.ui.adapter.ActivityListAdapter;
+import com.owncloud.android.ui.interfaces.ActivityListInterface;
+import com.owncloud.android.ui.preview.PreviewImageActivity;
+import com.owncloud.android.ui.preview.PreviewImageFragment;
+import com.owncloud.android.utils.AnalyticsUtils;
+import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.ThemeUtils;
+
+import java.util.List;
+
+import butterknife.BindString;
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.Unbinder;
+
+public class ActivitiesActivity extends FileActivity implements ActivityListInterface, ActivitiesContract.View {
+    private static final String TAG = ActivitiesActivity.class.getSimpleName();
+    private static final String SCREEN_NAME = "Activities";
+
+    @BindView(R.id.empty_list_view)
+    public LinearLayout emptyContentContainer;
+
+    @BindView(R.id.swipe_containing_list)
+    public SwipeRefreshLayout swipeListRefreshLayout;
+
+    @BindView(R.id.empty_list_view_text)
+    public TextView emptyContentMessage;
+
+    @BindView(R.id.empty_list_view_headline)
+    public TextView emptyContentHeadline;
+
+    @BindView(R.id.empty_list_icon)
+    public ImageView emptyContentIcon;
+
+    @BindView(R.id.empty_list_progress)
+    public ProgressBar emptyContentProgressBar;
+
+    @BindView(android.R.id.list)
+    public RecyclerView recyclerView;
+
+    @BindView(R.id.bottom_navigation_view)
+    public BottomNavigationView bottomNavigationView;
+
+    @BindString(R.string.activities_no_results_headline)
+    public String noResultsHeadline;
+
+    @BindString(R.string.activities_no_results_message)
+    public String noResultsMessage;
+
+    private ActivityListAdapter adapter;
+    private Unbinder unbinder;
+    private String nextPageUrl;
+
+    private boolean isLoadingActivities;
+
+    private ActivitiesContract.ActionListener mActionListener;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Log_OC.v(TAG, "onCreate() start");
+        super.onCreate(savedInstanceState);
+
+        mActionListener = new ActivitiesPresenter(Injection.provideActivitiesRepository(),
+                Injection.provideFilesRepository(),
+                this);
+
+        setContentView(R.layout.activity_list_layout);
+        unbinder = ButterKnife.bind(this);
+
+        // setup toolbar
+        setupToolbar();
+
+        onCreateSwipeToRefresh(swipeListRefreshLayout);
+
+        // setup drawer
+        setupDrawer(R.id.nav_activity);
+        ActionBar actionBar = getSupportActionBar();
+        if (actionBar != null) {
+            ThemeUtils.setColoredTitle(actionBar, getString(R.string.drawer_item_activities), this);
+        }
+
+        swipeListRefreshLayout.setOnRefreshListener(() -> {
+            // We set the nextPageUrl variable to null here since when manually refreshing
+            // activities data we want to clear the list and reset the pagination.
+            nextPageUrl = null;
+            mActionListener.loadActivities(nextPageUrl);
+        });
+
+        // Since we use swipe-to-refresh for progress indication we can hide the inherited
+        // progressBar, message and headline
+        emptyContentProgressBar.setVisibility(View.GONE);
+        emptyContentMessage.setVisibility(View.INVISIBLE);
+        emptyContentHeadline.setVisibility(View.INVISIBLE);
+
+    }
+
+    protected void onCreateSwipeToRefresh(SwipeRefreshLayout refreshLayout) {
+        int primaryColor = ThemeUtils.primaryColor(this);
+        int darkColor = ThemeUtils.primaryDarkColor(this);
+        int accentColor = ThemeUtils.primaryAccentColor(this);
+
+        // Colors in animations
+        refreshLayout.setColorSchemeColors(accentColor, primaryColor, darkColor);
+    }
+
+    public void onDestroy() {
+        super.onDestroy();
+        unbinder.unbind();
+    }
+
+    @Override
+    public void showFiles(boolean onDeviceOnly) {
+        super.showFiles(onDeviceOnly);
+        Intent i = new Intent(getApplicationContext(), FileDisplayActivity.class);
+        i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        startActivity(i);
+    }
+
+    /**
+     * sets up the UI elements and loads all activity items.
+     */
+    private void setupContent() {
+        emptyContentIcon.setImageResource(R.drawable.ic_activity_light_grey);
+        emptyContentProgressBar.getIndeterminateDrawable().setColorFilter(ThemeUtils.primaryAccentColor(this),
+                PorterDuff.Mode.SRC_IN);
+
+        FileDataStorageManager storageManager = new FileDataStorageManager(getAccount(), getContentResolver());
+        adapter = new ActivityListAdapter(this, this, storageManager);
+        recyclerView.setAdapter(adapter);
+
+        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
+
+        recyclerView.setLayoutManager(layoutManager);
+        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+
+            @Override
+            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+                super.onScrolled(recyclerView, dx, dy);
+
+                int visibleItemCount = recyclerView.getChildCount();
+                int totalItemCount = layoutManager.getItemCount();
+                int firstVisibleItemIndex = layoutManager.findFirstVisibleItemPosition();
+
+                // synchronize loading state when item count changes
+                if (!isLoadingActivities && (totalItemCount - visibleItemCount) <= (firstVisibleItemIndex + 5)
+                        && nextPageUrl != null && !nextPageUrl.isEmpty()) {
+                    // Almost reached the end, continue to load new activities
+                    mActionListener.loadActivities(nextPageUrl);
+                }
+            }
+        });
+
+        if (getResources().getBoolean(R.bool.bottom_toolbar_enabled)) {
+            bottomNavigationView.setVisibility(View.VISIBLE);
+            DisplayUtils.setupBottomBar(bottomNavigationView, getResources(), this, -1);
+        }
+
+        mActionListener.loadActivities(null);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        boolean retval = true;
+
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                if (isDrawerOpen()) {
+                    closeDrawer();
+                } else {
+                    openDrawer();
+                }
+                break;
+            default:
+                Log_OC.w(TAG, "Unknown menu item triggered");
+                retval = super.onOptionsItemSelected(item);
+                break;
+        }
+
+        return retval;
+    }
+
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        setupContent();
+
+        AnalyticsUtils.setCurrentScreenName(this, SCREEN_NAME, TAG);
+    }
+
+    @Override
+    public void onActivityClicked(RichObject richObject) {
+        String path = FileUtils.PATH_SEPARATOR + richObject.getPath();
+        mActionListener.openActivity(path, this,
+                getFileOperationsHelper().isSharedSupported());
+    }
+
+    @Override
+    public void showActivities(List<Object> activities, OwnCloudClient client, String nextPageUrl) {
+        boolean clear = false;
+        if (this.nextPageUrl == null) {
+            clear = true;
+        }
+        adapter.setActivityItems(activities, client, clear);
+        this.nextPageUrl = nextPageUrl;
+        // Hide the recyclerView if list is empty
+        if (activities.size() == 0) {
+            recyclerView.setVisibility(View.INVISIBLE);
+
+            emptyContentMessage.setText(noResultsMessage);
+            emptyContentHeadline.setText(noResultsHeadline);
+            emptyContentMessage.setVisibility(View.VISIBLE);
+            emptyContentHeadline.setVisibility(View.VISIBLE);
+        } else {
+            emptyContentMessage.setVisibility(View.INVISIBLE);
+            emptyContentHeadline.setVisibility(View.INVISIBLE);
+
+            recyclerView.setVisibility(View.VISIBLE);
+        }
+    }
+
+    @Override
+    public void showActivitiesLoadError(String error) {
+        Toast.makeText(getBaseContext(), error, Toast.LENGTH_LONG).show();
+    }
+
+    @Override
+    public void showActivityDetailUI(OCFile ocFile) {
+        Intent showDetailsIntent;
+        if (PreviewImageFragment.canBePreviewed(ocFile)) {
+            showDetailsIntent = new Intent(getBaseContext(), PreviewImageActivity.class);
+        } else {
+            showDetailsIntent = new Intent(getBaseContext(), FileDisplayActivity.class);
+        }
+        showDetailsIntent.putExtra(EXTRA_FILE, ocFile);
+        showDetailsIntent.putExtra(EXTRA_ACCOUNT, getAccount());
+        startActivity(showDetailsIntent);
+
+    }
+
+    @Override
+    public void showActivityDetailUIIsNull() {
+        Toast.makeText(getBaseContext(), R.string.file_not_found, Toast.LENGTH_LONG).show();
+    }
+
+    @Override
+    public void showActivityDetailError(String error) {
+        Toast.makeText(getBaseContext(), error, Toast.LENGTH_LONG).show();
+    }
+
+    @Override
+    public void showLoadingMessage() {
+        emptyContentHeadline.setText(R.string.file_list_loading);
+        emptyContentMessage.setText("");
+
+        emptyContentIcon.setVisibility(View.GONE);
+        emptyContentProgressBar.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void showEmptyContent(String headline, String message) {
+        if (emptyContentContainer != null && emptyContentMessage != null) {
+            emptyContentHeadline.setText(headline);
+            emptyContentMessage.setText(message);
+
+            emptyContentProgressBar.setVisibility(View.GONE);
+            emptyContentIcon.setVisibility(View.VISIBLE);
+        }
+    }
+
+    @Override
+    public void setProgressIndicatorState(boolean isActive) {
+        isLoadingActivities = isActive;
+        swipeListRefreshLayout.post(() -> swipeListRefreshLayout.setRefreshing(isActive));
+
+    }
+}

+ 44 - 0
src/main/java/com/owncloud/android/ui/activities/ActivitiesContract.java

@@ -0,0 +1,44 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   Copyright (C) 2018 Edvard Holst
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.activities;
+
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.ui.activity.BaseActivity;
+
+import java.util.List;
+
+public interface ActivitiesContract {
+
+    interface View {
+        void showActivities(List<Object> activities, OwnCloudClient client, String nextPageUrl);
+        void showActivitiesLoadError(String error);
+        void showActivityDetailUI(OCFile ocFile);
+        void showActivityDetailUIIsNull();
+        void showActivityDetailError(String error);
+        void showLoadingMessage();
+        void showEmptyContent(String headline, String message);
+        void setProgressIndicatorState(boolean isActive);
+    }
+
+    interface ActionListener {
+        void loadActivities(String pageUrl);
+        void openActivity(String fileUrl, BaseActivity baseActivity, boolean isSharingSupported);
+    }
+}

+ 92 - 0
src/main/java/com/owncloud/android/ui/activities/ActivitiesPresenter.java

@@ -0,0 +1,92 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   Copyright (C) 2018 Edvard Holst
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.activities;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.ui.activities.data.activities.ActivitiesRepository;
+import com.owncloud.android.ui.activities.data.files.FilesRepository;
+import com.owncloud.android.ui.activity.BaseActivity;
+
+import java.util.List;
+
+public class ActivitiesPresenter implements ActivitiesContract.ActionListener {
+
+    private final ActivitiesContract.View activitiesView;
+    private final ActivitiesRepository activitiesRepository;
+    private final FilesRepository filesRepository;
+
+
+    public ActivitiesPresenter(@NonNull ActivitiesRepository activitiesRepository,
+                               @NonNull FilesRepository filesRepository,
+                               @NonNull ActivitiesContract.View activitiesView) {
+        this.activitiesRepository = activitiesRepository;
+        this.activitiesView = activitiesView;
+        this.filesRepository = filesRepository;
+    }
+
+    @Override
+    public void loadActivities(String pageUrl) {
+        activitiesView.setProgressIndicatorState(true);
+        activitiesRepository.getActivities(pageUrl, new ActivitiesRepository.LoadActivitiesCallback() {
+            @Override
+            public void onActivitiesLoaded(List<Object> activities, OwnCloudClient client,
+                                          String nextPageUrl) {
+                activitiesView.setProgressIndicatorState(false);
+                activitiesView.showActivities(activities, client, nextPageUrl);
+            }
+
+            @Override
+            public void onActivitiesLoadedError(String error) {
+                activitiesView.setProgressIndicatorState(false);
+                activitiesView.showActivitiesLoadError(error);
+            }
+        });
+
+
+    }
+
+    @Override
+    public void openActivity(String fileUrl, BaseActivity baseActivity, boolean isSharingSupported) {
+        activitiesView.setProgressIndicatorState(true);
+        filesRepository.readRemoteFile(fileUrl, baseActivity, isSharingSupported,
+                new FilesRepository.ReadRemoteFileCallback() {
+                    @Override
+                    public void onFileLoaded(@Nullable OCFile ocFile) {
+                        activitiesView.setProgressIndicatorState(false);
+                        if (ocFile != null) {
+                            activitiesView.showActivityDetailUI(ocFile);
+                        } else {
+                            activitiesView.showActivityDetailUIIsNull();
+                        }
+                    }
+
+                    @Override
+                    public void onFileLoadError(String error) {
+                        activitiesView.setProgressIndicatorState(false);
+                        activitiesView.showActivityDetailError(error);
+                    }
+                });
+    }
+
+}

+ 37 - 0
src/main/java/com/owncloud/android/ui/activities/data/Injection.java

@@ -0,0 +1,37 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   Copyright (C) 2018 Edvard Holst
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.activities.data;
+
+import com.owncloud.android.ui.activities.data.activities.ActivitiesRepository;
+import com.owncloud.android.ui.activities.data.activities.ActivitiesServiceApiImpl;
+import com.owncloud.android.ui.activities.data.activities.ActivityRepositories;
+import com.owncloud.android.ui.activities.data.files.FileRepositories;
+import com.owncloud.android.ui.activities.data.files.FilesRepository;
+import com.owncloud.android.ui.activities.data.files.FilesServiceApiImpl;
+
+public class Injection {
+
+    public static ActivitiesRepository provideActivitiesRepository() {
+        return ActivityRepositories.getRepository(new ActivitiesServiceApiImpl());
+    }
+
+    public static FilesRepository provideFilesRepository() {
+        return FileRepositories.getRepository(new FilesServiceApiImpl());
+    }
+}

+ 38 - 0
src/main/java/com/owncloud/android/ui/activities/data/activities/ActivitiesRepository.java

@@ -0,0 +1,38 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   Copyright (C) 2018 Edvard Holst
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.activities.data.activities;
+
+import android.support.annotation.NonNull;
+
+import com.owncloud.android.lib.common.OwnCloudClient;
+
+import java.util.List;
+
+/**
+ * Main entry point for accessing activities data.
+ */
+public interface ActivitiesRepository {
+    interface LoadActivitiesCallback {
+        void onActivitiesLoaded(List<Object> activities, OwnCloudClient client,
+                                String nextPageUrl);
+        void onActivitiesLoadedError(String error);
+    }
+
+    void getActivities(String pageUrl, @NonNull LoadActivitiesCallback callback);
+}

+ 40 - 0
src/main/java/com/owncloud/android/ui/activities/data/activities/ActivitiesServiceApi.java

@@ -0,0 +1,40 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   Copyright (C) 2018 Edvard Holst
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.activities.data.activities;
+
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.resources.activities.models.Activity;
+
+import java.util.List;
+
+/**
+ * Defines an interface to the Activities service API. All ({@link Activity}) data requests should
+ * be piped through this interface.
+ */
+
+public interface ActivitiesServiceApi {
+
+    interface ActivitiesServiceCallback<T> {
+        void onLoaded (T activities, OwnCloudClient client, String nextPageUrl);
+        void onError (String error);
+    }
+
+    void getAllActivities(String pageUrl, ActivitiesServiceApi.ActivitiesServiceCallback<List<Object>> callback);
+
+}

+ 130 - 0
src/main/java/com/owncloud/android/ui/activities/data/activities/ActivitiesServiceApiImpl.java

@@ -0,0 +1,130 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   Copyright (C) 2018 Edvard Holst
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.activities.data.activities;
+
+import android.accounts.Account;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.content.Context;
+import android.os.AsyncTask;
+
+import com.owncloud.android.MainApp;
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.lib.common.OwnCloudAccount;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.activities.GetRemoteActivitiesOperation;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of the Activities Service API that communicates with the NextCloud remote server
+ */
+public class ActivitiesServiceApiImpl implements ActivitiesServiceApi {
+
+    private static final String TAG = ActivitiesServiceApiImpl.class.getSimpleName();
+
+    @Override
+    public void getAllActivities(String pageUrl, ActivitiesServiceCallback<List<Object>> callback) {
+        GetActivityListTask getActivityListTask = new GetActivityListTask(pageUrl, callback);
+        getActivityListTask.execute();
+    }
+
+    private static class GetActivityListTask extends AsyncTask<Void, Void, Boolean> {
+
+        private final ActivitiesServiceCallback<List<Object>> callback;
+        private List<Object> activities;
+        private String pageUrl;
+        private String noResultsMessage = "no results";
+        private String errorMessage;
+        private OwnCloudClient ownCloudClient;
+
+        private GetActivityListTask(String pageUrl, ActivitiesServiceCallback<List<Object>> callback) {
+            this.pageUrl = pageUrl;
+            this.callback = callback;
+            activities = new ArrayList<>();
+        }
+
+
+        @Override
+        protected Boolean doInBackground(Void... voids) {
+            final Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(MainApp.getAppContext());
+            final Context context = MainApp.getAppContext();
+            OwnCloudAccount ocAccount;
+            try {
+                ocAccount = new OwnCloudAccount(currentAccount, context);
+                ownCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+                        getClientFor(ocAccount, MainApp.getAppContext());
+                ownCloudClient.setOwnCloudVersion(AccountUtils.getServerVersion(currentAccount));
+
+                GetRemoteActivitiesOperation getRemoteNotificationOperation = new GetRemoteActivitiesOperation();
+                if (pageUrl != null) {
+                    getRemoteNotificationOperation.setNextUrl(pageUrl);
+                }
+
+                final RemoteOperationResult result = getRemoteNotificationOperation.execute(ownCloudClient);
+
+                if (result.isSuccess() && result.getData() != null) {
+                    final ArrayList<Object> data = result.getData();
+                    activities = (ArrayList) data.get(0);
+
+                    pageUrl = (String) data.get(1);
+                    return true;
+                } else {
+                    Log_OC.d(TAG, result.getLogMessage());
+                    // show error
+                    errorMessage = result.getLogMessage();
+                    if (result.getHttpCode() == 304) {
+                        errorMessage = noResultsMessage;
+                    }
+                    return false;
+
+                }
+            } catch (com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) {
+                Log_OC.e(TAG, "Account not found", e);
+                errorMessage = "Account not found";
+            } catch (IOException e) {
+                Log_OC.e(TAG, "IO error", e);
+                errorMessage = "IO error";
+            } catch (OperationCanceledException e) {
+                Log_OC.e(TAG, "Operation has been canceled", e);
+                errorMessage = "Operation has been canceled";
+            } catch (AuthenticatorException e) {
+                Log_OC.e(TAG, "Authentication Exception", e);
+                errorMessage = "Authentication Exception";
+            }
+
+            return false;
+        }
+
+        @Override
+        protected void onPostExecute(Boolean success) {
+            super.onPostExecute(success);
+            if (success) {
+                callback.onLoaded(activities, ownCloudClient, pageUrl);
+            } else {
+                callback.onError(errorMessage);
+            }
+        }
+    }
+}

+ 34 - 0
src/main/java/com/owncloud/android/ui/activities/data/activities/ActivityRepositories.java

@@ -0,0 +1,34 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   Copyright (C) 2018 Edvard Holst
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.activities.data.activities;
+
+import android.support.annotation.NonNull;
+
+public class ActivityRepositories {
+
+    private ActivityRepositories() {
+        // No instance
+    }
+
+    public static synchronized ActivitiesRepository getRepository(@NonNull ActivitiesServiceApi activitiesServiceApi) {
+        return new RemoteActivitiesRepository(activitiesServiceApi);
+    }
+
+}
+

+ 51 - 0
src/main/java/com/owncloud/android/ui/activities/data/activities/RemoteActivitiesRepository.java

@@ -0,0 +1,51 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   Copyright (C) 2018 Edvard Holst
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.activities.data.activities;
+
+import android.support.annotation.NonNull;
+
+import com.owncloud.android.lib.common.OwnCloudClient;
+
+import java.util.List;
+
+public class RemoteActivitiesRepository implements ActivitiesRepository {
+
+    private final ActivitiesServiceApi activitiesServiceApi;
+
+    public RemoteActivitiesRepository(@NonNull ActivitiesServiceApi activitiesServiceApi) {
+        this.activitiesServiceApi = activitiesServiceApi;
+    }
+
+
+    @Override
+    public void getActivities(String pageUrl, @NonNull LoadActivitiesCallback callback) {
+        activitiesServiceApi.getAllActivities(pageUrl, new ActivitiesServiceApi.ActivitiesServiceCallback<List<Object>>() {
+            @Override
+            public void onLoaded(List<Object> activities, OwnCloudClient client,
+                                 String nextPageUrl) {
+                callback.onActivitiesLoaded(activities, client, nextPageUrl);
+            }
+
+            @Override
+            public void onError(String error) {
+                callback.onActivitiesLoadedError(error);
+            }
+        });
+    }
+}

+ 32 - 0
src/main/java/com/owncloud/android/ui/activities/data/files/FileRepositories.java

@@ -0,0 +1,32 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   Copyright (C) 2018 Edvard Holst
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.activities.data.files;
+
+import android.support.annotation.NonNull;
+
+public class FileRepositories {
+
+    private FileRepositories() {
+        // No instance
+    }
+
+    public static synchronized FilesRepository getRepository(@NonNull FilesServiceApi filesServiceApi) {
+        return new RemoteFilesRepository(filesServiceApi);
+    }
+}

+ 38 - 0
src/main/java/com/owncloud/android/ui/activities/data/files/FilesRepository.java

@@ -0,0 +1,38 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   Copyright (C) 2018 Edvard Holst
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.activities.data.files;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.ui.activity.BaseActivity;
+
+/**
+ * Main entry point for accessing remote files
+ */
+public interface FilesRepository {
+    interface ReadRemoteFileCallback {
+        void onFileLoaded(@Nullable OCFile ocFile);
+        void onFileLoadError(String error);
+    }
+
+    void readRemoteFile(String path, BaseActivity activity, boolean isSharingSupported,
+                        @NonNull ReadRemoteFileCallback callback);
+}

+ 37 - 0
src/main/java/com/owncloud/android/ui/activities/data/files/FilesServiceApi.java

@@ -0,0 +1,37 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   Copyright (C) 2018 Edvard Holst
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.activities.data.files;
+
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.ui.activity.BaseActivity;
+
+/**
+ * Defines an interface to the Files service API. All {{@link OCFile}} remote data requests
+ * should be piped through this interface.
+ */
+public interface FilesServiceApi {
+
+    interface FilesServiceCallback<T> {
+        void onLoaded(OCFile ocFile);
+        void onError(String error);
+    }
+
+    void readRemoteFile(String fileUrl, BaseActivity activity, boolean isSharingSupported,
+                        FilesServiceApi.FilesServiceCallback<OCFile> callback);
+}

+ 142 - 0
src/main/java/com/owncloud/android/ui/activities/data/files/FilesServiceApiImpl.java

@@ -0,0 +1,142 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   Copyright (C) 2018 Edvard Holst
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.activities.data.files;
+
+import android.accounts.Account;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.content.Context;
+import android.os.AsyncTask;
+
+import com.owncloud.android.MainApp;
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.lib.common.OwnCloudAccount;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
+import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
+import com.owncloud.android.lib.resources.files.RemoteFile;
+import com.owncloud.android.operations.RefreshFolderOperation;
+import com.owncloud.android.ui.activity.BaseActivity;
+import com.owncloud.android.utils.FileStorageUtils;
+
+import java.io.IOException;
+
+/**
+ * Implementation of the Files service API that communicates with the NextCloud remote server.
+ */
+public class FilesServiceApiImpl implements FilesServiceApi {
+
+    private static final String TAG = FilesServiceApiImpl.class.getSimpleName();
+
+    @Override
+    public void readRemoteFile(String fileUrl, BaseActivity activity,
+                               boolean isSharingSupported, FilesServiceCallback<OCFile> callback) {
+        ReadRemoteFileTask readRemoteFileTask = new ReadRemoteFileTask(fileUrl, activity,
+                isSharingSupported, callback);
+        readRemoteFileTask.execute();
+    }
+
+    private static class ReadRemoteFileTask extends AsyncTask<Void, Object, Boolean> {
+        private final FilesServiceCallback<OCFile> callback;
+        private OCFile remoteOcFile;
+        private String errorMessage;
+        // TODO: Figure out a better way to do this than passing a BaseActivity reference.
+        private final BaseActivity baseActivity;
+        private final boolean isSharingSupported;
+        private final String fileUrl;
+
+        private ReadRemoteFileTask(String fileUrl, BaseActivity baseActivity,
+                                   boolean isSharingSupported,
+                                   FilesServiceCallback<OCFile> callback) {
+            this.callback = callback;
+            this.baseActivity = baseActivity;
+            this.isSharingSupported = isSharingSupported;
+            this.fileUrl = fileUrl;
+        }
+
+        @Override
+        protected Boolean doInBackground(Void... voids) {
+            final Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(MainApp.getAppContext());
+            final Context context = MainApp.getAppContext();
+            OwnCloudAccount ocAccount;
+            OwnCloudClient ownCloudClient;
+            try {
+                ocAccount = new OwnCloudAccount(currentAccount, context);
+                ownCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+                        getClientFor(ocAccount, MainApp.getAppContext());
+                ownCloudClient.setOwnCloudVersion(AccountUtils.getServerVersion(currentAccount));
+                // always update file as it could be an old state saved in database
+                ReadRemoteFileOperation operation = new ReadRemoteFileOperation(fileUrl);
+                RemoteOperationResult resultRemoteFileOp = operation.execute(ownCloudClient);
+                if (resultRemoteFileOp.isSuccess()) {
+                    OCFile temp = FileStorageUtils.fillOCFile((RemoteFile) resultRemoteFileOp.getData().get(0));
+                    remoteOcFile = baseActivity.getStorageManager().saveFileWithParent(temp, context);
+
+                    if (remoteOcFile.isFolder()) {
+                        // perform folder synchronization
+                        RemoteOperation synchFolderOp = new RefreshFolderOperation(remoteOcFile,
+                                System.currentTimeMillis(),
+                                false,
+                                isSharingSupported,
+                                true,
+                                baseActivity.getStorageManager(),
+                                baseActivity.getAccount(),
+                                context);
+                        synchFolderOp.execute(ownCloudClient);
+                    }
+                }
+                return true;
+            } catch (com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) {
+                Log_OC.e(TAG, "Account not found", e);
+                errorMessage = "Account not found";
+            } catch (IOException e) {
+                Log_OC.e(TAG, "IO error", e);
+                errorMessage = "IO error";
+            } catch (OperationCanceledException e) {
+                Log_OC.e(TAG, "Operation has been canceled", e);
+                errorMessage = "Operation has been canceled";
+            } catch (AuthenticatorException e) {
+                Log_OC.e(TAG, "Authentication Exception", e);
+                errorMessage = "Authentication Exception";
+            }
+
+            return false;
+        }
+
+        @Override
+        protected void onPostExecute(Boolean success) {
+            super.onPostExecute(success);
+
+            if (success) {
+                if (remoteOcFile != null) {
+                    callback.onLoaded(remoteOcFile);
+                    return;
+                } else {
+                    errorMessage = "File not found";
+                }
+            }
+
+            callback.onError(errorMessage);
+        }
+    }
+}

+ 50 - 0
src/main/java/com/owncloud/android/ui/activities/data/files/RemoteFilesRepository.java

@@ -0,0 +1,50 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   Copyright (C) 2018 Edvard Holst
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.activities.data.files;
+
+import android.support.annotation.NonNull;
+
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.ui.activity.BaseActivity;
+
+class RemoteFilesRepository implements FilesRepository {
+
+    private final FilesServiceApi filesServiceApi;
+
+    public RemoteFilesRepository(@NonNull FilesServiceApi filesServiceApi) {
+        this.filesServiceApi = filesServiceApi;
+    }
+
+
+    @Override
+    public void readRemoteFile(String path, BaseActivity activity, boolean isSharingSupported, @NonNull ReadRemoteFileCallback callback) {
+        filesServiceApi.readRemoteFile(path, activity, isSharingSupported,
+                new FilesServiceApi.FilesServiceCallback<OCFile>() {
+                    @Override
+                    public void onLoaded(OCFile ocFile) {
+                        callback.onFileLoaded(ocFile);
+                    }
+
+                    @Override
+                    public void onError(String error) {
+                        callback.onFileLoadError(error);
+                    }
+                });
+    }
+}

+ 0 - 452
src/main/java/com/owncloud/android/ui/activity/ActivitiesListActivity.java

@@ -1,452 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Andy Scherzinger
- * @author Mario Danic
- * Copyright (C) 2017 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 Affero 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package com.owncloud.android.ui.activity;
-
-import android.accounts.Account;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.PorterDuff;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.support.design.widget.BottomNavigationView;
-import android.support.v4.widget.SwipeRefreshLayout;
-import android.support.v7.app.ActionBar;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.owncloud.android.MainApp;
-import com.owncloud.android.R;
-import com.owncloud.android.authentication.AccountUtils;
-import com.owncloud.android.datamodel.FileDataStorageManager;
-import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.lib.common.OwnCloudAccount;
-import com.owncloud.android.lib.common.OwnCloudClient;
-import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
-import com.owncloud.android.lib.common.operations.RemoteOperation;
-import com.owncloud.android.lib.common.operations.RemoteOperationResult;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.lib.resources.activities.GetRemoteActivitiesOperation;
-import com.owncloud.android.lib.resources.activities.models.RichObject;
-import com.owncloud.android.lib.resources.files.FileUtils;
-import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
-import com.owncloud.android.lib.resources.files.RemoteFile;
-import com.owncloud.android.operations.RefreshFolderOperation;
-import com.owncloud.android.ui.adapter.ActivityListAdapter;
-import com.owncloud.android.ui.interfaces.ActivityListInterface;
-import com.owncloud.android.ui.preview.PreviewImageActivity;
-import com.owncloud.android.ui.preview.PreviewImageFragment;
-import com.owncloud.android.utils.AnalyticsUtils;
-import com.owncloud.android.utils.DisplayUtils;
-import com.owncloud.android.utils.FileStorageUtils;
-import com.owncloud.android.utils.ThemeUtils;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import butterknife.BindString;
-import butterknife.BindView;
-import butterknife.ButterKnife;
-import butterknife.Unbinder;
-
-/**
- * Activity displaying all server side stored activity items.
- */
-public class ActivitiesListActivity extends FileActivity implements ActivityListInterface {
-
-    private static final String TAG = ActivitiesListActivity.class.getSimpleName();
-    private static final String SCREEN_NAME = "Activities";
-
-    @BindView(R.id.empty_list_view)
-    public LinearLayout emptyContentContainer;
-
-    @BindView(R.id.swipe_containing_list)
-    public SwipeRefreshLayout swipeListRefreshLayout;
-
-    @BindView(R.id.swipe_containing_empty)
-    public SwipeRefreshLayout swipeEmptyListRefreshLayout;
-
-    @BindView(R.id.empty_list_view_text)
-    public TextView emptyContentMessage;
-
-    @BindView(R.id.empty_list_view_headline)
-    public TextView emptyContentHeadline;
-
-    @BindView(R.id.empty_list_icon)
-    public ImageView emptyContentIcon;
-
-    @BindView(R.id.empty_list_progress)
-    public ProgressBar emptyContentProgressBar;
-
-    @BindView(android.R.id.list)
-    public RecyclerView recyclerView;
-
-    @BindView(R.id.bottom_navigation_view)
-    public BottomNavigationView bottomNavigationView;
-
-    @BindString(R.string.activities_no_results_headline)
-    public String noResultsHeadline;
-
-    @BindString(R.string.activities_no_results_message)
-    public String noResultsMessage;
-
-    private ActivityListAdapter adapter;
-    private Unbinder unbinder;
-    private OwnCloudClient ownCloudClient;
-    private AsyncTask<String, Object, OCFile> updateTask;
-
-    private String nextPageUrl;
-    private boolean isLoadingActivities;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        Log_OC.v(TAG, "onCreate() start");
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.activity_list_layout);
-        unbinder = ButterKnife.bind(this);
-
-        // setup toolbar
-        setupToolbar();
-
-        onCreateSwipeToRefresh(swipeEmptyListRefreshLayout);
-        onCreateSwipeToRefresh(swipeListRefreshLayout);
-
-        // setup drawer
-        setupDrawer(R.id.nav_activity);
-        ActionBar actionBar = getSupportActionBar();
-        if(actionBar != null) {
-            ThemeUtils.setColoredTitle(actionBar, getString(R.string.drawer_item_activities), this);
-        }
-
-        swipeListRefreshLayout.setOnRefreshListener(() -> {
-                    setLoadingMessage();
-                    if (swipeListRefreshLayout != null && swipeListRefreshLayout.isRefreshing()) {
-                        swipeListRefreshLayout.setRefreshing(false);
-                    }
-                    fetchAndSetData(null);
-                }
-        );
-
-        swipeEmptyListRefreshLayout.setOnRefreshListener(() -> {
-                    setLoadingMessage();
-                    if (swipeEmptyListRefreshLayout != null && swipeEmptyListRefreshLayout.isRefreshing()) {
-                        swipeEmptyListRefreshLayout.setRefreshing(false);
-                    }
-                    fetchAndSetData(null);
-                }
-        );
-    }
-
-    protected void onCreateSwipeToRefresh(SwipeRefreshLayout refreshLayout) {
-        int primaryColor = ThemeUtils.primaryColor(this);
-        int darkColor = ThemeUtils.primaryDarkColor(this);
-        int accentColor = ThemeUtils.primaryAccentColor(this);
-
-        // Colors in animations
-        refreshLayout.setColorSchemeColors(accentColor, primaryColor, darkColor);
-    }
-
-    public void onDestroy() {
-        super.onDestroy();
-        unbinder.unbind();
-    }
-
-    @Override
-    public void showFiles(boolean onDeviceOnly) {
-        super.showFiles(onDeviceOnly);
-        Intent i = new Intent(getApplicationContext(), FileDisplayActivity.class);
-        i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        startActivity(i);
-    }
-
-    /**
-     * sets up the UI elements and loads all activity items.
-     */
-    private void setupContent() {
-        emptyContentIcon.setImageResource(R.drawable.ic_activity_light_grey);
-        emptyContentProgressBar.getIndeterminateDrawable().setColorFilter(ThemeUtils.primaryAccentColor(this),
-                PorterDuff.Mode.SRC_IN);
-        setLoadingMessage();
-
-        FileDataStorageManager storageManager = new FileDataStorageManager(getAccount(), getContentResolver());
-        adapter = new ActivityListAdapter(this, this, storageManager);
-        recyclerView.setAdapter(adapter);
-
-        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
-
-        recyclerView.setLayoutManager(layoutManager);
-        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
-
-            @Override
-            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
-                super.onScrolled(recyclerView, dx, dy);
-
-                int visibleItemCount = recyclerView.getChildCount();
-                int totalItemCount = layoutManager.getItemCount();
-                int firstVisibleItemIndex = layoutManager.findFirstVisibleItemPosition();
-
-                // synchronize loading state when item count changes
-                if (!isLoadingActivities && (totalItemCount - visibleItemCount) <= (firstVisibleItemIndex + 5)
-                        && nextPageUrl != null && !nextPageUrl.isEmpty()) {
-                    // Almost reached the end, continue to load new activities
-                    fetchAndSetData(nextPageUrl);
-                }
-            }
-        });
-
-        if (getResources().getBoolean(R.bool.bottom_toolbar_enabled)) {
-            bottomNavigationView.setVisibility(View.VISIBLE);
-            DisplayUtils.setupBottomBar(bottomNavigationView, getResources(), this, -1);
-        }
-
-        fetchAndSetData(null);
-    }
-
-    /**
-     * @param pageUrl String
-     */
-    private void fetchAndSetData(String pageUrl) {
-        final Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(MainApp.getAppContext());
-        final Context context = MainApp.getAppContext();
-
-        Thread t = new Thread(() -> {
-                OwnCloudAccount ocAccount;
-                try {
-                    ocAccount = new OwnCloudAccount(currentAccount, context);
-                    ownCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
-                            getClientFor(ocAccount, MainApp.getAppContext());
-                    ownCloudClient.setOwnCloudVersion(AccountUtils.getServerVersion(currentAccount));
-                    isLoadingActivities = true;
-                    runOnUiThread(() -> setIndeterminate(isLoadingActivities));
-
-                    GetRemoteActivitiesOperation getRemoteNotificationOperation = new GetRemoteActivitiesOperation();
-                    if (pageUrl != null) {
-                        getRemoteNotificationOperation.setNextUrl(pageUrl);
-                    }
-
-                    Log_OC.d(TAG, "BEFORE getRemoteActivitiesOperation.execute");
-                    final RemoteOperationResult result = getRemoteNotificationOperation.execute(ownCloudClient);
-
-                    if (result.isSuccess() && result.getData() != null) {
-                        final ArrayList<Object> data = result.getData();
-                        final ArrayList<Object> activities = (ArrayList) data.get(0);
-                        nextPageUrl = (String) data.get(1);
-
-                        runOnUiThread(() -> {
-                            populateList(activities, ownCloudClient, pageUrl == null);
-                            if (activities.size() > 0) {
-                                swipeEmptyListRefreshLayout.setVisibility(View.GONE);
-                                swipeListRefreshLayout.setVisibility(View.VISIBLE);
-                            } else {
-                                setEmptyContent(noResultsHeadline, noResultsMessage);
-                                swipeListRefreshLayout.setVisibility(View.GONE);
-                                swipeEmptyListRefreshLayout.setVisibility(View.VISIBLE);
-                            }
-                            isLoadingActivities = false;
-                            setIndeterminate(isLoadingActivities);
-                        });
-                    } else {
-                        Log_OC.d(TAG, result.getLogMessage());
-                        // show error
-                        String logMessage = result.getLogMessage();
-                        if (result.getHttpCode() == 304) {
-                            logMessage = noResultsMessage;
-                        }
-                        final String finalLogMessage = logMessage;
-                        runOnUiThread(() -> {
-                            setEmptyContent(noResultsHeadline, finalLogMessage);
-                            isLoadingActivities = false;
-                            setIndeterminate(isLoadingActivities);
-                        });
-                    }
-
-                    hideRefreshLayoutLoader();
-                } catch (com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) {
-                    Log_OC.e(TAG, "Account not found", e);
-                } catch (IOException e) {
-                    Log_OC.e(TAG, "IO error", e);
-                } catch (OperationCanceledException e) {
-                    Log_OC.e(TAG, "Operation has been canceled", e);
-                } catch (AuthenticatorException e) {
-                    Log_OC.e(TAG, "Authentication Exception", e);
-                }
-            }
-        );
-
-        t.start();
-    }
-
-    private void hideRefreshLayoutLoader() {
-        runOnUiThread(() -> {
-            if (swipeListRefreshLayout != null) {
-                swipeListRefreshLayout.setRefreshing(false);
-            }
-            if (swipeEmptyListRefreshLayout != null) {
-                swipeEmptyListRefreshLayout.setRefreshing(false);
-            }
-            isLoadingActivities = false;
-            setIndeterminate(isLoadingActivities);
-        });
-    }
-
-    private void populateList(List<Object> activities, OwnCloudClient mClient, boolean clear) {
-        adapter.setActivityItems(activities, mClient, clear);
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        boolean retval = true;
-
-        switch (item.getItemId()) {
-            case android.R.id.home:
-                if (isDrawerOpen()) {
-                    closeDrawer();
-                } else {
-                    openDrawer();
-                }
-                break;
-            default:
-                Log_OC.w(TAG, "Unknown menu item triggered");
-                retval = super.onOptionsItemSelected(item);
-                break;
-        }
-
-        return retval;
-    }
-
-    private void setLoadingMessage() {
-        emptyContentHeadline.setText(R.string.file_list_loading);
-        emptyContentMessage.setText("");
-
-        emptyContentIcon.setVisibility(View.GONE);
-        emptyContentProgressBar.setVisibility(View.VISIBLE);
-    }
-
-    private void setEmptyContent(String headline, String message) {
-        if (emptyContentContainer != null && emptyContentMessage != null) {
-            emptyContentHeadline.setText(headline);
-            emptyContentMessage.setText(message);
-
-            emptyContentProgressBar.setVisibility(View.GONE);
-            emptyContentIcon.setVisibility(View.VISIBLE);
-        }
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-
-        setupContent();
-
-        AnalyticsUtils.setCurrentScreenName(this, SCREEN_NAME, TAG);
-    }
-
-    @Override
-    protected void onStop() {
-        if (updateTask != null) {
-            updateTask.cancel(true);
-        }
-
-        super.onStop();
-    }
-
-    @Override
-    public void onActivityClicked(RichObject richObject) {
-        String path = FileUtils.PATH_SEPARATOR + richObject.getPath();
-
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                swipeEmptyListRefreshLayout.setVisibility(View.VISIBLE);
-                swipeListRefreshLayout.setVisibility(View.GONE);
-                setLoadingMessage();
-            }
-        });
-
-        updateTask = new AsyncTask<String, Object, OCFile>() {
-            @Override
-            protected OCFile doInBackground(String... path) {
-                OCFile ocFile = null;
-
-                // always update file as it could be an old state saved in database
-                ReadRemoteFileOperation operation = new ReadRemoteFileOperation(path[0]);
-                RemoteOperationResult resultRemoteFileOp = operation.execute(ownCloudClient);
-                if (resultRemoteFileOp.isSuccess()) {
-                    OCFile temp = FileStorageUtils.fillOCFile((RemoteFile) resultRemoteFileOp.getData().get(0));
-
-                    ocFile = getStorageManager().saveFileWithParent(temp, getBaseContext());
-
-                    if (ocFile.isFolder()) {
-                        // perform folder synchronization
-                        RemoteOperation synchFolderOp = new RefreshFolderOperation(ocFile,
-                                System.currentTimeMillis(),
-                                false,
-                                getFileOperationsHelper().isSharedSupported(),
-                                true,
-                                getStorageManager(),
-                                getAccount(),
-                                getApplicationContext());
-                        synchFolderOp.execute(ownCloudClient);
-                    }
-                }
-
-                return ocFile;
-            }
-
-            @Override
-            protected void onPostExecute(OCFile ocFile) {
-                if (!isCancelled()) {
-                    if (ocFile == null) {
-                        Toast.makeText(getBaseContext(), R.string.file_not_found, Toast.LENGTH_LONG).show();
-
-                        swipeEmptyListRefreshLayout.setVisibility(View.GONE);
-                        swipeListRefreshLayout.setVisibility(View.VISIBLE);
-                        dismissLoadingDialog();
-
-                    } else {
-                        Intent showDetailsIntent;
-                        if (PreviewImageFragment.canBePreviewed(ocFile)) {
-                            showDetailsIntent = new Intent(getBaseContext(), PreviewImageActivity.class);
-                        } else {
-                            showDetailsIntent = new Intent(getBaseContext(), FileDisplayActivity.class);
-                        }
-                        showDetailsIntent.putExtra(EXTRA_FILE, ocFile);
-                        showDetailsIntent.putExtra(EXTRA_ACCOUNT, getAccount());
-                        startActivity(showDetailsIntent);
-                    }
-                }
-            }
-        };
-
-        updateTask.execute(path);
-    }
-}

+ 2 - 1
src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java

@@ -76,6 +76,7 @@ import com.owncloud.android.lib.resources.status.OwnCloudVersion;
 import com.owncloud.android.lib.resources.users.GetRemoteUserInfoOperation;
 import com.owncloud.android.operations.GetCapabilitiesOperarion;
 import com.owncloud.android.ui.TextDrawable;
+import com.owncloud.android.ui.activities.ActivitiesActivity;
 import com.owncloud.android.ui.events.AccountRemovedEvent;
 import com.owncloud.android.ui.events.ChangeMenuEvent;
 import com.owncloud.android.ui.events.DummyDrawerEvent;
@@ -443,7 +444,7 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
                 startActivity(uploadListIntent);
                 break;
             case R.id.nav_activity:
-                Intent activityIntent = new Intent(getApplicationContext(), ActivitiesListActivity.class);
+                Intent activityIntent = new Intent(getApplicationContext(), ActivitiesActivity.class);
                 activityIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                 startActivity(activityIntent);
                 break;

+ 11 - 20
src/main/res/layout/activity_list_layout.xml

@@ -18,12 +18,12 @@
   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 -->
 <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
-                                        xmlns:app="http://schemas.android.com/apk/res-auto"
-                                        android:id="@+id/drawer_layout"
-                                        android:layout_width="match_parent"
-                                        android:layout_height="match_parent"
-                                        android:clickable="true"
-                                        android:fitsSystemWindows="true">
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/drawer_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:clickable="true"
+    android:fitsSystemWindows="true">
 
     <!-- The main content view -->
     <RelativeLayout
@@ -33,7 +33,7 @@
 
         <include
             android:id="@+id/navigation_bar"
-            layout="@layout/toolbar_standard"/>
+            layout="@layout/toolbar_standard" />
 
         <FrameLayout
             android:layout_width="match_parent"
@@ -58,20 +58,11 @@
                     android:clipToPadding="false"
                     android:scrollbarStyle="outsideOverlay"
                     android:scrollbars="vertical"
-                    android:visibility="visible"/>
+                    android:visibility="visible" />
 
             </android.support.v4.widget.SwipeRefreshLayout>
 
-            <android.support.v4.widget.SwipeRefreshLayout
-                android:id="@+id/swipe_containing_empty"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:footerDividersEnabled="false"
-                android:visibility="visible">
-
-                <include layout="@layout/empty_list"/>
-            </android.support.v4.widget.SwipeRefreshLayout>
-
+            <include layout="@layout/empty_list" />
         </FrameLayout>
 
         <android.support.design.widget.BottomNavigationView
@@ -83,7 +74,7 @@
             app:itemBackground="@color/primary_button_background_color"
             app:itemIconTint="@color/primary_button_text_color"
             app:itemTextColor="@color/primary_button_text_color"
-            app:menu="@menu/navigation_bar_menu"/>
+            app:menu="@menu/navigation_bar_menu" />
 
     </RelativeLayout>
 
@@ -92,6 +83,6 @@
         layout="@layout/drawer"
         android:layout_width="@dimen/drawer_width"
         android:layout_height="match_parent"
-        android:layout_gravity="start"/>
+        android:layout_gravity="start" />
 
 </android.support.v4.widget.DrawerLayout>

+ 164 - 0
src/test/java/com/owncloud/android/ui/activities/ActivitiesPresenterTest.java

@@ -0,0 +1,164 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   Copyright (C) 2018 Edvard Holst
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.activities;
+
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.resources.activities.models.Activity;
+import com.owncloud.android.ui.activities.data.activities.ActivitiesRepository;
+import com.owncloud.android.ui.activities.data.files.FilesRepository;
+import com.owncloud.android.ui.activity.BaseActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+public class ActivitiesPresenterTest {
+
+    @Mock
+    private FilesRepository mFileRepository;
+
+    @Mock
+    private ActivitiesContract.View mView;
+
+    @Mock
+    private ActivitiesRepository mActivitiesRepository;
+
+    @Mock
+    private BaseActivity mBaseActivity;
+
+    @Mock
+    private OwnCloudClient mOwnCloudClient;
+
+    @Mock
+    private OCFile mOCFile;
+
+    @Captor
+    private ArgumentCaptor<FilesRepository.ReadRemoteFileCallback> mReadRemoteFilleCallbackCaptor;
+
+    @Captor
+    private ArgumentCaptor<ActivitiesRepository.LoadActivitiesCallback> mLoadActivitiesCallbackCaptor;
+
+    private ActivitiesPresenter mPresenter;
+
+    private List<Object> activitiesList;
+
+
+    @Before
+    public void setupActivitiesPresenter() {
+        MockitoAnnotations.initMocks(this);
+        mPresenter = new ActivitiesPresenter(mActivitiesRepository, mFileRepository, mView);
+
+        activitiesList = new ArrayList<>();
+        activitiesList.add(new Activity());
+    }
+
+    @Test
+    public void loadActivitiesFromRepositoryIntoView() {
+        // When loading activities from repository is requested from presenter...
+        mPresenter.loadActivities(null);
+        // Progress indicator is shown in view
+        verify(mView).setProgressIndicatorState(eq(true));
+        // Repository starts retrieving activities from server
+        verify(mActivitiesRepository).getActivities(eq(null), mLoadActivitiesCallbackCaptor.capture());
+        // Repository returns data
+        mLoadActivitiesCallbackCaptor.getValue().onActivitiesLoaded(activitiesList,
+                mOwnCloudClient, null);
+        // Progress indicator is hidden
+        verify(mView).setProgressIndicatorState(eq(false));
+        // List of activities is shown in view.
+        verify(mView).showActivities(eq(activitiesList), eq(mOwnCloudClient), eq(null));
+    }
+
+    @Test
+    public void loadActivitiesFromRepositoryShowError() {
+        // When loading activities from repository is requested from presenter...
+        mPresenter.loadActivities(null);
+        // Progress indicator is shown in view
+        verify(mView).setProgressIndicatorState(eq(true));
+        // Repository starts retrieving activities from server
+        verify(mActivitiesRepository).getActivities(eq(null), mLoadActivitiesCallbackCaptor.capture());
+        // Repository returns data
+        mLoadActivitiesCallbackCaptor.getValue().onActivitiesLoadedError("error");
+        // Progress indicator is hidden
+        verify(mView).setProgressIndicatorState(eq(false));
+        // Correct error is shown in view
+        verify(mView).showActivitiesLoadError(eq("error"));
+    }
+
+    @Test
+    public void loadRemoteFileFromRepositoryShowDetailUI() {
+        // When retrieving remote file from repository...
+        mPresenter.openActivity("null", mBaseActivity, true);
+        // Progress indicator is shown in view
+        verify(mView).setProgressIndicatorState(eq(true));
+        // Repository retrieves remote file
+        verify(mFileRepository).readRemoteFile(eq("null"), eq(mBaseActivity), eq(true),
+                mReadRemoteFilleCallbackCaptor.capture());
+        // Repository returns valid file object
+        mReadRemoteFilleCallbackCaptor.getValue().onFileLoaded(mOCFile);
+        // Progress indicator is hidden
+        verify(mView).setProgressIndicatorState(eq(false));
+        // File detail UI is shown
+        verify(mView).showActivityDetailUI(eq(mOCFile));
+    }
+
+    @Test
+    public void loadRemoteFileFromRepositoryShowEmptyFile() {
+        // When retrieving remote file from repository...
+        mPresenter.openActivity("null", mBaseActivity, true);
+        // Progress indicator is shown in view
+        verify(mView).setProgressIndicatorState(eq(true));
+        // Repository retrieves remote file
+        verify(mFileRepository).readRemoteFile(eq("null"), eq(mBaseActivity), eq(true),
+                mReadRemoteFilleCallbackCaptor.capture());
+        // Repository returns an valid but Null value file object.
+        mReadRemoteFilleCallbackCaptor.getValue().onFileLoaded(null);
+        // Progress indicator is hidden
+        verify(mView).setProgressIndicatorState(eq(false));
+        // Returned file is null. Inform user.
+        verify(mView).showActivityDetailUIIsNull();
+    }
+
+    @Test
+    public void loadRemoteFileFromRepositoryShowError() {
+        // When retrieving remote file from repository...
+        mPresenter.openActivity("null", mBaseActivity, true);
+        // Progress indicator is shown in view
+        verify(mView).setProgressIndicatorState(eq(true));
+        // Repository retrieves remote file
+        verify(mFileRepository).readRemoteFile(eq("null"), eq(mBaseActivity), eq(true),
+                mReadRemoteFilleCallbackCaptor.capture());
+        // Repository returns valid file object
+        mReadRemoteFilleCallbackCaptor.getValue().onFileLoadError("error");
+        // Progress indicator is hidden
+        verify(mView).setProgressIndicatorState(eq(false));
+        // Error message is shown to the user.
+        verify(mView).showActivityDetailError(eq("error"));
+    }
+}

+ 77 - 0
src/test/java/com/owncloud/android/ui/activities/data/activities/RemoteActivitiesRepositoryTest.java

@@ -0,0 +1,77 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   Copyright (C) 2018 Edvard Holst
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.activities.data.activities;
+
+import com.owncloud.android.lib.common.OwnCloudClient;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+public class RemoteActivitiesRepositoryTest {
+
+    @Mock
+    ActivitiesServiceApi serviceApi;
+
+    @Mock
+    ActivitiesRepository.LoadActivitiesCallback mockedLoadActivitiesCallback;
+
+    @Mock
+    OwnCloudClient ownCloudClient;
+
+    @Captor
+    private ArgumentCaptor<ActivitiesServiceApi.ActivitiesServiceCallback> activitiesServiceCallbackCaptor;
+
+    private ActivitiesRepository mActivitiesRepository;
+
+    private List<Object> activitiesList;
+
+    @Before
+    public void setUpActivitiesRepository() {
+        MockitoAnnotations.initMocks(this);
+        mActivitiesRepository = new RemoteActivitiesRepository(serviceApi);
+        activitiesList = new ArrayList<>();
+    }
+
+    @Test
+    public void loadActivitiesReturnSuccess() {
+        mActivitiesRepository.getActivities("null", mockedLoadActivitiesCallback);
+        verify(serviceApi).getAllActivities(eq("null"), activitiesServiceCallbackCaptor.capture());
+        activitiesServiceCallbackCaptor.getValue().onLoaded(activitiesList, ownCloudClient, "nextPageUrl");
+        verify(mockedLoadActivitiesCallback).onActivitiesLoaded(eq(activitiesList), eq(ownCloudClient), eq("nextPageUrl"));
+    }
+
+    @Test
+    public void loadActivitiesReturnError() {
+        mActivitiesRepository.getActivities("null", mockedLoadActivitiesCallback);
+        verify(serviceApi).getAllActivities(eq("null"), activitiesServiceCallbackCaptor.capture());
+        activitiesServiceCallbackCaptor.getValue().onError("error");
+        verify(mockedLoadActivitiesCallback).onActivitiesLoadedError(eq("error"));
+    }
+
+}

+ 77 - 0
src/test/java/com/owncloud/android/ui/activities/data/files/RemoteFilesRepositoryTest.java

@@ -0,0 +1,77 @@
+/**
+ *   Nextcloud Android client application
+ *
+ *   Copyright (C) 2018 Edvard Holst
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ *   License as published by the Free Software Foundation; either
+ *   version 3 of the License, or 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ *   You should have received a copy of the GNU Affero General Public
+ *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.activities.data.files;
+
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.ui.activity.BaseActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+public class RemoteFilesRepositoryTest {
+
+    @Mock
+    private FilesServiceApi serviceApi;
+
+    @Mock
+    private FilesRepository.ReadRemoteFileCallback mockedReadRemoteFileCallback;
+
+    @Mock
+    private BaseActivity baseActivity;
+
+    @Captor
+    private ArgumentCaptor<FilesServiceApi.FilesServiceCallback> filesServiceCallbackCaptor;
+
+    private FilesRepository mFilesRepository;
+
+    private OCFile mOCFile = null;
+
+    @Before
+    public void setUpFilesRepository() {
+        MockitoAnnotations.initMocks(this);
+        mFilesRepository = new RemoteFilesRepository(serviceApi);
+    }
+
+    @Test
+    public void readRemoteFileReturnSuccess() {
+        mFilesRepository.readRemoteFile("path", baseActivity, true,
+                mockedReadRemoteFileCallback);
+        verify(serviceApi).readRemoteFile(eq("path"), eq(baseActivity), eq(true),
+                filesServiceCallbackCaptor.capture());
+        filesServiceCallbackCaptor.getValue().onLoaded(mOCFile);
+        verify(mockedReadRemoteFileCallback).onFileLoaded(eq(mOCFile));
+    }
+
+    @Test
+    public void readRemoteFileReturnError() {
+        mFilesRepository.readRemoteFile("path", baseActivity, true,
+                mockedReadRemoteFileCallback);
+        verify(serviceApi).readRemoteFile(eq("path"), eq(baseActivity), eq(true),
+                filesServiceCallbackCaptor.capture());
+        filesServiceCallbackCaptor.getValue().onError("error");
+        verify(mockedReadRemoteFileCallback).onFileLoadError(eq("error"));
+    }
+}