├── .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 |
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 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_account_add.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_folder_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_ftp_client_file.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_service_account.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/navigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
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":{}}]
--------------------------------------------------------------------------------