From 7e4552f5a51f4f40330392688b71eb4298ed1494 Mon Sep 17 00:00:00 2001 From: Lionel Date: Sat, 7 Nov 2020 14:27:56 +0100 Subject: [PATCH] first commit --- .gitignore | 14 + .idea/codeStyles/Project.xml | 116 +++++ .idea/gradle.xml | 21 + .idea/jarRepositories.xml | 25 + .idea/misc.xml | 9 + .idea/runConfigurations.xml | 12 + app/.gitignore | 1 + app/build.gradle | 42 ++ app/proguard-rules.pro | 21 + .../ExampleInstrumentedTest.java | 26 + app/src/main/AndroidManifest.xml | 60 +++ .../main/java/com/localtransfer/Extra.java | 58 +++ .../com/localtransfer/HttpErrorException.java | 9 + .../java/com/localtransfer/MainActivity.java | 212 ++++++++ .../main/java/com/localtransfer/Progress.java | 188 +++++++ .../localtransfer/SectionsPagerAdapter.java | 56 +++ .../com/localtransfer/SettingsActivity.java | 93 ++++ .../main/java/com/localtransfer/Transfer.java | 461 ++++++++++++++++++ .../com/localtransfer/TransferService.java | 75 +++ .../fragment/DownloadFragment.java | 416 ++++++++++++++++ .../fragment/ProgressFragment.java | 70 +++ .../fragment/UploadFragment.java | 103 ++++ app/src/main/res/drawable/circle_shape.xml | 11 + .../res/drawable/circular_progress_bar.xml | 19 + app/src/main/res/drawable/ic_download.xml | 9 + app/src/main/res/drawable/ic_download_24.xml | 11 + app/src/main/res/drawable/ic_folder_open.xml | 10 + app/src/main/res/drawable/ic_icon_file.xml | 10 + app/src/main/res/drawable/ic_icon_image.xml | 10 + app/src/main/res/drawable/ic_icon_music.xml | 10 + app/src/main/res/drawable/ic_icon_video.xml | 10 + app/src/main/res/drawable/ic_launcher.xml | 14 + .../res/drawable/ic_launcher_foreground.xml | 14 + app/src/main/res/drawable/ic_open.xml | 10 + app/src/main/res/drawable/ic_settings.xml | 10 + app/src/main/res/drawable/ic_share.xml | 10 + app/src/main/res/drawable/ic_spinner.xml | 4 + .../main/res/drawable/ic_spinner_rotate.xml | 10 + app/src/main/res/drawable/ic_upload.xml | 9 + app/src/main/res/drawable/ic_upload_24.xml | 11 + app/src/main/res/drawable/ic_upload_32.xml | 11 + .../ic_upload_and_download_from_the_cloud.xml | 9 + app/src/main/res/layout/activity_main.xml | 42 ++ app/src/main/res/layout/file_info.xml | 97 ++++ app/src/main/res/layout/fragment_download.xml | 37 ++ app/src/main/res/layout/fragment_main.xml | 23 + app/src/main/res/layout/fragment_progress.xml | 22 + app/src/main/res/layout/fragment_upload.xml | 26 + app/src/main/res/layout/horizontal_line.xml | 13 + app/src/main/res/layout/progress.xml | 65 +++ app/src/main/res/layout/settings_activity.xml | 9 + app/src/main/res/menu/main.xml | 16 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2202 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4189 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1523 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2626 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 2993 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 5915 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 4444 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 8959 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 6133 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 12844 bytes app/src/main/res/values-w820dp/dimens.xml | 6 + app/src/main/res/values/arrays.xml | 12 + app/src/main/res/values/colors.xml | 6 + app/src/main/res/values/dimens.xml | 9 + .../res/values/ic_launcher_background.xml | 4 + app/src/main/res/values/ids.xml | 9 + app/src/main/res/values/strings.xml | 43 ++ app/src/main/res/values/styles.xml | 19 + .../res/xml/preference_edit_text_input.xml | 19 + app/src/main/res/xml/provider_paths.xml | 8 + app/src/main/res/xml/root_preferences.xml | 42 ++ .../com/localtransfer/ExampleUnitTest.java | 17 + build.gradle | 24 + gradle.properties | 19 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 +++++++ gradlew.bat | 84 ++++ settings.gradle | 2 + 83 files changed, 3161 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/localtransfer/ExampleInstrumentedTest.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/localtransfer/Extra.java create mode 100644 app/src/main/java/com/localtransfer/HttpErrorException.java create mode 100644 app/src/main/java/com/localtransfer/MainActivity.java create mode 100644 app/src/main/java/com/localtransfer/Progress.java create mode 100644 app/src/main/java/com/localtransfer/SectionsPagerAdapter.java create mode 100644 app/src/main/java/com/localtransfer/SettingsActivity.java create mode 100644 app/src/main/java/com/localtransfer/Transfer.java create mode 100644 app/src/main/java/com/localtransfer/TransferService.java create mode 100644 app/src/main/java/com/localtransfer/fragment/DownloadFragment.java create mode 100644 app/src/main/java/com/localtransfer/fragment/ProgressFragment.java create mode 100644 app/src/main/java/com/localtransfer/fragment/UploadFragment.java create mode 100644 app/src/main/res/drawable/circle_shape.xml create mode 100644 app/src/main/res/drawable/circular_progress_bar.xml create mode 100644 app/src/main/res/drawable/ic_download.xml create mode 100644 app/src/main/res/drawable/ic_download_24.xml create mode 100644 app/src/main/res/drawable/ic_folder_open.xml create mode 100644 app/src/main/res/drawable/ic_icon_file.xml create mode 100644 app/src/main/res/drawable/ic_icon_image.xml create mode 100644 app/src/main/res/drawable/ic_icon_music.xml create mode 100644 app/src/main/res/drawable/ic_icon_video.xml create mode 100644 app/src/main/res/drawable/ic_launcher.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_open.xml create mode 100644 app/src/main/res/drawable/ic_settings.xml create mode 100644 app/src/main/res/drawable/ic_share.xml create mode 100644 app/src/main/res/drawable/ic_spinner.xml create mode 100644 app/src/main/res/drawable/ic_spinner_rotate.xml create mode 100644 app/src/main/res/drawable/ic_upload.xml create mode 100644 app/src/main/res/drawable/ic_upload_24.xml create mode 100644 app/src/main/res/drawable/ic_upload_32.xml create mode 100644 app/src/main/res/drawable/ic_upload_and_download_from_the_cloud.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/file_info.xml create mode 100644 app/src/main/res/layout/fragment_download.xml create mode 100644 app/src/main/res/layout/fragment_main.xml create mode 100644 app/src/main/res/layout/fragment_progress.xml create mode 100644 app/src/main/res/layout/fragment_upload.xml create mode 100644 app/src/main/res/layout/horizontal_line.xml create mode 100644 app/src/main/res/layout/progress.xml create mode 100644 app/src/main/res/layout/settings_activity.xml create mode 100644 app/src/main/res/menu/main.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values-w820dp/dimens.xml create mode 100644 app/src/main/res/values/arrays.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/ic_launcher_background.xml create mode 100644 app/src/main/res/values/ids.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/xml/preference_edit_text_input.xml create mode 100644 app/src/main/res/xml/provider_paths.xml create mode 100644 app/src/main/res/xml/root_preferences.xml create mode 100644 app/src/test/java/com/localtransfer/ExampleUnitTest.java create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..603b140 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..681f41a --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,116 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..ac6b0ae --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..a5f05cd --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..7bfef59 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..f369b87 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,42 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.1" + + defaultConfig { + applicationId "com.localtransfer" + minSdkVersion 24 + targetSdkVersion 30 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'com.google.android.material:material:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation 'androidx.preference:preference:1.1.1' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + implementation 'lib.kashif:folderpicker:2.4' + +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/localtransfer/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/localtransfer/ExampleInstrumentedTest.java new file mode 100644 index 0000000..7241b24 --- /dev/null +++ b/app/src/androidTest/java/com/localtransfer/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.localtransfer; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.localtransfer", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..30b41ea --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/localtransfer/Extra.java b/app/src/main/java/com/localtransfer/Extra.java new file mode 100644 index 0000000..7ea5bcd --- /dev/null +++ b/app/src/main/java/com/localtransfer/Extra.java @@ -0,0 +1,58 @@ +package com.localtransfer; + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.View; + +public class Extra implements Parcelable { + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Extra createFromParcel(Parcel in) { + return new Extra(in); + } + + public Extra[] newArray(int size) { + return new Extra[size]; + } + }; + + public String name; + public long size; + public String href; + public String uri; + public int id; + + // Constructor + public Extra(Uri uri, String name, long size, String href, View view){ + this.uri = uri.toString(); + this.name = name; + this.size = size; + this.href = href; + this.id = view.getId(); + } + + + // Parcelling part + public Extra(Parcel in){ + this.uri = in.readString(); + this.name = in.readString(); + this.size = in.readLong(); + this.href = in.readString(); + this.id = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.uri); + dest.writeString(this.name); + dest.writeLong(this.size); + dest.writeString(this.href); + dest.writeInt(this.id); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/localtransfer/HttpErrorException.java b/app/src/main/java/com/localtransfer/HttpErrorException.java new file mode 100644 index 0000000..aada94c --- /dev/null +++ b/app/src/main/java/com/localtransfer/HttpErrorException.java @@ -0,0 +1,9 @@ +package com.localtransfer; + +import java.io.IOException; + +public class HttpErrorException extends IOException { + public HttpErrorException(String message) { + super(message); + } +} diff --git a/app/src/main/java/com/localtransfer/MainActivity.java b/app/src/main/java/com/localtransfer/MainActivity.java new file mode 100644 index 0000000..57cdb31 --- /dev/null +++ b/app/src/main/java/com/localtransfer/MainActivity.java @@ -0,0 +1,212 @@ +package com.localtransfer; + +import android.Manifest; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.tabs.TabLayout; + +import androidx.appcompat.widget.Toolbar; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.preference.PreferenceManager; +import androidx.viewpager.widget.ViewPager; +import androidx.appcompat.app.AppCompatActivity; + +import android.os.Environment; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; + +import java.io.File; +import java.util.ArrayList; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager()); + ViewPager viewPager = findViewById(R.id.view_pager); + viewPager.setAdapter(sectionsPagerAdapter); + TabLayout tabs = findViewById(R.id.tabs); + tabs.setupWithViewPager(viewPager); + FloatingActionButton fab = findViewById(R.id.flotUpload); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + + } + }); + + checkAndRequestPermissions(); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + + if(prefs.getString("host", null) == null) + prefs.edit() + .putString("host", getString(R.string.server_host_def)) + .putString("port", getString(R.string.server_port_def)) + .putString("root", getString(R.string.server_root_def)) + .putString("local_storage", getString(R.string.local_storage_def)) + .apply(); + + Transfer.parameter(this); + Transfer.activity = this; + + Intent intent = getIntent(); + String action = intent.getAction(); + String type = intent.getType(); + + Transfer tr = new Transfer(); + + if (Intent.ACTION_SEND.equals(action) && type != null) { + viewPager.setCurrentItem(2); + if ("text/plain".equals(type)) { + String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT); + tr.handleSendText(sharedText); + } else { + Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM); + new Thread(() -> tr.handleSendFile(uri)).start(); + } + } else if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) { + viewPager.setCurrentItem(2); + ArrayList fileUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); + int nbItem = fileUris.size(); + Toast.makeText(this, "You select " + nbItem + " files", Toast.LENGTH_SHORT).show(); + if (fileUris != null) + for(Uri uri : fileUris) tr.handleSendFile(uri); + } + + } + + @Override + public void onResume(){ + super.onResume(); + + Progress.app_started = true; + } + + @Override + public void onPause(){ + super.onPause(); + + Progress.app_started = false; + } + + @Override + public void onDestroy() { + super.onDestroy(); + + File dir = getCacheDir(); + deleteDirContent(dir); + } + + private static void deleteDirContent(File dir) { + if (dir != null && dir.isDirectory()) { + String[] children = dir.list(); + for (int i = 0; i < children.length; i++) { + File file = new File(dir, children[i]); + if (file.isFile()) + file.delete(); + } + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + switch (id) { + case R.id.action_settings: + Intent settings = new Intent(this, SettingsActivity.class); + startActivity(settings); + break; + case R.id.action_folder: + String save_location = Environment.getExternalStorageDirectory() + "/" + Transfer.local_storage; + Uri selectedUri = Uri.parse(save_location.toString()); + boolean intentSuccess = false; + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(selectedUri, "resource/folder"); + intent.setFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT | + Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + startActivity(intent); + intentSuccess = true; + } catch (ActivityNotFoundException e) { + e.printStackTrace(); + intentSuccess = false; + } + if( ! intentSuccess && isPackageInstalled("com.sec.android.app.myfiles", getPackageManager())){ + + Intent intent = getPackageManager().getLaunchIntentForPackage("com.sec.android.app.myfiles"); + intent.setData(selectedUri); + startActivity(intent); + } + else if(! intentSuccess) + Toast.makeText(this, "No file Manager", Toast.LENGTH_SHORT).show(); + break; + } + + return super.onOptionsItemSelected(item); + } + + public static final int REQUEST_ID_READ_EXTERNAL_STORAGE = 2001; + + private boolean checkAndRequestPermissions() { + + if (ContextCompat.checkSelfPermission( + this, Manifest.permission.READ_EXTERNAL_STORAGE) == + PackageManager.PERMISSION_GRANTED) { + Log.d("READ_EXTERNAL_STORAGE", "already granted"); + } + else { + ActivityCompat.requestPermissions(this, new String[] {android.Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_ID_READ_EXTERNAL_STORAGE); + } + return true; + } + + @Override + public void onRequestPermissionsResult(int requestCode, + String permissions[], + int[] grantResults) { + switch (requestCode) { + case REQUEST_ID_READ_EXTERNAL_STORAGE: + if (grantResults.length > 0 && + grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Log.d("READ_EXTERNAL_STORAGE", "granted"); + } else { + Log.d("READ_EXTERNAL_STORAGE", "No granted"); + System.exit(1); + } + return; + } + } + + private boolean isPackageInstalled(String packageName, PackageManager packageManager) { + try { + packageManager.getPackageInfo(packageName, 0); + return true; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/localtransfer/Progress.java b/app/src/main/java/com/localtransfer/Progress.java new file mode 100644 index 0000000..a9ab623 --- /dev/null +++ b/app/src/main/java/com/localtransfer/Progress.java @@ -0,0 +1,188 @@ +package com.localtransfer; + +import android.app.PendingIntent; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + +import java.util.ArrayList; +import java.util.ConcurrentModificationException; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +public class Progress { + + public static final int UPLOAD = 1000; + public static final int DOWNLOAD = 1001; + + + private static NotificationCompat.Builder bl = new NotificationCompat.Builder(Transfer.activity, null); + private static NotificationManagerCompat notifiManager = NotificationManagerCompat.from(Transfer.activity); + private static final LayoutInflater inflater = (LayoutInflater) Transfer.activity.getSystemService(Transfer.activity.LAYOUT_INFLATER_SERVICE); + + private static List instances = new ArrayList(); + + private Integer id; + private String name; + private long size; + + public long loaded; + public long percent; + + public static View root; + public static boolean fragment_on = false; + public static boolean app_started = false; + + private Timer timer = new Timer(); + private int drawable; + private String info; + + public Button button; + private boolean run = false; + + public Progress(String name, long size, int type) { + + instances.add(this); + + run = true; + + this.name = name; + this.size = size; + + id = View.generateViewId(); + + switch (type) { + case Progress.DOWNLOAD: + drawable = R.drawable.ic_download_24; + info = "Download complete"; + break; + case Progress.UPLOAD: + drawable = R.drawable.ic_upload_24; + info = "Upload complete"; + break; + default: + throw new IllegalStateException("Unexpected value: " + type); + } + + timer.schedule(new TimerTask() { + @Override + public void run() { + showProgress(); + } + }, 0, 1000); + } + + public static List getInstances() { + return instances; + } + + public void stopProgress() { + timer.cancel(); + showProgress(); + + run = false; + + if(! app_started) { + bl.setSmallIcon(R.drawable.ic_upload_and_download_from_the_cloud) + .setContentTitle(name) + .setContentText(info) + .setProgress(0, 0, false) + .setAutoCancel(true) + .setOngoing(false); + notifiManager.notify(id, bl.build()); + } + } + + public void delete() { + instances.remove(this); + } + + public static Progress getProgress(int id) { + + for (Object obj: instances) { + Progress p = (Progress) obj; + if (p.id == id) return p; + } + return null; + } + + public static boolean setButton(String name, Button button) { + + for (Object obj: instances) { + Progress p = (Progress) obj; + if (name.equals(p.name) && p.run) { + p.button = button; + return true; + } + } + return false; + } + + + public void showProgress(){ + + try { + + String HumanLoaded = Transfer.humanReadableByteCountBin(loaded); + String HumanSize = Transfer.humanReadableByteCountBin(size); + + if(app_started) { + notifiManager.cancel(id); + } + else { + + Intent intent = new Intent(Transfer.activity, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + PendingIntent pendingIntent = PendingIntent.getActivity(Transfer.activity, 0, intent, 0); + + bl.setSmallIcon(R.drawable.ic_upload_and_download_from_the_cloud) + .setContentTitle(name) + .setContentText(String.format("%d%% %s/%s", percent, HumanLoaded, HumanSize)) + .setProgress(100, (int) percent, false) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + .setOngoing(true); + notifiManager.notify(id, bl.build()); + } + + if(app_started && fragment_on) { + LinearLayout groot = root.findViewById(R.id.groot); + + final View progress; + + if (groot.findViewById(id) != null) { + progress = groot.findViewById(id); + } else { + progress = inflater.inflate(R.layout.progress, null); + progress.setId(id); + Transfer.activity.runOnUiThread(() -> { + groot.addView(progress, 0); + }); + } + + Transfer.activity.runOnUiThread(() -> { + ((ProgressBar) progress.findViewById(R.id.progressBar)).setProgress((int) percent); + ((ImageView) progress.findViewById(R.id.transferType)).setImageResource(drawable); + ((TextView) progress.findViewById(R.id.progressText)).setText(percent + " %"); + ((TextView) progress.findViewById(R.id.fileName)).setText(name); + ((TextView) progress.findViewById(R.id.fileSize)).setText(String.format("%s/%s", HumanLoaded, HumanSize)); + }); + + } + + } catch (ConcurrentModificationException e) { + e.printStackTrace(); + } + + } + +} diff --git a/app/src/main/java/com/localtransfer/SectionsPagerAdapter.java b/app/src/main/java/com/localtransfer/SectionsPagerAdapter.java new file mode 100644 index 0000000..f2dcbc2 --- /dev/null +++ b/app/src/main/java/com/localtransfer/SectionsPagerAdapter.java @@ -0,0 +1,56 @@ +package com.localtransfer; + +import android.content.Context; + +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; + +import com.localtransfer.fragment.DownloadFragment; +import com.localtransfer.fragment.ProgressFragment; +import com.localtransfer.fragment.UploadFragment; + +/** + * A [FragmentPagerAdapter] that returns a fragment corresponding to + * one of the sections/tabs/pages. + */ +public class SectionsPagerAdapter extends FragmentPagerAdapter { + + @StringRes + private static final int[] TAB_TITLES = new int[]{R.string.download, R.string.upload, R.string.progress}; + private final Context mContext; + + public SectionsPagerAdapter(Context context, FragmentManager fm) { + super(fm); + mContext = context; + } + + @Override + public Fragment getItem(int position) { + // getItem is called to instantiate the fragment for the given page. + // Return a PlaceholderFragment (defined as a static inner class below). + switch (position){ + case 0: //Page number 1 + return DownloadFragment.newInstance(); + case 1: //Page number 2 + return UploadFragment.newInstance(); + case 2: //Page number 3 + return ProgressFragment.newInstance(); + default: + return null; + } + } + + @Nullable + @Override + public CharSequence getPageTitle(int position) { + return mContext.getResources().getString(TAB_TITLES[position]); + } + + @Override + public int getCount() { + return 3; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/localtransfer/SettingsActivity.java b/app/src/main/java/com/localtransfer/SettingsActivity.java new file mode 100644 index 0000000..43d97d6 --- /dev/null +++ b/app/src/main/java/com/localtransfer/SettingsActivity.java @@ -0,0 +1,93 @@ +package com.localtransfer; + +import android.app.Activity; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.Environment; +import android.util.Log; +import android.view.MenuItem; + +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; + +import lib.folderpicker.FolderPicker; + +public class SettingsActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.settings_activity); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.settings, new SettingsFragment()) + .commit(); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + } + + public static class SettingsFragment extends PreferenceFragmentCompat { + + private final static int REQUEST_DIRECTORY_PICKER = 4000; + static Preference directory; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(R.xml.root_preferences, rootKey); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); + directory = findPreference("local_storage"); + directory.setSummary(prefs.getString("local_storage", null)); + directory.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + Intent intent = new Intent(getContext(), FolderPicker.class); + startActivityForResult(intent, REQUEST_DIRECTORY_PICKER); + return false; + } + }); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case REQUEST_DIRECTORY_PICKER: + if (resultCode == Activity.RESULT_OK) { + String currentPath = data.getExtras().getString("data"); + if(currentPath.contains(Environment.getExternalStorageDirectory() + "/")) { + String save_location = currentPath.replace(Environment.getExternalStorageDirectory() + "/", ""); + Log.d("Path", save_location); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); + prefs.edit().putString("local_storage", save_location).apply(); + directory.setSummary(save_location); + } + } + break; + } + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + Transfer.parameter(this); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + + if (id == android.R.id.home) { + onBackPressed(); return true; + } + + return super.onOptionsItemSelected(item); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/localtransfer/Transfer.java b/app/src/main/java/com/localtransfer/Transfer.java new file mode 100644 index 0000000..b752e6c --- /dev/null +++ b/app/src/main/java/com/localtransfer/Transfer.java @@ -0,0 +1,461 @@ +package com.localtransfer; + +import android.animation.ObjectAnimator; +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.provider.OpenableColumns; +import android.util.Log; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.LinearInterpolator; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; +import androidx.core.content.FileProvider; +import androidx.preference.PreferenceManager; + +import com.google.android.material.snackbar.Snackbar; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Serializable; +import java.net.HttpURLConnection; +import java.net.ProtocolException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.CharacterIterator; +import java.text.SimpleDateFormat; +import java.text.StringCharacterIterator; +import java.util.ConcurrentModificationException; +import java.util.Date; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import static java.lang.Integer.valueOf; + +public class Transfer { + + public static String host; + public static Integer port; + public static String root; + public static String protocol; + public static String local_storage; + + public static Activity activity; + + public static void parameter(Context context) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + host = prefs.getString("host", null); + port = valueOf(prefs.getString("port", null)); + root = prefs.getString("root", null); + local_storage = prefs.getString("local_storage", null); + if(prefs.getBoolean("protocol", false)) + protocol = "https"; + else + protocol = "http"; + } + + private URL checkRedirection(URL url) throws IOException { + String location = null; + HttpURLConnection conn = null; + int responseCode; + int nbRedirect = 0; + + do { + + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setInstanceFollowRedirects(false); + conn.setConnectTimeout(2000); + + conn.connect(); + responseCode = conn.getResponseCode(); + + Log.d("Réponse Code", String.valueOf(responseCode)); + + if(responseCode != HttpURLConnection.HTTP_MOVED_PERM && responseCode != HttpURLConnection.HTTP_MOVED_TEMP) { + break; + } + + location = conn.getHeaderField("Location"); + Log.d("Location", location); + + url = new URL(location); + + + conn.disconnect(); + + nbRedirect++; + if(nbRedirect > 10) { + throw new ProtocolException("Too many follow-up requests: " + nbRedirect); + } + + + } while (responseCode == HttpURLConnection.HTTP_MOVED_PERM || responseCode == HttpURLConnection.HTTP_MOVED_TEMP); + + return url; + } + + public void handleSendFile(Uri uri) { + if (uri != null) { + new Thread(() -> { + try { + uploadFile(uri); + } catch (IOException e) { + final String ExceptionName = e.getClass().getSimpleName(); + final String ExceptionMess = e.getMessage(); + + if(ExceptionName != null && ExceptionMess != null) { + Transfer.error(ExceptionName + ": " + ExceptionMess, null, null); + } + + } + }).start(); + } + } + + public void handleSendText(String sharedText) { + if (sharedText != null) { + SimpleDateFormat sdfDate = new SimpleDateFormat("yyyyMMdd_HHmmss"); + Date now = new Date(); + String strDate = sdfDate.format(now); + new Thread(() -> { + try { + File outputFile = new File(activity.getCacheDir(), strDate + ".txt"); + FileOutputStream fileOut = new FileOutputStream(outputFile); + OutputStreamWriter OutWriter = new OutputStreamWriter(fileOut); + OutWriter.append(sharedText); + OutWriter.close(); + + fileOut.flush(); + fileOut.close(); + + Uri uri = FileProvider.getUriForFile(activity, activity.getPackageName(), outputFile); + + uploadFile(uri); + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); + } + } + + public String uploadFile(Uri uri) throws IOException { + String message = null; + URL url = null; + HttpURLConnection conn = null; + DataOutputStream request = null; + String boundary = "*****"; + int maxBufferSize = 1 * 1024 * 1024; + byte[] buffer = new byte[maxBufferSize]; + int bufferLength; + long loaded = 0; + + Cursor cursor = activity.getContentResolver().query(uri, null, null, null, null); + cursor.moveToFirst(); + String fileName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + String fileNameUTF8 = new String(fileName.getBytes(), StandardCharsets.ISO_8859_1); + long fileSize = cursor.getLong(cursor.getColumnIndex(OpenableColumns.SIZE)); + String type = activity.getContentResolver().getType(uri); + + url = new URL(protocol, host, port, root + "/upload.php"); + + url = checkRedirection(url); + + Progress p = new Progress(fileName, fileSize, Progress.UPLOAD); + + if(url != null) { + + Log.d("URL", url.toString()); + + conn = (HttpURLConnection) url.openConnection(); + conn.setDoInput(true); // Allow Inputs + conn.setDoOutput(true); // Allow Outputs + conn.setUseCaches(false); // Don't use a Cached Copy + conn.setChunkedStreamingMode(maxBufferSize); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Connection", "keep-alive"); + conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); + + conn.connect(); + + request = new DataOutputStream(conn.getOutputStream()); + + request.writeBytes("--" + boundary + "\r\n"); + request.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + fileNameUTF8 + "\"\r\n"); + request.writeBytes("Content-Type: " + type + "\r\n\r\n"); + + InputStream in = activity.getContentResolver().openInputStream(uri); + + while ((bufferLength = in.read(buffer)) > 0) { + request.write(buffer, 0, bufferLength); + loaded+= (long) bufferLength; + p.loaded = loaded; + p.percent = ((loaded * 100) / fileSize); + } + + request.writeBytes("\r\n"); + request.writeBytes("--" + boundary + "--\r\n"); + + in.close(); + + int responseCode = conn.getResponseCode(); + + if (responseCode == HttpURLConnection.HTTP_OK) { + message = "File " + fileName + " successful upload"; + p.stopProgress(); + } + else { + String s = conn.getResponseMessage(); + throw new HttpErrorException("error code: " + responseCode + " " + s); + } + + request.flush(); + + request.close(); + + conn.disconnect(); + + System.out.println(message); + if (activity != null) { + String finalMessage = message; + activity.runOnUiThread(() -> + Snackbar.make(activity.findViewById(R.id.view_pager), finalMessage, Snackbar.LENGTH_LONG) + .setAction("Action", null).show()); + } + } + + return message; + } + + public String getFileList() throws IOException { + URL url = null; + HttpURLConnection conn = null; + + String fileList = ""; + int buf; + + url = new URL(protocol, host, port, root + "/list.php"); + conn = (HttpURLConnection) url.openConnection(); + conn.setConnectTimeout(2000); + + conn.connect(); + + int responseCode = conn.getResponseCode(); + + if (responseCode == HttpURLConnection.HTTP_OK) { + InputStreamReader isw = new InputStreamReader(conn.getInputStream()); + + while ((buf = isw.read()) > 0) { + fileList = fileList + (char) buf; + } + } + else { + String s = conn.getResponseMessage(); + throw new HttpErrorException("error code: " + responseCode + " " + s); + } + + conn.disconnect(); + + return fileList; + } + + public void downloadFile(final Uri uri, String name, long fileSize, String href, Button button) throws IOException { + + /*final LinearLayout layout = activity.findViewById(id); + final Button button; + if(layout != null) + button = layout.findViewById(R.id.file_download); + else + button = null;*/ + + final File file = new File(uri.getPath()); + + URL url = null; + HttpURLConnection conn = null; + int maxBufferSize = 1 * 1024 * 1024; + byte[] buffer = new byte[maxBufferSize]; + int bufferLength; + long loaded = 0; + + final Drawable image = activity.getDrawable(R.drawable.ic_spinner_rotate); + + Progress p = new Progress(name, fileSize, Progress.DOWNLOAD); + if(button != null) + p.button = button; + + if(p.button != null) { + activity.runOnUiThread(() -> { + int h = image.getIntrinsicHeight(); + int w = image.getIntrinsicWidth(); + image.setBounds(0, 0, w, h); + p.button.setCompoundDrawables(button.getCompoundDrawables()[0], null, image, null); + ObjectAnimator anim = ObjectAnimator.ofInt(image, "level", 0, 10000); + anim.setDuration(1000); + anim.setRepeatCount(Animation.INFINITE); + anim.setInterpolator(new LinearInterpolator()); + anim.start(); + }); + } + + String[] parts = href.split("/"); + String path = ""; + for(int i=0; i< parts.length - 1; i++) { + path = path + parts[i] + "/"; + } + String query = URLEncoder.encode(parts[parts.length - 1], StandardCharsets.UTF_8.toString()); + url = new URL(protocol, host, port, path + query.replace("+", "%20")); + conn = (HttpURLConnection) url.openConnection(); + conn.setDoOutput(true); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(2000); + + Log.d("URL", url.toString()); + + conn.connect(); + + int responseCode = conn.getResponseCode(); + + if (responseCode == HttpURLConnection.HTTP_OK) { + + FileOutputStream fileOutput = new FileOutputStream(file); + InputStream in = conn.getInputStream(); + + while ((bufferLength = in.read(buffer)) > 0) { + fileOutput.write(buffer, 0, bufferLength); + loaded+= (long) bufferLength; + p.loaded = loaded; + p.percent = ((loaded * 100) / fileSize); + if(Progress.app_started && p.button != null) + activity.runOnUiThread(() -> p.button.setText(String.format("Download in progress %d%%", p.percent))); + } + fileOutput.close(); + + conn.disconnect(); + + p.stopProgress(); + + if(p.button != null) { + activity.runOnUiThread(() -> { + final LinearLayout layout = (LinearLayout) p.button.getParent(); + layout.findViewById(R.id.file_view).setVisibility(LinearLayout.VISIBLE); + layout.findViewById(R.id.file_share).setVisibility(LinearLayout.VISIBLE); + p.button.setVisibility(LinearLayout.GONE); + p.button.setEnabled(true); + + p.button.setText(activity.getString(R.string.file_download)); + p.button.setCompoundDrawables(p.button.getCompoundDrawables()[0], null, null, null); + }); + } + activity.runOnUiThread(() -> { + String message = "File " + name + " successful download"; + System.out.println(message); + Snackbar.make(activity.findViewById(R.id.view_pager), message, Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + }); + } + else { + String s = conn.getResponseMessage(); + throw new HttpErrorException("error code: " + responseCode + " " + s); + } + + conn.disconnect(); + } + + public String deleteFile(String file) throws IOException { + + URL url = null; + HttpURLConnection conn = null; + + String message; + + String query = URLEncoder.encode(file, StandardCharsets.UTF_8.toString()); + String serverUrl = root + "/delete.php?file=" + query; + url = new URL(protocol, host, port, serverUrl); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(2000); + + conn.connect(); + + int responseCode = conn.getResponseCode(); + + if (responseCode == HttpURLConnection.HTTP_OK) { + + message = "File " + file + " successful deleted"; + } + else { + String s = conn.getResponseMessage(); + throw new HttpErrorException("error code: " + responseCode + " " + s); + } + + conn.disconnect(); + + return message; + } + + private static int timeout; + + public static void error(final String message, final TextView title, final LinearLayout layout) { + + System.out.println(message); + + if (timeout == 0) { + if (activity != null) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if(layout != null) + layout.removeAllViews(); + if(title != null) { + layout.addView(title); + title.setText(message); + } + Snackbar.make(activity.findViewById(R.id.view_pager), message, Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + } + }); + } + timeout = 1; + new Timer().schedule(new TimerTask() { + @Override + public void run() { + timeout = 0; + } + }, 3000); + } + } + + public static String humanReadableByteCountBin(long bytes) { + long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes); + if (absB < 1024) { + return bytes + " B"; + } + long value = absB; + CharacterIterator ci = new StringCharacterIterator("KMGTPE"); + for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) { + value >>= 10; + ci.next(); + } + value *= Long.signum(bytes); + return String.format("%.1f %ciB", value / 1024.0, ci.current()); + } +} + diff --git a/app/src/main/java/com/localtransfer/TransferService.java b/app/src/main/java/com/localtransfer/TransferService.java new file mode 100644 index 0000000..91dc265 --- /dev/null +++ b/app/src/main/java/com/localtransfer/TransferService.java @@ -0,0 +1,75 @@ +package com.localtransfer; + +import android.app.IntentService; +import android.content.Intent; +import android.net.Uri; +import android.widget.Button; +import android.widget.LinearLayout; + +import java.io.IOException; + +public class TransferService extends IntentService { + + public static final String ACTION_UPLOAD = "com.localtransfer.action.FOO"; + public static final String ACTION_DOWNLOAD = "com.localtransfer.action.BAZ"; + + public static final String EXTRA_URI = "com.localtransfer.extra.URI"; + public static final String EXTRA = "com.localtransfer.extra.EXTRA"; + + public TransferService() { + super("TransferService"); + } + + @Override + protected void onHandleIntent(Intent intent) { + if (intent != null) { + final String action = intent.getAction(); + switch (action) { + case ACTION_UPLOAD: + handleUpload(intent); + break; + case ACTION_DOWNLOAD: + handleDownload(intent); + break; + } + } + } + + private void handleUpload(Intent intent) { + final Uri uri = intent.getParcelableExtra(EXTRA_URI); + + Transfer tr = new Transfer(); + try { + tr.uploadFile(uri); + } catch (IOException e) { + final String ExceptionName = e.getClass().getSimpleName(); + final String ExceptionMess = e.getMessage(); + + if(ExceptionName != null && ExceptionMess != null) { + Transfer.error(ExceptionName + ": " + ExceptionMess, null, null); + } + + } + } + + private void handleDownload(Intent intent) { + Extra ex = (Extra) intent.getExtras().getParcelable(EXTRA); + final LinearLayout layout = Transfer.activity.findViewById(ex.id); + Button button = null; + if(layout != null) + button = layout.findViewById(R.id.file_download); + + Transfer tr = new Transfer(); + try { + tr.downloadFile(Uri.parse(ex.uri), ex.name, ex.size, ex.href, button); + } catch (IOException e) { + final String ExceptionName = e.getClass().getSimpleName(); + final String ExceptionMess = e.getMessage(); + + if(ExceptionName != null && ExceptionMess != null) { + Transfer.error(ExceptionName + ": " + ExceptionMess, null, null); + } + + } + } +} diff --git a/app/src/main/java/com/localtransfer/fragment/DownloadFragment.java b/app/src/main/java/com/localtransfer/fragment/DownloadFragment.java new file mode 100644 index 0000000..08776fe --- /dev/null +++ b/app/src/main/java/com/localtransfer/fragment/DownloadFragment.java @@ -0,0 +1,416 @@ +package com.localtransfer.fragment; + +import android.animation.ObjectAnimator; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; + +import androidx.core.content.FileProvider; +import androidx.fragment.app.Fragment; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import android.os.Environment; +import android.os.Parcelable; +import android.util.Log; +import android.view.ContextMenu; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.LinearInterpolator; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.google.android.material.snackbar.Snackbar; +import com.localtransfer.BuildConfig; +import com.localtransfer.Extra; +import com.localtransfer.Progress; +import com.localtransfer.R; +import com.localtransfer.Transfer; +import com.localtransfer.TransferService; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.text.CharacterIterator; +import java.text.StringCharacterIterator; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +public class DownloadFragment extends Fragment { + + public DownloadFragment() { + // Required empty public constructor + } + + private View root; + + public static DownloadFragment newInstance() { + DownloadFragment fragment = new DownloadFragment(); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + root = inflater.inflate(R.layout.fragment_download, container, false); + + new Timer().schedule(new TimerTask() { + @Override + public void run() { + showFileList(); + } + }, 100); + + SwipeRefreshLayout refresh = root.findViewById(R.id.refresh); + + refresh.setOnRefreshListener(() -> { + new Thread(() -> showFileList()).start(); + refresh.setRefreshing(false); + }); + + return root; + } + + @Override + public void onPause(){ + super.onPause(); + + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + menu.add ( 0 , v.getId (), 0 , "Delete" ); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + if (item.getTitle() == "Delete") { + LinearLayout fileDesc = (LinearLayout) root.findViewById(item.getItemId()); + final String file = String.valueOf(fileDesc.getTag(R.id.ID_FILE_NAME)); + new Thread(() -> { + try { + Transfer tr = new Transfer(); + String message = tr.deleteFile(file); + System.out.println(message); + Transfer.activity.runOnUiThread(() -> + Snackbar.make(getActivity().findViewById(R.id.view_pager), message, Snackbar.LENGTH_LONG) + .setAction("Action", null).show()); + showFileList(); + } catch (IOException e) { + String ExceptionName = e.getClass().getSimpleName(); + String ExceptionMess = e.getMessage(); + + if(ExceptionName != null && ExceptionMess != null) { + Transfer.error(ExceptionName + ": " + ExceptionMess, null, null); + } + + } + }).start(); + } + else { + return false; + } + return true; + } + + public void showFileList() { + + final TextView title = new TextView(Transfer.activity); + title.setTextSize(18); + title.setGravity(Gravity.CENTER); + final LinearLayout main_layout = root.findViewById(R.id.main_layout); + final LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(getContext().LAYOUT_INFLATER_SERVICE); + + Transfer tr = new Transfer(); + + String data = ""; + + try { + data = tr.getFileList(); + } catch (IOException e) { + final String ExceptionName = e.getClass().getSimpleName(); + final String ExceptionMess = e.getMessage(); + + if(ExceptionName != null && ExceptionMess != null) { + Transfer.error(ExceptionName + ": " + ExceptionMess, title, main_layout); + } + + } + if (data.equals("[]")) { + Transfer.activity.runOnUiThread(() -> { + main_layout.removeAllViews(); + main_layout.addView(title); + title.setHeight(1000); + title.setText("No file to download"); + }); + return; + } + + try { + + JSONArray array; + array = new JSONArray(data); + + Transfer.activity.runOnUiThread(() -> { + main_layout.removeAllViews(); + final View line = inflater.inflate(R.layout.horizontal_line, null); + main_layout.addView(line); + }); + for (int i = 0; i < array.length(); i++) { + JSONObject row = array.getJSONObject(i); + final String name = row.getString("name"); + final String href = row.getString("href"); + final long size = row.getLong("size"); + final String mime = row.getString("mime"); + final String type = row.getString("type"); + + Transfer.activity.runOnUiThread(() -> { + title.setText("Choose file to download"); + View file_info = inflater.inflate(R.layout.file_info, null); + main_layout.addView(file_info); + ImageView image = file_info.findViewById(R.id.file_image); + TextView viewName = file_info.findViewById(R.id.file_name); + TextView viewType = file_info.findViewById(R.id.file_type); + TextView viewSize = file_info.findViewById(R.id.file_size); + LinearLayout fileDesc = file_info.findViewById(R.id.file_desc); + LinearLayout file_buttons = file_info.findViewById(R.id.file_buttons); + + fileDesc.setId(View.generateViewId()); + file_buttons.setId(View.generateViewId()); + file_buttons.setTag(R.id.ID_FILE_BUTTONS, "FOR VISIBILITY"); + + registerForContextMenu(fileDesc); + fileDesc.setTag(R.id.ID_FILE_NAME, name); + + viewName.setText(name); + viewType.setText(mime); + viewSize.setText(Transfer.humanReadableByteCountBin(size)); + + switch (type) { + case "file-image": + image.setImageResource(R.drawable.ic_icon_image); + break; + case "file-audio": + image.setImageResource(R.drawable.ic_icon_music); + break; + case "file-video": + image.setImageResource(R.drawable.ic_icon_video); + break; + default: + image.setImageResource(R.drawable.ic_icon_file); + } + + fileDesc.setOnClickListener(view -> { + String save_location; + + if (mime.equals("text/plain")) + save_location = getContext().getCacheDir().toString(); + else + save_location = Environment.getExternalStorageDirectory() + "/" + Transfer.local_storage; + + File file = new File(save_location, name); + + file_buttons.setId(View.generateViewId()); + + Button Bview = file_buttons.findViewById(R.id.file_view); + Button Bshare = file_buttons.findViewById(R.id.file_share); + Button dl = file_buttons.findViewById(R.id.file_download); + + dl.setOnClickListener(ListenerDL); + Bview.setOnClickListener(ListenerView); + Bshare.setOnClickListener(ListenerShare); + + if (file.exists() && file.length() == size) { + Bview.setVisibility(LinearLayout.VISIBLE); + Bshare.setVisibility(LinearLayout.VISIBLE); + dl.setVisibility(LinearLayout.GONE); + } + else { + dl.setVisibility(LinearLayout.VISIBLE); + Bview.setVisibility(LinearLayout.GONE); + Bshare.setVisibility(LinearLayout.GONE); + } + + if (Progress.setButton(name, dl)) { + dl.setEnabled(false); + final Drawable spinner = Transfer.activity.getDrawable(R.drawable.ic_spinner_rotate); + int h = spinner.getIntrinsicHeight(); + int w = spinner.getIntrinsicWidth(); + spinner.setBounds(0, 0, w, h); + dl.setCompoundDrawables(dl.getCompoundDrawables()[0], null, spinner, null); + ObjectAnimator anim = ObjectAnimator.ofInt(spinner, "level", 0, 10000); + anim.setDuration(1000); + anim.setRepeatCount(Animation.INFINITE); + anim.setInterpolator(new LinearInterpolator()); + anim.start(); + } + + file_buttons.setTag(R.id.ID_FILE_NAME, name); + file_buttons.setTag(R.id.ID_FILE_SIZE, size); + file_buttons.setTag(R.id.ID_FILE_HREF, href); + file_buttons.setTag(R.id.ID_FILE_MIME, mime); + file_buttons.setTag(R.id.ID_SAVE_LOCATION, save_location); + int state = file_buttons.getVisibility(); + if(state == LinearLayout.GONE) { + for(View child : getAllChildren(main_layout)) { + if(child.getTag(R.id.ID_FILE_BUTTONS) != null) + child.setVisibility(LinearLayout.GONE); + } + file_buttons.setVisibility(LinearLayout.VISIBLE); + } + else if(state == LinearLayout.VISIBLE) + file_buttons.setVisibility(LinearLayout.GONE); + }); + + }); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + + private View.OnClickListener ListenerDL = v -> { + v.setEnabled(false); + final LinearLayout layout = (LinearLayout) v.getParent(); + final String name = (String) layout.getTag(R.id.ID_FILE_NAME); + final String save_location = String.valueOf(layout.getTag(R.id.ID_SAVE_LOCATION)); + final long fileSize = (long) layout.getTag(R.id.ID_FILE_SIZE); + final String href = (String) layout.getTag(R.id.ID_FILE_HREF); + + new File(save_location).mkdirs(); + Uri uri = Uri.fromFile(new File(save_location, name)); + + Intent intent = new Intent(getActivity(), TransferService.class); + intent.setAction(TransferService.ACTION_DOWNLOAD); + intent.putExtra(TransferService.EXTRA, new Extra(uri, name, fileSize, href, layout)); + getActivity().startService(intent); + + }; + + private View.OnClickListener ListenerShare = v -> { + LinearLayout layout = (LinearLayout) v.getParent(); + final String name = (String) layout.getTag(R.id.ID_FILE_NAME); + final String type = (String) layout.getTag(R.id.ID_FILE_MIME); + + final String save_location = String.valueOf(layout.getTag(R.id.ID_SAVE_LOCATION)); + File file = new File(save_location, name); + Uri uri = FileProvider.getUriForFile(getContext(), BuildConfig.APPLICATION_ID, file); + + if(type.equals("text/plain")) { + String text = null; + try { + InputStream is = new FileInputStream(file); + BufferedReader buf = new BufferedReader(new InputStreamReader(is)); + + String line; + StringBuilder sb = new StringBuilder(); + + while((line = buf.readLine()) != null) { + sb.append(line).append("\n"); + } + + text = sb.toString(); + } catch (IOException e) { + e.printStackTrace(); + } + + + try { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType(type); + intent.putExtra(Intent.EXTRA_TEXT, text); + startActivity(Intent.createChooser(intent, getString(R.string.share_title))); + } catch (ActivityNotFoundException e) { + e.printStackTrace(); + } + } + else { + try { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.putExtra(Intent.EXTRA_STREAM, uri); + intent.setType(type); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + startActivity(Intent.createChooser(intent, getString(R.string.share_title))); + } catch (ActivityNotFoundException e) { + e.printStackTrace(); + } + } + + + }; + + private View.OnClickListener ListenerView = v -> { + LinearLayout layout = (LinearLayout) v.getParent(); + final String name = (String) layout.getTag(R.id.ID_FILE_NAME); + final String type = (String) layout.getTag(R.id.ID_FILE_MIME); + + final String save_location = String.valueOf(layout.getTag(R.id.ID_SAVE_LOCATION)); + File file = new File(save_location, name); + Uri uri = FileProvider.getUriForFile(getContext(), BuildConfig.APPLICATION_ID, file); + + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(uri, type); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + startActivity(intent); + } catch (ActivityNotFoundException e) { + e.printStackTrace(); + } + + + }; + + private List getAllChildren(View v) { + + if (!(v instanceof ViewGroup)) { + ArrayList viewArrayList = new ArrayList(); + viewArrayList.add(v); + return viewArrayList; + } + + ArrayList result = new ArrayList(); + + ViewGroup viewGroup = (ViewGroup) v; + for (int i = 0; i < viewGroup.getChildCount(); i++) { + + View child = viewGroup.getChildAt(i); + + //add child + result.add(child); + + //Do the same with child of child + result.addAll(getAllChildren(child)); + } + return result; + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/localtransfer/fragment/ProgressFragment.java b/app/src/main/java/com/localtransfer/fragment/ProgressFragment.java new file mode 100644 index 0000000..57396f0 --- /dev/null +++ b/app/src/main/java/com/localtransfer/fragment/ProgressFragment.java @@ -0,0 +1,70 @@ +package com.localtransfer.fragment; + +import android.os.Bundle; + +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; +import androidx.fragment.app.Fragment; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.localtransfer.Progress; +import com.localtransfer.R; +import com.localtransfer.Transfer; + +import java.util.ConcurrentModificationException; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +public class ProgressFragment extends Fragment { + + public ProgressFragment() { + // Required empty public constructor + } + + public static ProgressFragment newInstance() { + ProgressFragment fragment = new ProgressFragment(); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + Progress.root = inflater.inflate(R.layout.fragment_progress, container, false); + + return Progress.root; + } + + @Override + public void onResume(){ + super.onResume(); + + Progress.fragment_on = true; + + List instances = Progress.getInstances(); + for (Object obj: instances) { + Progress p = (Progress) obj; + p.showProgress(); + } + } + + @Override + public void onPause(){ + super.onPause(); + + Progress.fragment_on = false; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/localtransfer/fragment/UploadFragment.java b/app/src/main/java/com/localtransfer/fragment/UploadFragment.java new file mode 100644 index 0000000..638671f --- /dev/null +++ b/app/src/main/java/com/localtransfer/fragment/UploadFragment.java @@ -0,0 +1,103 @@ +package com.localtransfer.fragment; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; + +import androidx.fragment.app.Fragment; +import androidx.viewpager.widget.ViewPager; + +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.Toast; + +import com.localtransfer.MainActivity; +import com.localtransfer.R; +import com.localtransfer.Transfer; +import com.localtransfer.TransferService; + +import java.util.ArrayList; + +public class UploadFragment extends Fragment { + + public UploadFragment() { + // Required empty public constructor + } + + public static UploadFragment newInstance() { + UploadFragment fragment = new UploadFragment(); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + final View root = inflater.inflate(R.layout.fragment_upload, container, false); + + Button upload = (Button) root.findViewById(R.id.upload); + upload.setOnClickListener(v -> selectFile()); + + return root; + } + + public static final int REQUEST_ID_CHOOSE_FILES = 2002; + + private void selectFile() { + Intent intent = new Intent() + .setType("*/*") + .setAction(Intent.ACTION_GET_CONTENT) + .putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + + startActivityForResult(Intent.createChooser(intent, "Select a file"), REQUEST_ID_CHOOSE_FILES); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + Log.d("RequestCode", String.valueOf(requestCode)); + if (requestCode == REQUEST_ID_CHOOSE_FILES && resultCode == Activity.RESULT_OK) { + String type = data.getType(); + + ViewPager viewPager = getActivity().findViewById(R.id.view_pager); + viewPager.setCurrentItem(2); + + Transfer tr = new Transfer(); + + if (type != null) { + String sharedText = data.getStringExtra(Intent.EXTRA_TEXT); + tr.handleSendText(sharedText); + } + else { + ArrayList fileUris = new ArrayList<>(); + if (data.getClipData() != null) { // Checking for selection multiple files + int nbItem = data.getClipData().getItemCount(); + Toast.makeText(getContext(), "You select " + nbItem + " files", Toast.LENGTH_SHORT).show(); + for (int i = 0; i < nbItem; i++) { + Uri uri = data.getClipData().getItemAt(i).getUri(); + fileUris.add(uri); + } + } else { + Uri uri = data.getData(); //The uri with the location of the file + fileUris.add(uri); + } + for (Uri uri : fileUris) { + Intent intent = new Intent(getActivity(), TransferService.class); + intent.setAction(TransferService.ACTION_UPLOAD); + intent.putExtra(TransferService.EXTRA_URI, uri); + getActivity().startService(intent); + //tr.handleSendFile(uri); + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/circle_shape.xml b/app/src/main/res/drawable/circle_shape.xml new file mode 100644 index 0000000..b6b2750 --- /dev/null +++ b/app/src/main/res/drawable/circle_shape.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/circular_progress_bar.xml b/app/src/main/res/drawable/circular_progress_bar.xml new file mode 100644 index 0000000..ff1b6f9 --- /dev/null +++ b/app/src/main/res/drawable/circular_progress_bar.xml @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_download.xml b/app/src/main/res/drawable/ic_download.xml new file mode 100644 index 0000000..ddfad24 --- /dev/null +++ b/app/src/main/res/drawable/ic_download.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_download_24.xml b/app/src/main/res/drawable/ic_download_24.xml new file mode 100644 index 0000000..15a0bf9 --- /dev/null +++ b/app/src/main/res/drawable/ic_download_24.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_folder_open.xml b/app/src/main/res/drawable/ic_folder_open.xml new file mode 100644 index 0000000..f58b501 --- /dev/null +++ b/app/src/main/res/drawable/ic_folder_open.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_icon_file.xml b/app/src/main/res/drawable/ic_icon_file.xml new file mode 100644 index 0000000..f404fbf --- /dev/null +++ b/app/src/main/res/drawable/ic_icon_file.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_icon_image.xml b/app/src/main/res/drawable/ic_icon_image.xml new file mode 100644 index 0000000..8232c4d --- /dev/null +++ b/app/src/main/res/drawable/ic_icon_image.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_icon_music.xml b/app/src/main/res/drawable/ic_icon_music.xml new file mode 100644 index 0000000..35dcb2e --- /dev/null +++ b/app/src/main/res/drawable/ic_icon_music.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_icon_video.xml b/app/src/main/res/drawable/ic_icon_video.xml new file mode 100644 index 0000000..13c137a --- /dev/null +++ b/app/src/main/res/drawable/ic_icon_video.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher.xml b/app/src/main/res/drawable/ic_launcher.xml new file mode 100644 index 0000000..95befc7 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..95befc7 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_open.xml b/app/src/main/res/drawable/ic_open.xml new file mode 100644 index 0000000..455b503 --- /dev/null +++ b/app/src/main/res/drawable/ic_open.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 0000000..41a82ed --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml new file mode 100644 index 0000000..2f13bb3 --- /dev/null +++ b/app/src/main/res/drawable/ic_share.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_spinner.xml b/app/src/main/res/drawable/ic_spinner.xml new file mode 100644 index 0000000..283a32d --- /dev/null +++ b/app/src/main/res/drawable/ic_spinner.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_spinner_rotate.xml b/app/src/main/res/drawable/ic_spinner_rotate.xml new file mode 100644 index 0000000..b5a4b52 --- /dev/null +++ b/app/src/main/res/drawable/ic_spinner_rotate.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_upload.xml b/app/src/main/res/drawable/ic_upload.xml new file mode 100644 index 0000000..2c224eb --- /dev/null +++ b/app/src/main/res/drawable/ic_upload.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_upload_24.xml b/app/src/main/res/drawable/ic_upload_24.xml new file mode 100644 index 0000000..8915f78 --- /dev/null +++ b/app/src/main/res/drawable/ic_upload_24.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_upload_32.xml b/app/src/main/res/drawable/ic_upload_32.xml new file mode 100644 index 0000000..7f9f4b5 --- /dev/null +++ b/app/src/main/res/drawable/ic_upload_32.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_upload_and_download_from_the_cloud.xml b/app/src/main/res/drawable/ic_upload_and_download_from_the_cloud.xml new file mode 100644 index 0000000..e39b205 --- /dev/null +++ b/app/src/main/res/drawable/ic_upload_and_download_from_the_cloud.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..96cd935 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/file_info.xml b/app/src/main/res/layout/file_info.xml new file mode 100644 index 0000000..e9e76d3 --- /dev/null +++ b/app/src/main/res/layout/file_info.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + +