.
@@ -1,2 +1,24 @@
|
||||
# a-sync-browser
|
||||
A Sync Browser: a browser app for Syncthing-compatible shares
|
||||
|
||||
[](https://www.mozilla.org/MPL/2.0/)
|
||||
|
||||
This project is an android app, that works as a client for a [Syncthing][1] share (accessing syncthing devices in the same way a client-server file sharing app access its proprietary server).
|
||||
This project is based on [a-sync][2], a java implementation of Syncthing protocols (bep, discovery, relay).
|
||||
|
||||
NOTE: this is a client-oriented implementation, designed to work online by downloading and uploading files from an active device on the network (instead of synchronizing a local copy of the entire repository). This is quite different from the way the original Syncthing app works, and its useful from those devices that cannot or wish not to download the entire repository (for example, mobile devices with limited storage available, wishing to access a syncthing share).
|
||||
|
||||
DISCLAMER: I'm not the owner fo Syncthing. The Syncthing name, logo and reference documentation is property of the Syncthing team, and licensed under the MPLv2 License. This project is not affiliated with Syncthing, and uses its public avaliable documentation and protocol specification as a reference to implement protocol compatibility.
|
||||
|
||||
Documentation is non-existing as know, if you have any question feel free to contact the developer (me).
|
||||
|
||||
All code is licensed under the [MPLv2 License][3].
|
||||
|
||||
If you like this work, please donate something to help its development!
|
||||
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FVB93HNCH5NL8)
|
||||
|
||||
[1]: https://syncthing.net/
|
||||
[2]: https://github.com/davide-imbriaco/a-sync
|
||||
[3]: https://github.com/davide-imbriaco/a-sync-browser/blob/master/LICENSE
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion "25.0.0"
|
||||
defaultConfig {
|
||||
applicationId "it.anyplace.syncbrowser"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 25
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
jackOptions {
|
||||
enabled true
|
||||
}
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.android.support:appcompat-v7:25.0.1'
|
||||
compile ('it.anyplace.sync:a-sync-client:1.0') {
|
||||
exclude group: 'commons-logging', module:'commons-logging'
|
||||
exclude group: 'commons-cli'
|
||||
exclude group: 'commons-codec'
|
||||
exclude group: 'org.apache.httpcomponents', module:'httpclient'
|
||||
exclude group: 'org.slf4j'
|
||||
exclude group: 'ch.qos.logback'
|
||||
}
|
||||
compile 'org.apache.httpcomponents:httpclient-android:4.3.5.1'
|
||||
compile 'org.slf4j:slf4j-android:1.7.13'
|
||||
compile 'com.google.zxing:android-integration:3.3.0'
|
||||
compile 'com.nononsenseapps:filepicker:2.5.2'
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package it.anyplace.syncbrowser;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumentation test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() throws Exception {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||
|
||||
assertEquals("it.anyplace.syncthingbrowser", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="it.anyplace.syncbrowser">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:banner="@drawable/banner"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Syncthing">
|
||||
<activity android:name="it.anyplace.syncbrowser.MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="*/*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="*/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".filepicker.MIVFilePickerActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/FilePickerTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.GET_CONTENT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,968 @@
|
||||
package it.anyplace.syncbrowser;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.os.Bundle;
|
||||
import android.text.format.DateFormat;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
import com.nononsenseapps.filepicker.FilePickerActivity;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import it.anyplace.sync.bep.BlockPuller;
|
||||
import it.anyplace.sync.bep.BlockPusher;
|
||||
import it.anyplace.sync.bep.FolderBrowser;
|
||||
import it.anyplace.sync.bep.IndexBrowser;
|
||||
import it.anyplace.sync.bep.IndexHandler;
|
||||
import it.anyplace.sync.client.SyncthingClient;
|
||||
import it.anyplace.sync.core.Configuration;
|
||||
import it.anyplace.sync.core.beans.FileInfo;
|
||||
import it.anyplace.sync.core.beans.FolderInfo;
|
||||
import it.anyplace.sync.core.beans.FolderStats;
|
||||
import it.anyplace.sync.core.beans.IndexInfo;
|
||||
import it.anyplace.sync.core.security.KeystoreHandler;
|
||||
import it.anyplace.sync.core.utils.PathUtils;
|
||||
import it.anyplace.syncbrowser.filepicker.MIVFilePickerActivity;
|
||||
|
||||
import static com.google.common.base.Objects.equal;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static org.apache.commons.io.FileUtils.getFile;
|
||||
import static org.apache.commons.lang3.StringUtils.capitalize;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
private Configuration configuration;
|
||||
private SyncthingClient syncthingClient;
|
||||
private Exception statupError;
|
||||
|
||||
private void closeClient(){
|
||||
if(indexBrowser!=null){
|
||||
indexBrowser.close();
|
||||
indexBrowser=null;
|
||||
}
|
||||
if(folderBrowser!=null){
|
||||
folderBrowser.close();
|
||||
folderBrowser=null;
|
||||
}
|
||||
if(syncthingClient!=null){
|
||||
syncthingClient.close();
|
||||
syncthingClient=null;
|
||||
}
|
||||
}
|
||||
|
||||
private void initClient() {
|
||||
closeClient();
|
||||
try {
|
||||
final String configurationFile="config.properties";
|
||||
Reader configReader;
|
||||
try{
|
||||
configReader=new InputStreamReader(openFileInput(configurationFile)) ;
|
||||
}catch (FileNotFoundException e) {
|
||||
Log.w("initClient","config file "+configurationFile+" not found",e);
|
||||
configReader=null;
|
||||
}
|
||||
File configFile = new File(getExternalFilesDir(null),"config.properties");
|
||||
configuration = new Configuration(configFile);
|
||||
configuration.setCache(new File(getExternalCacheDir(),"cache"));
|
||||
configuration.setDatabase(new File(getExternalFilesDir(null),"database"));
|
||||
configuration.setDeviceName(getDeviceName());
|
||||
configuration.clearTempDir();
|
||||
KeystoreHandler keystoreHandler=new KeystoreHandler(configuration);
|
||||
Log.i("initClient","loaded configuration = "+configuration.dumpToString());
|
||||
Log.i("initClient","storage space = "+configuration.getStorageInfo().dumpAvailableSpace());
|
||||
syncthingClient=new SyncthingClient(configuration);
|
||||
syncthingClient.getIndexHandler().getEventBus().register(new Object(){
|
||||
|
||||
@Subscribe
|
||||
public void handleIndexRecordAquiredEvent(IndexHandler.IndexRecordAquiredEvent event){
|
||||
final String label=syncthingClient.getIndexHandler().getFolderInfo(event.getFolder()).getLabel();
|
||||
final IndexInfo indexInfo=event.getIndexInfo();
|
||||
final long count=event.getNewRecords().size();
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i("handleIndexRecordEvent","trigger folder list update from index record acquired");
|
||||
((TextView)findViewById(R.id.index_update_message)).setText("index update, folder "
|
||||
+label+" "+((int)(indexInfo.getCompleted()*100))+ "% synchronized");
|
||||
updateFolderListView();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void handleRemoteIndexAquiredEvent(IndexHandler.RemoteIndexAquiredEvent event){
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i("handleIndexAquiredEvent","trigger folder list update from index acquired");
|
||||
findViewById(R.id.index_loading_bar).setVisibility(View.GONE);
|
||||
updateFolderListView();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
//TODO listen for device events, update device list
|
||||
folderBrowser=syncthingClient.getIndexHandler().newFolderBrowser();
|
||||
statupError=null;
|
||||
}catch(Exception ex){
|
||||
Log.e("Main","error",ex);
|
||||
statupError=ex;
|
||||
closeClient();
|
||||
}
|
||||
}
|
||||
|
||||
private Typeface fontAwesome;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
Log.i("onCreate","BEGIN");
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
fontAwesome = Typeface.createFromAsset(getAssets(), "fontawesome-webfont.ttf");
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
((TextView)findViewById(R.id.current_folder_name_header)).setText(R.string.app_name);
|
||||
|
||||
for(int id:Arrays.asList(R.id.file_upload_button,
|
||||
R.id.cancel_file_upload_button,
|
||||
R.id.show_menu_button,
|
||||
R.id.exit_menu_button_icon,
|
||||
R.id.qr_button_icon,
|
||||
R.id.cleanup_button_icon,
|
||||
R.id.update_index_button_icon,
|
||||
R.id.add_device_here_button,
|
||||
R.id.upload_here_button)){
|
||||
((TextView)findViewById(id)).setTypeface(fontAwesome);
|
||||
}
|
||||
|
||||
((View)findViewById(R.id.show_menu_button)).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
((DrawerLayout)findViewById(R.id.activity_main)).openDrawer(Gravity.LEFT);
|
||||
}
|
||||
});
|
||||
((View)findViewById(R.id.exitMenuButton)).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
((DrawerLayout)findViewById(R.id.activity_main)).closeDrawer(Gravity.LEFT);
|
||||
}
|
||||
});
|
||||
((View)findViewById(R.id.qrButton)).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
openQrcode();
|
||||
((DrawerLayout)findViewById(R.id.activity_main)).closeDrawer(Gravity.LEFT);
|
||||
}
|
||||
});
|
||||
((View)findViewById(R.id.add_device_here_button)).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
openQrcode();
|
||||
}
|
||||
});
|
||||
((View)findViewById(R.id.cleanupButton)).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
new AlertDialog.Builder(MainActivity.this)
|
||||
.setTitle("clear cache and index")
|
||||
.setMessage("clear all cache data and index data?")
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setPositiveButton("yes", new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
cleanCacheAndIndex();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("no",null)
|
||||
.show();
|
||||
((DrawerLayout)findViewById(R.id.activity_main)).closeDrawer(Gravity.LEFT);
|
||||
}
|
||||
});
|
||||
((View)findViewById(R.id.updateIndexButton)).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
updateIndexFromRemote();
|
||||
((DrawerLayout)findViewById(R.id.activity_main)).closeDrawer(Gravity.LEFT);
|
||||
}
|
||||
});
|
||||
((View)findViewById(R.id.upload_here_button)).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
showUploadHereDialog();
|
||||
}
|
||||
});
|
||||
((DrawerLayout)findViewById(R.id.activity_main)).addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
|
||||
@Override
|
||||
public void onDrawerOpened(View drawerView) {
|
||||
if(drawerView.getId()==R.id.devicesDrawer){
|
||||
updateDeviceList();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
new AsyncTask<Void,Void,Void>(){
|
||||
|
||||
private ProgressDialog progressDialog;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
progressDialog = new ProgressDialog(MainActivity.this);
|
||||
progressDialog.setMessage("loading config, starting syncthing client");
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.setIndeterminate(true);
|
||||
progressDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voidd) {
|
||||
initClient();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute (Void voidd){
|
||||
progressDialog.dismiss();
|
||||
if(syncthingClient==null){
|
||||
Toast.makeText(MainActivity.this,"error starting syncthing client: "+statupError , Toast.LENGTH_LONG).show();
|
||||
}else{
|
||||
restoreBrowserFolderFromPref();
|
||||
Date lastUpdate = getLastIndexUpdateFromPref();
|
||||
if(lastUpdate==null || new Date().getTime()-lastUpdate.getTime() > 10*60*1000) { //trigger update if last was more than 10mins ago
|
||||
Log.d("onCreate", "trigger index update, last was "+lastUpdate);
|
||||
updateIndexFromRemote();
|
||||
}
|
||||
initDeviceList();
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
|
||||
Log.i("onCreate","app ready, scanning intent");
|
||||
Intent intent = getIntent();
|
||||
if (equal(Intent.ACTION_SEND, intent.getAction())) {
|
||||
handleSendSingle(intent);
|
||||
} else if (equal(Intent.ACTION_SEND_MULTIPLE, intent.getAction())) {
|
||||
handleSendMany(intent);
|
||||
}
|
||||
Log.i("onCreate","END");
|
||||
}
|
||||
|
||||
private void showUploadHereDialog() {
|
||||
if(indexBrowser==null){
|
||||
Log.w("showUploadHereDialog","unable to open dialog, null index browser");
|
||||
}else{
|
||||
Intent i = new Intent(this, MIVFilePickerActivity.class);
|
||||
String path=Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
|
||||
Log.i("showUploadHereDialog","path = "+path);
|
||||
i.putExtra(FilePickerActivity.EXTRA_START_PATH, path);
|
||||
startActivityForResult(i, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanCacheAndIndex() {
|
||||
if(syncthingClient!=null){
|
||||
syncthingClient.clearCacheAndIndex();
|
||||
recreate();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSendSingle(Intent intent){
|
||||
Uri fileUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
if(fileUri!=null){
|
||||
handleSend(Collections.singletonList(fileUri));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSendMany(Intent intent){
|
||||
List<Uri> fileUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
||||
if(fileUris!=null && !fileUris.isEmpty()){
|
||||
handleSend(fileUris);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateButtonsVisibility(){
|
||||
if(isHandlingUploadIntent){
|
||||
findViewById(R.id.upload_here_button).setVisibility(View.GONE);
|
||||
findViewById(R.id.file_upload_bar).setVisibility(View.VISIBLE);//todo lock if not in folder
|
||||
if(isBrowsingFolder){
|
||||
((TextView)findViewById(R.id.file_upload_button)).setEnabled(true);
|
||||
}else{
|
||||
((TextView)findViewById(R.id.file_upload_button)).setEnabled(false);
|
||||
}
|
||||
}else{
|
||||
if(isBrowsingFolder){
|
||||
findViewById(R.id.upload_here_button).setVisibility(View.VISIBLE);
|
||||
}else{
|
||||
findViewById(R.id.upload_here_button).setVisibility(View.GONE);
|
||||
}
|
||||
findViewById(R.id.file_upload_bar).setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Uri> filesToUpload;
|
||||
|
||||
private void handleSend(List<Uri> list){
|
||||
Log.i("Main","handle send of files = "+list);
|
||||
isHandlingUploadIntent=true;
|
||||
filesToUpload=list;
|
||||
updateButtonsVisibility();
|
||||
((TextView)findViewById(R.id.file_upload_message)).setText(Joiner.on(", ").join(Iterables.transform(list,new Function<Uri,String>(){
|
||||
|
||||
@Override
|
||||
public String apply( Uri input) {
|
||||
return getContentFileName(input);
|
||||
}
|
||||
})));
|
||||
((TextView)findViewById(R.id.file_upload_button)).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if(indexBrowser!=null) {
|
||||
doUpload(indexBrowser.getFolder(), indexBrowser.getCurrentPath(), filesToUpload);
|
||||
filesToUpload = null;
|
||||
isHandlingUploadIntent=false;
|
||||
updateButtonsVisibility();
|
||||
}else{
|
||||
Toast.makeText(MainActivity.this,"choose a folder for upload" , Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
((TextView)findViewById(R.id.cancel_file_upload_button)).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
filesToUpload = null;
|
||||
isHandlingUploadIntent=false;
|
||||
updateButtonsVisibility();
|
||||
Toast.makeText(MainActivity.this,"file upload cancelled" , Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getContentFileName(Uri contentUri){
|
||||
String fileName = contentUri.getLastPathSegment();
|
||||
if(equal(contentUri.getScheme(),"content")) {
|
||||
try (Cursor cursor = MainActivity.this.getContentResolver().query(contentUri, new String[]{MediaStore.Images.Media.DATA}, null, null, null)) {
|
||||
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
|
||||
cursor.moveToFirst();
|
||||
String path = cursor.getString(column_index);
|
||||
Log.d("Main", "recovered 'content' uri real path = " + path);
|
||||
fileName = Uri.parse(path).getLastPathSegment();
|
||||
}
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
private void doUpload(final String folder,final String dir,final List<Uri> filesToUpload) {
|
||||
if(!filesToUpload.isEmpty()) {
|
||||
new AsyncTask<Void, BlockPusher.FileUploadObserver, Exception>() {
|
||||
private ProgressDialog progressDialog;
|
||||
private Thread thread;
|
||||
private boolean cancelled = false;
|
||||
private Uri fileToUpload = filesToUpload.iterator().next();
|
||||
private List<Uri> nextFilesToUpload = filesToUpload.subList(1, filesToUpload.size());
|
||||
private String fileName = getContentFileName(fileToUpload);
|
||||
private String path = PathUtils.buildPath(dir, fileName);
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
Log.i("doUpload","upload of file "+fileName+" to folder "+folder+":"+dir);
|
||||
progressDialog = new ProgressDialog(MainActivity.this);
|
||||
progressDialog.setMessage("uploading file " + fileName);
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
progressDialog.setCancelable(true);
|
||||
progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialogInterface) {
|
||||
cancelled = true;
|
||||
if (thread != null) {
|
||||
thread.interrupt();
|
||||
}
|
||||
Toast.makeText(MainActivity.this, "upload aborted by user", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
progressDialog.setIndeterminate(true);
|
||||
progressDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Exception doInBackground(Void... voidd) {
|
||||
try {
|
||||
try (BlockPusher.FileUploadObserver observer = syncthingClient.pushFile(getContentResolver().openInputStream(fileToUpload), folder, path)) {
|
||||
Log.i("doUpload","pushing file "+fileName+" to folder "+folder+":"+dir);
|
||||
publishProgress(observer);
|
||||
while (!observer.isCompleted() && !cancelled) {
|
||||
observer.waitForProgressUpdate();
|
||||
Log.i("Main", "upload progress = " + observer.getProgressMessage());
|
||||
publishProgress(observer);
|
||||
}
|
||||
if (cancelled) {
|
||||
return null;
|
||||
}
|
||||
Log.i("Main", "uploaded file = " + path);
|
||||
return null;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (cancelled) {
|
||||
return null;
|
||||
}
|
||||
Log.e("Main", "file upload exception", ex);
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(BlockPusher.FileUploadObserver... observer) {
|
||||
if (observer[0].getProgress() > 0) {
|
||||
progressDialog.setIndeterminate(false);
|
||||
progressDialog.setMax((int)observer[0].getDataSource().getSize());
|
||||
progressDialog.setProgress((int) (observer[0].getProgress() * observer[0].getDataSource().getSize()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Exception res) {
|
||||
progressDialog.dismiss();
|
||||
if (cancelled) {
|
||||
// do nothing
|
||||
} else if (res != null) {
|
||||
Toast.makeText(MainActivity.this, "error uploading file: " + res, Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
Log.i("doUpload","uploaded file "+fileName+" to folder "+folder+":"+dir);
|
||||
Toast.makeText(MainActivity.this, "uploaded file: " + fileName, Toast.LENGTH_SHORT).show();
|
||||
updateFolderListView();
|
||||
if (!nextFilesToUpload.isEmpty()) {
|
||||
doUpload(folder, dir, nextFilesToUpload);
|
||||
}
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
|
||||
private FolderBrowser folderBrowser;
|
||||
private IndexBrowser indexBrowser;
|
||||
private boolean isBrowsingFolder=false, isHandlingUploadIntent=false, indexUpdateInProgress = false;
|
||||
|
||||
private final static String CURRENT_FOLDER_PREF="CURRENT_FOLDER";
|
||||
private void saveCurrentFolder(){
|
||||
Log.d("saveCurrentFolder","saveCurrentFolder");
|
||||
if(isBrowsingFolder){
|
||||
getPreferences(MODE_PRIVATE).edit()
|
||||
.putString(CURRENT_FOLDER_PREF,new Gson().toJson(Arrays.asList(indexBrowser.getFolder(),indexBrowser.getCurrentPath())))
|
||||
.apply();
|
||||
}else{
|
||||
getPreferences(MODE_PRIVATE).edit().remove(CURRENT_FOLDER_PREF).apply();
|
||||
}
|
||||
}
|
||||
private void restoreBrowserFolderFromPref(){
|
||||
Log.d("restoreBrowserFolder...","restoreBrowserFolderFromPref");
|
||||
String value = getPreferences(MODE_PRIVATE).getString(CURRENT_FOLDER_PREF,null);
|
||||
if(isBlank(value)){
|
||||
showAllFoldersListView();
|
||||
}else{
|
||||
try {
|
||||
List<String> list = new Gson().fromJson(value, new TypeToken<List<String>>() {
|
||||
}.getType());
|
||||
checkArgument(list.size() == 2);
|
||||
showFolderListView(list.get(0), list.get(1));
|
||||
}catch(Exception ex){
|
||||
Log.e("restoreBrowserFolder...","error restoring browser folder from preferences",ex);
|
||||
showAllFoldersListView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showAllFoldersListView(){
|
||||
Log.d("Main","showAllFoldersListView BEGIN");
|
||||
if(indexBrowser!=null) {
|
||||
indexBrowser.close();
|
||||
indexBrowser = null;
|
||||
}
|
||||
ListView listView=(ListView)findViewById(R.id.listView);
|
||||
List<Pair<FolderInfo, FolderStats>> list=Lists.newArrayList(folderBrowser.getFolderInfoAndStatsList());
|
||||
Collections.sort(list, Ordering.natural().onResultOf(new Function<Pair<FolderInfo, FolderStats>, String>() {
|
||||
@Override
|
||||
public String apply(Pair<FolderInfo, FolderStats> input) {
|
||||
return input.getLeft().getLabel();
|
||||
}
|
||||
}));
|
||||
Log.i("Main","list folders = "+list+" ("+list.size()+" records");
|
||||
ArrayAdapter adapter = new ArrayAdapter<Pair<FolderInfo, FolderStats>>(this,R.layout.listview_folder,list){
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, View v, ViewGroup parent) {
|
||||
if (v==null) {
|
||||
v= LayoutInflater.from(getContext()).inflate(R.layout.listview_folder, null);
|
||||
}
|
||||
FolderInfo folderInfo = getItem(position).getLeft();
|
||||
FolderStats folderStats = getItem(position).getRight();
|
||||
((TextView)v.findViewById(R.id.folder_icon)).setTypeface(fontAwesome);
|
||||
((TextView)v.findViewById(R.id.folder_name)).setText(folderInfo.getLabel()+" ("+folderInfo.getFolder()+")");
|
||||
((TextView)v.findViewById(R.id.folder_lastmod_info)).setText(folderStats.getLastUpdate()==null?"last change: unknown":("last change: "+folderStats.getLastUpdate().toLocaleString()));
|
||||
((TextView)v.findViewById(R.id.folder_content_info)).setText(folderStats.describeSize()+", "+folderStats.getFileCount()+" files, "+folderStats.getDirCount()+" dirs");
|
||||
return v;
|
||||
}
|
||||
|
||||
};
|
||||
listView.setAdapter(adapter);
|
||||
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
|
||||
String folder= ((Pair<FolderInfo, FolderStats>)listView.getItemAtPosition(position)).getLeft().getFolder();
|
||||
showFolderListView(folder,null);
|
||||
}
|
||||
});
|
||||
findViewById(R.id.no_folder_shared_message).setVisibility(list.isEmpty()?View.VISIBLE:View.GONE);
|
||||
isBrowsingFolder=false;
|
||||
updateButtonsVisibility();
|
||||
saveCurrentFolder();
|
||||
Log.d("Main","showAllFoldersListView END");
|
||||
}
|
||||
|
||||
private void showFolderListView(String folder, @Nullable String previousPath){
|
||||
Log.d("showFolderListView","showFolderListView BEGIN");
|
||||
if(indexBrowser!=null &&equal(folder,indexBrowser.getFolder())){
|
||||
Log.d("showFolderListView","reuse current index browser");
|
||||
indexBrowser.navigateToNearestPath(previousPath);
|
||||
}else {
|
||||
if (indexBrowser != null) {
|
||||
indexBrowser.close();
|
||||
}
|
||||
Log.d("showFolderListView","open new index browser");
|
||||
indexBrowser = syncthingClient.getIndexHandler()
|
||||
.newIndexBrowserBuilder().setFolder(folder)
|
||||
.includeParentInList(true).allowParentInRoot(true)
|
||||
.buildToNearestPath(previousPath);
|
||||
}
|
||||
ListView listView=(ListView)findViewById(R.id.listView);
|
||||
ArrayAdapter adapter = new ArrayAdapter<FileInfo>(this,R.layout.listview_file, Lists.newArrayList()){
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, View v, ViewGroup parent) {
|
||||
if (v==null) {
|
||||
v= LayoutInflater.from(getContext()).inflate(R.layout.listview_file, null);
|
||||
}
|
||||
FileInfo fileInfo = getItem(position);
|
||||
((TextView)v.findViewById(R.id.file_label)).setText(fileInfo.getFileName());
|
||||
((TextView)v.findViewById(R.id.file_icon)).setTypeface(fontAwesome);
|
||||
if(fileInfo.isDirectory()){
|
||||
((TextView)v.findViewById(R.id.file_icon)).setText(R.string.icon_folder_o);
|
||||
((TextView)v.findViewById(R.id.file_size)).setVisibility(View.GONE);
|
||||
}else{
|
||||
((TextView)v.findViewById(R.id.file_icon)).setText(R.string.icon_file_o);
|
||||
((TextView)v.findViewById(R.id.file_size)).setVisibility(View.VISIBLE);
|
||||
((TextView)v.findViewById(R.id.file_size)).setText(FileUtils.byteCountToDisplaySize(fileInfo.getSize()));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
};
|
||||
listView.setAdapter(adapter);
|
||||
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
|
||||
FileInfo fileInfo= (FileInfo)listView.getItemAtPosition(position);
|
||||
Log.d("showFolderListView","navigate to path = '"+fileInfo.getPath()+"' from path = '"+indexBrowser.getCurrentPath()+"'");
|
||||
navigateToFolder(fileInfo);
|
||||
}
|
||||
});
|
||||
isBrowsingFolder=true;
|
||||
findViewById(R.id.no_folder_shared_message).setVisibility(View.GONE);
|
||||
navigateToFolder(indexBrowser.getCurrentPathInfo());
|
||||
updateButtonsVisibility();
|
||||
Log.d("Main","showFolderListView END");
|
||||
}
|
||||
|
||||
private void navigateToFolder(FileInfo fileInfo){
|
||||
if(indexBrowser.isRoot() && PathUtils.isParent(fileInfo.getPath())) {
|
||||
showAllFoldersListView(); //navigate back to folder list
|
||||
}else{
|
||||
if (fileInfo.isDirectory()) {
|
||||
indexBrowser.navigateTo(fileInfo);
|
||||
if(!indexBrowser.isCacheReady()){
|
||||
new AsyncTask<Void,Void,Void>(){
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
findViewById(R.id.mainProgressBarHolder).setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
indexBrowser.waitForCacheReady();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
findViewById(R.id.mainProgressBarHolder).setVisibility(View.GONE);
|
||||
navigateToFolder(indexBrowser.getCurrentPathInfo());
|
||||
}
|
||||
}.execute();
|
||||
}else {
|
||||
List<FileInfo> list = indexBrowser.listFiles();
|
||||
Log.i("showFolderListView", "list for path = '" + indexBrowser.getCurrentPath() + "' list = " + list + " (" + list.size() + " records)");
|
||||
checkArgument(!list.isEmpty());//list must contain at least the 'parent' path
|
||||
ListView listView = (ListView) findViewById(R.id.listView);
|
||||
ArrayAdapter adapter = (ArrayAdapter) listView.getAdapter();
|
||||
adapter.clear();
|
||||
adapter.addAll(list);
|
||||
adapter.notifyDataSetChanged();
|
||||
listView.setSelection(0);
|
||||
saveCurrentFolder();
|
||||
((TextView) findViewById(R.id.current_folder_name_header)).setText(indexBrowser.isRoot() ? folderBrowser.getFolderInfo(indexBrowser.getFolder()).getLabel() : fileInfo.getFileName());
|
||||
}
|
||||
} else {
|
||||
pullFile(fileInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFolderListView(){
|
||||
Log.d("updateFolderListView","BEGIN");
|
||||
if(indexBrowser==null){
|
||||
showAllFoldersListView();
|
||||
}else{
|
||||
showFolderListView(indexBrowser.getFolder(), indexBrowser.getCurrentPath());
|
||||
}
|
||||
Log.d("updateFolderListView","END");
|
||||
}
|
||||
|
||||
private void initDeviceList(){
|
||||
ListView listView=(ListView)findViewById(R.id.devicesListView);
|
||||
ArrayAdapter adapter = new ArrayAdapter<String>(this,R.layout.listview_device, Lists.newArrayList()){ //TODO replace string with some sort of 'Device' bean
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, View v, ViewGroup parent) {
|
||||
if (v==null) {
|
||||
v= LayoutInflater.from(getContext()).inflate(R.layout.listview_device, null);
|
||||
}
|
||||
String deviceId = getItem(position);
|
||||
((TextView)v.findViewById(R.id.device_name)).setText(deviceId.substring(0,7));
|
||||
((TextView)v.findViewById(R.id.device_icon)).setTypeface(fontAwesome);
|
||||
//TODO show more data; show status in icon color
|
||||
return v;
|
||||
}
|
||||
};
|
||||
listView.setAdapter(adapter);
|
||||
// listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
// @Override
|
||||
// public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
|
||||
// FileInfo fileInfo= (FileInfo)listView.getItemAtPosition(position);
|
||||
// Log.d("showFolderListView","navigate to path = '"+fileInfo.getPath()+"' from path = '"+indexBrowser.getCurrentPath()+"'");
|
||||
// navigateToFolder(fileInfo);
|
||||
// }
|
||||
// });
|
||||
//TODO add button for device removal
|
||||
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int position, long l) {
|
||||
String deviceId= (String)listView.getItemAtPosition(position);
|
||||
new AlertDialog.Builder(MainActivity.this)
|
||||
.setTitle("remove device "+deviceId.substring(0,7))
|
||||
.setMessage("remove device"+deviceId.substring(0,7)+" from list of known devices?")
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setPositiveButton("yes", new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Set<String> peers= Sets.newHashSet(configuration.getPeers());
|
||||
peers.remove(deviceId);
|
||||
configuration.setPeers(peers);
|
||||
configuration.storeConfiguration();//TODO add 'store configuration bg' method
|
||||
updateDeviceList();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("no",null)
|
||||
.show();
|
||||
Log.d("showFolderListView","delete device = '"+deviceId+"'");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
updateDeviceList();
|
||||
}
|
||||
|
||||
private void updateDeviceList(){
|
||||
ListView listView=(ListView)findViewById(R.id.devicesListView);
|
||||
List<String> peerList = configuration.getPeers();
|
||||
((ArrayAdapter)listView.getAdapter()).clear();
|
||||
((ArrayAdapter)listView.getAdapter()).addAll(peerList);
|
||||
((ArrayAdapter)listView.getAdapter()).notifyDataSetChanged();
|
||||
listView.setSelection(0);
|
||||
//TODO update devices from some sort of device info repository/handler, not from config
|
||||
}
|
||||
|
||||
private void pullFile(final FileInfo fileInfo){
|
||||
Log.i("pullFile","pulling file = "+fileInfo);
|
||||
new AsyncTask<Void,BlockPuller.FileDownloadObserver,Pair<File,Exception>>(){
|
||||
private ProgressDialog progressDialog;
|
||||
private Thread thread;
|
||||
private boolean cancelled=false;
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
progressDialog = new ProgressDialog(MainActivity.this);
|
||||
progressDialog.setMessage("downloading file "+fileInfo.getFileName());
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
progressDialog.setCancelable(true);
|
||||
progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialogInterface) {
|
||||
cancelled=true;
|
||||
if(thread!=null){
|
||||
thread.interrupt();
|
||||
}
|
||||
Toast.makeText(MainActivity.this, "download aborted by user", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
progressDialog.setIndeterminate(true);
|
||||
progressDialog.show();
|
||||
}
|
||||
@Override
|
||||
protected Pair<File,Exception> doInBackground(Void... voidd) {
|
||||
try {
|
||||
try (BlockPuller.FileDownloadObserver fileDownloadObserver = syncthingClient.pullFile(fileInfo.getFolder(), fileInfo.getPath())) {
|
||||
publishProgress(fileDownloadObserver);
|
||||
while (!fileDownloadObserver.isCompleted() &&!cancelled) {
|
||||
fileDownloadObserver.waitForProgressUpdate();
|
||||
Log.i("pullFile", "download progress = " + fileDownloadObserver.getProgressMessage());
|
||||
publishProgress(fileDownloadObserver);
|
||||
}
|
||||
if(cancelled){
|
||||
return null;
|
||||
}
|
||||
File outputDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
File outputFile = new File(outputDir, fileInfo.getFileName());
|
||||
FileUtils.copyInputStreamToFile(fileDownloadObserver.getInputStream(), outputFile);
|
||||
Log.i("pullFile", "downloaded file = " + fileInfo.getPath());
|
||||
return Pair.of(outputFile, null);
|
||||
}
|
||||
}catch(Exception ex){
|
||||
if(cancelled){
|
||||
return null;
|
||||
}
|
||||
Log.e("pullFile","file download exception",ex);
|
||||
return Pair.of(null,ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(BlockPuller.FileDownloadObserver... fileDownloadObserver) {
|
||||
if(fileDownloadObserver[0].getProgress()>0){
|
||||
progressDialog.setIndeterminate(false);
|
||||
progressDialog.setMax((int)(long)fileInfo.getSize());
|
||||
progressDialog.setProgress((int)(fileDownloadObserver[0].getProgress()*fileInfo.getSize()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute (Pair<File,Exception> res){
|
||||
progressDialog.dismiss();
|
||||
if(cancelled){
|
||||
// do nothing
|
||||
}else if(res.getLeft()==null){
|
||||
Toast.makeText(MainActivity.this, "error downloading file: " + res.getRight(), Toast.LENGTH_LONG).show();
|
||||
}else{
|
||||
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(FilenameUtils.getExtension(res.getLeft().getName()));
|
||||
Intent newIntent = new Intent(Intent.ACTION_VIEW);
|
||||
Log.i("Main", "open file = "+ res.getLeft().getName()+" ("+mimeType+")");
|
||||
newIntent.setDataAndType(Uri.fromFile(res.getLeft()),mimeType);
|
||||
newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
Intent chooser = Intent.createChooser(newIntent, null);
|
||||
try {
|
||||
startActivity(chooser);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(MainActivity.this, "no handler found for this file: "+ res.getLeft().getName()+" ("+mimeType+")", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private final static String LAST_INDEX_UPDATE_TS_PREF="LAST_INDEX_UPDATE_TS";
|
||||
|
||||
private @Nullable Date getLastIndexUpdateFromPref(){
|
||||
long lastUpdate = getPreferences(MODE_PRIVATE).getLong(LAST_INDEX_UPDATE_TS_PREF,-1);
|
||||
if(lastUpdate<0){
|
||||
return null;
|
||||
}else{
|
||||
return new Date(lastUpdate);
|
||||
}
|
||||
}
|
||||
private void updateIndexFromRemote(){
|
||||
Log.d("Main","updateIndexFromRemote BEGIN");
|
||||
if(indexUpdateInProgress) {
|
||||
Toast.makeText(MainActivity.this, "index update already in progress", Toast.LENGTH_SHORT).show();
|
||||
}else{
|
||||
indexUpdateInProgress = true;
|
||||
new AsyncTask<Void, Void, Exception>() {
|
||||
private View indexLoadingBar = (View) findViewById(R.id.index_loading_bar);
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
indexLoadingBar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Exception doInBackground(Void... voidd) {
|
||||
try {
|
||||
syncthingClient.waitForRemoteIndexAquired();
|
||||
return null;
|
||||
} catch (Exception ex) {
|
||||
Log.e("Main", "index dump exception", ex);
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Exception ex) {
|
||||
indexLoadingBar.setVisibility(View.GONE);
|
||||
if (ex != null) {
|
||||
Toast.makeText(MainActivity.this, "error updating index: " + ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
updateFolderListView();
|
||||
indexUpdateInProgress = false;
|
||||
getPreferences(MODE_PRIVATE).edit()
|
||||
.putLong(LAST_INDEX_UPDATE_TS_PREF,new Date().getTime())
|
||||
.apply();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
Log.d("Main","updateIndexFromRemote END (running bg)");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
try {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
closeClient();
|
||||
return null;
|
||||
}
|
||||
}.execute().get();
|
||||
}catch(Exception ex){
|
||||
Log.w("Main","error closing client",ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void openQrcode(){
|
||||
IntentIntegrator integrator = new IntentIntegrator(MainActivity.this);
|
||||
integrator.initiateScan();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Receives value of scanned QR code and sets it as device ID.
|
||||
*/
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
|
||||
if (scanResult != null) {
|
||||
String deviceId = scanResult.getContents();
|
||||
if(!isBlank(deviceId)) {
|
||||
Log.i("Main", "qrcode text = " + deviceId);
|
||||
importDeviceId(deviceId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Uri fileUri = intent.getData();
|
||||
doUpload(indexBrowser.getFolder(),indexBrowser.getCurrentPath(), Arrays.asList(fileUri));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if(indexBrowser!=null){
|
||||
ListView listView=(ListView)findViewById(R.id.listView);
|
||||
listView.performItemClick(listView.getAdapter().getView(0, null, null), 0, listView.getItemIdAtPosition(0)); //click item '0', ie '..' (go to parent)
|
||||
}else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
private void importDeviceId(String deviceId){
|
||||
try {
|
||||
KeystoreHandler.validateDeviceId(deviceId);
|
||||
boolean modified = configuration.addPeers(deviceId);
|
||||
if(modified) {
|
||||
configuration.storeConfiguration();
|
||||
Toast.makeText(this, "successfully imported device: " + deviceId, Toast.LENGTH_SHORT).show();
|
||||
updateDeviceList();//TODO remove this if event triggered (and handler trigger update)
|
||||
updateIndexFromRemote();
|
||||
}else{
|
||||
Toast.makeText(this, "device already present: " + deviceId, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}catch(Exception ex){
|
||||
Log.e("Main","error importing deviceId = "+deviceId,ex);
|
||||
Toast.makeText(this, "error importing device: "+ex , Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private String getDeviceName() {
|
||||
String manufacturer = Build.MANUFACTURER;
|
||||
String model = Build.MODEL;
|
||||
if (model.startsWith(manufacturer)) {
|
||||
return capitalize(model);
|
||||
} else {
|
||||
return capitalize(manufacturer) + " " + model;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package it.anyplace.syncbrowser.filepicker;
|
||||
|
||||
import com.nononsenseapps.filepicker.AbstractFilePickerActivity;
|
||||
import com.nononsenseapps.filepicker.AbstractFilePickerFragment;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Created by aleph on 27/05/16.
|
||||
*/
|
||||
public class MIVFilePickerActivity extends AbstractFilePickerActivity<File> {
|
||||
|
||||
|
||||
@Override
|
||||
protected AbstractFilePickerFragment<File> getFragment(
|
||||
final String startPath, final int mode, final boolean allowMultiple,
|
||||
final boolean allowCreateDir) {
|
||||
// Only the fragment in this line needs to be changed
|
||||
AbstractFilePickerFragment<File> fragment = new MIVFilePickerFragment();
|
||||
fragment.setArgs(startPath, mode, allowMultiple, allowCreateDir);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package it.anyplace.syncbrowser.filepicker;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.nononsenseapps.filepicker.FilePickerFragment;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Created by aleph on 27/05/16.
|
||||
*/
|
||||
public class MIVFilePickerFragment extends FilePickerFragment {
|
||||
|
||||
@Override
|
||||
public void onClickCheckable(View v, CheckableViewHolder vh) {
|
||||
// auto open file on click
|
||||
if (!allowMultiple) {
|
||||
// Clear is necessary, in case user clicked some checkbox directly
|
||||
mCheckedItems.clear();
|
||||
mCheckedItems.add(vh.file);
|
||||
onClickOk(null);
|
||||
} else {
|
||||
super.onClickCheckable(v, vh);
|
||||
}
|
||||
}
|
||||
|
||||
// private static final String EXTENSION = ".*[.](jpg|png|jpeg)";
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean isItemVisible(final File file) {
|
||||
// return isDir(file) || file.getName().toLowerCase().matches(EXTENSION);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 137 B |
|
After Width: | Height: | Size: 136 B |
|
After Width: | Height: | Size: 367 B |
|
After Width: | Height: | Size: 161 B |
|
After Width: | Height: | Size: 161 B |
|
After Width: | Height: | Size: 137 B |
|
After Width: | Height: | Size: 204 B |
|
After Width: | Height: | Size: 195 B |
|
After Width: | Height: | Size: 156 B |
|
After Width: | Height: | Size: 277 B |
|
After Width: | Height: | Size: 255 B |
|
After Width: | Height: | Size: 155 B |
|
After Width: | Height: | Size: 123 KiB |
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_cellphone_black_24dp_inactive" android:state_enabled="false" />
|
||||
<item android:drawable="@drawable/ic_cellphone_black_24dp_active" />
|
||||
</selector>
|
||||
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,341 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.v4.widget.DrawerLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/activity_main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<!-- The main content view -->
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/topBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:background="@color/primary">
|
||||
<TextView
|
||||
android:id="@+id/show_menu_button"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="24dp"
|
||||
android:textColor="@color/white_on_primary"
|
||||
android:text="@string/icon_fa_bars" />
|
||||
<TextView
|
||||
android:id="@+id/current_folder_name_header"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:maxLines="1"
|
||||
android:textSize="18dp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/white_on_primary"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/topBar"
|
||||
android:orientation="vertical"
|
||||
android:divider="?android:listDivider"
|
||||
android:showDividers="middle">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:padding="8dp"
|
||||
android:id="@+id/index_loading_bar"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/primary"
|
||||
android:visibility="gone">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="0.5"
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
<ProgressBar
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:indeterminate="true"/>
|
||||
<TextView
|
||||
android:id="@+id/index_update_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="18dp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/white_on_primary"
|
||||
android:text="index update..."
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textAlignment="gravity"
|
||||
/>
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="0.5"
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/no_folder_shared_message"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="20dp"
|
||||
android:padding="8dp"
|
||||
android:text="no shared folders available..."
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textAlignment="gravity"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ListView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:id="@+id/listView"
|
||||
android:divider="@color/divider"
|
||||
android:dividerHeight="2dp">
|
||||
|
||||
</ListView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/file_upload_bar"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp"
|
||||
android:visibility="gone" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/file_upload_message"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
android:text="file upload..."
|
||||
android:textSize="20dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textAlignment="gravity"
|
||||
android:paddingRight="8dp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/cancel_file_upload_button"
|
||||
android:textSize="32dp"
|
||||
android:textColor="@color/primary"
|
||||
android:background="?selectableItemBackground"
|
||||
android:text="@string/icon_fa_times"
|
||||
android:layout_marginRight="8dp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/file_upload_button"
|
||||
android:textSize="32dp"
|
||||
android:textColor="@color/primary"
|
||||
android:background="?selectableItemBackground"
|
||||
android:text="@string/icon_fa_cloud_upload"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/upload_here_button"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:textSize="48dp"
|
||||
android:textColor="@color/primary"
|
||||
android:background="?selectableItemBackground"
|
||||
android:text="@string/icon_plus_circle"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/mainProgressBarHolder"
|
||||
android:animateLayoutChanges="true"
|
||||
android:visibility="gone"
|
||||
android:alpha="0.4"
|
||||
android:background="#000000"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:layout_gravity="center" />
|
||||
</FrameLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- The navigation drawer -->
|
||||
|
||||
<ScrollView
|
||||
android:background="?android:windowBackground"
|
||||
android:layout_width="240dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:fadeScrollbars="false">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<LinearLayout
|
||||
android:id="@+id/qrButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp">
|
||||
<TextView
|
||||
android:id="@+id/qr_button_icon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="24dp"
|
||||
android:text="@string/icon_qrcode"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true" />
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="14dp"
|
||||
android:text="add device"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textAlignment="gravity"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/cleanupButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp">
|
||||
<TextView
|
||||
android:id="@+id/cleanup_button_icon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="24dp"
|
||||
android:text="@string/icon_cleanup"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"/>
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="14dp"
|
||||
android:text="clear local cache/index"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textAlignment="gravity"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/updateIndexButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp">
|
||||
<TextView
|
||||
android:id="@+id/update_index_button_icon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="24dp"
|
||||
android:text="@string/icon_update"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true" />
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="14dp"
|
||||
android:text="update remote index"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textAlignment="gravity"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/exitMenuButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp">
|
||||
<TextView
|
||||
android:id="@+id/exit_menu_button_icon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="24dp"
|
||||
android:text="@string/icon_fa_sign_out"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true" />
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="14dp"
|
||||
android:text="exit menu"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textAlignment="gravity"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<!-- device drawer (right) -->
|
||||
<RelativeLayout
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/devicesDrawer"
|
||||
android:background="?android:windowBackground"
|
||||
android:layout_width="280dp"
|
||||
android:layout_gravity="end">
|
||||
<ListView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/devicesListView"
|
||||
android:divider="@color/divider"
|
||||
android:dividerHeight="2dp">
|
||||
|
||||
</ListView>
|
||||
<TextView
|
||||
android:id="@+id/add_device_here_button"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginRight="18dp"
|
||||
android:layout_marginBottom="18dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:textSize="48dp"
|
||||
android:textColor="@color/primary"
|
||||
android:background="?selectableItemBackground"
|
||||
android:text="@string/icon_qrcode"/>
|
||||
</RelativeLayout>
|
||||
|
||||
</android.support.v4.widget.DrawerLayout>
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="32dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingLeft="@dimen/abc_action_bar_content_inset_material"
|
||||
android:paddingRight="@dimen/abc_action_bar_content_inset_material"
|
||||
android:paddingTop="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_icon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="32dp"
|
||||
android:textColor="@color/primary"
|
||||
android:text="@string/icon_fa_laptop"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:layout_alignParentTop="true"
|
||||
android:gravity="top"
|
||||
android:textAlignment="gravity"
|
||||
android:paddingLeft="40dp"
|
||||
android:textSize="18dp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingRight="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/file_icon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="32dp"
|
||||
android:textColor="@color/primary"
|
||||
android:text="@string/icon_folder"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/file_label"
|
||||
android:maxLines="1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_toEndOf="@+id/file_icon"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:textSize="22dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/file_size"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:layout_below="@id/file_label"
|
||||
android:layout_toEndOf="@+id/file_icon"/>
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,56 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="32dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingLeft="@dimen/abc_action_bar_content_inset_material"
|
||||
android:paddingRight="@dimen/abc_action_bar_content_inset_material"
|
||||
android:paddingTop="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/folder_icon"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="60dp"
|
||||
android:textColor="@color/primary"
|
||||
android:text="@string/icon_fa_share_alt"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/folder_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:layout_alignParentTop="true"
|
||||
android:gravity="top"
|
||||
android:textAlignment="gravity"
|
||||
android:paddingLeft="75dp"
|
||||
android:textSize="20dp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/folder_lastmod_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:layout_below="@id/folder_name"
|
||||
android:paddingLeft="75dp"
|
||||
android:textSize="14dp"
|
||||
android:layout_alignParentStart="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/folder_content_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:layout_below="@id/folder_lastmod_info"
|
||||
android:paddingLeft="75dp"
|
||||
android:textSize="14dp"
|
||||
android:layout_alignParentStart="true" />
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="primary">#f43703</color>
|
||||
<color name="primary_dark">#d13602</color>
|
||||
<color name="white_on_primary">#fefefe</color>
|
||||
<color name="accent">#FFC107</color>
|
||||
<color name="divider">#1F000000</color>
|
||||
|
||||
<color name="text_red">#ffff4444</color>
|
||||
<color name="text_blue">#ff33b5e5</color>
|
||||
<color name="text_green">#ff99cc00</color>
|
||||
<color name="light_grey">#cccccc</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,17 @@
|
||||
<resources>
|
||||
<string name="app_name">a-sync-browser</string>
|
||||
|
||||
<string name="icon_cleanup"></string>
|
||||
<string name="icon_qrcode"></string>
|
||||
<string name="icon_folder"></string>
|
||||
<string name="icon_folder_o"></string>
|
||||
<string name="icon_file_o"></string>
|
||||
<string name="icon_update"></string>
|
||||
<string name="icon_plus_circle"></string>
|
||||
<string name="icon_fa_bars"></string>
|
||||
<string name="icon_fa_share_alt"></string>
|
||||
<string name="icon_fa_sign_out"></string>
|
||||
<string name="icon_fa_cloud_upload"></string>
|
||||
<string name="icon_fa_times"></string>
|
||||
<string name="icon_fa_laptop"></string>
|
||||
</resources>
|
||||
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Widget" />
|
||||
<style name="Widget.Syncthing" />
|
||||
|
||||
<style name="Widget.Syncthing.ListView" parent="Widget.AppCompat.ListView">
|
||||
<item name="android:paddingTop">8dp</item>
|
||||
<item name="android:paddingBottom">8dp</item>
|
||||
<item name="android:clipToPadding">false</item>
|
||||
<item name="android:scrollbarStyle">outsideOverlay</item>
|
||||
<item name="android:divider">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Syncthing.TextView" />
|
||||
|
||||
<style name="Widget.Syncthing.TextView.Label" parent="@android:style/Widget.TextView">
|
||||
<item name="android:background">?selectableItemBackground</item>
|
||||
<item name="android:drawablePadding">32dp</item>
|
||||
<item name="android:gravity">start|center_vertical</item>
|
||||
<item name="android:textAppearance">@style/TextAppearance.AppCompat.Body1</item>
|
||||
<item name="android:paddingLeft">@dimen/abc_action_bar_content_inset_material</item>
|
||||
<item name="android:paddingRight">@dimen/abc_action_bar_content_inset_material</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Syncthing.TextView.Label.Details">
|
||||
<item name="android:textColor">?attr/editTextColor</item>
|
||||
<item name="android:minHeight">56dp</item>
|
||||
<item name="android:paddingTop">8dp</item>
|
||||
<item name="android:paddingBottom">8dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Syncthing.TextView.Label.Details.DeviceList">
|
||||
<item name="android:paddingLeft">4dp</item>
|
||||
<item name="android:minHeight">48dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Syncthing.TextView.Label.Details.Field">
|
||||
<item name="android:background">@null</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Syncthing.DrawerArrowToggle" parent="Widget.AppCompat.DrawerArrowToggle">
|
||||
<item name="spinBars">false</item>
|
||||
<item name="color">@android:color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance" />
|
||||
<style name="TextAppearance.Syncthing" />
|
||||
<style name="TextAppearance.Syncthing.ListItemPrimary" parent="TextAppearance.AppCompat.Subhead">
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.Syncthing.ListItemSecondary" parent="TextAppearance.AppCompat.Body1">
|
||||
<item name="android:textColor">?android:textColorSecondary</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.Syncthing.ListItemSmall" parent="TextAppearance.AppCompat.Caption" />
|
||||
|
||||
|
||||
|
||||
<!-- You can also inherit from NNF_BaseTheme.Light -->
|
||||
<style name="FilePickerTheme" parent="NNF_BaseTheme">
|
||||
<!-- Set these to match your theme -->
|
||||
<item name="colorPrimary">@color/primary</item>
|
||||
<item name="colorPrimaryDark">@color/primary_dark</item>
|
||||
<item name="colorAccent">@color/accent</item>
|
||||
|
||||
<!-- Need to set this also to style create folder dialog -->
|
||||
<item name="alertDialogTheme">@style/FilePickerAlertDialogTheme</item>
|
||||
|
||||
<!-- If you want to set a specific toolbar theme, do it here -->
|
||||
<!-- <item name="nnf_toolbarTheme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item> -->
|
||||
</style>
|
||||
|
||||
<style name="FilePickerAlertDialogTheme" parent="Theme.AppCompat.Dialog.Alert">
|
||||
<item name="colorPrimary">@color/primary</item>
|
||||
<item name="colorPrimaryDark">@color/primary_dark</item>
|
||||
<item name="colorAccent">@color/accent</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Activity themes -->
|
||||
<eat-comment/>
|
||||
|
||||
<style name="Theme.Syncthing.Base" parent="@style/Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="colorPrimary">@color/primary</item>
|
||||
<item name="colorPrimaryDark">@color/primary_dark</item>
|
||||
<item name="colorAccent">@color/accent</item>
|
||||
|
||||
<!--<item name="textAppearanceListItemPrimary">@style/TextAppearance.Syncthing.ListItemPrimary</item>-->
|
||||
<!--<item name="textAppearanceListItemSecondary">@style/TextAppearance.Syncthing.ListItemSecondary</item>-->
|
||||
<!--<item name="textAppearanceListItemSmall">@style/TextAppearance.Syncthing.ListItemSmall</item>-->
|
||||
|
||||
<item name="windowActionModeOverlay">true</item>
|
||||
<item name="android:listViewStyle">@style/Widget.Syncthing.ListView</item>
|
||||
<!--<item name="android:listDivider">@drawable/list_divider_inset</item>-->
|
||||
<item name="drawerArrowStyle">@style/Widget.Syncthing.DrawerArrowToggle</item>
|
||||
|
||||
<!--<item name="android:actionBarSize">@dimen/abc_action_bar_default_height_material</item>-->
|
||||
</style>
|
||||
|
||||
<style name="Theme.Syncthing" parent="Theme.Syncthing.Base"/>
|
||||
|
||||
<!--<style name="Theme.Syncthing.Translucent">-->
|
||||
<!--<item name="android:windowBackground">@android:color/transparent</item>-->
|
||||
<!--<item name="android:colorBackgroundCacheHint">@null</item>-->
|
||||
<!--<item name="android:windowIsTranslucent">true</item>-->
|
||||
<!--<item name="android:windowAnimationStyle">@android:style/Animation</item>-->
|
||||
<!--</style>-->
|
||||
|
||||
<!--<!– Dialog themes –>-->
|
||||
<!--<eat-comment/>-->
|
||||
|
||||
<style name="Theme.Syncthing.Dialog" parent="Theme.AppCompat.Light.Dialog.Alert">
|
||||
<item name="colorAccent">@color/primary_dark</item>
|
||||
</style>
|
||||
|
||||
<!--<!– Toolbar themes –>-->
|
||||
<!--<eat-comment/>-->
|
||||
|
||||
<!--<style name="ThemeOverlay.Syncthing.Toolbar" parent="@style/ThemeOverlay.AppCompat.Dark.ActionBar">-->
|
||||
<!--<item name="android:windowBackground">@null</item>-->
|
||||
<!--<item name="colorAccent">@android:color/white</item>-->
|
||||
<!--</style>-->
|
||||
</resources>
|
||||
@@ -0,0 +1,17 @@
|
||||
package it.anyplace.syncbrowser;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() throws Exception {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 123 KiB |
@@ -0,0 +1,25 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.2.2'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
#org.gradle.jvmargs=-Xmx1536m
|
||||
#org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=1g
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
@@ -0,0 +1,6 @@
|
||||
#Mon Dec 28 10:00:20 PST 2015
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
|
||||
|
After Width: | Height: | Size: 114 KiB |
@@ -0,0 +1,11 @@
|
||||
## This file is automatically generated by Android Studio.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must *NOT* be checked into Version Control Systems,
|
||||
# as it contains information specific to your local configuration.
|
||||
#
|
||||
# Location of the SDK. This is only used by Gradle.
|
||||
# For customization when using a Version Control System, please read the
|
||||
# header note.
|
||||
#Sun Nov 20 21:34:36 CET 2016
|
||||
sdk.dir=/home/aleph/android-sdk
|
||||
@@ -0,0 +1 @@
|
||||
include ':app'
|
||||