* Add "recent changes" UI to drawer * Update build.gradle - guava 26.0-android * Add REST endpoint * Add DiskEvent to model * Add DiskEventData to model * Add RestApi#getDiskEvents * Add ChangeListAdapter#clear * Implement data exchange between UI and service * Display DiskEvents * Add icons * Return DiskEvents in reverse order * Display device name instead of partial ID * Format dateTime * Update whatsnew * Imported translations * Update APK version to 0.14.51.12 / 4175 * Fix lint * Review * Update README.md
|
@ -2,10 +2,11 @@
|
|||
|
||||
[![License: MPLv2](https://img.shields.io/badge/License-MPLv2-blue.svg)](https://opensource.org/licenses/MPL-2.0)
|
||||
<a href="https://github.com/Catfriend1/syncthing-android/releases" alt="GitHub release"><img src="https://img.shields.io/github/release/Catfriend1/syncthing-android/all.svg" /></a>
|
||||
<a href="https://f-droid.org/de/packages/com.github.catfriend1.syncthingandroid" alt="F-Droid release"><img src="https://img.shields.io/badge/f--droid-4170-brightgreen.svg" /></a>
|
||||
<a href="https://f-droid.org/de/packages/com.github.catfriend1.syncthingandroid" alt="F-Droid release"><img src="https://img.shields.io/badge/f--droid-4173-brightgreen.svg" /></a>
|
||||
|
||||
# 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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -46,6 +46,15 @@
|
|||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask">
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activities.RecentChangesActivity"
|
||||
android:label="@string/recent_changes_title"
|
||||
android:parentActivityName=".activities.MainActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".activities.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activities.TipsAndTricksActivity"
|
||||
android:label="@string/tips_and_tricks_title"
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
package com.nutomic.syncthingandroid.activities;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.nutomic.syncthingandroid.R;
|
||||
import com.nutomic.syncthingandroid.model.Device;
|
||||
import com.nutomic.syncthingandroid.model.DiskEvent;
|
||||
import com.nutomic.syncthingandroid.service.RestApi;
|
||||
import com.nutomic.syncthingandroid.service.SyncthingService;
|
||||
import com.nutomic.syncthingandroid.service.SyncthingServiceBinder;
|
||||
import com.nutomic.syncthingandroid.views.ChangeListAdapter;
|
||||
import com.nutomic.syncthingandroid.views.ChangeListAdapter.ItemClickListener;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holds a RecyclerView that shows recent changes to files and folders.
|
||||
*/
|
||||
public class RecentChangesActivity extends SyncthingActivity
|
||||
implements SyncthingService.OnServiceStateChangeListener {
|
||||
|
||||
private static final String TAG = "RecentChangesActivity";
|
||||
|
||||
private static int DISK_EVENT_LIMIT = 100;
|
||||
|
||||
private List<Device> 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<DiskEvent> 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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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<String, String> params, OnSuccessListener listener) {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 = "";
|
||||
}
|
|
@ -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<List<DiskEvent>> listener) {
|
||||
new GetRequest(
|
||||
mContext, mUrl,
|
||||
GetRequest.URI_EVENTS_DISK, mApiKey,
|
||||
ImmutableMap.of("limit", Integer.toString(limit)),
|
||||
result -> {
|
||||
List<DiskEvent> 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}.
|
||||
*/
|
||||
|
|
|
@ -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<ChangeListAdapter.ViewHolder> {
|
||||
|
||||
// private static final String TAG = "ChangeListAdapter";
|
||||
|
||||
private final Context mContext;
|
||||
private final Resources mResources;
|
||||
private ArrayList<DiskEvent> mChangeData = new ArrayList<DiskEvent>();
|
||||
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 "";
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
BIN
app/src/main/res/drawable-hdpi/ic_file_add_black_24dp.png
Normal file
After Width: | Height: | Size: 663 B |
BIN
app/src/main/res/drawable-hdpi/ic_file_edit_black_24dp.png
Normal file
After Width: | Height: | Size: 632 B |
BIN
app/src/main/res/drawable-hdpi/ic_file_remove_black_24dp.png
Normal file
After Width: | Height: | Size: 613 B |
BIN
app/src/main/res/drawable-hdpi/ic_folder_add_black_24dp.png
Normal file
After Width: | Height: | Size: 377 B |
BIN
app/src/main/res/drawable-hdpi/ic_folder_delete_black_24dp.png
Normal file
After Width: | Height: | Size: 325 B |
BIN
app/src/main/res/drawable-hdpi/ic_folder_edit_black_24dp.png
Normal file
After Width: | Height: | Size: 418 B |
BIN
app/src/main/res/drawable-ldpi/ic_file_add_black_24dp.png
Normal file
After Width: | Height: | Size: 405 B |
BIN
app/src/main/res/drawable-ldpi/ic_file_edit_black_24dp.png
Normal file
After Width: | Height: | Size: 383 B |
BIN
app/src/main/res/drawable-ldpi/ic_file_remove_black_24dp.png
Normal file
After Width: | Height: | Size: 396 B |
BIN
app/src/main/res/drawable-ldpi/ic_folder_add_black_24dp.png
Normal file
After Width: | Height: | Size: 282 B |
BIN
app/src/main/res/drawable-ldpi/ic_folder_delete_black_24dp.png
Normal file
After Width: | Height: | Size: 262 B |
BIN
app/src/main/res/drawable-ldpi/ic_folder_edit_black_24dp.png
Normal file
After Width: | Height: | Size: 268 B |
BIN
app/src/main/res/drawable-mdpi/ic_file_add_black_24dp.png
Normal file
After Width: | Height: | Size: 462 B |
BIN
app/src/main/res/drawable-mdpi/ic_file_edit_black_24dp.png
Normal file
After Width: | Height: | Size: 437 B |
BIN
app/src/main/res/drawable-mdpi/ic_file_remove_black_24dp.png
Normal file
After Width: | Height: | Size: 442 B |
BIN
app/src/main/res/drawable-mdpi/ic_folder_add_black_24dp.png
Normal file
After Width: | Height: | Size: 253 B |
BIN
app/src/main/res/drawable-mdpi/ic_folder_delete_black_24dp.png
Normal file
After Width: | Height: | Size: 234 B |
BIN
app/src/main/res/drawable-mdpi/ic_folder_edit_black_24dp.png
Normal file
After Width: | Height: | Size: 269 B |
BIN
app/src/main/res/drawable-xhdpi/ic_file_add_black_24dp.png
Normal file
After Width: | Height: | Size: 651 B |
BIN
app/src/main/res/drawable-xhdpi/ic_file_edit_black_24dp.png
Normal file
After Width: | Height: | Size: 623 B |
BIN
app/src/main/res/drawable-xhdpi/ic_file_remove_black_24dp.png
Normal file
After Width: | Height: | Size: 609 B |
BIN
app/src/main/res/drawable-xhdpi/ic_folder_add_black_24dp.png
Normal file
After Width: | Height: | Size: 382 B |
BIN
app/src/main/res/drawable-xhdpi/ic_folder_delete_black_24dp.png
Normal file
After Width: | Height: | Size: 364 B |
BIN
app/src/main/res/drawable-xhdpi/ic_folder_edit_black_24dp.png
Normal file
After Width: | Height: | Size: 441 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_file_add_black_24dp.png
Normal file
After Width: | Height: | Size: 1,005 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_file_edit_black_24dp.png
Normal file
After Width: | Height: | Size: 1,012 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_file_remove_black_24dp.png
Normal file
After Width: | Height: | Size: 953 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_folder_add_black_24dp.png
Normal file
After Width: | Height: | Size: 637 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_folder_delete_black_24dp.png
Normal file
After Width: | Height: | Size: 586 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_folder_edit_black_24dp.png
Normal file
After Width: | Height: | Size: 714 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_file_add_black_24dp.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_file_edit_black_24dp.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_file_remove_black_24dp.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_folder_add_black_24dp.png
Normal file
After Width: | Height: | Size: 864 B |
After Width: | Height: | Size: 843 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_folder_edit_black_24dp.png
Normal file
After Width: | Height: | Size: 908 B |
15
app/src/main/res/layout/activity_recent_changes.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include layout="@layout/widget_toolbar" />
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/changes_recycler_view"
|
||||
android:paddingTop="?attr/actionBarSize"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
</RelativeLayout>
|
|
@ -103,6 +103,17 @@
|
|||
android:focusable="true"
|
||||
android:text="@string/show_device_id" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/drawerActionRecentChanges"
|
||||
style="@style/Widget.Syncthing.TextView.Label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:drawableLeft="@drawable/ic_history_black_24dp_active"
|
||||
android:drawableStart="@drawable/ic_history_black_24dp_active"
|
||||
android:text="@string/recent_changes_title"
|
||||
android:clickable="true"
|
||||
android:focusable="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/drawerActionWebGui"
|
||||
style="@style/Widget.Syncthing.TextView.Label"
|
||||
|
|
51
app/src/main/res/layout/item_recent_change.xml
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:padding="6dip"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/typeIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dip"
|
||||
android:layout_marginRight="6dip"
|
||||
android:contentDescription="@string/generic_help"
|
||||
android:gravity="top"
|
||||
android:src="@drawable/ic_help_outline_black_24dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filename"
|
||||
android:layout_marginTop="-25dp"
|
||||
android:layout_marginLeft="30dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/folderPath"
|
||||
android:layout_marginLeft="30dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/modifiedByDevice"
|
||||
android:layout_marginLeft="30dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dateTime"
|
||||
android:layout_marginLeft="30dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
|
@ -306,6 +306,13 @@ Bitte melden Sie auftretende Probleme via GitHub.</string>
|
|||
<string name="tip_huawei_device_disconnected_text">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</string>
|
||||
<string name="tip_phone_ip_address_syntax">TELEFON_IP_ADRESSE</string>
|
||||
|
||||
|
||||
<!-- RecentChangesActivity -->
|
||||
<string name="recent_changes_title">Letzte Änderungen</string>
|
||||
<string name="modified_by_device">Gerät: %1$s</string>
|
||||
<string name="modification_time">Zeit: %1$s</string>
|
||||
|
||||
|
||||
<!-- WebGuiActivity -->
|
||||
|
||||
<!-- Title of the web gui activity -->
|
||||
|
|
|
@ -306,6 +306,13 @@ Please report any problems you encounter via Github.</string>
|
|||
<string name="tip_huawei_device_disconnected_text">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</string>
|
||||
<string name="tip_phone_ip_address_syntax">PHONE_IP_ADDRESS</string>
|
||||
|
||||
|
||||
<!-- RecentChangesActivity -->
|
||||
<string name="recent_changes_title">Recent changes</string>
|
||||
<string name="modified_by_device">Device: %1$s</string>
|
||||
<string name="modification_time">Time: %1$s</string>
|
||||
|
||||
|
||||
<!-- WebGuiActivity -->
|
||||
|
||||
<!-- Title of the web gui activity -->
|
||||
|
|