diff --git a/README.md b/README.md
index 86de1e83..ee0bd359 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,11 @@
[![License: MPLv2](https://img.shields.io/badge/License-MPLv2-blue.svg)](https://opensource.org/licenses/MPL-2.0)
-
+
# Major enhancements in this fork are:
- Individual sync conditions can be applied per device and per folder (for expert users).
+- Recent changes UI.
- UI explains why syncthing is running or not running according to the run conditions set in preferences.
- "Battery eater" problem is fixed.
- Android 8 and 9 support.
diff --git a/app/build.gradle b/app/build.gradle
index 3dc3e4ad..4f1762fe 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -10,7 +10,7 @@ dependencies {
implementation 'com.google.code.gson:gson:2.8.2'
implementation 'org.mindrot:jbcrypt:0.4'
// com.google.guava:guava:24.1-jre will crash on Android 5.x
- implementation 'com.google.guava:guava:23.6-android'
+ implementation 'com.google.guava:guava:26.0-android'
implementation 'com.annimon:stream:1.1.9'
implementation 'com.android.volley:volley:1.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
@@ -37,8 +37,8 @@ android {
applicationId "com.github.catfriend1.syncthingandroid"
minSdkVersion 16
targetSdkVersion 26
- versionCode 4174
- versionName "0.14.51.11"
+ versionCode 4175
+ versionName "0.14.51.12"
testApplicationId 'com.github.catfriend1.syncthingandroid.test'
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
playAccountConfig = playAccountConfigs.defaultAccountConfig
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1c9c78c5..bf5db0ac 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -46,6 +46,15 @@
android:label="@string/app_name"
android:launchMode="singleTask">
+
+
+
mDevices;
+ private ChangeListAdapter mRecentChangeAdapter;
+ private RecyclerView mRecyclerView;
+ private RecyclerView.LayoutManager mLayoutManager;
+ private SyncthingService.State mServiceState = SyncthingService.State.INIT;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_recent_changes);
+ mRecyclerView = findViewById(R.id.changes_recycler_view);
+ mRecyclerView.setHasFixedSize(true);
+ mLayoutManager = new LinearLayoutManager(this);
+ mRecyclerView.setLayoutManager(mLayoutManager);
+ mRecentChangeAdapter = new ChangeListAdapter(this);
+
+ // Set onClick listener and add adapter to recycler view.
+ mRecentChangeAdapter.setOnClickListener(
+ new ItemClickListener() {
+ @Override
+ public void onItemClick(DiskEvent diskEvent) {
+ Log.v(TAG, "User clicked item with title \'" + diskEvent.data.path + "\'");
+ /**
+ * Future improvement:
+ * Collapse texts to the first three lines and open a DialogFragment
+ * if the user clicks an item from the list.
+ */
+ }
+ }
+ );
+ mRecyclerView.setAdapter(mRecentChangeAdapter);
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+ super.onServiceConnected(componentName, iBinder);
+ SyncthingServiceBinder syncthingServiceBinder = (SyncthingServiceBinder) iBinder;
+ syncthingServiceBinder.getService().registerOnServiceStateChangeListener(this);
+ }
+
+ @Override
+ public void onServiceStateChange(SyncthingService.State newState) {
+ Log.v(TAG, "onServiceStateChange(" + newState + ")");
+ mServiceState = newState;
+ if (newState == SyncthingService.State.ACTIVE) {
+ onTimerEvent();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ SyncthingService syncthingService = getService();
+ if (syncthingService != null) {
+ syncthingService.unregisterOnServiceStateChangeListener(this);
+ }
+ super.onDestroy();
+ }
+
+ private void onTimerEvent() {
+ if (isFinishing()) {
+ return;
+ }
+ if (mServiceState != SyncthingService.State.ACTIVE) {
+ return;
+ }
+ SyncthingService syncthingService = getService();
+ if (syncthingService == null) {
+ Log.e(TAG, "syncthingService == null");
+ return;
+ }
+ RestApi restApi = syncthingService.getApi();
+ if (restApi == null) {
+ Log.e(TAG, "restApi == null");
+ return;
+ }
+ mDevices = restApi.getDevices(true);
+ Log.v(TAG, "Querying disk events");
+ restApi.getDiskEvents(DISK_EVENT_LIMIT, this::onReceiveDiskEvents);
+ }
+
+ private void onReceiveDiskEvents(List diskEvents) {
+ Log.v(TAG, "onReceiveDiskEvents");
+ if (isFinishing()) {
+ return;
+ }
+
+ mRecentChangeAdapter.clear();
+ for (DiskEvent diskEvent : diskEvents) {
+ if (diskEvent.data != null) {
+ // Replace "modifiedBy" partial device ID by readable device name.
+ if (!TextUtils.isEmpty(diskEvent.data.modifiedBy)) {
+ for (Device device : mDevices) {
+ if (diskEvent.data.modifiedBy.equals(device.deviceID.substring(0, diskEvent.data.modifiedBy.length()))) {
+ diskEvent.data.modifiedBy = device.getDisplayName();
+ break;
+ }
+ }
+ }
+ mRecentChangeAdapter.add(diskEvent);
+ }
+ }
+ mRecentChangeAdapter.notifyDataSetChanged();
+ }
+}
diff --git a/app/src/main/java/com/nutomic/syncthingandroid/fragments/DrawerFragment.java b/app/src/main/java/com/nutomic/syncthingandroid/fragments/DrawerFragment.java
index 63ef1aed..271de321 100644
--- a/app/src/main/java/com/nutomic/syncthingandroid/fragments/DrawerFragment.java
+++ b/app/src/main/java/com/nutomic/syncthingandroid/fragments/DrawerFragment.java
@@ -17,6 +17,7 @@ import android.widget.Toast;
import com.google.common.collect.ImmutableMap;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.activities.MainActivity;
+import com.nutomic.syncthingandroid.activities.RecentChangesActivity;
import com.nutomic.syncthingandroid.activities.SettingsActivity;
import com.nutomic.syncthingandroid.activities.TipsAndTricksActivity;
import com.nutomic.syncthingandroid.activities.WebGuiActivity;
@@ -44,6 +45,7 @@ public class DrawerFragment extends Fragment implements SyncthingService.OnServi
*/
private TextView mVersion = null;
private TextView mDrawerActionShowQrCode;
+ private TextView mDrawerRecentChanges;
private TextView mDrawerActionWebGui;
private TextView mDrawerActionImportExport;
private TextView mDrawerActionRestart;
@@ -91,6 +93,7 @@ public class DrawerFragment extends Fragment implements SyncthingService.OnServi
mVersion = view.findViewById(R.id.version);
mDrawerActionShowQrCode = view.findViewById(R.id.drawerActionShowQrCode);
+ mDrawerRecentChanges = view.findViewById(R.id.drawerActionRecentChanges);
mDrawerActionWebGui = view.findViewById(R.id.drawerActionWebGui);
mDrawerActionImportExport = view.findViewById(R.id.drawerActionImportExport);
mDrawerActionRestart = view.findViewById(R.id.drawerActionRestart);
@@ -100,6 +103,7 @@ public class DrawerFragment extends Fragment implements SyncthingService.OnServi
// Add listeners to buttons.
mDrawerActionShowQrCode.setOnClickListener(this);
+ mDrawerRecentChanges.setOnClickListener(this);
mDrawerActionWebGui.setOnClickListener(this);
mDrawerActionImportExport.setOnClickListener(this);
mDrawerActionRestart.setOnClickListener(this);
@@ -134,6 +138,7 @@ public class DrawerFragment extends Fragment implements SyncthingService.OnServi
// Show buttons if syncthing is running.
mVersion.setVisibility(synthingRunning ? View.VISIBLE : View.GONE);
mDrawerActionShowQrCode.setVisibility(synthingRunning ? View.VISIBLE : View.GONE);
+ mDrawerRecentChanges.setVisibility(synthingRunning ? View.VISIBLE : View.GONE);
mDrawerActionWebGui.setVisibility(synthingRunning ? View.VISIBLE : View.GONE);
mDrawerActionRestart.setVisibility(synthingRunning ? View.VISIBLE : View.GONE);
mDrawerTipsAndTricks.setVisibility(View.VISIBLE);
@@ -171,6 +176,10 @@ public class DrawerFragment extends Fragment implements SyncthingService.OnServi
case R.id.drawerActionShowQrCode:
showQrCode();
break;
+ case R.id.drawerActionRecentChanges:
+ startActivity(new Intent(mActivity, RecentChangesActivity.class));
+ mActivity.closeDrawer();
+ break;
case R.id.drawerActionWebGui:
startActivity(new Intent(mActivity, WebGuiActivity.class));
mActivity.closeDrawer();
diff --git a/app/src/main/java/com/nutomic/syncthingandroid/http/GetRequest.java b/app/src/main/java/com/nutomic/syncthingandroid/http/GetRequest.java
index f1d3463b..64d7498d 100644
--- a/app/src/main/java/com/nutomic/syncthingandroid/http/GetRequest.java
+++ b/app/src/main/java/com/nutomic/syncthingandroid/http/GetRequest.java
@@ -26,6 +26,7 @@ public class GetRequest extends ApiRequest {
public static final String URI_DEVICEID = "/rest/svc/deviceid";
public static final String URI_REPORT = "/rest/svc/report";
public static final String URI_EVENTS = "/rest/events";
+ public static final String URI_EVENTS_DISK = "/rest/events/disk";
public GetRequest(Context context, URL url, String path, String apiKey,
@Nullable Map params, OnSuccessListener listener) {
diff --git a/app/src/main/java/com/nutomic/syncthingandroid/model/DiskEvent.java b/app/src/main/java/com/nutomic/syncthingandroid/model/DiskEvent.java
new file mode 100644
index 00000000..01ff74da
--- /dev/null
+++ b/app/src/main/java/com/nutomic/syncthingandroid/model/DiskEvent.java
@@ -0,0 +1,15 @@
+package com.nutomic.syncthingandroid.model;
+
+/**
+ * REST API endpoint "/rest/events/disk"
+ */
+public class DiskEvent {
+ public long id = 0;
+ public long globalID = 0;
+ public String time = "";
+
+ // type = {"LocalChangeDetected", "RemoteChangeDetected"}
+ public String type = "";
+
+ public DiskEventData data;
+}
diff --git a/app/src/main/java/com/nutomic/syncthingandroid/model/DiskEventData.java b/app/src/main/java/com/nutomic/syncthingandroid/model/DiskEventData.java
new file mode 100644
index 00000000..bf4f5c73
--- /dev/null
+++ b/app/src/main/java/com/nutomic/syncthingandroid/model/DiskEventData.java
@@ -0,0 +1,18 @@
+package com.nutomic.syncthingandroid.model;
+
+/**
+ * REST API endpoint "/rest/events/disk"
+ */
+public class DiskEventData {
+ // action = {"added", "deleted", "modified"}
+ public String action = "";
+
+ public String folder = "";
+ public String folderID = "";
+ public String label = "";
+ public String modifiedBy = "";
+ public String path = "";
+
+ // type = {"file", "dir"}
+ public String type = "";
+}
diff --git a/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java b/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java
index 74fdecfc..bd47db1f 100644
--- a/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java
+++ b/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java
@@ -27,6 +27,7 @@ import com.nutomic.syncthingandroid.model.Completion;
import com.nutomic.syncthingandroid.model.CompletionInfo;
import com.nutomic.syncthingandroid.model.Connections;
import com.nutomic.syncthingandroid.model.Device;
+import com.nutomic.syncthingandroid.model.DiskEvent;
import com.nutomic.syncthingandroid.model.Event;
import com.nutomic.syncthingandroid.model.Folder;
import com.nutomic.syncthingandroid.model.FolderIgnoreList;
@@ -42,6 +43,7 @@ import com.nutomic.syncthingandroid.service.Constants;
import java.lang.reflect.Type;
import java.net.URL;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -671,6 +673,30 @@ public class RestApi {
});
}
+ /**
+ * Requests and parses information about recent changes.
+ */
+ public void getDiskEvents(int limit, OnResultListener1> listener) {
+ new GetRequest(
+ mContext, mUrl,
+ GetRequest.URI_EVENTS_DISK, mApiKey,
+ ImmutableMap.of("limit", Integer.toString(limit)),
+ result -> {
+ List diskEvents = new ArrayList<>();
+ try {
+ JsonArray jsonDiskEvents = new JsonParser().parse(result).getAsJsonArray();
+ for (int i = jsonDiskEvents.size()-1; i >= 0; i--) {
+ JsonElement jsonDiskEvent = jsonDiskEvents.get(i);
+ diskEvents.add(new Gson().fromJson(jsonDiskEvent, DiskEvent.class));
+ }
+ listener.onResult(diskEvents);
+ } catch (Exception e) {
+ Log.e(TAG, "getDiskEvents: Parsing REST API result failed. result=" + result);
+ }
+ }
+ );
+ }
+
/**
* Listener for {@link #getEvents}.
*/
diff --git a/app/src/main/java/com/nutomic/syncthingandroid/views/ChangeListAdapter.java b/app/src/main/java/com/nutomic/syncthingandroid/views/ChangeListAdapter.java
new file mode 100644
index 00000000..239e5444
--- /dev/null
+++ b/app/src/main/java/com/nutomic/syncthingandroid/views/ChangeListAdapter.java
@@ -0,0 +1,177 @@
+package com.nutomic.syncthingandroid.views;
+
+import android.annotation.TargetApi;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v7.widget.RecyclerView;
+import android.net.Uri;
+import android.os.Build;
+// import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.nutomic.syncthingandroid.R;
+import com.nutomic.syncthingandroid.model.DiskEvent;
+
+import java.io.File;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
+import java.time.ZonedDateTime;
+import java.time.ZoneId;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+public class ChangeListAdapter extends RecyclerView.Adapter {
+
+ // private static final String TAG = "ChangeListAdapter";
+
+ private final Context mContext;
+ private final Resources mResources;
+ private ArrayList mChangeData = new ArrayList();
+ private ItemClickListener mOnClickListener;
+ private LayoutInflater mLayoutInflater;
+
+ public interface ItemClickListener {
+ void onItemClick(DiskEvent diskEvent);
+ }
+
+ public ChangeListAdapter(Context context) {
+ mContext = context;
+ mResources = mContext.getResources();
+ mLayoutInflater = LayoutInflater.from(mContext);
+ }
+
+ public void clear() {
+ mChangeData.clear();
+ }
+
+ public void add(DiskEvent diskEvent) {
+ mChangeData.add(diskEvent);
+ }
+
+ public void setOnClickListener(ItemClickListener onClickListener) {
+ mOnClickListener = onClickListener;
+ }
+
+ public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
+ public ImageView typeIcon;
+ public TextView filename;
+ public TextView folderPath;
+ public TextView modifiedByDevice;
+ public TextView dateTime;
+ public View layout;
+
+ public ViewHolder(View view) {
+ super(view);
+ typeIcon = view.findViewById(R.id.typeIcon);
+ filename = view.findViewById(R.id.filename);
+ folderPath = view.findViewById(R.id.folderPath);
+ modifiedByDevice = view.findViewById(R.id.modifiedByDevice);
+ dateTime = view.findViewById(R.id.dateTime);
+ view.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View view) {
+ int position = getAdapterPosition();
+ DiskEvent diskEvent = mChangeData.get(position);
+ if (mOnClickListener != null) {
+ mOnClickListener.onItemClick(diskEvent);
+ }
+ }
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = mLayoutInflater.inflate(R.layout.item_recent_change, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @SuppressLint("SetTextI18n")
+ @Override
+ public void onBindViewHolder(ViewHolder viewHolder, final int position) {
+ DiskEvent diskEvent = mChangeData.get(position);
+
+ // Separate path and filename.
+ Uri uri = Uri.parse(diskEvent.data.path);
+ String filename = uri.getLastPathSegment();
+ String path = getPathFromFullFN(diskEvent.data.path);
+
+ // Decide which icon to show.
+ int drawableId = R.drawable.ic_help_outline_black_24dp;
+ switch (diskEvent.data.type) {
+ case "dir":
+ switch (diskEvent.data.action) {
+ case "added":
+ drawableId = R.drawable.ic_folder_add_black_24dp;
+ break;
+ case "deleted":
+ drawableId = R.drawable.ic_folder_delete_black_24dp;
+ break;
+ case "modified":
+ drawableId = R.drawable.ic_folder_edit_black_24dp;
+ break;
+ default:
+ }
+ break;
+ case "file":
+ switch (diskEvent.data.action) {
+ case "added":
+ drawableId = R.drawable.ic_file_add_black_24dp;
+ break;
+ case "deleted":
+ drawableId = R.drawable.ic_file_remove_black_24dp;
+ break;
+ case "modified":
+ drawableId = R.drawable.ic_file_edit_black_24dp;
+ break;
+ default:
+ }
+ break;
+ default:
+ }
+ viewHolder.typeIcon.setImageResource(drawableId);
+
+ // Fill text views.
+ viewHolder.filename.setText(filename);
+ viewHolder.folderPath.setText(diskEvent.data.label + File.separator + path);
+ viewHolder.modifiedByDevice.setText(mResources.getString(R.string.modified_by_device, diskEvent.data.modifiedBy));
+
+ // Convert dateTime to readable localized string.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ viewHolder.dateTime.setText(mResources.getString(R.string.modification_time, diskEvent.time));
+ } else {
+ viewHolder.dateTime.setText(mResources.getString(R.string.modification_time, formatDateTime(diskEvent.time)));
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mChangeData.size();
+ }
+
+ /**
+ * Converts dateTime to readable localized string.
+ */
+ @TargetApi(26)
+ private String formatDateTime(String dateTime) {
+ ZonedDateTime parsedDateTime = ZonedDateTime.parse(dateTime);
+ ZonedDateTime zonedDateTime = parsedDateTime.withZoneSameInstant(ZoneId.systemDefault());
+ DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault());
+ return formatter.format(zonedDateTime);
+ }
+
+ private String getPathFromFullFN(String fullFN) {
+ int index = fullFN.lastIndexOf('/');
+ if (index > 0) {
+ return fullFN.substring(0, index);
+ }
+ return "";
+ }
+}
diff --git a/app/src/main/play/en-GB/whatsnew b/app/src/main/play/en-GB/whatsnew
index c16c41ab..e402b2ba 100644
--- a/app/src/main/play/en-GB/whatsnew
+++ b/app/src/main/play/en-GB/whatsnew
@@ -1,4 +1,5 @@
Enhancements
+* Added "Recent changes" UI [NEW]
* Specify sync conditions differently for each folder, device [NEW]
* Added offline 'tips & tricks' content [NEW]
* UI explains why syncthing is running (or not)
@@ -6,6 +7,5 @@ Enhancements
Fixes
* Fixed the "battery eater"
* Android 8 and 9 support
-* Fixed phone plugged to charger detection
Maintenance
* Updated syncthing to v0.14.51 (receiveOnly folders)
diff --git a/app/src/main/res/drawable-hdpi/ic_file_add_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_file_add_black_24dp.png
new file mode 100644
index 00000000..25d1e5ac
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_file_add_black_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_file_edit_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_file_edit_black_24dp.png
new file mode 100644
index 00000000..3bfaa9e2
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_file_edit_black_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_file_remove_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_file_remove_black_24dp.png
new file mode 100644
index 00000000..d288235e
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_file_remove_black_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_folder_add_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_folder_add_black_24dp.png
new file mode 100644
index 00000000..d60b84ff
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_folder_add_black_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_folder_delete_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_folder_delete_black_24dp.png
new file mode 100644
index 00000000..f63ef914
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_folder_delete_black_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_folder_edit_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_folder_edit_black_24dp.png
new file mode 100644
index 00000000..0cd74ce0
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_folder_edit_black_24dp.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_file_add_black_24dp.png b/app/src/main/res/drawable-ldpi/ic_file_add_black_24dp.png
new file mode 100644
index 00000000..8d6377ea
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_file_add_black_24dp.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_file_edit_black_24dp.png b/app/src/main/res/drawable-ldpi/ic_file_edit_black_24dp.png
new file mode 100644
index 00000000..c1b706e7
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_file_edit_black_24dp.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_file_remove_black_24dp.png b/app/src/main/res/drawable-ldpi/ic_file_remove_black_24dp.png
new file mode 100644
index 00000000..b06f3a28
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_file_remove_black_24dp.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_folder_add_black_24dp.png b/app/src/main/res/drawable-ldpi/ic_folder_add_black_24dp.png
new file mode 100644
index 00000000..5684c383
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_folder_add_black_24dp.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_folder_delete_black_24dp.png b/app/src/main/res/drawable-ldpi/ic_folder_delete_black_24dp.png
new file mode 100644
index 00000000..438f3e1d
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_folder_delete_black_24dp.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_folder_edit_black_24dp.png b/app/src/main/res/drawable-ldpi/ic_folder_edit_black_24dp.png
new file mode 100644
index 00000000..abea0c4a
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_folder_edit_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_file_add_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_file_add_black_24dp.png
new file mode 100644
index 00000000..3cb286f9
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_file_add_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_file_edit_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_file_edit_black_24dp.png
new file mode 100644
index 00000000..663e19a6
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_file_edit_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_file_remove_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_file_remove_black_24dp.png
new file mode 100644
index 00000000..c6f0c972
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_file_remove_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_folder_add_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_folder_add_black_24dp.png
new file mode 100644
index 00000000..860df71c
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_folder_add_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_folder_delete_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_folder_delete_black_24dp.png
new file mode 100644
index 00000000..28647286
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_folder_delete_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_folder_edit_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_folder_edit_black_24dp.png
new file mode 100644
index 00000000..14f1e65f
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_folder_edit_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_file_add_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_file_add_black_24dp.png
new file mode 100644
index 00000000..282b4455
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_file_add_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_file_edit_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_file_edit_black_24dp.png
new file mode 100644
index 00000000..95a30c0f
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_file_edit_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_file_remove_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_file_remove_black_24dp.png
new file mode 100644
index 00000000..debc48ea
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_file_remove_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_folder_add_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_folder_add_black_24dp.png
new file mode 100644
index 00000000..6277acb1
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_folder_add_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_folder_delete_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_folder_delete_black_24dp.png
new file mode 100644
index 00000000..197d29db
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_folder_delete_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_folder_edit_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_folder_edit_black_24dp.png
new file mode 100644
index 00000000..b6fa419e
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_folder_edit_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_file_add_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_file_add_black_24dp.png
new file mode 100644
index 00000000..2129a823
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_file_add_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_file_edit_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_file_edit_black_24dp.png
new file mode 100644
index 00000000..3bd5cc77
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_file_edit_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_file_remove_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_file_remove_black_24dp.png
new file mode 100644
index 00000000..6b5533b8
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_file_remove_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_folder_add_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_folder_add_black_24dp.png
new file mode 100644
index 00000000..e2a5345d
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_folder_add_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_folder_delete_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_folder_delete_black_24dp.png
new file mode 100644
index 00000000..eb75193d
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_folder_delete_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_folder_edit_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_folder_edit_black_24dp.png
new file mode 100644
index 00000000..1bd5b004
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_folder_edit_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_file_add_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_file_add_black_24dp.png
new file mode 100644
index 00000000..60f3f7a4
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_file_add_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_file_edit_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_file_edit_black_24dp.png
new file mode 100644
index 00000000..32bca46e
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_file_edit_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_file_remove_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_file_remove_black_24dp.png
new file mode 100644
index 00000000..efe176db
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_file_remove_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_folder_add_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_folder_add_black_24dp.png
new file mode 100644
index 00000000..0a788e15
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_folder_add_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_folder_delete_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_folder_delete_black_24dp.png
new file mode 100644
index 00000000..e4225138
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_folder_delete_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_folder_edit_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_folder_edit_black_24dp.png
new file mode 100644
index 00000000..2c85a310
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_folder_edit_black_24dp.png differ
diff --git a/app/src/main/res/layout/activity_recent_changes.xml b/app/src/main/res/layout/activity_recent_changes.xml
new file mode 100644
index 00000000..b4a72835
--- /dev/null
+++ b/app/src/main/res/layout/activity_recent_changes.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_drawer.xml b/app/src/main/res/layout/fragment_drawer.xml
index ce6f2d5b..da222448 100644
--- a/app/src/main/res/layout/fragment_drawer.xml
+++ b/app/src/main/res/layout/fragment_drawer.xml
@@ -103,6 +103,17 @@
android:focusable="true"
android:text="@string/show_device_id" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 48e1e30d..04db8b41 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -306,6 +306,13 @@ Bitte melden Sie auftretende Probleme via GitHub.
Falls der Computer das Huawei-Gerät permanent als \"getrennt\" meldet, öffne die Syncthing-Oberfläche auf dem Computer. Gehe zu \"Externe Geräte\", klappe den Eintrag des Mobilgeräts aus, klicke \"Bearbeiten\", wechsle zu \"Erweitert\". Trage die IP-Adresse deines Mobilgeräts in \"Adressen\" wie folgt ein:\ntcp4://%1$s, dynamic\nFunktioniert sicher auf: Huawei P10
TELEFON_IP_ADRESSE
+
+
+ Letzte Änderungen
+ Gerät: %1$s
+ Zeit: %1$s
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 40538b07..221be274 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -306,6 +306,13 @@ Please report any problems you encounter via Github.
If your desktop constantly reports your Huawei device as disconnected, open Syncthing UI of your desktop. Go to \'remote devices\', expand the phone\'s entry, click \'Edit\', switch to \'Advanced\'. Put your phone IP address into \'Addresses\' like this:\ntcp4://%1$s, dynamic\nConfirmed working for: Huawei P10
PHONE_IP_ADDRESS
+
+
+ Recent changes
+ Device: %1$s
+ Time: %1$s
+
+