├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── libs │ ├── commons-logging-1.2.jar │ ├── commons-net-3.8.0-sources.jar │ ├── commons-net-3.8.0.jar │ ├── core-3.2.1.jar │ ├── ftplet-api-1.1.1.jar │ ├── ftpserver-core-1.1.1-edited.jar │ ├── ftpserver-core-1.1.1.jar │ ├── log4j-1.2.17.jar │ ├── mina-core-2.0.16.jar │ ├── slf4j-api-1.7.21.jar │ └── slf4j-log4j12-1.7.21.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── github │ │ └── ghmxr │ │ └── ftpshare │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ ├── com │ │ │ └── github │ │ │ │ └── ghmxr │ │ │ │ └── ftpshare │ │ │ │ ├── Constants.java │ │ │ │ ├── MyApplication.java │ │ │ │ ├── activities │ │ │ │ ├── AccountActivity.java │ │ │ │ ├── AddAccountActivity.java │ │ │ │ ├── AddClientActivity.kt │ │ │ │ ├── BaseActivity.java │ │ │ │ ├── ClientInfoActivity.kt │ │ │ │ ├── EditAccountActivity.java │ │ │ │ ├── EditClientActivity.kt │ │ │ │ ├── FolderSelectorActivity.kt │ │ │ │ ├── FtpClientActivity.kt │ │ │ │ ├── MainActivity.java │ │ │ │ ├── ServiceAccountActivity.kt │ │ │ │ └── SettingActivity.java │ │ │ │ ├── adapers │ │ │ │ ├── AccountListAdapter.java │ │ │ │ ├── FtpAddressesAdapter.java │ │ │ │ ├── FtpClientListAdapter.kt │ │ │ │ └── FtpFileListAdapter.kt │ │ │ │ ├── data │ │ │ │ ├── AccountItem.java │ │ │ │ └── ClientBean.kt │ │ │ │ ├── fragments │ │ │ │ ├── AccountFragment.java │ │ │ │ ├── ClientFragment.kt │ │ │ │ ├── MainFragment.java │ │ │ │ └── SettingFragment.kt │ │ │ │ ├── ftpclient │ │ │ │ ├── FtpClientManager.kt │ │ │ │ └── FtpClientUtil.kt │ │ │ │ ├── receivers │ │ │ │ └── BootCompletedReceiver.java │ │ │ │ ├── services │ │ │ │ ├── FtpService.java │ │ │ │ └── MyTileService.java │ │ │ │ ├── ui │ │ │ │ ├── DialogOfFolderSelector.java │ │ │ │ ├── DisconnectSelectionDialog.java │ │ │ │ ├── FtpAddressesDialog.java │ │ │ │ ├── ProgressDialog.java │ │ │ │ └── RadioSelectionDialog.java │ │ │ │ ├── utils │ │ │ │ ├── CommonUtils.java │ │ │ │ ├── MySQLiteOpenHelper.java │ │ │ │ ├── NetworkEnvironmentUtil.java │ │ │ │ ├── NetworkStatusMonitor.java │ │ │ │ └── StorageUtil.java │ │ │ │ ├── views │ │ │ │ └── AutoMarqueeTextView.java │ │ │ │ └── widgets │ │ │ │ └── FtpWidget.java │ │ └── org │ │ │ └── apache │ │ │ └── ftpserver │ │ │ ├── command │ │ │ └── impl │ │ │ │ └── OPTS_UTF8.java │ │ │ ├── impl │ │ │ └── IODataConnection.java │ │ │ └── listener │ │ │ └── nio │ │ │ ├── FtpResponseEncoder.java │ │ │ └── FtpServerProtocolCodecFactory.java │ └── res │ │ ├── anim │ │ ├── entry_300.xml │ │ └── exit_300.xml │ │ ├── color │ │ └── selector_bottom_nav.xml │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── arrow_right.xml │ │ ├── ic_alipay.xml │ │ ├── ic_device.xml │ │ ├── ic_download.xml │ │ ├── ic_ex_24dp.xml │ │ ├── ic_file.xml │ │ ├── ic_folder.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_more.xml │ │ ├── ic_new_folder.xml │ │ ├── ic_round_check.xml │ │ ├── ic_select_all.xml │ │ ├── ic_select_all_off.xml │ │ ├── ic_server.xml │ │ ├── ic_stop.xml │ │ ├── ic_upload.xml │ │ ├── ic_warn.xml │ │ ├── ic_wheel_24dp.xml │ │ ├── icon_account.png │ │ ├── icon_add.xml │ │ ├── icon_alarm.xml │ │ ├── icon_ap_disconnected.png │ │ ├── icon_check.png │ │ ├── icon_close.xml │ │ ├── icon_close_white.xml │ │ ├── icon_delete.xml │ │ ├── icon_face_ops.png │ │ ├── icon_folder.png │ │ ├── icon_save.xml │ │ ├── icon_wifi_disconnected.png │ │ ├── shape_5dp_light.xml │ │ ├── switch_off.png │ │ └── switch_on.png │ │ ├── layout │ │ ├── activity_account.xml │ │ ├── activity_ftp_client.xml │ │ ├── activity_main.xml │ │ ├── activity_settings.xml │ │ ├── dialog_recyclerview.xml │ │ ├── dialog_with_progress.xml │ │ ├── fragment_account.xml │ │ ├── fragment_client.xml │ │ ├── fragment_main.xml │ │ ├── item_account.xml │ │ ├── item_folder.xml │ │ ├── item_ftp_address.xml │ │ ├── item_ftp_client.xml │ │ ├── item_ftp_file.xml │ │ ├── item_radio_button.xml │ │ ├── item_spinner_storage.xml │ │ ├── layout_client_info.xml │ │ ├── layout_dialog_about.xml │ │ ├── layout_dialog_charset.xml │ │ ├── layout_dialog_disconnection.xml │ │ ├── layout_dialog_folder_selector.xml │ │ ├── layout_disconnection.xml │ │ ├── layout_with_edittext.xml │ │ ├── popup_client_item.xml │ │ ├── popup_ftp_file_item.xml │ │ └── widget_switch.xml │ │ ├── menu │ │ ├── menu_account.xml │ │ ├── menu_account_add.xml │ │ ├── menu_folder_selector.xml │ │ ├── menu_ftp_client_file.xml │ │ ├── menu_main.xml │ │ ├── menu_service_account.xml │ │ └── navigation.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-en │ │ └── strings.xml │ │ ├── values-night │ │ └── colors.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── ftp_desktop_widget.xml │ └── test │ └── java │ └── com │ └── github │ └── ghmxr │ └── ftpshare │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── preview ├── ftpshare_1.png ├── ftpshare_2.png ├── ftpshare_3.png └── ftpshare_4.png ├── settings.gradle └── signedAPKs └── release ├── build-1.apk ├── build-10.apk ├── build-11.apk ├── build-2.apk ├── build-3.apk ├── build-4.apk ├── build-5.apk ├── build-6.apk ├── build-8.apk ├── build-9.apk └── output.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.ap_ 3 | *.aab 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/ 38 | 39 | # Keystore files 40 | # Uncomment the following lines if you do not want to check your keystore files in. 41 | #*.jks 42 | #*.keystore 43 | 44 | # External native build folder generated in Android Studio 2.2 and later 45 | .externalNativeBuild 46 | 47 | # Google Services (e.g. APIs or Firebase) 48 | google-services.json 49 | 50 | # Freeline 51 | freeline.py 52 | freeline/ 53 | freeline_project_description.json 54 | 55 | # fastlane 56 | fastlane/report.xml 57 | fastlane/Preview.html 58 | fastlane/screenshots 59 | fastlane/test_output 60 | fastlane/readme.md 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FTP Share (Scroll down for English) 2 | 一个基于Apache FtpServer组件开发的Android平台实现FTP文件共享的应用。 3 | 4 | + 可使用匿名模式登录,并指定共享主路径和设置写入权限; 5 | 6 | + 可设定若干用户,并单独指定共享主路径和写入权限; 7 | 8 | + 可以设定主路径至外置存储(由于Android版本和设备型号不同,实际使用可能会有差异); 9 | 10 | + 可以选择在某些情况下自动断开FTP服务,例如WiFi连接断开时,热点断开时,指定时间后; 11 | 12 | + 暗色模式,中英文模式。 13 | 14 | 关于外部调用FTP服务: 15 | 16 | 本应用的FTP服务是对外暴露的,如果希望通过其他应用或者自动化应用例如Tasker来启动本应用的FTP服务,请参考下面的代码和参数: 17 | 18 | ~~~ 19 | Intent intent=new Intent(); 20 | intent.setComponent(new ComponentName("com.github.ghmxr.ftpshare","com.github.ghmxr.ftpshare.services.FtpService")); 21 | context.startService(intent);//启动FTP服务 22 | context.stopService(intent);//停止FTP服务 23 | ~~~ 24 | 25 | 26 | ********************* 27 | The English contents are translated by machine: 28 | 29 | An application of FTP file sharing based on Android platform developed by Apache FTP server component. 30 | 31 | + You can use anonymous mode to log in, specify the shared main path and set the write permission; 32 | 33 | + Several users can be set, and the shared main path and write permission can be specified separately; 34 | 35 | + You can set the main path to external storage (due to different Android versions and device models, the actual use may be different); 36 | 37 | + You can choose to disconnect FTP service automatically in some cases, such as when WiFi connection is disconnected, when hotspot is disconnected, after a specified time; 38 | 39 | + Dark mode, Chinese and English support. 40 | 41 | About external calls to the FTP service: 42 | 43 | The FTP service of this application is exposed to outside. If you want to start the FTP service of this application through other applications or automation applications such as Tasker, please refer to the following codes and parameters: 44 | 45 | ~~~ 46 | Intent intent=new Intent(); 47 | intent.setComponent(new ComponentName("com.github.ghmxr.ftpshare","com.github.ghmxr.ftpshare.services.FtpService")); 48 | context.startService(intent);//Start the FTP service 49 | context.stopService(intent);//Stop the FTP service 50 | ~~~ 51 | 52 | ********************* 53 | 酷安市场:https://www.coolapk.com/apk/com.github.ghmxr.ftpshare 54 | 55 |
56 | avator 57 | avator 58 | avator 59 | avator 60 |
61 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion 28 6 | defaultConfig { 7 | applicationId "com.github.ghmxr.ftpshare" 8 | minSdkVersion 14 9 | targetSdkVersion 28 10 | versionCode 12 11 | versionName "Build 12" 12 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 13 | } 14 | buildTypes { 15 | release { 16 | lintOptions { 17 | checkReleaseBuilds false 18 | abortOnError false 19 | } 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | packagingOptions { 25 | exclude 'META-INF/LICENSE' 26 | exclude 'META-INF/DEPENDENCIES' 27 | } 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_1_8 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | } 32 | kotlinOptions { 33 | jvmTarget = '1.8' 34 | } 35 | } 36 | 37 | dependencies { 38 | //implementation fileTree(dir: 'libs', include: ['*.jar']) 39 | //implementation files('libs/core-3.2.1.jar') 40 | implementation files('libs/ftplet-api-1.1.1.jar') 41 | implementation files('libs/log4j-1.2.17.jar') 42 | implementation files('libs/mina-core-2.0.16.jar') 43 | implementation files('libs/slf4j-api-1.7.21.jar') 44 | implementation files('libs/slf4j-log4j12-1.7.21.jar') 45 | implementation files('libs/commons-logging-1.2.jar') 46 | implementation files('libs/commons-net-3.8.0.jar') 47 | implementation files('libs/ftpserver-core-1.1.1-edited.jar') 48 | //由于修改并重写了jar包中的几个类,所以删掉了这些class,修改后的类在主路径org对应的包中,用于支持编码修改 49 | 50 | implementation 'androidx.appcompat:appcompat:1.0.0' 51 | implementation 'com.google.android.material:material:1.0.0' 52 | //implementation 'com.android.support.constraint:constraint-layout:1.1.3' 53 | //implementation 'com.android.support:support-vector-drawable:28.0.0' 54 | testImplementation 'junit:junit:4.12' 55 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 56 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' 57 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 58 | } 59 | -------------------------------------------------------------------------------- /app/libs/commons-logging-1.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/libs/commons-logging-1.2.jar -------------------------------------------------------------------------------- /app/libs/commons-net-3.8.0-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/libs/commons-net-3.8.0-sources.jar -------------------------------------------------------------------------------- /app/libs/commons-net-3.8.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/libs/commons-net-3.8.0.jar -------------------------------------------------------------------------------- /app/libs/core-3.2.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/libs/core-3.2.1.jar -------------------------------------------------------------------------------- /app/libs/ftplet-api-1.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/libs/ftplet-api-1.1.1.jar -------------------------------------------------------------------------------- /app/libs/ftpserver-core-1.1.1-edited.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/libs/ftpserver-core-1.1.1-edited.jar -------------------------------------------------------------------------------- /app/libs/ftpserver-core-1.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/libs/ftpserver-core-1.1.1.jar -------------------------------------------------------------------------------- /app/libs/log4j-1.2.17.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/libs/log4j-1.2.17.jar -------------------------------------------------------------------------------- /app/libs/mina-core-2.0.16.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/libs/mina-core-2.0.16.jar -------------------------------------------------------------------------------- /app/libs/slf4j-api-1.7.21.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/libs/slf4j-api-1.7.21.jar -------------------------------------------------------------------------------- /app/libs/slf4j-log4j12-1.7.21.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/libs/slf4j-log4j12-1.7.21.jar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/ghmxr/ftpshare/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getTargetContext(); 24 | 25 | assertEquals("com.github.ghmxr.ftpshare", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 43 | 46 | 49 | 52 | 53 | 56 | 59 | 60 | 63 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/Constants.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare; 2 | 3 | import androidx.appcompat.app.AppCompatDelegate; 4 | 5 | import com.github.ghmxr.ftpshare.utils.StorageUtil; 6 | 7 | public class Constants { 8 | public static class SQLConsts { 9 | public static final String SQL_USERS_FILENAME = "ftp_accounts.db"; 10 | public static final int SQL_VERSION = 1; 11 | public static final String TABLE_NAME = "ftp_account_table"; 12 | public static final String COLUMN_ID = "_id"; 13 | public static final String COLUMN_ACCOUNT_NAME = "name"; 14 | public static final String COLUMN_PASSWORD = "password"; 15 | public static final String COLUMN_PATH = "path"; 16 | public static final String COLUMN_WRITABLE = "writable"; 17 | } 18 | 19 | public static class FTPConsts { 20 | public static final String NAME_ANONYMOUS = "anonymous"; 21 | } 22 | 23 | public static class PreferenceConsts { 24 | public static final String FILE_NAME = "settings"; 25 | /** 26 | * this stands for a boolean value 27 | */ 28 | public static final String ANONYMOUS_MODE = "anonymous_mode"; 29 | public static final boolean ANONYMOUS_MODE_DEFAULT = true; 30 | /** 31 | * this stands for a string value 32 | */ 33 | public static final String ANONYMOUS_MODE_PATH = "anonymous_mode_path"; 34 | public static final String ANONYMOUS_MODE_PATH_DEFAULT = StorageUtil.getMainStoragePath(); 35 | /** 36 | * this stands for a boolean value 37 | */ 38 | public static final String ANONYMOUS_MODE_WRITABLE = "anonymous_mode_writable"; 39 | public static final boolean ANONYMOUS_MODE_WRITABLE_DEFAULT = false; 40 | 41 | /** 42 | * this stands for a boolean value 43 | */ 44 | public static final String WAKE_LOCK = "wake_lock"; 45 | public static final boolean WAKE_LOCK_DEFAULT = false; 46 | /** 47 | * this stands for a int value 48 | */ 49 | public static final String PORT_NUMBER = "port_number"; 50 | public static final int PORT_NUMBER_DEFAULT = 5656; 51 | /** 52 | * this stands for a string value 53 | */ 54 | public static final String CHARSET_TYPE = "charset_type"; 55 | public static final String CHARSET_TYPE_DEFAULT = "UTF-8"; 56 | public static final String MAX_ANONYMOUS_NUM = "max_anonymous_logins"; 57 | public static final String MAX_LOGIN_NUM = "max_logins"; 58 | /** 59 | * int value 60 | */ 61 | public static final String NIGHT_MODE = "night_mode"; 62 | public static final int NIGHT_MODE_DEFAULT = AppCompatDelegate.MODE_NIGHT_NO; 63 | /** 64 | * int value 65 | */ 66 | public static final String LANGUAGE_SETTING = "language_setting"; 67 | public static final int LANGUAGE_FOLLOW_SYSTEM = 0; 68 | public static final int LANGUAGE_SIMPLIFIED_CHINESE = 1; 69 | public static final int LANGUAGE_ENGLISH = 2; 70 | public static final int LANGUAGE_SETTING_DEFAULT = LANGUAGE_FOLLOW_SYSTEM; 71 | /** 72 | * int value 73 | */ 74 | public static final String AUTO_STOP = "auto_stop"; 75 | public static final int AUTO_STOP_NONE = -1; 76 | public static final int AUTO_STOP_WIFI_DISCONNECTED = 0; 77 | public static final int AUTO_STOP_AP_DISCONNECTED = 1; 78 | public static final int AUTO_STOP_TIME_COUNT = 2; 79 | public static final int AUTO_STOP_DEFAULT = AUTO_STOP_NONE; 80 | /** 81 | * int value 82 | */ 83 | public static final String AUTO_STOP_VALUE = "auto_stop_value"; 84 | public static final int AUTO_STOP_VALUE_DEFAULT = 600; 85 | /** 86 | * boolean value 87 | */ 88 | public static final String START_AFTER_BOOT = "start_after_boot"; 89 | public static final boolean START_AFTER_BOOT_DEFAULT = false; 90 | } 91 | 92 | public static class Charset { 93 | public static final String CHAR_UTF = "UTF-8"; 94 | public static final String CHAR_GBK = "GBK"; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.view.ViewConfiguration; 8 | 9 | import androidx.appcompat.app.AppCompatDelegate; 10 | 11 | import com.github.ghmxr.ftpshare.utils.CommonUtils; 12 | import com.github.ghmxr.ftpshare.utils.NetworkStatusMonitor; 13 | 14 | import java.lang.reflect.Field; 15 | 16 | public class MyApplication extends Application { 17 | 18 | public static final Handler handler = new Handler(Looper.getMainLooper()); 19 | private static MyApplication myApplication; 20 | 21 | public static Context getGlobalBaseContext() { 22 | return myApplication.getApplicationContext(); 23 | } 24 | 25 | @Override 26 | public void onCreate() { 27 | myApplication = this; 28 | try { 29 | ViewConfiguration config = ViewConfiguration.get(this); 30 | Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey"); 31 | if (menuKeyField != null) { 32 | menuKeyField.setAccessible(true); 33 | menuKeyField.setBoolean(config, false); 34 | } 35 | } catch (Exception e) { 36 | e.printStackTrace(); 37 | } 38 | super.onCreate(); 39 | NetworkStatusMonitor.init(this); 40 | AppCompatDelegate.setDefaultNightMode(CommonUtils.getSettingSharedPreferences(this) 41 | .getInt(Constants.PreferenceConsts.NIGHT_MODE, Constants.PreferenceConsts.NIGHT_MODE_DEFAULT)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/activities/AddAccountActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.activities; 2 | 3 | import android.os.Bundle; 4 | import android.view.Menu; 5 | import android.view.MenuItem; 6 | 7 | import androidx.annotation.Nullable; 8 | 9 | import com.github.ghmxr.ftpshare.R; 10 | import com.github.ghmxr.ftpshare.data.AccountItem; 11 | import com.github.ghmxr.ftpshare.services.FtpService; 12 | 13 | public class AddAccountActivity extends AccountActivity { 14 | 15 | @Override 16 | public void onCreate(@Nullable Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | try { 19 | getSupportActionBar().setTitle(getResources().getString(R.string.activity_title_add)); 20 | } catch (Exception e) { 21 | e.printStackTrace(); 22 | } 23 | } 24 | 25 | @Override 26 | public void initializeAccountItem() { 27 | item = new AccountItem(); 28 | item.account = "user" + (FtpService.getUserAccountList(this).size() + 1); 29 | } 30 | 31 | @Override 32 | public boolean onCreateOptionsMenu(Menu menu) { 33 | getMenuInflater().inflate(R.menu.menu_account_add, menu); 34 | return super.onCreateOptionsMenu(menu); 35 | } 36 | 37 | @Override 38 | public boolean onOptionsItemSelected(MenuItem item) { 39 | if (item.getItemId() == R.id.action_account_add_save) { 40 | if (save2DB(null) >= 0) { 41 | setResult(RESULT_OK); 42 | finish(); 43 | return true; 44 | } 45 | } 46 | return super.onOptionsItemSelected(item); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/activities/AddClientActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.activities 2 | 3 | import android.os.Bundle 4 | import android.view.Menu 5 | import android.view.MenuItem 6 | import com.github.ghmxr.ftpshare.R 7 | import com.github.ghmxr.ftpshare.data.ClientBean 8 | import com.google.android.material.snackbar.Snackbar 9 | 10 | class AddClientActivity : ClientInfoActivity() { 11 | 12 | private var lastClick = 0L 13 | 14 | override fun onCreate(bundle: Bundle?) { 15 | super.onCreate(bundle) 16 | supportActionBar?.title = resources?.getString(R.string.activity_title_add) 17 | } 18 | 19 | override fun getClientBean(): ClientBean = ClientBean() 20 | 21 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { 22 | menuInflater.inflate(R.menu.menu_account_add, menu) 23 | return super.onCreateOptionsMenu(menu) 24 | } 25 | 26 | override fun onOptionsItemSelected(item: MenuItem?): Boolean { 27 | if (item?.itemId == R.id.action_account_add_save) { 28 | if (saveOrUpdateClientBean()) { 29 | setResult(RESULT_OK) 30 | finish() 31 | } 32 | } 33 | return super.onOptionsItemSelected(item) 34 | } 35 | 36 | override fun onBackPressed() { 37 | val current = System.currentTimeMillis() 38 | if (current - lastClick > 2000L) { 39 | lastClick = current 40 | Snackbar.make(findViewById(android.R.id.content), resources.getString(R.string.attention_changes_confirm), Snackbar.LENGTH_SHORT).show() 41 | return 42 | } 43 | super.onBackPressed() 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/activities/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.activities; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.appcompat.app.AppCompatActivity; 6 | 7 | import com.github.ghmxr.ftpshare.utils.CommonUtils; 8 | 9 | public abstract class BaseActivity extends AppCompatActivity { 10 | 11 | @Override 12 | protected void onCreate(Bundle bundle) { 13 | super.onCreate(bundle); 14 | setAndRefreshLanguage(); 15 | } 16 | 17 | public void setAndRefreshLanguage() { 18 | CommonUtils.updateResourcesOfContext(this); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/activities/EditAccountActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.activities; 2 | 3 | import android.os.Bundle; 4 | import android.view.KeyEvent; 5 | import android.view.Menu; 6 | import android.view.MenuItem; 7 | import android.widget.Toast; 8 | 9 | import androidx.annotation.Nullable; 10 | 11 | import com.github.ghmxr.ftpshare.R; 12 | import com.github.ghmxr.ftpshare.data.AccountItem; 13 | import com.github.ghmxr.ftpshare.services.FtpService; 14 | import com.github.ghmxr.ftpshare.utils.MySQLiteOpenHelper; 15 | import com.google.android.material.snackbar.Snackbar; 16 | 17 | public class EditAccountActivity extends AccountActivity { 18 | 19 | public static final String EXTRA_SERIALIZED_ACCOUNT_ITEM = "account_item"; 20 | private long first_clicked_delete = 0; 21 | 22 | @Override 23 | public void onCreate(@Nullable Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | try { 26 | getSupportActionBar().setTitle(getResources().getString(R.string.activity_title_edit)); 27 | } catch (Exception e) { 28 | e.printStackTrace(); 29 | } 30 | } 31 | 32 | @Override 33 | public void initializeAccountItem() { 34 | try { 35 | item = (AccountItem) getIntent().getSerializableExtra(EXTRA_SERIALIZED_ACCOUNT_ITEM); 36 | } catch (Exception e) { 37 | e.printStackTrace(); 38 | Toast.makeText(this, e.toString(), Toast.LENGTH_SHORT).show(); 39 | finish(); 40 | } 41 | } 42 | 43 | @Override 44 | public boolean onCreateOptionsMenu(Menu menu) { 45 | getMenuInflater().inflate(R.menu.menu_account, menu); 46 | return super.onCreateOptionsMenu(menu); 47 | } 48 | 49 | @Override 50 | public boolean onOptionsItemSelected(MenuItem item) { 51 | switch (item.getItemId()) { 52 | default: 53 | break; 54 | case R.id.action_account_save: { 55 | if (save2DB(this.item.id) >= 0) { 56 | setResult(RESULT_OK); 57 | finish(); 58 | return true; 59 | } 60 | 61 | } 62 | break; 63 | case R.id.action_account_delete: { 64 | long time = System.currentTimeMillis(); 65 | if (FtpService.isFTPServiceRunning()) { 66 | Snackbar.make(findViewById(android.R.id.content), getResources().getString(R.string.attention_ftp_is_running), Snackbar.LENGTH_SHORT).show(); 67 | return true; 68 | } 69 | if (time - first_clicked_delete > 1000) { 70 | Snackbar.make(findViewById(android.R.id.content), getResources().getString(R.string.attention_delete_confirm), Snackbar.LENGTH_SHORT).show(); 71 | first_clicked_delete = time; 72 | return true; 73 | } 74 | if (deleteRow(this.item.id) <= 0) { 75 | Toast.makeText(this, "Can not find the row", Toast.LENGTH_SHORT).show(); 76 | } 77 | setResult(RESULT_OK); 78 | finish(); 79 | } 80 | break; 81 | } 82 | return super.onOptionsItemSelected(item); 83 | } 84 | 85 | @Override 86 | public boolean onKeyDown(int keyCode, KeyEvent event) { 87 | if (keyCode == KeyEvent.KEYCODE_BACK) { 88 | checkChangesAndExit(); 89 | return true; 90 | } 91 | return super.onKeyDown(keyCode, event); 92 | } 93 | 94 | private long deleteRow(long id) { 95 | try { 96 | return MySQLiteOpenHelper.deleteRow(this, id); 97 | } catch (Exception e) { 98 | e.printStackTrace(); 99 | } 100 | return -1; 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/activities/EditClientActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.activities 2 | 3 | import android.os.Bundle 4 | import android.view.Menu 5 | import android.view.MenuItem 6 | import com.github.ghmxr.ftpshare.R 7 | import com.github.ghmxr.ftpshare.ftpclient.FtpClientManager 8 | import com.google.android.material.snackbar.Snackbar 9 | 10 | class EditClientActivity : ClientInfoActivity() { 11 | 12 | companion object { 13 | @JvmStatic 14 | val EXTRA_CLIENT_BEAN_ID: String = "client_bean_id" 15 | } 16 | 17 | private var lastClick = 0L 18 | 19 | override fun onCreate(bundle: Bundle?) { 20 | super.onCreate(bundle) 21 | supportActionBar?.title = resources?.getString(R.string.activity_title_edit) 22 | } 23 | 24 | override fun getClientBean() = FtpClientManager.instance.getClientBeanOfId(intent.getIntExtra(EXTRA_CLIENT_BEAN_ID, -1)) 25 | 26 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { 27 | menuInflater.inflate(R.menu.menu_account, menu) 28 | return super.onCreateOptionsMenu(menu) 29 | } 30 | 31 | override fun onOptionsItemSelected(item: MenuItem?): Boolean { 32 | if (item?.itemId == R.id.action_account_delete) { 33 | val current = System.currentTimeMillis() 34 | if (current - lastClick > 2000L) { 35 | lastClick = current 36 | Snackbar.make(findViewById(android.R.id.content), resources.getString(R.string.attention_delete_confirm), Snackbar.LENGTH_SHORT).show() 37 | return super.onOptionsItemSelected(item) 38 | } 39 | if (deleteClientBean()) { 40 | setResult(RESULT_OK) 41 | finish() 42 | } 43 | } 44 | if (item?.itemId == R.id.action_account_save) { 45 | if (saveOrUpdateClientBean()) { 46 | setResult(RESULT_OK) 47 | finish() 48 | } 49 | } 50 | return super.onOptionsItemSelected(item) 51 | } 52 | 53 | override fun onBackPressed() { 54 | if (saveOrUpdateClientBean()) { 55 | setResult(RESULT_OK) 56 | } 57 | super.onBackPressed() 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/adapers/AccountListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.adapers; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.AdapterView; 9 | import android.widget.BaseAdapter; 10 | import android.widget.ListView; 11 | import android.widget.TextView; 12 | 13 | import androidx.annotation.NonNull; 14 | 15 | import com.github.ghmxr.ftpshare.R; 16 | import com.github.ghmxr.ftpshare.activities.EditAccountActivity; 17 | import com.github.ghmxr.ftpshare.data.AccountItem; 18 | import com.github.ghmxr.ftpshare.services.FtpService; 19 | 20 | import java.util.ArrayList; 21 | 22 | public class AccountListAdapter extends BaseAdapter implements AdapterView.OnItemClickListener { 23 | 24 | private final ArrayList accountItems = new ArrayList<>(); 25 | private final Activity activity; 26 | 27 | public AccountListAdapter(@NonNull Activity activity, @NonNull ListView listView) { 28 | super(); 29 | this.activity = activity; 30 | accountItems.addAll(FtpService.getUserAccountList(activity)); 31 | listView.setOnItemClickListener(this); 32 | listView.setDivider(null); 33 | } 34 | 35 | @Override 36 | public int getCount() { 37 | return accountItems.size() + 2; 38 | } 39 | 40 | @Override 41 | public Object getItem(int position) { 42 | return accountItems.get(position); 43 | } 44 | 45 | @Override 46 | public long getItemId(int position) { 47 | return position; 48 | } 49 | 50 | @Override 51 | public View getView(int position, View convertView, ViewGroup parent) { 52 | if (convertView == null) { 53 | convertView = LayoutInflater.from(activity).inflate(R.layout.item_account, parent, false); 54 | } 55 | ViewGroup viewGroup = convertView.findViewById(R.id.item_account_root); 56 | if (position >= accountItems.size()) { 57 | viewGroup.setVisibility(View.INVISIBLE); 58 | return convertView; 59 | } else { 60 | viewGroup.setVisibility(View.VISIBLE); 61 | } 62 | final AccountItem accountItem = accountItems.get(position); 63 | TextView tv_account = convertView.findViewById(R.id.text_account); 64 | TextView tv_path = convertView.findViewById(R.id.text_path); 65 | View writable = convertView.findViewById(R.id.area_writable); 66 | tv_account.setText(accountItem.account); 67 | tv_path.setText(accountItem.path); 68 | writable.setVisibility(accountItem.writable ? View.VISIBLE : View.GONE); 69 | return convertView; 70 | } 71 | 72 | @Override 73 | public void onItemClick(AdapterView parent, View view, int position, long id) { 74 | Intent intent = new Intent(activity, EditAccountActivity.class); 75 | intent.putExtra(EditAccountActivity.EXTRA_SERIALIZED_ACCOUNT_ITEM, accountItems.get(position)); 76 | activity.startActivityForResult(intent, 1); 77 | } 78 | 79 | public ArrayList getAccountItems() { 80 | return accountItems; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/adapers/FtpAddressesAdapter.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.adapers; 2 | 3 | import android.content.ClipData; 4 | import android.content.ClipboardManager; 5 | import android.content.Context; 6 | import android.graphics.Paint; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.TextView; 11 | import android.widget.Toast; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.recyclerview.widget.RecyclerView; 15 | 16 | import com.github.ghmxr.ftpshare.R; 17 | import com.github.ghmxr.ftpshare.utils.CommonUtils; 18 | import com.github.ghmxr.ftpshare.utils.NetworkEnvironmentUtil; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | public class FtpAddressesAdapter extends RecyclerView.Adapter { 24 | 25 | private final ArrayList ftpAddresses = new ArrayList<>(); 26 | private final Context context; 27 | 28 | public FtpAddressesAdapter(@NonNull Context context) { 29 | this.context = context; 30 | List ips = NetworkEnvironmentUtil.getLocalIpv4Addresses(); 31 | for (String ip : ips) { 32 | ftpAddresses.add(CommonUtils.getFtpServiceAddress(context, ip)); 33 | } 34 | } 35 | 36 | @NonNull 37 | @Override 38 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { 39 | return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_ftp_address, viewGroup, false)); 40 | } 41 | 42 | @Override 43 | public void onBindViewHolder(@NonNull final ViewHolder viewHolder, int i) { 44 | viewHolder.tv.getPaint().setFlags(Paint.UNDERLINE_TEXT_FLAG); 45 | viewHolder.tv.setText(ftpAddresses.get(viewHolder.getAdapterPosition())); 46 | viewHolder.itemView.setOnClickListener(new View.OnClickListener() { 47 | @Override 48 | public void onClick(View v) { 49 | try { 50 | ClipboardManager manager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); 51 | manager.setPrimaryClip(ClipData.newPlainText("message", viewHolder.tv.getText().toString())); 52 | Toast.makeText(context, context.getResources().getString(R.string.attention_clipboard), Toast.LENGTH_SHORT).show(); 53 | } catch (Exception e) { 54 | e.printStackTrace(); 55 | } 56 | } 57 | }); 58 | } 59 | 60 | @Override 61 | public int getItemCount() { 62 | return ftpAddresses.size(); 63 | } 64 | 65 | static class ViewHolder extends RecyclerView.ViewHolder { 66 | private TextView tv; 67 | 68 | ViewHolder(@NonNull View itemView) { 69 | super(itemView); 70 | tv = itemView.findViewById(R.id.tv_ftp_address); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/adapers/FtpClientListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.adapers 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.ImageView 9 | import android.widget.ProgressBar 10 | import android.widget.TextView 11 | import com.github.ghmxr.ftpshare.R 12 | import com.github.ghmxr.ftpshare.data.ClientBean 13 | import com.github.ghmxr.ftpshare.ftpclient.FtpClientManager 14 | 15 | class FtpClientListAdapter(private val context: Context, private val c: ItemCallback?) : androidx.recyclerview.widget.RecyclerView.Adapter() { 16 | 17 | interface ItemCallback { 18 | fun onItemLongClick(i: Int, b: ClientBean, a: FtpClientListAdapter, h: ViewHolder) 19 | fun onItemClick(i: Int, b: ClientBean, a: FtpClientListAdapter, h: ViewHolder) 20 | fun onActionButtonClicked(b: ClientBean, a: FtpClientListAdapter, h: ViewHolder) 21 | } 22 | 23 | override fun onCreateViewHolder(p0: ViewGroup, p1: Int): ViewHolder { 24 | return ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_ftp_client, p0, false)) 25 | } 26 | 27 | @SuppressLint("SetTextI18n") 28 | override fun onBindViewHolder(p0: ViewHolder, p1: Int) { 29 | val clientBean = FtpClientManager.instance.getClientBeanOfId(p0.adapterPosition) 30 | p0.apply { 31 | iconStatus.visibility = if (clientBean.status == ClientBean.Status.CONNECTED) View.VISIBLE else View.GONE 32 | progressBar.visibility = if (clientBean.status == ClientBean.Status.CONNECTING || clientBean.status == ClientBean.Status.DISCONNECTING) View.VISIBLE else View.GONE 33 | tvAction.visibility = if (clientBean.status == ClientBean.Status.CONNECTING || clientBean.status == ClientBean.Status.DISCONNECTING) View.GONE else View.VISIBLE 34 | tvName.text = clientBean.nickName 35 | tvAddress.text = "ftp://${clientBean.host}:${clientBean.port}" 36 | tvAction.text = context.resources.getString(if (clientBean.status == ClientBean.Status.CONNECTED) R.string.word_disconnect else R.string.word_connect) 37 | tvAction.setOnClickListener { 38 | c?.onActionButtonClicked(clientBean, this@FtpClientListAdapter, p0) 39 | } 40 | /*p0.progressBar.setOnClickListener{ 41 | c?.onActionButtonClicked(clientBean,this@FtpClientListAdapter,p0) 42 | }*/ 43 | p0.itemView.setOnLongClickListener { 44 | c?.onItemLongClick(p0.adapterPosition, clientBean, this@FtpClientListAdapter, p0) 45 | true 46 | } 47 | p0.itemView.setOnClickListener { 48 | c?.onItemClick(p0.adapterPosition, clientBean, this@FtpClientListAdapter, p0) 49 | } 50 | } 51 | } 52 | 53 | override fun getItemCount(): Int { 54 | return FtpClientManager.instance.getClientBeanSize() 55 | } 56 | } 57 | 58 | class ViewHolder(v: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(v) { 59 | val iconStatus: ImageView = v.findViewById(R.id.item_server_status) 60 | val progressBar: ProgressBar = v.findViewById(R.id.item_server_progress) 61 | val tvName: TextView = v.findViewById(R.id.item_server_name) 62 | val tvAddress: TextView = v.findViewById(R.id.item_server_address) 63 | val tvAction: TextView = v.findViewById(R.id.item_server_action) 64 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/adapers/FtpFileListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.adapers 2 | 3 | import android.content.Context 4 | import android.text.format.Formatter 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.CheckBox 9 | import android.widget.ImageView 10 | import android.widget.TextView 11 | import com.github.ghmxr.ftpshare.R 12 | import com.github.ghmxr.ftpshare.utils.CommonUtils 13 | import org.apache.commons.net.ftp.FTPFile 14 | 15 | class FtpFileListAdapter(val context: Context, list: Array?, private val c: AdapterCallback?) : androidx.recyclerview.widget.RecyclerView.Adapter() { 16 | private val dataList: ArrayList = ArrayList() 17 | 18 | interface AdapterCallback { 19 | fun onCdClicked() 20 | fun onItemClicked(f: FTPFile?, h: ViewHolderFtpFile) 21 | fun onMoreClicked(f: FTPFile?, h: ViewHolderFtpFile) 22 | fun onMultiSelectModeTurnedOn() 23 | } 24 | 25 | private var currentLongClick: Int = -1 26 | 27 | public var adapterCallback: AdapterCallback? = null 28 | 29 | private var selected: Array? = null 30 | 31 | var isMultiSelectMode: Boolean = false 32 | set(value) { 33 | field = value 34 | if (field) { 35 | selected = Array(dataList.size) { 36 | false 37 | } 38 | } 39 | selected?.set(currentLongClick, true) 40 | notifyDataSetChanged() 41 | } 42 | 43 | var showCd: Boolean = true 44 | set(value) { 45 | field = value 46 | notifyDataSetChanged() 47 | } 48 | 49 | init { 50 | setData(list) 51 | } 52 | 53 | public fun setData(a: Array?) { 54 | dataList.clear() 55 | a?.asList()?.let { 56 | dataList.addAll(it) 57 | } 58 | notifyDataSetChanged() 59 | } 60 | 61 | public fun addData(a: Array?) { 62 | a?.asList()?.let { 63 | dataList.addAll(it) 64 | } 65 | notifyDataSetChanged() 66 | } 67 | 68 | fun getSelectedItems() = ArrayList().apply { 69 | selected?.let { 70 | for (i in it.indices) { 71 | if (it[i]) this.add(dataList[i]) 72 | } 73 | } 74 | 75 | } 76 | 77 | fun setSelectedState(b: Boolean) { 78 | selected?.fill(b) 79 | notifyDataSetChanged() 80 | } 81 | 82 | override fun onCreateViewHolder(p0: ViewGroup, p1: Int) = ViewHolderFtpFile(LayoutInflater.from(context).inflate(R.layout.item_ftp_file, p0, false)) 83 | 84 | override fun onBindViewHolder(p0: ViewHolderFtpFile, p1: Int) { 85 | p0.apply { 86 | if (showCd) { 87 | if (p0.adapterPosition == 0) { 88 | icon.setImageResource(R.drawable.ic_folder) 89 | tvName.text = ".." 90 | tvDate.text = "" 91 | tvSize.text = "" 92 | tvSize.visibility = View.GONE 93 | cb.visibility = View.GONE 94 | p0.itemView.setOnClickListener { 95 | c?.onCdClicked() 96 | } 97 | more.visibility = View.GONE 98 | } else { 99 | bindItem(p0.adapterPosition - 1, p0) 100 | } 101 | 102 | } else { 103 | bindItem(p0.adapterPosition, p0) 104 | } 105 | 106 | } 107 | 108 | } 109 | 110 | private fun bindItem(i: Int, p0: ViewHolderFtpFile) { 111 | p0.apply { 112 | val ftpFile: FTPFile = dataList[i] 113 | icon.setImageResource(if (ftpFile.isFile) R.drawable.ic_file else R.drawable.ic_folder) 114 | tvName.text = ftpFile.name 115 | tvDate.text = CommonUtils.getDisplayTimeOfMillis(ftpFile.timestamp?.timeInMillis 116 | ?: 0L) 117 | tvSize.text = Formatter.formatFileSize(context, ftpFile.size) 118 | tvSize.visibility = if (ftpFile.isDirectory) View.GONE else if (isMultiSelectMode) View.GONE else View.VISIBLE 119 | cb.visibility = if (isMultiSelectMode) View.VISIBLE else View.GONE 120 | cb.isChecked = isMultiSelectMode && selected?.get(i) == true 121 | more.visibility = if (isMultiSelectMode) View.GONE else View.VISIBLE 122 | itemView.setOnClickListener { 123 | if (isMultiSelectMode) { 124 | selected?.set(i, selected?.get(i) != true) 125 | notifyItemChanged(p0.adapterPosition) 126 | return@setOnClickListener 127 | } 128 | c?.onItemClicked(ftpFile, p0) 129 | } 130 | itemView.setOnLongClickListener(object : View.OnLongClickListener { 131 | override fun onLongClick(v: View?): Boolean { 132 | if (isMultiSelectMode) { 133 | return true 134 | } 135 | if (showCd && p0.adapterPosition == 0) { 136 | return true 137 | } 138 | currentLongClick = i 139 | isMultiSelectMode = true 140 | c?.onMultiSelectModeTurnedOn() 141 | return true 142 | } 143 | }) 144 | more.setOnClickListener { 145 | c?.onMoreClicked(ftpFile, p0) 146 | } 147 | } 148 | 149 | 150 | } 151 | 152 | override fun getItemCount(): Int { 153 | return if (showCd) 1 + dataList.size else dataList.size 154 | } 155 | } 156 | 157 | class ViewHolderFtpFile(itemView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(itemView) { 158 | val icon: ImageView = itemView.findViewById(R.id.item_ftp_file_icon) 159 | val tvName: TextView = itemView.findViewById(R.id.item_ftp_file_name) 160 | val tvDate: TextView = itemView.findViewById(R.id.item_ftp_file_info) 161 | val tvSize: TextView = itemView.findViewById(R.id.item_ftp_file_info2) 162 | val cb: CheckBox = itemView.findViewById(R.id.item_ftp_file_cb) 163 | val more: ImageView = itemView.findViewById(R.id.item_ftp_file_more) 164 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/data/AccountItem.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.data; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.github.ghmxr.ftpshare.utils.StorageUtil; 6 | 7 | import java.io.Serializable; 8 | 9 | public class AccountItem implements Serializable { 10 | public long id = -1; 11 | public String account = ""; 12 | public String password = ""; 13 | public String path = StorageUtil.getMainStoragePath(); 14 | public boolean writable = false; 15 | 16 | @Override 17 | @NonNull 18 | public String toString() { 19 | return "AccountItem{" + 20 | "id=" + id + 21 | ", account='" + account + '\'' + 22 | ", password='" + password + '\'' + 23 | ", path='" + path + '\'' + 24 | ", writable=" + writable + 25 | '}'; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/data/ClientBean.kt: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.data 2 | 3 | import android.content.Intent 4 | import com.github.ghmxr.ftpshare.Constants 5 | import com.github.ghmxr.ftpshare.MyApplication 6 | import com.github.ghmxr.ftpshare.R 7 | import com.github.ghmxr.ftpshare.fragments.ClientFragment 8 | import com.github.ghmxr.ftpshare.utils.StorageUtil 9 | import org.apache.commons.net.ftp.FTPClient 10 | import org.apache.commons.net.ftp.FTPReply 11 | import org.json.JSONObject 12 | 13 | data class ClientBean constructor(var _id: Int, var nickName: String, var userName: String, var password: String, var host: String, var port: Int) { 14 | enum class Status { 15 | CONNECTED, CONNECTING, DISCONNECTED, DISCONNECTING 16 | } 17 | 18 | @Transient 19 | val client: FTPClient = FTPClient() 20 | 21 | @Transient 22 | var status: Status = Status.DISCONNECTED 23 | 24 | @Transient 25 | val connectCallbacks = ArrayList<(Boolean, Exception?) -> Unit>() 26 | 27 | @Transient 28 | val disconnectCallbacks = ArrayList<(Exception?) -> Unit>() 29 | 30 | /*@Transient 31 | var isConnecting=false 32 | 33 | @Transient 34 | var isDisconnecting=false*/ 35 | /*get() { 36 | field = if (FTPReply.isPositiveCompletion(client.replyCode)) { 37 | Status.CONNECTED 38 | } else { 39 | Status.DISCONNECTED 40 | } 41 | return field 42 | }*/ 43 | 44 | var downloadPath = StorageUtil.getMainStoragePath() + "/Download/FTPShare/$nickName" 45 | 46 | var downloadConfirm = true 47 | 48 | var encode: String = Constants.Charset.CHAR_UTF 49 | 50 | var passiveMode = false 51 | 52 | var connectTimeout = 10 //second 53 | 54 | constructor() : this(-1, MyApplication.getGlobalBaseContext().resources.getString(R.string.client_initial_nickname), "", "", 55 | MyApplication.getGlobalBaseContext().resources.getString(R.string.client_initial_host), 5656) 56 | 57 | constructor(jsonObject: JSONObject) : this(jsonObject.optInt("_id"), jsonObject.optString("nickName"), 58 | jsonObject.optString("userName"), jsonObject.optString("password"), jsonObject.optString("host"), 59 | jsonObject.optInt("port")) { 60 | downloadPath = jsonObject.optString("downloadPath") 61 | downloadConfirm = jsonObject.optBoolean("downloadConfirm") 62 | encode = jsonObject.optString("encode") 63 | passiveMode = jsonObject.optBoolean("passiveMode") 64 | connectTimeout = jsonObject.optInt("connectTimeout") 65 | } 66 | 67 | fun connect(callback: ((Boolean, Exception?) -> Unit)?) { 68 | callback?.let { 69 | synchronized(connectCallbacks) { 70 | connectCallbacks.add(it) 71 | } 72 | } 73 | if (status == Status.CONNECTING) { 74 | return 75 | } 76 | status = Status.CONNECTING 77 | Thread { 78 | synchronized(client) { 79 | try { 80 | client.controlEncoding = encode // 中文支持 81 | client.connectTimeout = connectTimeout * 1000 82 | if (passiveMode) client.enterLocalPassiveMode() 83 | else client.enterLocalActiveMode() 84 | client.connect(host, port) 85 | if (FTPReply.isPositiveCompletion(client.replyCode)) { 86 | val login = client.login(if (userName.isEmpty()) Constants.FTPConsts.NAME_ANONYMOUS else userName, password) 87 | //if(!login)login = client.login(if (userName.isEmpty()) "IUSR" else userName, password) 88 | //if(!login)login = client.login(if (userName.isEmpty()) "FTP" else userName, password) 89 | //if(!login)login = client.login(if (userName.isEmpty()) "USER" else userName, password) 90 | status = if (login) Status.CONNECTED else Status.DISCONNECTED 91 | if (login) { 92 | /*if(FTPReply.isPositiveCompletion(client.sendCommand("OPTS UTF8", "ON"))){ 93 | client.controlEncoding = "UTF-8" // 中文支持 94 | }else{ 95 | client.controlEncoding="GBK" 96 | }*/ 97 | } 98 | /*MyApplication.handler.post { 99 | callback?.invoke(login, null) 100 | }*/ 101 | invokeConnectCallbacksAndClear(login, null) 102 | } 103 | } catch (e: Exception) { 104 | status = Status.DISCONNECTED 105 | /*MyApplication.handler.post { 106 | callback?.invoke(false, e) 107 | }*/ 108 | invokeConnectCallbacksAndClear(false, e) 109 | } 110 | } 111 | }.start() 112 | } 113 | 114 | fun disconnect(callback: ((Exception?) -> Unit)?) { 115 | callback?.let { 116 | synchronized(disconnectCallbacks) { 117 | disconnectCallbacks.add(it) 118 | } 119 | } 120 | if (status == Status.DISCONNECTING) { 121 | return 122 | } 123 | status = Status.DISCONNECTING 124 | Thread { 125 | synchronized(client) { 126 | try { 127 | client.disconnect() 128 | status = Status.DISCONNECTED 129 | /*MyApplication.handler.post { 130 | callback?.invoke(null) 131 | }*/ 132 | invokeDisconnectCallbacksAndClear(null) 133 | } catch (e: Exception) { 134 | status = Status.DISCONNECTED 135 | /*MyApplication.handler.post { 136 | callback?.invoke(e) 137 | }*/ 138 | invokeDisconnectCallbacksAndClear(e) 139 | } 140 | } 141 | }.start() 142 | } 143 | 144 | private fun invokeConnectCallbacksAndClear(b: Boolean, e: Exception?) { 145 | MyApplication.handler.post { 146 | synchronized(connectCallbacks) { 147 | for (c in connectCallbacks) { 148 | c.invoke(b, e) 149 | } 150 | MyApplication.getGlobalBaseContext().sendBroadcast(Intent(ClientFragment.ACTION_REFRESH_LIST)) 151 | connectCallbacks.clear() 152 | } 153 | } 154 | } 155 | 156 | private fun invokeDisconnectCallbacksAndClear(e: Exception?) { 157 | MyApplication.handler.post { 158 | synchronized(connectCallbacks) { 159 | for (c in disconnectCallbacks) { 160 | c.invoke(e) 161 | } 162 | disconnectCallbacks.clear() 163 | } 164 | } 165 | } 166 | 167 | fun toJsonObject(): JSONObject = 168 | JSONObject().apply { 169 | put("userName", userName) 170 | put("password", password) 171 | put("host", host) 172 | put("port", port) 173 | put("nickName", nickName) 174 | put("_id", _id) 175 | put("downloadPath", downloadPath) 176 | put("downloadConfirm", downloadConfirm) 177 | put("encode", encode) 178 | put("passiveMode", passiveMode) 179 | put("connectTimeout", connectTimeout) 180 | } 181 | 182 | 183 | } 184 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/fragments/AccountFragment.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.fragments; 2 | 3 | import android.Manifest; 4 | import android.app.Activity; 5 | import android.content.Intent; 6 | import android.content.SharedPreferences; 7 | import android.os.Build; 8 | import android.os.Bundle; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.CheckBox; 13 | import android.widget.ListView; 14 | import android.widget.TextView; 15 | import android.widget.Toast; 16 | 17 | import androidx.annotation.NonNull; 18 | import androidx.annotation.Nullable; 19 | import androidx.core.content.PermissionChecker; 20 | import androidx.fragment.app.Fragment; 21 | 22 | import com.github.ghmxr.ftpshare.Constants; 23 | import com.github.ghmxr.ftpshare.MyApplication; 24 | import com.github.ghmxr.ftpshare.R; 25 | import com.github.ghmxr.ftpshare.adapers.AccountListAdapter; 26 | import com.github.ghmxr.ftpshare.services.FtpService; 27 | import com.github.ghmxr.ftpshare.ui.DialogOfFolderSelector; 28 | import com.github.ghmxr.ftpshare.utils.CommonUtils; 29 | 30 | @Deprecated 31 | public class AccountFragment extends Fragment implements View.OnClickListener { 32 | 33 | private final SharedPreferences settings = CommonUtils.getSettingSharedPreferences(MyApplication.getGlobalBaseContext()); 34 | private final SharedPreferences.Editor editor = settings.edit(); 35 | private ViewGroup viewGroup_anonymous; 36 | private ListView accountListView; 37 | private ViewGroup viewGroup_no_account; 38 | private TextView anonymous_path; 39 | private CheckBox writable_cb; 40 | 41 | @Nullable 42 | @Override 43 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 44 | return inflater.inflate(R.layout.fragment_account, container, false); 45 | } 46 | 47 | @Override 48 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 49 | super.onViewCreated(view, savedInstanceState); 50 | viewGroup_anonymous = view.findViewById(R.id.mode_anonymous); 51 | accountListView = view.findViewById(R.id.view_user_list); 52 | anonymous_path = view.findViewById(R.id.mode_anonymous_value); 53 | writable_cb = view.findViewById(R.id.anonymous_writable_cb); 54 | viewGroup_no_account = view.findViewById(R.id.add_user_att); 55 | 56 | view.findViewById(R.id.anonymous_path).setOnClickListener(this); 57 | view.findViewById(R.id.anonymous_writable).setOnClickListener(this); 58 | refreshContents(); 59 | } 60 | 61 | /** 62 | * 在API22的虚拟机测试,当startActivityForResult()回来以后再切换fragment,这个方法不会被回调回来,暂不知道原因,所以 63 | * {@link #refreshContents()}方法只能调用多次来适配这些系统。(在{@link #onViewCreated(View, Bundle)})方法里加入了这个方法。 64 | * 重写这个方法是用来适配API21及以下版本系统的,因为onActivityResult()方法并不会在界面完成后回调,而是调用startActivityForResult()方法的瞬时就完成 65 | * 了回调,而且页面完成了也就不再回调了,暂不知原因。 66 | */ 67 | @Override 68 | public void onResume() { 69 | super.onResume(); 70 | refreshContents(); 71 | } 72 | 73 | @Override 74 | public void onClick(View v) { 75 | if (getActivity() == null || getContext() == null || getView() == null) return; 76 | switch (v.getId()) { 77 | default: 78 | break; 79 | case R.id.anonymous_path: { 80 | if (FtpService.isFTPServiceRunning()) { 81 | CommonUtils.showSnackBarOfFtpServiceIsRunning(getActivity()); 82 | return; 83 | } 84 | if (Build.VERSION.SDK_INT >= 23 && PermissionChecker.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PermissionChecker.PERMISSION_GRANTED) { 85 | CommonUtils.showSnackBarOfRequestingWritingPermission(getActivity()); 86 | requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0); 87 | return; 88 | } 89 | final Activity activity = getActivity(); 90 | DialogOfFolderSelector dialog = new DialogOfFolderSelector(activity, 91 | String.valueOf(settings.getString(Constants.PreferenceConsts.ANONYMOUS_MODE_PATH, Constants.PreferenceConsts.ANONYMOUS_MODE_PATH_DEFAULT))); 92 | dialog.show(); 93 | dialog.setOnFolderSelectorDialogConfirmedListener(new DialogOfFolderSelector.OnFolderSelectorDialogConfirmed() { 94 | @Override 95 | public void onFolderSelectorDialogConfirmed(String path) { 96 | if (FtpService.isFTPServiceRunning()) { 97 | Toast.makeText(activity, getResources().getString(R.string.attention_ftp_is_running), Toast.LENGTH_SHORT).show(); 98 | return; 99 | } 100 | editor.putString(Constants.PreferenceConsts.ANONYMOUS_MODE_PATH, path); 101 | editor.apply(); 102 | anonymous_path.setText(path); 103 | } 104 | }); 105 | } 106 | break; 107 | case R.id.anonymous_writable: { 108 | if (FtpService.isFTPServiceRunning()) { 109 | CommonUtils.showSnackBarOfFtpServiceIsRunning(getActivity()); 110 | return; 111 | } 112 | writable_cb.toggle(); 113 | editor.putBoolean(Constants.PreferenceConsts.ANONYMOUS_MODE_WRITABLE, writable_cb.isChecked()); 114 | editor.apply(); 115 | } 116 | break; 117 | } 118 | } 119 | 120 | public void processingActivityResult(int requestCode, int resultCode, Intent data) { 121 | refreshContents(); 122 | } 123 | 124 | public void refreshContents() { 125 | if (getActivity() == null || getContext() == null || getView() == null) return; 126 | anonymous_path.setText(settings.getString(Constants.PreferenceConsts.ANONYMOUS_MODE_PATH, Constants.PreferenceConsts.ANONYMOUS_MODE_PATH_DEFAULT)); 127 | writable_cb.setChecked(settings.getBoolean(Constants.PreferenceConsts.ANONYMOUS_MODE_WRITABLE, Constants.PreferenceConsts.ANONYMOUS_MODE_WRITABLE_DEFAULT)); 128 | AccountListAdapter accountListAdapter = new AccountListAdapter(getActivity(), accountListView); 129 | accountListView.setAdapter(accountListAdapter); 130 | viewGroup_anonymous.setVisibility(CommonUtils.isAnonymousMode(getActivity()) ? View.VISIBLE : View.GONE); 131 | accountListView.setVisibility(CommonUtils.isAnonymousMode(getActivity()) ? View.GONE : View.VISIBLE); 132 | viewGroup_no_account.setVisibility(CommonUtils.isAnonymousMode(getActivity()) ? View.GONE : (accountListAdapter.getAccountItems().size() > 0 ? View.GONE : View.VISIBLE)); 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/ftpclient/FtpClientManager.kt: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.ftpclient 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import com.github.ghmxr.ftpshare.MyApplication 6 | import com.github.ghmxr.ftpshare.data.ClientBean 7 | import org.json.JSONArray 8 | import org.json.JSONObject 9 | 10 | class FtpClientManager private constructor() { 11 | 12 | private val list = mutableListOf() 13 | 14 | companion object { 15 | @JvmStatic 16 | val instance: FtpClientManager by lazy { 17 | FtpClientManager() 18 | } 19 | } 20 | 21 | init { 22 | synchronized(list) { 23 | list.clear() 24 | list.addAll(getClientBeans()) 25 | } 26 | } 27 | 28 | fun getClientBeanSize(): Int = list.size 29 | 30 | fun getClientBeanOfId(id: Int) = synchronized(list) { list[id] } 31 | 32 | fun updateClientBean(id: Int, bean: ClientBean) { 33 | synchronized(list) { 34 | val oldBean = list[id] 35 | oldBean.disconnect {} 36 | bean._id = id 37 | list[bean._id] = bean 38 | flushListToSp() 39 | } 40 | } 41 | 42 | fun saveClientBean(bean: ClientBean) { 43 | synchronized(list) { 44 | list.add(bean) 45 | updateIdOfBeans() 46 | flushListToSp() 47 | } 48 | } 49 | 50 | fun deleteClientBean(bean: ClientBean): Boolean { 51 | synchronized(list) { 52 | if (!list.remove(bean)) return false 53 | updateIdOfBeans() 54 | flushListToSp() 55 | } 56 | return true 57 | } 58 | 59 | private fun updateIdOfBeans() { 60 | for (i in 0 until list.size) { 61 | list[i]._id = i 62 | } 63 | } 64 | 65 | private fun flushListToSp() { 66 | val jsonArray = JSONArray() 67 | for (b in list) { 68 | jsonArray.put(b.toJsonObject()) 69 | } 70 | getPreference().edit().putString("clients", jsonArray.toString()).apply() 71 | } 72 | 73 | private fun getClientBeans(): List = mutableListOf().apply { 74 | val jsonArray = JSONArray(getPreference().getString("clients", "[]")) 75 | for (i in 0 until jsonArray.length()) { 76 | add(ClientBean(jsonArray[i] as JSONObject)) 77 | } 78 | } 79 | 80 | private fun getPreference(): SharedPreferences = MyApplication.getGlobalBaseContext().getSharedPreferences("clientConfig", Context.MODE_PRIVATE) 81 | 82 | 83 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/receivers/BootCompletedReceiver.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.receivers; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import com.github.ghmxr.ftpshare.Constants; 8 | import com.github.ghmxr.ftpshare.services.FtpService; 9 | import com.github.ghmxr.ftpshare.utils.CommonUtils; 10 | 11 | public class BootCompletedReceiver extends BroadcastReceiver { 12 | @Override 13 | public void onReceive(Context context, Intent intent) { 14 | if (Intent.ACTION_BOOT_COMPLETED.equalsIgnoreCase(intent.getAction())) { 15 | if (CommonUtils.getSettingSharedPreferences(context) 16 | .getBoolean(Constants.PreferenceConsts.START_AFTER_BOOT, Constants.PreferenceConsts.START_AFTER_BOOT_DEFAULT)) { 17 | FtpService.startService(context); 18 | } 19 | } 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/services/MyTileService.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.services; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Notification; 5 | import android.app.NotificationChannel; 6 | import android.app.NotificationManager; 7 | import android.app.PendingIntent; 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.os.Build; 11 | import android.service.quicksettings.Tile; 12 | import android.service.quicksettings.TileService; 13 | 14 | import androidx.core.app.NotificationCompat; 15 | 16 | import com.github.ghmxr.ftpshare.R; 17 | import com.github.ghmxr.ftpshare.activities.MainActivity; 18 | 19 | @TargetApi(24) 20 | public class MyTileService extends TileService implements FtpService.OnFTPServiceStatusChangedListener { 21 | 22 | @Override 23 | public void onStartListening() { 24 | super.onStartListening(); 25 | FtpService.addOnFtpServiceStatusChangedListener(this); 26 | refreshTileState(); 27 | } 28 | 29 | @Override 30 | public void onStopListening() { 31 | super.onStopListening(); 32 | FtpService.removeOnFtpServiceStatusChangedListener(this); 33 | } 34 | 35 | @Override 36 | public void onClick() { 37 | super.onClick(); 38 | Tile tile = getQsTile(); 39 | switch (tile.getState()) { 40 | default: 41 | break; 42 | case Tile.STATE_INACTIVE: { 43 | tile.setState(Tile.STATE_UNAVAILABLE); 44 | tile.updateTile(); 45 | if (!FtpService.startService(this)) { 46 | showNotification(getResources().getString(R.string.notification_ftp_start_error_title), getResources().getString(R.string.notification_ftp_start_errer_message)); 47 | tile.setState(Tile.STATE_INACTIVE); 48 | tile.updateTile(); 49 | } 50 | } 51 | break; 52 | case Tile.STATE_ACTIVE: { 53 | tile.setState(Tile.STATE_UNAVAILABLE); 54 | tile.updateTile(); 55 | FtpService.stopService(); 56 | } 57 | break; 58 | } 59 | } 60 | 61 | @Override 62 | public void onFTPServiceStarted() { 63 | Tile tile = getQsTile(); 64 | tile.setState(Tile.STATE_ACTIVE); 65 | tile.updateTile(); 66 | try { 67 | ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).cancel(2); 68 | } catch (Exception e) { 69 | e.printStackTrace(); 70 | } 71 | } 72 | 73 | @Override 74 | public void onFTPServiceStartError(Exception e) { 75 | showNotification(getResources().getString(R.string.notification_ftp_start_error_title), getResources().getString(R.string.notification_ftp_start_errer_message)); 76 | } 77 | 78 | @Override 79 | public void onRemainingSeconds(int seconds) { 80 | } 81 | 82 | @Override 83 | public void onFTPServiceDestroyed() { 84 | Tile tile = getQsTile(); 85 | tile.setState(Tile.STATE_INACTIVE); 86 | tile.updateTile(); 87 | } 88 | 89 | private void refreshTileState() { 90 | Tile tile = getQsTile(); 91 | tile.setState(FtpService.isFTPServiceRunning() ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE); 92 | tile.updateTile(); 93 | } 94 | 95 | private void showNotification(String title, String content) { 96 | NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 97 | if (notificationManager == null) return; 98 | if (Build.VERSION.SDK_INT >= 26) { 99 | NotificationChannel channel = new NotificationChannel("attention" 100 | , getResources().getString(R.string.notification_channel_attention) 101 | , NotificationManager.IMPORTANCE_DEFAULT); 102 | notificationManager.createNotificationChannel(channel); 103 | } 104 | Notification notification = new NotificationCompat.Builder(this, "attention") 105 | .setSmallIcon(R.mipmap.ic_launcher) 106 | .setContentTitle(String.valueOf(title)) 107 | .setContentText(String.valueOf(content)) 108 | .setAutoCancel(true) 109 | .setOngoing(false) 110 | .setContentIntent(PendingIntent.getActivity(this, 1, new Intent(this, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT)) 111 | .build(); 112 | notificationManager.notify(2, notification); 113 | } 114 | 115 | /* 116 | * 收起通知栏 117 | */ 118 | /*public static void collapseStatusBar(Context context) { 119 | try{ 120 | Object statusBarManager = context.getSystemService("statusbar"); 121 | Method collapse; 122 | if (Build.VERSION.SDK_INT <= 16) { 123 | collapse = statusBarManager.getClass().getMethod("collapse"); 124 | } else { 125 | collapse = statusBarManager.getClass().getMethod("collapsePanels"); 126 | } 127 | collapse.invoke(statusBarManager); 128 | } catch (Exception localException) { 129 | localException.printStackTrace(); 130 | } 131 | }*/ 132 | } 133 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/ui/DisconnectSelectionDialog.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.ui; 2 | 3 | import android.content.Context; 4 | import android.content.DialogInterface; 5 | import android.content.SharedPreferences; 6 | import android.text.TextUtils; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.widget.EditText; 10 | import android.widget.RadioButton; 11 | import android.widget.Toast; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.appcompat.app.AlertDialog; 15 | 16 | import com.github.ghmxr.ftpshare.Constants; 17 | import com.github.ghmxr.ftpshare.R; 18 | import com.github.ghmxr.ftpshare.activities.MainActivity; 19 | import com.github.ghmxr.ftpshare.services.FtpService; 20 | import com.github.ghmxr.ftpshare.utils.CommonUtils; 21 | 22 | public class DisconnectSelectionDialog extends AlertDialog implements View.OnClickListener { 23 | 24 | private RadioButton ra_none, ra_wifi, ra_ap, ra_time; 25 | private EditText editText_time; 26 | private int selection = -1; 27 | 28 | public DisconnectSelectionDialog(@NonNull Context context) { 29 | super(context); 30 | View dialogView = LayoutInflater.from(context).inflate(R.layout.layout_dialog_disconnection, null); 31 | setView(dialogView); 32 | ra_none = dialogView.findViewById(R.id.dialog_disconnection_none); 33 | ra_wifi = dialogView.findViewById(R.id.dialog_disconnection_wifi); 34 | ra_ap = dialogView.findViewById(R.id.dialog_disconnection_ap); 35 | ra_time = dialogView.findViewById(R.id.dialog_disconnect_time); 36 | editText_time = dialogView.findViewById(R.id.dialog_disconnect_time_edit); 37 | setTitle(context.getResources().getString(R.string.setting_disconnect)); 38 | setButton(AlertDialog.BUTTON_POSITIVE, getContext().getResources().getString(R.string.dialog_button_confirm), (DialogInterface.OnClickListener) null); 39 | setButton(AlertDialog.BUTTON_NEGATIVE, getContext().getResources().getString(R.string.dialog_button_cancel), new DialogInterface.OnClickListener() { 40 | @Override 41 | public void onClick(DialogInterface dialog, int which) { 42 | 43 | } 44 | }); 45 | } 46 | 47 | @Override 48 | public void onClick(View v) { 49 | switch (v.getId()) { 50 | default: 51 | break; 52 | case R.id.dialog_disconnection_none: { 53 | selection = Constants.PreferenceConsts.AUTO_STOP_NONE; 54 | } 55 | break; 56 | case R.id.dialog_disconnection_wifi: { 57 | selection = Constants.PreferenceConsts.AUTO_STOP_WIFI_DISCONNECTED; 58 | } 59 | break; 60 | case R.id.dialog_disconnection_ap: { 61 | selection = Constants.PreferenceConsts.AUTO_STOP_AP_DISCONNECTED; 62 | } 63 | break; 64 | case R.id.dialog_disconnect_time: { 65 | selection = Constants.PreferenceConsts.AUTO_STOP_TIME_COUNT; 66 | } 67 | break; 68 | } 69 | refreshViews(); 70 | } 71 | 72 | @Override 73 | public void show() { 74 | super.show(); 75 | selection = CommonUtils.getSettingSharedPreferences(getContext()).getInt(Constants.PreferenceConsts.AUTO_STOP, Constants.PreferenceConsts.AUTO_STOP_DEFAULT); 76 | ra_time.setOnClickListener(this); 77 | ra_ap.setOnClickListener(this); 78 | ra_wifi.setOnClickListener(this); 79 | ra_none.setOnClickListener(this); 80 | getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() { 81 | @Override 82 | public void onClick(View v) { 83 | SharedPreferences.Editor editor = CommonUtils.getSettingSharedPreferences(getContext()).edit(); 84 | if (selection == Constants.PreferenceConsts.AUTO_STOP_TIME_COUNT) { 85 | final String value = editText_time.getText().toString(); 86 | if (TextUtils.isEmpty(value)) { 87 | Toast.makeText(getContext(), getContext().getResources().getString(R.string.toast_invalid_value), Toast.LENGTH_SHORT).show(); 88 | return; 89 | } 90 | int seconds; 91 | try { 92 | seconds = Integer.parseInt(value); 93 | } catch (Exception e) { 94 | e.printStackTrace(); 95 | Toast.makeText(getContext(), getContext().getResources().getString(R.string.toast_invalid_value), Toast.LENGTH_SHORT).show(); 96 | return; 97 | } 98 | if (seconds <= 0) { 99 | Toast.makeText(getContext(), getContext().getResources().getString(R.string.toast_invalid_value), Toast.LENGTH_SHORT).show(); 100 | return; 101 | } 102 | editor.putInt(Constants.PreferenceConsts.AUTO_STOP_VALUE, seconds); 103 | FtpService.enableAutoDisconnectThisTime(); 104 | FtpService.setTimeCounts(seconds); 105 | } else { 106 | FtpService.cancelTimeCounts(); 107 | } 108 | FtpService.enableAutoDisconnectThisTime(); 109 | editor.putInt(Constants.PreferenceConsts.AUTO_STOP, selection); 110 | editor.apply(); 111 | cancel(); 112 | if (MainActivity.mainActivity != null) 113 | MainActivity.mainActivity.refreshDisconnectView(); 114 | } 115 | }); 116 | refreshViews(); 117 | } 118 | 119 | private void refreshViews() { 120 | ra_none.setChecked(selection == Constants.PreferenceConsts.AUTO_STOP_NONE); 121 | ra_wifi.setChecked(selection == Constants.PreferenceConsts.AUTO_STOP_WIFI_DISCONNECTED); 122 | ra_ap.setChecked(selection == Constants.PreferenceConsts.AUTO_STOP_AP_DISCONNECTED); 123 | final boolean isTime = selection == Constants.PreferenceConsts.AUTO_STOP_TIME_COUNT; 124 | ra_time.setChecked(isTime); 125 | editText_time.setText(String.valueOf(CommonUtils.getSettingSharedPreferences(getContext()) 126 | .getInt(Constants.PreferenceConsts.AUTO_STOP_VALUE, Constants.PreferenceConsts.AUTO_STOP_VALUE_DEFAULT))); 127 | editText_time.setEnabled(isTime); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/ui/FtpAddressesDialog.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.ui; 2 | 3 | import android.content.Context; 4 | import android.content.DialogInterface; 5 | import android.view.ViewGroup; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.appcompat.app.AlertDialog; 9 | import androidx.recyclerview.widget.LinearLayoutManager; 10 | import androidx.recyclerview.widget.RecyclerView; 11 | 12 | import com.github.ghmxr.ftpshare.R; 13 | import com.github.ghmxr.ftpshare.adapers.FtpAddressesAdapter; 14 | 15 | public class FtpAddressesDialog extends AlertDialog { 16 | 17 | public FtpAddressesDialog(@NonNull Context context) { 18 | super(context); 19 | RecyclerView recyclerView = new RecyclerView(context); 20 | ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 21 | recyclerView.setLayoutParams(layoutParams); 22 | LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext()); 23 | linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); 24 | recyclerView.setLayoutManager(linearLayoutManager); 25 | recyclerView.setAdapter(new FtpAddressesAdapter(context)); 26 | setTitle(context.getResources().getString(R.string.item_ftp_addresses)); 27 | setView(recyclerView); 28 | setButton(AlertDialog.BUTTON_NEGATIVE, context.getResources().getString(R.string.dialog_button_cancel), new DialogInterface.OnClickListener() { 29 | @Override 30 | public void onClick(DialogInterface dialog, int which) { 31 | } 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/ui/ProgressDialog.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.ui; 2 | 3 | import android.content.Context; 4 | import android.text.format.Formatter; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.WindowManager; 8 | import android.widget.CompoundButton; 9 | import android.widget.ProgressBar; 10 | import android.widget.TextView; 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.appcompat.app.AlertDialog; 14 | import androidx.appcompat.widget.AppCompatCheckBox; 15 | 16 | import com.github.ghmxr.ftpshare.R; 17 | 18 | import java.text.DecimalFormat; 19 | 20 | public class ProgressDialog extends AlertDialog { 21 | 22 | ProgressBar progressBar; 23 | TextView att; 24 | TextView att_left; 25 | TextView att_right; 26 | 27 | public ProgressDialog(@NonNull Context context, @NonNull String title) { 28 | super(context); 29 | View dialog_view = LayoutInflater.from(context).inflate(R.layout.dialog_with_progress, null); 30 | setView(dialog_view); 31 | progressBar = dialog_view.findViewById(R.id.dialog_progress_bar); 32 | att = dialog_view.findViewById(R.id.dialog_att); 33 | att_left = dialog_view.findViewById(R.id.dialog_att_left); 34 | att_right = dialog_view.findViewById(R.id.dialog_att_right); 35 | ((AppCompatCheckBox) dialog_view.findViewById(R.id.dialog_progress_keep_on)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 36 | @Override 37 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 38 | try { 39 | if (isChecked) { 40 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 41 | } else { 42 | getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 43 | } 44 | } catch (Exception e) { 45 | e.printStackTrace(); 46 | } 47 | } 48 | }); 49 | setTitle(title); 50 | progressBar.setIndeterminate(true); 51 | } 52 | 53 | public void setProgressIndeterminate(boolean b) { 54 | progressBar.setIndeterminate(b); 55 | } 56 | 57 | public void setProgress(long progress, long total) { 58 | if (progress < 0) return; 59 | if (progress > total) return; 60 | progressBar.setMax((int) (total / 1024)); 61 | progressBar.setProgress((int) (progress / 1024)); 62 | DecimalFormat dm = new DecimalFormat("#.00"); 63 | int percent = (int) (Double.valueOf(dm.format((double) progress / total)) * 100); 64 | att_right.setText(Formatter.formatFileSize(getContext(), progress) + "/" + Formatter.formatFileSize(getContext(), total) + "(" + percent + "%)"); 65 | } 66 | 67 | public void setSpeed(long bytes) { 68 | att_left.setText(Formatter.formatFileSize(getContext(), bytes) + "/s"); 69 | } 70 | 71 | public void setContentText(@NonNull String s) { 72 | att.setText(s); 73 | } 74 | 75 | @Override 76 | public void show() { 77 | super.show(); 78 | try { 79 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 80 | } catch (Exception e) { 81 | e.printStackTrace(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/ui/RadioSelectionDialog.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.ui; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.text.TextUtils; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.view.Window; 11 | import android.view.WindowManager; 12 | import android.widget.RadioButton; 13 | import android.widget.TextView; 14 | 15 | import androidx.annotation.NonNull; 16 | import androidx.annotation.Nullable; 17 | import androidx.recyclerview.widget.LinearLayoutManager; 18 | import androidx.recyclerview.widget.RecyclerView; 19 | 20 | import com.github.ghmxr.ftpshare.R; 21 | 22 | public class RadioSelectionDialog extends Dialog { 23 | 24 | private final String[] selections; 25 | private final V[] values; 26 | private final ConfirmedCallback callback; 27 | private final String title; 28 | private V selected; 29 | 30 | public RadioSelectionDialog(@NonNull Context context, @Nullable String title, @NonNull String[] selections, V[] values, V selected 31 | , @NonNull ConfirmedCallback callback) { 32 | super(context); 33 | this.selections = selections; 34 | this.values = values; 35 | this.selected = selected; 36 | this.callback = callback; 37 | this.title = title; 38 | } 39 | 40 | @Override 41 | protected void onCreate(Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | setContentView(R.layout.dialog_recyclerview); 44 | TextView tv_title = findViewById(R.id.dialog_title); 45 | if (!TextUtils.isEmpty(title)) { 46 | tv_title.setText(title); 47 | } else { 48 | tv_title.setVisibility(View.GONE); 49 | } 50 | Window window = getWindow(); 51 | if (window != null) { 52 | WindowManager.LayoutParams layoutParams = window.getAttributes(); 53 | //layoutParams.gravity= Gravity.BOTTOM; 54 | layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT; 55 | layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; 56 | window.setAttributes(layoutParams); 57 | window.setBackgroundDrawableResource(android.R.color.transparent); 58 | //window.setWindowAnimations(R.style.DialogAnimStyle); 59 | } 60 | 61 | } 62 | 63 | @Override 64 | public void show() { 65 | super.show(); 66 | RecyclerView recyclerView = findViewById(R.id.recycler_view); 67 | LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext()); 68 | linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); 69 | recyclerView.setLayoutManager(linearLayoutManager); 70 | recyclerView.setAdapter(new ListAdapter()); 71 | } 72 | 73 | public interface ConfirmedCallback { 74 | void onConfirmed(String selection, V value); 75 | } 76 | 77 | private static class ViewHolder extends RecyclerView.ViewHolder { 78 | RadioButton radioButton; 79 | 80 | ViewHolder(@NonNull View itemView) { 81 | super(itemView); 82 | radioButton = itemView.findViewById(R.id.radio_button); 83 | } 84 | } 85 | 86 | private class ListAdapter extends RecyclerView.Adapter { 87 | private ListAdapter() { 88 | } 89 | 90 | @NonNull 91 | @Override 92 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { 93 | return new ViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.item_radio_button, viewGroup, false)); 94 | } 95 | 96 | @Override 97 | public void onBindViewHolder(@NonNull final ViewHolder viewHolder, int i) { 98 | viewHolder.radioButton.setText(selections[viewHolder.getAdapterPosition()]); 99 | viewHolder.radioButton.setChecked(values[viewHolder.getAdapterPosition()].equals(selected)); 100 | viewHolder.itemView.setOnClickListener(new View.OnClickListener() { 101 | @Override 102 | public void onClick(View v) { 103 | if (callback != null) 104 | callback.onConfirmed(selections[viewHolder.getAdapterPosition()], values[viewHolder.getAdapterPosition()]); 105 | cancel(); 106 | } 107 | }); 108 | } 109 | 110 | @Override 111 | public int getItemCount() { 112 | return selections.length; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/utils/MySQLiteOpenHelper.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.utils; 2 | 3 | import android.content.ContentValues; 4 | import android.content.Context; 5 | import android.database.sqlite.SQLiteDatabase; 6 | import android.database.sqlite.SQLiteOpenHelper; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | 11 | import com.github.ghmxr.ftpshare.Constants; 12 | import com.github.ghmxr.ftpshare.data.AccountItem; 13 | 14 | public class MySQLiteOpenHelper extends SQLiteOpenHelper { 15 | 16 | 17 | public MySQLiteOpenHelper(Context context) { 18 | super(context, Constants.SQLConsts.SQL_USERS_FILENAME, null, Constants.SQLConsts.SQL_VERSION); 19 | } 20 | 21 | /** 22 | * 插入或者更新AccountItem到数据库 23 | * 24 | * @param id_update 不为空则更新指定行,指定为item.id即可 25 | * @return 执行结果 26 | */ 27 | public static long saveOrUpdateAccountItem2DB(@NonNull Context context, AccountItem item, @Nullable Long id_update) { 28 | SQLiteDatabase db = new MySQLiteOpenHelper(context).getWritableDatabase(); 29 | ContentValues contentValues = new ContentValues(); 30 | contentValues.put(Constants.SQLConsts.COLUMN_ACCOUNT_NAME, item.account); 31 | contentValues.put(Constants.SQLConsts.COLUMN_PASSWORD, item.password); 32 | contentValues.put(Constants.SQLConsts.COLUMN_PATH, item.path); 33 | contentValues.put(Constants.SQLConsts.COLUMN_WRITABLE, item.writable ? 1 : 0); 34 | long result; 35 | if (id_update == null) 36 | result = db.insert(Constants.SQLConsts.TABLE_NAME, null, contentValues); 37 | else 38 | result = db.update(Constants.SQLConsts.TABLE_NAME, contentValues, Constants.SQLConsts.COLUMN_ID + "=" + id_update, null); 39 | db.close(); 40 | return result; 41 | } 42 | 43 | /** 44 | * 根据ID值删除指定行 45 | */ 46 | public static long deleteRow(Context context, long id) { 47 | SQLiteDatabase database = new MySQLiteOpenHelper(context).getWritableDatabase(); 48 | long result = database.delete(Constants.SQLConsts.TABLE_NAME, Constants.SQLConsts.COLUMN_ID + "=" + id, null); 49 | database.close(); 50 | return result; 51 | } 52 | 53 | @Override 54 | public void onCreate(SQLiteDatabase db) { 55 | db.execSQL("create table if not exists " + Constants.SQLConsts.TABLE_NAME + " (" 56 | + Constants.SQLConsts.COLUMN_ID + " integer primary key autoincrement not null," 57 | + Constants.SQLConsts.COLUMN_ACCOUNT_NAME + " text," 58 | + Constants.SQLConsts.COLUMN_PASSWORD + " text," 59 | + Constants.SQLConsts.COLUMN_PATH + " text," 60 | + Constants.SQLConsts.COLUMN_WRITABLE + " integer not null default 0);"); 61 | } 62 | 63 | @Override 64 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/utils/NetworkEnvironmentUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.utils; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | import android.net.wifi.WifiManager; 7 | import android.text.format.Formatter; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.annotation.Nullable; 11 | 12 | import java.lang.reflect.Field; 13 | import java.lang.reflect.Method; 14 | import java.net.Inet4Address; 15 | import java.net.InetAddress; 16 | import java.net.NetworkInterface; 17 | import java.util.ArrayList; 18 | import java.util.Enumeration; 19 | 20 | public class NetworkEnvironmentUtil { 21 | 22 | /** 23 | * 反射拿热点是否开启 24 | */ 25 | public static boolean isAPEnabled(Context context) { 26 | try { 27 | WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); 28 | Method method = wifiManager.getClass().getDeclaredMethod("getWifiApState"); 29 | Field field = wifiManager.getClass().getDeclaredField("WIFI_AP_STATE_ENABLED"); 30 | int value_wifi_enabled = (int) field.get(wifiManager); 31 | return ((int) method.invoke(wifiManager)) == value_wifi_enabled; 32 | } catch (Exception e) { 33 | e.printStackTrace(); 34 | } 35 | return false; 36 | } 37 | 38 | /** 39 | * 获取WiFi网络的IPv4地址 40 | * 41 | * @return 可能为null 42 | */ 43 | public static @Nullable 44 | String getWifiIp(@NonNull Context context) { 45 | try { 46 | return Formatter.formatIpAddress(((WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE)) 47 | .getConnectionInfo().getIpAddress()); 48 | } catch (Exception e) { 49 | e.printStackTrace(); 50 | } 51 | return null; 52 | } 53 | 54 | /** 55 | * 获取本地网络环境所有可用的IPv4地址 56 | * 57 | * @return 类似"192.168.1.101"的IP地址集 58 | */ 59 | public static ArrayList getLocalIpv4Addresses() { 60 | final ArrayList result = new ArrayList<>(); 61 | try { 62 | Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); 63 | NetworkInterface networkInterface; 64 | while ((networkInterface = networkInterfaces.nextElement()) != null) { 65 | Enumeration inetAddresses = networkInterface.getInetAddresses(); 66 | try { 67 | InetAddress inetAddress; 68 | while ((inetAddress = inetAddresses.nextElement()) != null) { 69 | if (!inetAddress.isLoopbackAddress() && (inetAddress instanceof Inet4Address)) { 70 | String ip = inetAddress.getHostAddress(); 71 | if (!result.contains(ip)) result.add(ip); 72 | } 73 | } 74 | } catch (Exception e) { 75 | // 76 | } 77 | } 78 | } catch (Exception e) { 79 | // 80 | } 81 | return result; 82 | } 83 | 84 | public static boolean isWifiConnected(@NonNull Context context) { 85 | return isNetworkConnected(context, ConnectivityManager.TYPE_WIFI); 86 | } 87 | 88 | static boolean isCellularNetworkConnected(@NonNull Context context) { 89 | return isNetworkConnected(context, ConnectivityManager.TYPE_MOBILE); 90 | } 91 | 92 | private static boolean isNetworkConnected(@NonNull Context context, int type) { 93 | try { 94 | ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 95 | NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); 96 | return networkInfo != null && networkInfo.getType() == type; 97 | } catch (Exception e) { 98 | e.printStackTrace(); 99 | } 100 | return false; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/utils/StorageUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.utils; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.os.Environment; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.File; 11 | import java.io.InputStreamReader; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Locale; 15 | 16 | public class StorageUtil { 17 | public static String getMainStoragePath() { 18 | try { 19 | return Environment.getExternalStorageDirectory().getAbsolutePath(); 20 | } catch (Exception e) { 21 | e.printStackTrace(); 22 | } 23 | return ""; 24 | } 25 | 26 | /** 27 | * get all available storage paths on the device. 28 | */ 29 | public static List getAvailableStoragePaths() { 30 | try { 31 | List paths = new ArrayList<>(); 32 | String mainStorage = getMainStoragePath().toLowerCase(Locale.getDefault()).trim(); 33 | try { 34 | paths.add(mainStorage); 35 | } catch (Exception e) { 36 | } 37 | 38 | Runtime runtime = Runtime.getRuntime(); 39 | Process process = runtime.exec("mount"); 40 | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 41 | String line; 42 | while ((line = reader.readLine()) != null) { 43 | //Log.d("", line); 44 | if (line.contains("proc") || line.contains("tmpfs") || line.contains("media") || line.contains("asec") || line.contains("secure") || line.contains("system") || line.contains("cache") 45 | || line.contains("sys") || line.contains("data") || line.contains("shell") || line.contains("root") || line.contains("acct") || line.contains("misc") || line.contains("obb")) { 46 | continue; 47 | } 48 | if (line.contains("fat") || line.contains("fuse") || (line.contains("ntfs"))) { 49 | 50 | String items[] = line.split(" "); 51 | if (items != null && items.length > 1) { 52 | String path = items[1].toLowerCase(Locale.getDefault()); 53 | if (!path.toLowerCase(Locale.getDefault()).trim().equals(mainStorage)) 54 | paths.add(path); 55 | } 56 | } 57 | 58 | 59 | } 60 | //Log.d("StoragePaths", Arrays.toString(paths.toArray())); 61 | return paths; 62 | } catch (Exception e) { 63 | e.printStackTrace(); 64 | } 65 | return new ArrayList<>(); 66 | } 67 | 68 | @TargetApi(19) 69 | public static List getAvailableStoragePaths(@NonNull Context context) { 70 | final ArrayList arrayList = new ArrayList<>(); 71 | try { 72 | File[] exFiles = context.getExternalFilesDirs(null); 73 | if (exFiles != null) { 74 | for (File file : exFiles) { 75 | try { 76 | String path = file.getAbsolutePath().toLowerCase(); 77 | path = path.substring(0, path.indexOf("/android/data")); 78 | arrayList.add(path); 79 | } catch (Exception e) { 80 | e.printStackTrace(); 81 | } 82 | } 83 | } 84 | } catch (Exception e) { 85 | e.printStackTrace(); 86 | } 87 | return arrayList; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/views/AutoMarqueeTextView.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.views; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.graphics.Rect; 6 | import android.util.AttributeSet; 7 | 8 | import androidx.appcompat.widget.AppCompatTextView; 9 | 10 | public class AutoMarqueeTextView extends AppCompatTextView { 11 | 12 | public AutoMarqueeTextView(Context context) { 13 | super(context); 14 | setFocusable(true);//在每个构造方法中,将TextView设置为可获取焦点 15 | } 16 | 17 | public AutoMarqueeTextView(Context context, AttributeSet attrs, int defStyle) { 18 | super(context, attrs, defStyle); 19 | setFocusable(true); 20 | } 21 | 22 | public AutoMarqueeTextView(Context context, AttributeSet attrs) { 23 | super(context, attrs); 24 | setFocusable(true); 25 | } 26 | 27 | @Override 28 | public boolean isFocused() {//这个方法必须返回true,制造假象,当系统调用该方法的时候,会一直以为TextView已经获取了焦点 29 | return true; 30 | } 31 | 32 | @SuppressLint("MissingSuperCall") 33 | @Override 34 | protected void onFocusChanged(boolean focused, int direction, 35 | Rect previouslyFocusedRect) {//这个方法必须删除其方法体内的实现,也就是让他空实现,也就是说,TextView的焦点获取状态永远都不会改变 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/ghmxr/ftpshare/widgets/FtpWidget.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare.widgets; 2 | 3 | import android.app.PendingIntent; 4 | import android.appwidget.AppWidgetManager; 5 | import android.appwidget.AppWidgetProvider; 6 | import android.content.BroadcastReceiver; 7 | import android.content.ComponentName; 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.widget.RemoteViews; 11 | import android.widget.Toast; 12 | 13 | import com.github.ghmxr.ftpshare.R; 14 | import com.github.ghmxr.ftpshare.activities.MainActivity; 15 | import com.github.ghmxr.ftpshare.services.FtpService; 16 | 17 | public class FtpWidget extends AppWidgetProvider implements FtpService.OnFTPServiceStatusChangedListener { 18 | 19 | private Context context; 20 | 21 | @Override 22 | public void onEnabled(Context context) { 23 | super.onEnabled(context); 24 | this.context = context; 25 | FtpService.addOnFtpServiceStatusChangedListener(this); 26 | } 27 | 28 | @Override 29 | public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { 30 | super.onUpdate(context, appWidgetManager, appWidgetIds); 31 | appWidgetManager.updateAppWidget(new ComponentName(context, FtpWidget.class), getCurrentRemoteView(context)); 32 | } 33 | 34 | @Override 35 | public void onFTPServiceStarted() { 36 | if (context == null) return; 37 | AppWidgetManager.getInstance(context).updateAppWidget(new ComponentName(context, FtpWidget.class), getCurrentRemoteView(context)); 38 | } 39 | 40 | @Override 41 | public void onFTPServiceStartError(Exception e) { 42 | if (context == null) return; 43 | Toast.makeText(context, String.valueOf(e), Toast.LENGTH_SHORT).show(); 44 | } 45 | 46 | @Override 47 | public void onRemainingSeconds(int seconds) { 48 | } 49 | 50 | @Override 51 | public void onFTPServiceDestroyed() { 52 | if (context == null) return; 53 | AppWidgetManager.getInstance(context).updateAppWidget(new ComponentName(context, FtpWidget.class), getCurrentRemoteView(context)); 54 | } 55 | 56 | private RemoteViews getCurrentRemoteView(Context context) { 57 | RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_switch); 58 | boolean isRunning = FtpService.isFTPServiceRunning(); 59 | remoteViews.setImageViewResource(R.id.widget_switch_icon, isRunning ? R.drawable.switch_on : R.drawable.switch_off); 60 | remoteViews.setTextViewText(R.id.widget_description, isRunning ? FtpService.getFTPStatusDescription(context) : 61 | context.getResources().getString(R.string.ftp_status_not_running)); 62 | remoteViews.setOnClickPendingIntent(R.id.widget_switch_area, 63 | PendingIntent.getBroadcast(context, 64 | 1, 65 | new Intent(context, FtpWidgetReceiver.class), PendingIntent.FLAG_UPDATE_CURRENT)); 66 | remoteViews.setOnClickPendingIntent(R.id.widget_switch_root, 67 | PendingIntent.getActivity(context, 2, new Intent(context, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT)); 68 | return remoteViews; 69 | } 70 | 71 | @Override 72 | public void onDisabled(Context context) { 73 | super.onDisabled(context); 74 | FtpService.removeOnFtpServiceStatusChangedListener(this); 75 | } 76 | 77 | public static class FtpWidgetReceiver extends BroadcastReceiver { 78 | @Override 79 | public void onReceive(Context context, Intent intent) { 80 | boolean b = FtpService.isFTPServiceRunning(); 81 | if (b) FtpService.stopService(); 82 | else FtpService.startService(context); 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/org/apache/ftpserver/command/impl/OPTS_UTF8.java: -------------------------------------------------------------------------------- 1 | package org.apache.ftpserver.command.impl; 2 | 3 | import com.github.ghmxr.ftpshare.Constants; 4 | import com.github.ghmxr.ftpshare.services.FtpService; 5 | 6 | import org.apache.ftpserver.command.AbstractCommand; 7 | import org.apache.ftpserver.ftplet.FtpException; 8 | import org.apache.ftpserver.ftplet.FtpRequest; 9 | import org.apache.ftpserver.impl.FtpIoSession; 10 | import org.apache.ftpserver.impl.FtpServerContext; 11 | import org.apache.ftpserver.impl.LocalizedFtpReply; 12 | 13 | import java.io.IOException; 14 | 15 | public class OPTS_UTF8 extends AbstractCommand { 16 | public OPTS_UTF8() { 17 | } 18 | 19 | public void execute(FtpIoSession session, FtpServerContext context, FtpRequest request) throws IOException, FtpException { 20 | session.resetState(); 21 | session.write(LocalizedFtpReply.translate(session, request, context, 22 | FtpService.getCharsetFromSharedPreferences().equals(Constants.PreferenceConsts.CHARSET_TYPE_DEFAULT) ? 200 : 502 23 | , "OPTS.UTF8", (String) null)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/org/apache/ftpserver/listener/nio/FtpResponseEncoder.java: -------------------------------------------------------------------------------- 1 | package org.apache.ftpserver.listener.nio; 2 | 3 | import com.github.ghmxr.ftpshare.services.FtpService; 4 | 5 | import org.apache.mina.core.buffer.IoBuffer; 6 | import org.apache.mina.core.session.IoSession; 7 | import org.apache.mina.filter.codec.ProtocolEncoderAdapter; 8 | import org.apache.mina.filter.codec.ProtocolEncoderOutput; 9 | 10 | import java.nio.charset.Charset; 11 | import java.nio.charset.CharsetEncoder; 12 | 13 | public class FtpResponseEncoder extends ProtocolEncoderAdapter { 14 | private final CharsetEncoder ENCODER = Charset.forName(FtpService.getCharsetFromSharedPreferences()).newEncoder(); 15 | 16 | public FtpResponseEncoder() { 17 | //Log.d(getClass().getName(),"the charset is "+FtpService.getCharsetFromSharedPreferences()); 18 | } 19 | 20 | public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception { 21 | String value = message.toString(); 22 | IoBuffer buf = IoBuffer.allocate(value.length()).setAutoExpand(true); 23 | buf.putString(value, ENCODER); 24 | //buf.putString(value,Charset.forName(FtpService.getCharsetFromSharedPreferences()).newEncoder()); 25 | buf.flip(); 26 | out.write(buf); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/org/apache/ftpserver/listener/nio/FtpServerProtocolCodecFactory.java: -------------------------------------------------------------------------------- 1 | package org.apache.ftpserver.listener.nio; 2 | 3 | import com.github.ghmxr.ftpshare.services.FtpService; 4 | 5 | import org.apache.mina.core.session.IoSession; 6 | import org.apache.mina.filter.codec.ProtocolCodecFactory; 7 | import org.apache.mina.filter.codec.ProtocolDecoder; 8 | import org.apache.mina.filter.codec.ProtocolEncoder; 9 | import org.apache.mina.filter.codec.textline.TextLineDecoder; 10 | 11 | import java.nio.charset.Charset; 12 | 13 | public class FtpServerProtocolCodecFactory implements ProtocolCodecFactory { 14 | private final ProtocolDecoder decoder; 15 | private final ProtocolEncoder encoder = new FtpResponseEncoder(); 16 | 17 | public FtpServerProtocolCodecFactory() { 18 | String charset = FtpService.getCharsetFromSharedPreferences(); 19 | decoder = new TextLineDecoder(Charset.forName(charset)); 20 | //Log.d(getClass().getName(),"the charset is "+charset); 21 | } 22 | 23 | public ProtocolDecoder getDecoder(IoSession session) throws Exception { 24 | return this.decoder; 25 | } 26 | 27 | public ProtocolEncoder getEncoder(IoSession session) throws Exception { 28 | return this.encoder; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/res/anim/entry_300.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/anim/exit_300.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/color/selector_bottom_nav.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/arrow_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_alipay.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_device.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_download.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_ex_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_file.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_folder.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_more.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_new_folder.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_check.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_select_all.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_select_all_off.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_server.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_stop.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_upload.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_warn.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_wheel_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/drawable/icon_account.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_add.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_alarm.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 36 | 39 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_ap_disconnected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/drawable/icon_ap_disconnected.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/drawable/icon_check.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_close.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_close_white.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_face_ops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/drawable/icon_face_ops.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/drawable/icon_folder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_save.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_wifi_disconnected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/drawable/icon_wifi_disconnected.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_5dp_light.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/switch_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/drawable/switch_off.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/switch_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/drawable/switch_on.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_ftp_client.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 22 | 23 | 33 | 34 | 38 | 39 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 26 | 27 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_recyclerview.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_with_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 20 | 21 | 30 | 31 | 40 | 41 | 50 | 51 | 59 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_account.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 23 | 24 | 28 | 29 | 36 | 37 | 38 | 44 | 45 | 49 | 50 | 58 | 59 | 69 | 70 | 76 | 77 | 83 | 84 | 85 | 92 | 93 | 94 | 98 | 99 | 107 | 108 | 116 | 117 | 123 | 124 | 129 | 130 | 131 | 140 | 141 | 142 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_client.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 23 | 24 | 28 | 29 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_account.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 28 | 29 | 37 | 38 | 44 | 45 | 51 | 52 | 56 | 57 | 62 | 63 | 64 | 65 | 72 | 73 | 78 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_folder.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 27 | 28 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_ftp_address.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_ftp_client.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 27 | 28 | 39 | 40 | 49 | 50 | 59 | 60 | 61 | 73 | 74 | 87 | 88 | 94 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_ftp_file.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 26 | 27 | 36 | 37 | 46 | 47 | 48 | 53 | 54 | 65 | 66 | 76 | 77 | 86 | 87 | 88 | 89 | 94 | 95 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_radio_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_spinner_storage.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_dialog_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 18 | 19 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_dialog_charset.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | 27 | 28 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_dialog_disconnection.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 20 | 21 | 27 | 28 | 34 | 35 | 41 | 42 | 54 | 55 | 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_dialog_folder_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 22 | 23 | 28 | 29 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_disconnection.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 22 | 23 | 28 | 29 | 35 | 36 | 44 | 45 | 46 | 50 | 51 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_with_edittext.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/popup_client_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 23 | 24 | 25 | 29 | 30 | 35 | 36 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/popup_ftp_file_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 23 | 24 | 25 | 29 | 30 | 35 | 36 | 43 | 44 | 45 | 49 | 50 | 56 | 57 | 64 | 65 | 66 | 71 | 72 | 77 | 78 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /app/src/main/res/layout/widget_switch.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 24 | 25 | 33 | 34 | 35 | 42 | 43 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_account.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_account_add.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_folder_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | 16 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_ftp_client_file.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 11 | 18 | 25 | 32 | 39 | 46 | 53 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_service_account.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 11 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/menu/navigation.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 15 | 16 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #303030 4 | #202020 5 | #FF4081 6 | 7 | #FFFFFF 8 | #FFFFFF 9 | #666666 10 | #eeeeee 11 | #ffffff 12 | #666666 13 | #232323 14 | #232323 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #FF4081 6 | 7 | #000000 8 | #008577 9 | #dddddd 10 | #999999 11 | #008577 12 | #666666 13 | #f9f9f9 14 | #e7e7e7 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/xml/ftp_desktop_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/test/java/com/github/ghmxr/ftpshare/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.github.ghmxr.ftpshare; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Test; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.4.32' 5 | 6 | repositories { 7 | maven{url 'http://maven.aliyun.com/nexus/content/groups/public/'} 8 | google() 9 | jcenter() 10 | } 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:3.4.2' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | 15 | 16 | // NOTE: Do not place your application dependencies here; they belong 17 | // in the individual module build.gradle files 18 | } 19 | } 20 | 21 | allprojects { 22 | repositories { 23 | maven{url 'http://maven.aliyun.com/nexus/content/groups/public/'} 24 | google() 25 | jcenter() 26 | } 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | android.enableJetifier=true 10 | android.useAndroidX=true 11 | org.gradle.jvmargs=-Xmx1536m 12 | # When configured, Gradle will run in incubating parallel mode. 13 | # This option should only be used with decoupled projects. More details, visit 14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 15 | # org.gradle.parallel=true 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jan 21 17:00:40 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /preview/ftpshare_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/preview/ftpshare_1.png -------------------------------------------------------------------------------- /preview/ftpshare_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/preview/ftpshare_2.png -------------------------------------------------------------------------------- /preview/ftpshare_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/preview/ftpshare_3.png -------------------------------------------------------------------------------- /preview/ftpshare_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/preview/ftpshare_4.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /signedAPKs/release/build-1.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/signedAPKs/release/build-1.apk -------------------------------------------------------------------------------- /signedAPKs/release/build-10.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/signedAPKs/release/build-10.apk -------------------------------------------------------------------------------- /signedAPKs/release/build-11.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/signedAPKs/release/build-11.apk -------------------------------------------------------------------------------- /signedAPKs/release/build-2.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/signedAPKs/release/build-2.apk -------------------------------------------------------------------------------- /signedAPKs/release/build-3.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/signedAPKs/release/build-3.apk -------------------------------------------------------------------------------- /signedAPKs/release/build-4.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/signedAPKs/release/build-4.apk -------------------------------------------------------------------------------- /signedAPKs/release/build-5.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/signedAPKs/release/build-5.apk -------------------------------------------------------------------------------- /signedAPKs/release/build-6.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/signedAPKs/release/build-6.apk -------------------------------------------------------------------------------- /signedAPKs/release/build-8.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/signedAPKs/release/build-8.apk -------------------------------------------------------------------------------- /signedAPKs/release/build-9.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghmxr/ftpshare/372ba060e4d73b4ad270bb47f3f8d5539d4c5c76/signedAPKs/release/build-9.apk -------------------------------------------------------------------------------- /signedAPKs/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":11,"versionName":"Build 11","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] --------------------------------------------------------------------------------