) dbHandler.getAllTasks());
+
+ } catch (IOException | SAXException | ParserConfigurationException e) {
+ e.printStackTrace();
+ }
+
+ break;
+ }
+ }
+}
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Items/RemoteItem.java b/app/src/main/java/ca/pkay/rcloneexplorer/Items/RemoteItem.java
index f2ab72cc..76f5cbaa 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/Items/RemoteItem.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/Items/RemoteItem.java
@@ -44,6 +44,12 @@ public RemoteItem(String name, String type) {
this.type = getTypeFromString(type);
}
+ public RemoteItem(String name, int type, String typeReadable) {
+ this.name = name;
+ this.typeReadable = typeReadable;
+ this.type = type;
+ }
+
private RemoteItem(Parcel in) {
name = in.readString();
type = in.readInt();
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Items/SyncDirectionObject.java b/app/src/main/java/ca/pkay/rcloneexplorer/Items/SyncDirectionObject.java
new file mode 100644
index 00000000..432ba97f
--- /dev/null
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/Items/SyncDirectionObject.java
@@ -0,0 +1,39 @@
+package ca.pkay.rcloneexplorer.Items;
+
+import android.content.Context;
+
+import ca.pkay.rcloneexplorer.R;
+
+/**
+ * Copyright (C) 2019 Felix Nüsse
+ * Created on 22.12.19 - 14:46
+ *
+ * Edited by: Felix Nüsse felix.nuesse(at)t-online.de
+ *
+ * rcloneExplorer
+ *
+ * This program is released under the MIT license
+ *
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+public class SyncDirectionObject {
+
+ public static final int SYNC_LOCAL_TO_REMOTE = 1;
+ public static final int SYNC_REMOTE_TO_LOCAL = 2;
+ public static final int COPY_LOCAL_TO_REMOTE = 3;
+ public static final int COPY_REMOTE_TO_LOCAL = 4;
+
+
+ public static String[] getOptionsArray(Context context) {
+ return context.getResources().getStringArray(R.array.sync_direction_array);
+ }
+}
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/MainActivity.java b/app/src/main/java/ca/pkay/rcloneexplorer/MainActivity.java
index 2ea2b27b..8ab2ac36 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/MainActivity.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/MainActivity.java
@@ -35,10 +35,8 @@
import android.view.SubMenu;
import android.view.View;
import android.widget.Toast;
-
import com.crashlytics.android.Crashlytics;
import com.google.firebase.messaging.FirebaseMessaging;
-
import java.io.File;
import java.io.IOException;
import java.util.Collections;
@@ -49,6 +47,7 @@
import ca.pkay.rcloneexplorer.Dialogs.LoadingDialog;
import ca.pkay.rcloneexplorer.Fragments.FileExplorerFragment;
import ca.pkay.rcloneexplorer.Fragments.RemotesFragment;
+import ca.pkay.rcloneexplorer.Fragments.TasksFragment;
import ca.pkay.rcloneexplorer.Items.RemoteItem;
import ca.pkay.rcloneexplorer.Settings.SettingsActivity;
import es.dmoral.toasty.Toasty;
@@ -296,6 +295,9 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) {
case R.id.nav_remotes:
startRemotesFragment();
break;
+ case R.id.nav_tasks:
+ startTasksFragment();
+ break;
case R.id.nav_import:
if (rclone.isConfigFileCreated()) {
warnUserAboutOverwritingConfiguration();
@@ -359,6 +361,19 @@ private void startRemotesFragment() {
}
}
+ private void startTasksFragment() {
+ fragment = TasksFragment.newInstance();
+ FragmentManager fragmentManager = getSupportFragmentManager();
+
+ for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) {
+ fragmentManager.popBackStack();
+ }
+
+ if (!isFinishing()) {
+ fragmentManager.beginTransaction().replace(R.id.flFragment, fragment).commitAllowingStateLoss();
+ }
+ }
+
private RemoteItem getRemoteItemFromName(String remoteName) {
List remoteItemList = rclone.getRemotes();
for (RemoteItem remoteItem : remoteItemList) {
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java b/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java
index 6e4a5139..5594099b 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java
@@ -29,12 +29,10 @@
import ca.pkay.rcloneexplorer.Items.FileItem;
import ca.pkay.rcloneexplorer.Items.RemoteItem;
+import ca.pkay.rcloneexplorer.Items.SyncDirectionObject;
import es.dmoral.toasty.Toasty;
public class Rclone {
-
- public static final int SYNC_DIRECTION_LOCAL_TO_REMOTE = 1;
- public static final int SYNC_DIRECTION_REMOTE_TO_LOCAL = 2;
public static final int SERVE_PROTOCOL_HTTP = 1;
public static final int SERVE_PROTOCOL_WEBDAV = 2;
public static final int SERVE_PROTOCOL_FTP = 3;
@@ -416,11 +414,15 @@ public Process sync(RemoteItem remoteItem, String remote, String localPath, int
String localRemotePath = (remoteItem.isRemoteType(RemoteItem.LOCAL)) ? Environment.getExternalStorageDirectory().getAbsolutePath() + "/" : "";
String remotePath = (remote.compareTo("//" + remoteName) == 0) ? remoteName + ":" + localRemotePath : remoteName + ":" + localRemotePath + remote;
- if (syncDirection == 1) {
+ if (syncDirection == SyncDirectionObject.SYNC_LOCAL_TO_REMOTE) {
command = createCommandWithOptions("sync", localPath, remotePath, "--transfers", "1", "--stats=1s", "--stats-log-level", "NOTICE");
- } else if (syncDirection == 2) {
+ } else if (syncDirection == SyncDirectionObject.SYNC_REMOTE_TO_LOCAL) {
command = createCommandWithOptions("sync", remotePath, localPath, "--transfers", "1", "--stats=1s", "--stats-log-level", "NOTICE");
- } else {
+ } else if (syncDirection == SyncDirectionObject.COPY_LOCAL_TO_REMOTE) {
+ command = createCommandWithOptions("copy", localPath, remotePath, "--transfers", "1", "--stats=1s", "--stats-log-level", "NOTICE");
+ }else if (syncDirection == SyncDirectionObject.COPY_REMOTE_TO_LOCAL) {
+ command = createCommandWithOptions("copy", remotePath, localPath, "--transfers", "1", "--stats=1s", "--stats-log-level", "NOTICE");
+ }else {
return null;
}
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/RecyclerViewAdapters/TasksRecyclerViewAdapter.java b/app/src/main/java/ca/pkay/rcloneexplorer/RecyclerViewAdapters/TasksRecyclerViewAdapter.java
new file mode 100644
index 00000000..916fc83c
--- /dev/null
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/RecyclerViewAdapters/TasksRecyclerViewAdapter.java
@@ -0,0 +1,254 @@
+package ca.pkay.rcloneexplorer.RecyclerViewAdapters;
+
+
+import android.app.PendingIntent;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.PopupMenu;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import ca.pkay.rcloneexplorer.Database.DatabaseHandler;
+import ca.pkay.rcloneexplorer.Database.Task;
+import ca.pkay.rcloneexplorer.Dialogs.TaskDialog;
+import ca.pkay.rcloneexplorer.Items.RemoteItem;
+import ca.pkay.rcloneexplorer.Items.SyncDirectionObject;
+import ca.pkay.rcloneexplorer.R;
+import ca.pkay.rcloneexplorer.Services.SyncService;
+import ca.pkay.rcloneexplorer.Services.TaskStartService;
+import es.dmoral.toasty.Toasty;
+
+public class TasksRecyclerViewAdapter extends RecyclerView.Adapter{
+
+
+ private static String clipboardID="rclone_explorer_task_id";
+
+ private List tasks;
+ private View view;
+ private Context context;
+
+
+ public TasksRecyclerViewAdapter(List tasks, Context context) {
+ this.tasks = tasks;
+ this.context = context;
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_tasks_item, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) {
+ final Task selectedTask = tasks.get(position);
+ String remoteName = selectedTask.getTitle();
+
+ holder.taskName.setText(remoteName);
+
+ RemoteItem remote = new RemoteItem(selectedTask.getRemote_id(), String.valueOf(selectedTask.getRemote_type()));
+
+ holder.taskIcon.setImageDrawable(view.getResources().getDrawable(remote.getRemoteIcon()));
+
+ int direction = selectedTask.getDirection();
+
+ if(direction == SyncDirectionObject.SYNC_LOCAL_TO_REMOTE || direction == SyncDirectionObject.COPY_LOCAL_TO_REMOTE){
+ holder.fromID.setVisibility(View.GONE);
+ holder.fromPath.setText(selectedTask.getLocal_path());
+
+ holder.toID.setText(String.format("@%s", selectedTask.getRemote_id()));
+ holder.toPath.setText(selectedTask.getRemote_path());
+ }
+
+ if(direction == SyncDirectionObject.SYNC_REMOTE_TO_LOCAL || direction == SyncDirectionObject.COPY_REMOTE_TO_LOCAL){
+ holder.fromID.setText(String.format("@%s", selectedTask.getRemote_id()));
+ holder.fromPath.setText(selectedTask.getRemote_path());
+
+ holder.toID.setVisibility(View.GONE);
+ holder.toPath.setText(selectedTask.getLocal_path());
+ }
+
+ switch (direction){
+ case SyncDirectionObject.SYNC_REMOTE_TO_LOCAL: holder.taskSyncDirection.setText(view.getResources().getString(R.string.sync)); break;
+ case SyncDirectionObject.COPY_LOCAL_TO_REMOTE: holder.taskSyncDirection.setText(view.getResources().getString(R.string.copy)); break;
+ case SyncDirectionObject.COPY_REMOTE_TO_LOCAL: holder.taskSyncDirection.setText(view.getResources().getString(R.string.copy)); break;
+ default: holder.taskSyncDirection.setText(view.getResources().getString(R.string.sync)); break;
+ }
+
+ holder.fileOptions.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showFileMenu(v, selectedTask);
+ }
+ });
+
+ }
+
+ public void addTask(Task data) {
+ tasks.add(data);
+ notifyDataSetChanged();
+ }
+
+ public void setList(ArrayList data) {
+ tasks=data;
+ notifyDataSetChanged();
+ }
+
+ private void startTask(Task task){
+ String path = task.getLocal_path();
+ RemoteItem ri = new RemoteItem(task.getRemote_id(), task.getRemote_type(), "");
+ Intent intent = new Intent(context, SyncService.class);
+ intent.putExtra(SyncService.REMOTE_ARG, ri);
+ intent.putExtra(SyncService.LOCAL_PATH_ARG, path);
+ intent.putExtra(SyncService.SYNC_DIRECTION_ARG, task.getDirection());
+ intent.putExtra(SyncService.REMOTE_PATH_ARG, task.getRemote_path());
+ context.startService(intent);
+ }
+
+ private void editTask(Task task){
+ new TaskDialog(context, this, task).show();
+ }
+
+
+ public void removeItem(Task task) {
+ int index = tasks.indexOf(task);
+ if (index >= 0) {
+ tasks.remove(index);
+ notifyItemRemoved(index);
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ if (tasks == null) {
+ return 0;
+ }
+ return tasks.size();
+ }
+
+ private void showFileMenu(View view, final Task task) {
+ PopupMenu popupMenu = new PopupMenu(context, view);
+ popupMenu.getMenuInflater().inflate(R.menu.task_item_menu, popupMenu.getMenu());
+ popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_start_task:
+ startTask(task);
+ break;
+ case R.id.action_edit_task:
+ editTask(task);
+ break;
+ case R.id.action_delete_task:
+ new DatabaseHandler(context).deleteEntry(task.getId());
+ notifyDataSetChanged();
+ removeItem(task);
+ break;
+ case R.id.action_copy_id_task:
+ ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clip = ClipData.newPlainText(clipboardID, task.getId().toString());
+ clipboard.setPrimaryClip(clip);
+ Toasty.info(context, context.getResources().getString(R.string.task_copied_id_to_clipboard), Toast.LENGTH_SHORT, true).show();
+ break;
+ case R.id.action_add_to_home_screen:
+ createShortcut(context, task);
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+ });
+ popupMenu.show();
+ }
+
+ public class ViewHolder extends RecyclerView.ViewHolder {
+
+ final View view;
+ final ImageView taskIcon;
+ final TextView taskName;
+ final TextView toID;
+ final TextView fromID;
+ final TextView toPath;
+ final TextView fromPath;
+ final ImageButton fileOptions;
+ final TextView taskSyncDirection;
+
+ ViewHolder(View itemView) {
+ super(itemView);
+ this.view = itemView;
+ this.taskIcon = view.findViewById(R.id.taskIcon);
+ this.taskName = view.findViewById(R.id.taskName);
+ this.toID = view.findViewById(R.id.toID);
+ this.fromID = view.findViewById(R.id.fromID);
+ this.toPath = view.findViewById(R.id.toPath);
+ this.fromPath = view.findViewById(R.id.fromPath);
+ this.taskSyncDirection = view.findViewById(R.id.task_sync_direction);
+
+ this.fileOptions = view.findViewById(R.id.file_options);
+ }
+ }
+
+ private static void createShortcut(Context c, Task t) {
+
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ ShortcutManager shortcutManager = c.getSystemService(ShortcutManager.class);
+
+
+ Intent i = new Intent(c, TaskStartService.class);
+ i.putExtra("task", t.getId());
+ i.setAction(TaskStartService.TASK_ACTION);
+
+ ShortcutInfo shortcut = new ShortcutInfo.Builder(c, String.valueOf(t.getId()))
+ .setShortLabel(t.getTitle())
+ .setLongLabel(t.getRemote_path())
+ .setIcon(Icon.createWithResource(c, R.mipmap.ic_launcher))
+ .setIntent(i)
+ .build();
+
+ shortcutManager.setDynamicShortcuts(Arrays.asList(shortcut));
+
+ if (shortcutManager.isRequestPinShortcutSupported()) {
+ // Assumes there's already a shortcut with the ID "my-shortcut".
+ // The shortcut must be enabled.
+
+ // Create the PendingIntent object only if your app needs to be notified
+ // that the user allowed the shortcut to be pinned. Note that, if the
+ // pinning operation fails, your app isn't notified. We assume here that the
+ // app has implemented a method called createShortcutResultIntent() that
+ // returns a broadcast intent.
+ Intent pinnedShortcutCallbackIntent = shortcutManager.createShortcutResultIntent(shortcut);
+
+ // Configure the intent so that your app's broadcast receiver gets
+ // the callback successfully.For details, see PendingIntent.getBroadcast().
+ PendingIntent successCallback = PendingIntent.getBroadcast(c, 0, pinnedShortcutCallbackIntent, 0);
+
+ shortcutManager.requestPinShortcut(shortcut, successCallback.getIntentSender());
+ }
+
+ }
+ }
+
+}
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Services/FirebaseIdService.java b/app/src/main/java/ca/pkay/rcloneexplorer/Services/FirebaseIdService.java
index be81be79..81c0af7b 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/Services/FirebaseIdService.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/Services/FirebaseIdService.java
@@ -2,5 +2,5 @@
import com.google.firebase.iid.FirebaseInstanceIdService;
-public class FirebaseIdService extends FirebaseInstanceIdService {
+public class FirebaseIdService extends FirebaseInstanceIdService{
}
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Services/FirebaseMessagingService.java b/app/src/main/java/ca/pkay/rcloneexplorer/Services/FirebaseMessagingService.java
index 58eb1448..e2c792c2 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/Services/FirebaseMessagingService.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/Services/FirebaseMessagingService.java
@@ -11,7 +11,6 @@
import android.support.v4.app.NotificationManagerCompat;
import com.google.firebase.messaging.RemoteMessage;
-
import ca.pkay.rcloneexplorer.R;
public class FirebaseMessagingService extends com.google.firebase.messaging.FirebaseMessagingService {
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Services/SyncService.java b/app/src/main/java/ca/pkay/rcloneexplorer/Services/SyncService.java
index bb6c022a..9dffc5cd 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/Services/SyncService.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/Services/SyncService.java
@@ -18,7 +18,6 @@
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.content.LocalBroadcastManager;
-import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
@@ -36,11 +35,14 @@ public class SyncService extends IntentService {
public static final String REMOTE_PATH_ARG = "ca.pkay.rcexplorer.SYNC_SERVICE_REMOTE_PATH_ARG";
public static final String LOCAL_PATH_ARG = "ca.pkay.rcexplorer.SYNC_LOCAL_PATH_ARG";
public static final String SYNC_DIRECTION_ARG = "ca.pkay.rcexplorer.SYNC_DIRECTION_ARG";
+ public static final String SHOW_RESULT_NOTIFICATION = "ca.pkay.rcexplorer.SHOW_RESULT_NOTIFICATION";
private final String OPERATION_FAILED_GROUP = "ca.pkay.rcexplorer.OPERATION_FAILED_GROUP";
+ private final String OPERATION_SUCCESS_GROUP = "ca.pkay.rcexplorer.OPERATION_SUCCESS_GROUP";
private final String CHANNEL_ID = "ca.pkay.rcexplorer.sync_service";
private final String CHANNEL_NAME = "Sync service";
private final int PERSISTENT_NOTIFICATION_ID_FOR_SYNC = 162;
private final int OPERATION_FAILED_NOTIFICATION_ID = 89;
+ private final int OPERATION_SUCCESS_NOTIFICATION_ID = 698;
private final int CONNECTIVITY_CHANGE_NOTIFICATION_ID = 462;
private Rclone rclone;
private Log2File log2File;
@@ -84,6 +86,8 @@ protected void onHandleIntent(@Nullable Intent intent) {
final String localPath = intent.getStringExtra(LOCAL_PATH_ARG);
final int syncDirection = intent.getIntExtra(SYNC_DIRECTION_ARG, 1);
+ final boolean silentRun = intent.getBooleanExtra(SHOW_RESULT_NOTIFICATION, true);
+
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
Boolean isLoggingEnable = sharedPreferences.getBoolean(getString(R.string.pref_key_logs), false);
@@ -150,13 +154,19 @@ protected void onHandleIntent(@Nullable Intent intent) {
sendUploadFinishedBroadcast(remoteItem.getName(), remotePath);
- if (transferOnWiFiOnly && connectivityChanged) {
+ int notificationId = (int)System.currentTimeMillis();
+
+ if(silentRun){
+ if (transferOnWiFiOnly && connectivityChanged) {
showConnectivityChangedNotification();
} else if (currentProcess == null || currentProcess.exitValue() != 0) {
- String errorTitle = "Sync operation failed";
- int notificationId = (int)System.currentTimeMillis();
- showFailedNotification(errorTitle, title, notificationId);
+ String errorTitle = getString(R.string.notification_sync_failed);
+ showFailedNotification(errorTitle, title, notificationId);
+ }else{
+ showSuccessNotification(title, notificationId);
}
+ }
+
NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(this);
notificationManagerCompat.cancel(PERSISTENT_NOTIFICATION_ID_FOR_SYNC);
@@ -247,7 +257,7 @@ private void showFailedNotification(String title, String content, int notificati
createSummaryNotificationForFailed();
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
- .setSmallIcon(android.R.drawable.stat_sys_warning)
+ .setSmallIcon(R.drawable.ic_notification_error)
.setContentTitle(title)
.setContentText(content)
.setGroup(OPERATION_FAILED_GROUP)
@@ -258,6 +268,22 @@ private void showFailedNotification(String title, String content, int notificati
}
+ private void showSuccessNotification(String content, int notificationId) {
+ createSummaryNotificationForSuccess();
+
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_notification_success)
+ .setContentTitle(getString(R.string.operation_success))
+ .setContentText(content)
+ .setGroup(OPERATION_SUCCESS_GROUP)
+ .setPriority(NotificationCompat.PRIORITY_LOW);
+
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
+ notificationManager.notify(notificationId, builder.build());
+
+ }
+
+
private void showConnectivityChangedNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(android.R.drawable.stat_sys_warning)
@@ -275,7 +301,7 @@ private void createSummaryNotificationForFailed() {
.setContentTitle(getString(R.string.operation_failed))
//set content text to support devices running API level < 24
.setContentText(getString(R.string.operation_failed))
- .setSmallIcon(android.R.drawable.stat_sys_warning)
+ .setSmallIcon(R.drawable.ic_notification_error)
.setGroup(OPERATION_FAILED_GROUP)
.setGroupSummary(true)
.setAutoCancel(true)
@@ -285,6 +311,22 @@ private void createSummaryNotificationForFailed() {
notificationManager.notify(OPERATION_FAILED_NOTIFICATION_ID, summaryNotification);
}
+ private void createSummaryNotificationForSuccess() {
+ Notification summaryNotification =
+ new NotificationCompat.Builder(this, CHANNEL_ID)
+ .setContentTitle(getString(R.string.operation_success))
+ //set content text to support devices running API level < 24
+ .setContentText(getString(R.string.operation_success))
+ .setSmallIcon(R.drawable.ic_notification_success)
+ .setGroup(OPERATION_SUCCESS_GROUP)
+ .setGroupSummary(true)
+ .setAutoCancel(true)
+ .build();
+
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
+ notificationManager.notify(OPERATION_SUCCESS_NOTIFICATION_ID, summaryNotification);
+ }
+
private void setNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create the NotificationChannel, but only on API 26+ because
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Services/TaskStartService.java b/app/src/main/java/ca/pkay/rcloneexplorer/Services/TaskStartService.java
new file mode 100644
index 00000000..0b7f404b
--- /dev/null
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/Services/TaskStartService.java
@@ -0,0 +1,91 @@
+package ca.pkay.rcloneexplorer.Services;
+
+import android.app.IntentService;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.content.Context;
+import android.os.Build;
+import android.support.v4.app.NotificationCompat;
+import android.util.Log;
+
+import ca.pkay.rcloneexplorer.Database.DatabaseHandler;
+import ca.pkay.rcloneexplorer.Database.Task;
+import ca.pkay.rcloneexplorer.Items.RemoteItem;
+
+
+/**
+ * An {@link IntentService} subclass for handling asynchronous task requests in
+ * a service on a separate handler thread.
+ *
+ * TODO: Customize class - update intent actions, extra parameters and static
+ * helper methods.
+ */
+public class TaskStartService extends IntentService {
+
+ public static String TASK_ACTION= "START_TASK";
+ private static String EXTRA_TASK_ID= "task";
+ private static String EXTRA_TASK_SILENT= "notification";
+
+ public TaskStartService() {
+ super("TaskStartService");
+ Log.e("Service", "Start service intent!");
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ createPersistentNotification();
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ if (intent != null) {
+ final String action = intent.getAction();
+ if (action.equals(TASK_ACTION)) {
+ DatabaseHandler db = new DatabaseHandler(this);
+ for (Task task: db.getAllTasks()){
+ if(task.getId()==intent.getIntExtra(EXTRA_TASK_ID, -1)){
+ String path = task.getLocal_path();
+
+ boolean silentRun =intent.getBooleanExtra(EXTRA_TASK_SILENT, true);
+
+ RemoteItem remoteItem = new RemoteItem(task.getRemote_id(), task.getRemote_type(), "");
+ Intent taskIntent = new Intent();
+ taskIntent.setClass(this.getApplicationContext(), ca.pkay.rcloneexplorer.Services.SyncService.class);
+
+ taskIntent.putExtra(SyncService.REMOTE_ARG, remoteItem);
+ taskIntent.putExtra(SyncService.LOCAL_PATH_ARG, path);
+ taskIntent.putExtra(SyncService.SYNC_DIRECTION_ARG, task.getDirection());
+ taskIntent.putExtra(SyncService.REMOTE_PATH_ARG, task.getRemote_path());
+ taskIntent.putExtra(SyncService.SHOW_RESULT_NOTIFICATION, silentRun);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ startForegroundService(taskIntent);
+ }else {
+ startService(taskIntent);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * This can be called when an intent is recieved. If no notification is created when a service is started via startForegroundService(), the service is beeing killed by
+ * android after 5 seconds. In this case, we need to create a persistent notification because otherwise we cant start the sync task.
+ */
+ private void createPersistentNotification() {
+ if (Build.VERSION.SDK_INT >= 26) {
+ String CHANNEL_ID = "task_intent_notification";
+ NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Channel for intent notifications", NotificationManager.IMPORTANCE_DEFAULT);
+
+ ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
+
+ Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID).setContentTitle("").setContentText("").build();
+
+ startForeground(1, notification);
+ }
+ }
+
+}
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Settings/NotificationsSettingsFragment.java b/app/src/main/java/ca/pkay/rcloneexplorer/Settings/NotificationsSettingsFragment.java
index 09ef9d86..d85c8d3b 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/Settings/NotificationsSettingsFragment.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/Settings/NotificationsSettingsFragment.java
@@ -16,7 +16,6 @@
import android.widget.Toast;
import com.google.firebase.messaging.FirebaseMessaging;
-
import ca.pkay.rcloneexplorer.R;
import es.dmoral.toasty.Toasty;
@@ -171,7 +170,6 @@ private void onBetaAppUpdatesClicked(boolean isChecked) {
} else {
FirebaseMessaging.getInstance().unsubscribeFromTopic(getString(R.string.firebase_msg_beta_app_updates_topic));
}
-
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(getString(R.string.pref_key_app_updates_beta), isChecked);
diff --git a/app/src/main/res/drawable-xxhdpi/ic_notification_error.png b/app/src/main/res/drawable-xxhdpi/ic_notification_error.png
new file mode 100644
index 00000000..4efe14a4
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_notification_error.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_notification_success.png b/app/src/main/res/drawable-xxhdpi/ic_notification_success.png
new file mode 100644
index 00000000..c231de91
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_notification_success.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_notification_sync.png b/app/src/main/res/drawable-xxhdpi/ic_notification_sync.png
new file mode 100644
index 00000000..57d19b5e
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_notification_sync.png differ
diff --git a/app/src/main/res/drawable/ic_cloud_download_black_24dp.xml b/app/src/main/res/drawable/ic_cloud_download_black_24dp.xml
new file mode 100644
index 00000000..261c3121
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cloud_download_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_delete_black.xml b/app/src/main/res/drawable/ic_delete_black.xml
new file mode 100644
index 00000000..125a58f2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_delete_black.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_edit_black.xml b/app/src/main/res/drawable/ic_edit_black.xml
new file mode 100644
index 00000000..28787a15
--- /dev/null
+++ b/app/src/main/res/drawable/ic_edit_black.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/fragment_tasks.xml b/app/src/main/res/layout/fragment_tasks.xml
new file mode 100644
index 00000000..8ca9ce29
--- /dev/null
+++ b/app/src/main/res/layout/fragment_tasks.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_tasks_item.xml b/app/src/main/res/layout/fragment_tasks_item.xml
new file mode 100644
index 00000000..71832abb
--- /dev/null
+++ b/app/src/main/res/layout/fragment_tasks_item.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/task_dialog.xml b/app/src/main/res/layout/task_dialog.xml
new file mode 100644
index 00000000..eec84981
--- /dev/null
+++ b/app/src/main/res/layout/task_dialog.xml
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml
index a018a71c..18cb3d54 100644
--- a/app/src/main/res/menu/activity_main_drawer.xml
+++ b/app/src/main/res/menu/activity_main_drawer.xml
@@ -11,6 +11,10 @@
android:id="@+id/nav_remotes"
android:icon="@drawable/ic_cloud"
android:title="@string/remotes" />
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/tasks_fragment_menu.xml b/app/src/main/res/menu/tasks_fragment_menu.xml
new file mode 100644
index 00000000..5102b65f
--- /dev/null
+++ b/app/src/main/res/menu/tasks_fragment_menu.xml
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b2661bbe..305e1dcd 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -365,4 +365,32 @@
Authentication
Optional
Serve a remote
+
+ Back
+ save
+ Name
+
+ Sync local to remote
+ Sync remote to local
+ Tasks
+ Edit
+ Start Task
+ Copy TaskID
+ Copied ID to clipboard!
+ Export Tasks
+ Import Tasks
+ Unknown error importing task!
+ No File was selected
+ The file was successfully stored at:
+ Copy local to remote
+ Copy remote to local
+ Copy
+ Name
+ Remote
+ remote path
+ Local Path
+ Sync Direction
+ Sync was successful
+ Sync operation failed
+ Pin to Home
diff --git a/app/src/main/res/values/sync_direction_array.xml b/app/src/main/res/values/sync_direction_array.xml
new file mode 100644
index 00000000..70b199d4
--- /dev/null
+++ b/app/src/main/res/values/sync_direction_array.xml
@@ -0,0 +1,9 @@
+
+
+
+
- @string/sync_direction_local_remote
+ - @string/sync_direction_remote_local
+ - @string/sync_direction_copy_local_remote
+ - @string/sync_direction_copy_remote_local
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index c4460508..3ec84c71 100644
--- a/build.gradle
+++ b/build.gradle
@@ -12,7 +12,6 @@ buildscript {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'com.google.gms:google-services:4.0.1' // google-services plugin
classpath 'io.fabric.tools:gradle:1.25.4'
-
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files