From 100fd190786047aac2b39b63f94b08cbc6c82528 Mon Sep 17 00:00:00 2001 From: zhwanng <48609908+zhwanng@users.noreply.github.com> Date: Sat, 16 Dec 2023 12:00:13 +0800 Subject: [PATCH 1/5] stage1:first commit --- app/build.gradle | 44 +++-- app/src/main/AndroidManifest.xml | 34 ++-- .../seadroid2/SeadroidApplication.java | 8 +- .../seadroid2/account/Authenticator.java | 2 +- .../cameraupload/CameraSyncAdapter.java | 38 ++-- .../CameraUploadConfigActivity.java | 4 +- .../CloudLibraryAccountAdapter.java | 2 +- .../cameraupload/CloudLibraryAdapter.java | 2 +- .../CloudLibrarySelectionFragment.java | 4 +- .../{ui => context}/CopyMoveContext.java | 2 +- .../seadroid2/{ui => context}/NavContext.java | 4 +- .../seadroid2/editor/EditorActivity.java | 10 +- .../CloudLibraryChooserFragment.java | 4 +- .../FolderBackupConfigActivity.java | 16 +- .../FolderBackupSelectedPathActivity.java | 4 +- .../folderbackup/FolderBackupService.java | 15 +- .../seadroid2/gesturelock/DefaultAppLock.java | 2 +- .../BaseNotificationProvider.java | 8 +- .../DownloadNotificationProvider.java | 2 +- .../UploadNotificationProvider.java | 2 +- .../CustomExoVideoPlayerActivity.java | 3 +- .../seafile/seadroid2/task/StarItemsTask.java | 2 +- .../ui/AnimateFirstDisplayListener.java | 30 --- .../ui/{activity => }/BaseActivity.java | 2 +- .../ui/{activity => }/BrowserActivity.java | 30 +-- .../com/seafile/seadroid2/ui/WidgetUtils.java | 5 +- .../account}/AccountDetailActivity.java | 5 +- .../AccountsActivity.java | 8 +- .../BaseAuthenticatorActivity.java | 4 +- .../SeafileAuthenticatorActivity.java | 3 +- .../account}/SingleSignOnActivity.java | 4 +- .../SingleSignOnAuthorizeActivity.java | 4 +- .../{ => account}/adapter/AccountAdapter.java | 2 +- .../adapter/SeafAccountAdapter.java | 2 +- .../ActivitiesFragment.java | 7 +- .../ActivitiesItemAdapter.java | 6 +- .../seadroid2/ui/activity/FileActivity.java | 1 + .../ui/activity/GalleryActivity.java | 8 +- .../activity/SeafilePathChooserActivity.java | 14 +- .../ui/activity/ShareToSeafileActivity.java | 1 + .../seadroid2/ui/adapter/SeafItemAdapter.java | 9 +- .../ui/adapter/SeafItemCheckableAdapter.java | 4 +- .../seadroid2/ui/dialog/CopyMoveDialog.java | 2 +- .../seadroid2/ui/dialog/CopyMoveTask.java | 2 +- .../seadroid2/ui/dialog/FetchFileDialog.java | 2 +- .../seadroid2/ui/dialog/PolicyDialog.java | 2 +- .../ui/dialog/UploadChoiceDialog.java | 2 +- .../CreateGesturePasswordActivity.java | 3 +- .../UnlockGesturePasswordActivity.java | 5 +- .../MarkdownActivity.java | 3 +- .../ui/{adapter => repo}/DirentsAdapter.java | 2 +- .../ui/{adapter => repo}/ReposAdapter.java | 2 +- .../ui/{fragment => repo}/ReposFragment.java | 8 +- .../{adapter => repo}/SeafReposAdapter.java | 2 +- .../search/Search2Activity.java | 4 +- .../search/SearchRecyclerViewAdapter.java | 2 +- .../PrivacyPolicyActivity.java | 3 +- .../SettingsActivity.java | 5 +- .../SettingsCameraBackupAdvanceFragment.java | 5 +- .../SettingsFragment.java | 11 +- .../{fragment => star}/StarredFragment.java | 5 +- .../{adapter => star}/StarredItemAdapter.java | 4 +- .../DownloadTaskFragment.java | 3 +- .../TransferActivity.java | 6 +- .../TransferTaskAdapter.java | 4 +- .../TransferTaskFragment.java | 4 +- .../UploadTaskFragment.java | 3 +- .../webview/SeaWebViewActivity.java | 11 +- .../seadroid2/util/ContactsDialog.java | 2 +- .../seafile/seadroid2/util/CrashHandler.java | 6 +- .../java/com/seafile/seadroid2/util/Logs.java | 171 ++++++++++++++++++ .../seadroid2/util/PermissionUtil.java | 3 - .../com/seafile/seadroid2/util/SLogs.java | 33 ++++ .../seafile/seadroid2/util/SeafileLog.java | 163 ----------------- .../com/seafile/seadroid2/util/Utils.java | 12 -- .../{ui/widget => view}/CircleImageView.java | 2 +- .../{ui => view}/CustomClearableEditText.java | 2 +- .../{ui => view}/HackyViewPager.java | 2 +- .../{ui/widget => view}/PerformEdit.java | 2 +- .../{ui => view}/ZoomOutPageTransformer.java | 2 +- .../seadroid2/view/webview/SeaWebView.java | 3 - .../view/webview/SeaWebViewClient.java | 8 - .../res/layout/gallery_activity_layout.xml | 2 +- .../main/res/layout/list_item_activities.xml | 4 +- app/src/main/res/xml/file_paths.xml | 3 +- 85 files changed, 446 insertions(+), 440 deletions(-) rename app/src/main/java/com/seafile/seadroid2/{ui => context}/CopyMoveContext.java (98%) rename app/src/main/java/com/seafile/seadroid2/{ui => context}/NavContext.java (93%) delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/AnimateFirstDisplayListener.java rename app/src/main/java/com/seafile/seadroid2/ui/{activity => }/BaseActivity.java (99%) rename app/src/main/java/com/seafile/seadroid2/ui/{activity => }/BrowserActivity.java (99%) rename app/src/main/java/com/seafile/seadroid2/{account/ui => ui/account}/AccountDetailActivity.java (99%) rename app/src/main/java/com/seafile/seadroid2/ui/{activity => account}/AccountsActivity.java (98%) rename app/src/main/java/com/seafile/seadroid2/ui/{ => account}/BaseAuthenticatorActivity.java (96%) rename app/src/main/java/com/seafile/seadroid2/{account/ui => ui/account}/SeafileAuthenticatorActivity.java (98%) rename app/src/main/java/com/seafile/seadroid2/{account/ui => ui/account}/SingleSignOnActivity.java (97%) rename app/src/main/java/com/seafile/seadroid2/{account/ui => ui/account}/SingleSignOnAuthorizeActivity.java (99%) rename app/src/main/java/com/seafile/seadroid2/ui/{ => account}/adapter/AccountAdapter.java (98%) rename app/src/main/java/com/seafile/seadroid2/ui/{ => account}/adapter/SeafAccountAdapter.java (93%) rename app/src/main/java/com/seafile/seadroid2/ui/{fragment => activities}/ActivitiesFragment.java (98%) rename app/src/main/java/com/seafile/seadroid2/ui/{adapter => activities}/ActivitiesItemAdapter.java (98%) rename app/src/main/java/com/seafile/seadroid2/ui/{activity => gesture}/CreateGesturePasswordActivity.java (99%) rename app/src/main/java/com/seafile/seadroid2/ui/{activity => gesture}/UnlockGesturePasswordActivity.java (98%) rename app/src/main/java/com/seafile/seadroid2/ui/{activity => markdown}/MarkdownActivity.java (97%) rename app/src/main/java/com/seafile/seadroid2/ui/{adapter => repo}/DirentsAdapter.java (99%) rename app/src/main/java/com/seafile/seadroid2/ui/{adapter => repo}/ReposAdapter.java (98%) rename app/src/main/java/com/seafile/seadroid2/ui/{fragment => repo}/ReposFragment.java (99%) rename app/src/main/java/com/seafile/seadroid2/ui/{adapter => repo}/SeafReposAdapter.java (98%) rename app/src/main/java/com/seafile/seadroid2/ui/{activity => }/search/Search2Activity.java (99%) rename app/src/main/java/com/seafile/seadroid2/ui/{activity => }/search/SearchRecyclerViewAdapter.java (98%) rename app/src/main/java/com/seafile/seadroid2/ui/{activity => settings}/PrivacyPolicyActivity.java (95%) rename app/src/main/java/com/seafile/seadroid2/ui/{activity => settings}/SettingsActivity.java (93%) rename app/src/main/java/com/seafile/seadroid2/ui/{fragment => settings}/SettingsCameraBackupAdvanceFragment.java (97%) rename app/src/main/java/com/seafile/seadroid2/ui/{fragment => settings}/SettingsFragment.java (98%) rename app/src/main/java/com/seafile/seadroid2/ui/{fragment => star}/StarredFragment.java (99%) rename app/src/main/java/com/seafile/seadroid2/ui/{adapter => star}/StarredItemAdapter.java (98%) rename app/src/main/java/com/seafile/seadroid2/ui/{fragment => transfer}/DownloadTaskFragment.java (97%) rename app/src/main/java/com/seafile/seadroid2/ui/{activity => transfer}/TransferActivity.java (97%) rename app/src/main/java/com/seafile/seadroid2/ui/{adapter => transfer}/TransferTaskAdapter.java (99%) rename app/src/main/java/com/seafile/seadroid2/ui/{fragment => transfer}/TransferTaskFragment.java (98%) rename app/src/main/java/com/seafile/seadroid2/ui/{fragment => transfer}/UploadTaskFragment.java (97%) rename app/src/main/java/com/seafile/seadroid2/ui/{activity => }/webview/SeaWebViewActivity.java (95%) create mode 100644 app/src/main/java/com/seafile/seadroid2/util/Logs.java create mode 100644 app/src/main/java/com/seafile/seadroid2/util/SLogs.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/util/SeafileLog.java rename app/src/main/java/com/seafile/seadroid2/{ui/widget => view}/CircleImageView.java (99%) rename app/src/main/java/com/seafile/seadroid2/{ui => view}/CustomClearableEditText.java (99%) rename app/src/main/java/com/seafile/seadroid2/{ui => view}/HackyViewPager.java (98%) rename app/src/main/java/com/seafile/seadroid2/{ui/widget => view}/PerformEdit.java (99%) rename app/src/main/java/com/seafile/seadroid2/{ui => view}/ZoomOutPageTransformer.java (97%) diff --git a/app/build.gradle b/app/build.gradle index 6ebf7d2d8..dc70f2b47 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId 'com.seafile.seadroid2' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 139 - versionName "2.3.6" + versionCode 140 + versionName "2.3.7" multiDexEnabled true resValue "string", "authorities", applicationId + '.cameraupload.provider' resValue "string", "account_type", "com.seafile.seadroid2.account.api2" @@ -120,13 +120,39 @@ android { implementation "androidx.media3:media3-exoplayer:$media3_version" implementation "androidx.media3:media3-ui:$media3_version" - final def x_version = '1.5.0' - implementation "androidx.appcompat:appcompat:$x_version" - implementation "androidx.activity:activity:$x_version" - implementation "com.google.android.material:material:$x_version" + implementation "androidx.appcompat:appcompat:1.3.0" + implementation "androidx.activity:activity:1.7.2" implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation "androidx.preference:preference:1.2.1" + implementation "com.google.android.material:material:1.9.0" + + def lifecycle_version = "2.6.1" + implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" + //ProcessLifecycleOwner provides a lifecycle for the whole application process + implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version" + //helpers for implementing LifecycleOwner in a Service + //implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version" + // optional - ReactiveStreams support for LiveData + //implementation "androidx.lifecycle:lifecycle-reactivestreams:$lifecycle_version" + + //squareup + def retrofit_version = "2.3.0" + implementation "com.squareup.retrofit2:retrofit:$retrofit_version" + implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" + implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit_version" + implementation 'com.squareup.okhttp3:okhttp:3.9.1' + + //gson + implementation 'com.google.code.gson:gson:2.10' + + //rxjava + implementation 'io.reactivex.rxjava3:rxjava:3.1.5' + implementation 'io.reactivex.rxjava3:rxandroid:3.0.2' + + //https://github.com/elvishew/xLog + implementation 'com.elvishew:xlog:1.10.1' //live event bus // implementation 'io.github.jeremyliao:live-event-bus-x:1.8.0' @@ -135,11 +161,11 @@ android { implementation 'me.relex:circleindicator:2.1.6' implementation 'com.github.kevinsawicki:http-request:6.0' - implementation 'commons-io:commons-io:2.13.0' implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.3' //https://github.com/google/guava implementation "com.google.guava:guava:32.1.2-android" + implementation 'commons-io:commons-io:2.13.0' //https://github.com/Baseflow/PhotoView implementation 'com.github.Chrisbanes:PhotoView:2.3.0' @@ -157,10 +183,6 @@ android { implementation 'com.madgag.spongycastle:core:1.54.0.0' implementation 'com.madgag.spongycastle:prov:1.54.0.0' - implementation 'com.squareup.okhttp3:okhttp:3.9.1' - implementation 'io.reactivex.rxjava3:rxjava:3.1.5' - implementation 'io.reactivex.rxjava3:rxandroid:3.0.2' - implementation 'com.google.code.gson:gson:2.10' implementation 'org.greenrobot:eventbus:3.3.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 48060e42d..2f1ea8b1b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -62,19 +62,19 @@ android:value="AppGlideModule" /> - + - + - + - + @@ -85,19 +85,19 @@ - + + android:name=".ui.account.AccountDetailActivity" + android:parentActivityName=".ui.account.AccountsActivity" /> @@ -126,16 +126,16 @@ + android:name=".ui.account.SeafileAuthenticatorActivity" + android:parentActivityName=".ui.account.AccountsActivity"> + android:value=".ui.account.AccountsActivity" /> - + @@ -150,7 +150,7 @@ android:theme="@style/AppTheme.Editor" /> @@ -164,7 +164,7 @@ android:configChanges="orientation|keyboardHidden|screenSize" android:screenOrientation="sensor" android:theme="@style/Theme.Fullscreen" /> - + 0) { // create directories for media buckets createDirectories(dataManager); @@ -392,7 +394,7 @@ private void uploadImages(SyncResult syncResult, DataManager dataManager) throws } private void uploadVideos(SyncResult syncResult, DataManager dataManager) throws SeafException, InterruptedException { - Utils.utilsLogInfo(true, "Starting to upload videos..."); + SLogs.d("Starting to upload videos..."); // Log.d(DEBUG_TAG, "Starting to upload videos..."); if (isCancelled()) @@ -427,12 +429,11 @@ private void uploadVideos(SyncResult syncResult, DataManager dataManager) throws try { if (cursor == null) { - Log.e(DEBUG_TAG, "ContentResolver query failed!"); - Utils.utilsLogInfo(true, "====ContentResolver query failed!"); + SLogs.e("ContentResolver query failed!"); return; } // Log.d(DEBUG_TAG, "i see " + cursor.getCount() + " new videos."); - Utils.utilsLogInfo(true, "=====i see " + cursor.getCount() + " videos."); + SLogs.d("=====i see " + cursor.getCount() + " videos."); if (cursor.getCount() > 0) { // create directories for media buckets createDirectories(dataManager); @@ -568,19 +569,19 @@ private void iterateCursor(SyncResult syncResult, DataManager dataManager, Curso // Ignore all media by Seafile. We don't want to upload our own cached files. if (file.getAbsolutePath().startsWith(StorageManager.getInstance().getMediaDir().getAbsolutePath())) { // Log.d(DEBUG_TAG, "Skipping media "+file+" because it's part of the Seadroid cache"); - Utils.utilsLogInfo(true, "======Skipping media " + file + " because it's part of the Seadroid cache"); + SLogs.d("======Skipping media " + file + " because it's part of the Seadroid cache"); continue; } if (dbHelper.isUploaded(file)) { // Log.d(DEBUG_TAG, "Skipping media " + file + " because we have uploaded it in the past."); - Utils.utilsLogInfo(true, "=====Skipping media " + file + " because we have uploaded it in the past."); + SLogs.d("=====Skipping media " + file + " because we have uploaded it in the past."); continue; } uploadFile(dataManager, file, bucketName); } - Utils.utilsLogInfo(true, "=======waitForUploads==="); + SLogs.d("=======waitForUploads==="); waitForUploads(); checkUploadResult(syncResult); } @@ -635,11 +636,10 @@ private void checkUploadResult(SyncResult syncResult) throws SeafException { private void uploadFile(DataManager dataManager, File file, String bucketName) throws SeafException { String serverPath = Utils.pathJoin(BASE_DIR, bucketName); - Utils.utilsLogInfo(true, "=======uploadFile==="); + SLogs.d("=======uploadFile==="); List list = dataManager.getCachedDirents(targetRepoId, serverPath); if (list == null) { - Log.e(DEBUG_TAG, "Seadroid dirent cache is empty in uploadFile. Should not happen, aborting."); - Utils.utilsLogInfo(true, "=======Seadroid dirent cache is empty in uploadFile. Should not happen, aborting."); + SLogs.e("=======Seadroid dirent cache is empty in uploadFile. Should not happen, aborting."); // the dirents were supposed to be refreshed in createDirectories() // something changed, abort. throw SeafException.unknownException; @@ -657,15 +657,13 @@ private void uploadFile(DataManager dataManager, File file, String bucketName) t Pattern pattern = Pattern.compile(Pattern.quote(prefix) + "( \\(\\d+\\))?" + Pattern.quote(suffix)); for (SeafDirent dirent : list) { if (pattern.matcher(dirent.name).matches() && dirent.size == file.length()) { - // Log.d(DEBUG_TAG, "File " + file.getName() + " in bucket " + bucketName + " already exists on the server. Skipping."); - Utils.utilsLogInfo(true, "====File " + file.getName() + " in bucket " + bucketName + " already exists on the server. Skipping."); + SLogs.d("====File " + file.getName() + " in bucket " + bucketName + " already exists on the server. Skipping."); dbHelper.markAsUploaded(file); return; } } - // Log.d(DEBUG_TAG, "uploading file " + file.getName() + " to " + serverPath); - Utils.utilsLogInfo(true, "====uploading file " + file.getName() + " to " + serverPath); + SLogs.d("====uploading file " + file.getName() + " to " + serverPath); int taskID = txService.addCameraUploadTask(dataManager.getAccount(), targetRepoId, targetRepoName, serverPath, file.getAbsolutePath(), false, false); tasksInProgress.add(taskID); diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadConfigActivity.java b/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadConfigActivity.java index 550caadb8..b224dbeae 100644 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadConfigActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadConfigActivity.java @@ -13,9 +13,9 @@ import com.seafile.seadroid2.SettingsManager; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.ui.activity.BaseActivity; +import com.seafile.seadroid2.ui.BaseActivity; import com.seafile.seadroid2.ui.activity.SeafilePathChooserActivity; -import com.seafile.seadroid2.ui.fragment.SettingsFragment; +import com.seafile.seadroid2.ui.settings.SettingsFragment; import com.seafile.seadroid2.util.SystemSwitchUtils; import java.util.List; diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryAccountAdapter.java b/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryAccountAdapter.java index f6de5dd69..d6caaca67 100644 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryAccountAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryAccountAdapter.java @@ -2,7 +2,7 @@ import android.content.Context; import com.seafile.seadroid2.R; -import com.seafile.seadroid2.ui.adapter.AccountAdapter; +import com.seafile.seadroid2.ui.account.adapter.AccountAdapter; /** * Adapter for choosing an cloud account diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryAdapter.java b/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryAdapter.java index caedf486b..066341119 100644 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryAdapter.java @@ -4,7 +4,7 @@ import android.widget.ImageView; import com.seafile.seadroid2.R; import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.ui.adapter.ReposAdapter; +import com.seafile.seadroid2.ui.repo.ReposAdapter; /** * Cloud library adapter diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibrarySelectionFragment.java b/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibrarySelectionFragment.java index d76626f4f..c0231e044 100644 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibrarySelectionFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibrarySelectionFragment.java @@ -29,8 +29,8 @@ import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.data.SeafDirent; import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.ui.NavContext; -import com.seafile.seadroid2.ui.adapter.DirentsAdapter; +import com.seafile.seadroid2.context.NavContext; +import com.seafile.seadroid2.ui.repo.DirentsAdapter; import com.seafile.seadroid2.ui.dialog.PasswordDialog; import com.seafile.seadroid2.ui.dialog.TaskDialog; import com.seafile.seadroid2.util.ConcurrentAsyncTask; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/CopyMoveContext.java b/app/src/main/java/com/seafile/seadroid2/context/CopyMoveContext.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/ui/CopyMoveContext.java rename to app/src/main/java/com/seafile/seadroid2/context/CopyMoveContext.java index c6933099f..6c10b3b2d 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/CopyMoveContext.java +++ b/app/src/main/java/com/seafile/seadroid2/context/CopyMoveContext.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui; +package com.seafile.seadroid2.context; import com.seafile.seadroid2.data.SeafDirent; import com.seafile.seadroid2.util.Utils; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/NavContext.java b/app/src/main/java/com/seafile/seadroid2/context/NavContext.java similarity index 93% rename from app/src/main/java/com/seafile/seadroid2/ui/NavContext.java rename to app/src/main/java/com/seafile/seadroid2/context/NavContext.java index 02de03277..abd479a46 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/NavContext.java +++ b/app/src/main/java/com/seafile/seadroid2/context/NavContext.java @@ -1,6 +1,6 @@ -package com.seafile.seadroid2.ui; +package com.seafile.seadroid2.context; -import com.seafile.seadroid2.ui.activity.BrowserActivity; +import com.seafile.seadroid2.ui.BrowserActivity; public class NavContext { String repoID = null; diff --git a/app/src/main/java/com/seafile/seadroid2/editor/EditorActivity.java b/app/src/main/java/com/seafile/seadroid2/editor/EditorActivity.java index 6ec93c018..22f736720 100644 --- a/app/src/main/java/com/seafile/seadroid2/editor/EditorActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/editor/EditorActivity.java @@ -3,7 +3,9 @@ import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; + import androidx.appcompat.widget.Toolbar; + import android.text.Editable; import android.text.TextWatcher; import android.view.Menu; @@ -15,9 +17,10 @@ import com.seafile.seadroid2.SeafException; import com.seafile.seadroid2.editor.widget.HorizontalEditScrollView; import com.seafile.seadroid2.monitor.FileMonitorService; -import com.seafile.seadroid2.ui.activity.BaseActivity; -import com.seafile.seadroid2.ui.widget.PerformEdit; +import com.seafile.seadroid2.ui.BaseActivity; +import com.seafile.seadroid2.view.PerformEdit; import com.seafile.seadroid2.util.ConcurrentAsyncTask; +import com.seafile.seadroid2.util.SLogs; import com.seafile.seadroid2.util.Utils; import com.yydcdut.markdown.MarkdownConfiguration; import com.yydcdut.markdown.MarkdownEditText; @@ -175,7 +178,7 @@ protected void onResume() { if (!com.seafile.seadroid2.util.Utils.isServiceRunning(EditorActivity.this, "com.seafile.seadroid2.monitor.FileMonitorService")) { Intent monitorIntent = new Intent(EditorActivity.this, FileMonitorService.class); EditorActivity.this.startService(monitorIntent); - Utils.utilsLogInfo(true, "---------FileMonitorService is not running, start it in EditorActivity"); + SLogs.d("---------FileMonitorService is not running, start it in EditorActivity"); } } @@ -184,7 +187,6 @@ private void saveFile() { ConcurrentAsyncTask.execute(task); } - public class Task extends AsyncTask { private SeafException err; diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/CloudLibraryChooserFragment.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/CloudLibraryChooserFragment.java index 454c3aadf..878b8f437 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/CloudLibraryChooserFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/CloudLibraryChooserFragment.java @@ -33,8 +33,8 @@ import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.data.SeafDirent; import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.ui.NavContext; -import com.seafile.seadroid2.ui.adapter.DirentsAdapter; +import com.seafile.seadroid2.context.NavContext; +import com.seafile.seadroid2.ui.repo.DirentsAdapter; import com.seafile.seadroid2.ui.dialog.PasswordDialog; import com.seafile.seadroid2.ui.dialog.TaskDialog; import com.seafile.seadroid2.util.ConcurrentAsyncTask; diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupConfigActivity.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupConfigActivity.java index ba731e1fa..3525db047 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupConfigActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupConfigActivity.java @@ -7,10 +7,12 @@ import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; + import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.viewpager.widget.ViewPager; + import android.text.TextUtils; import android.widget.Toast; @@ -21,10 +23,10 @@ import com.seafile.seadroid2.data.SeafRepo; import com.seafile.seadroid2.folderbackup.selectfolder.SelectBackupFolderFragment; import com.seafile.seadroid2.folderbackup.selectfolder.StringTools; -import com.seafile.seadroid2.ui.activity.BaseActivity; +import com.seafile.seadroid2.ui.BaseActivity; import com.seafile.seadroid2.ui.activity.SeafilePathChooserActivity; -import com.seafile.seadroid2.ui.fragment.SettingsFragment; -import com.seafile.seadroid2.util.Utils; +import com.seafile.seadroid2.ui.settings.SettingsFragment; +import com.seafile.seadroid2.util.SLogs; import java.util.ArrayList; import java.util.List; @@ -104,7 +106,7 @@ public void saveRepoConfig() { //FIX an issue: When no folder or library is selected, a crash occurs if (null == mSeafRepo || null == mAccount) { - Utils.utilsLogInfo(false, "----------No repo is selected"); + SLogs.d("----------No repo is selected"); return; } @@ -126,7 +128,7 @@ public void saveRepoConfig() { } Toast.makeText(mActivity, mActivity.getString(R.string.folder_backup_select_repo_update), Toast.LENGTH_SHORT).show(); } catch (Exception e) { - Utils.utilsLogInfo(true, "=saveRepoConfig=======================" + e.toString()); + SLogs.d("=saveRepoConfig=======================" + e.toString()); } } @@ -145,7 +147,7 @@ public void saveFolderConfig() { //FIX an issue: When no folder or library is selected, a crash occurs if (selectFolderPaths == null || selectFolderPaths.isEmpty()) { - Utils.utilsLogInfo(false, "----------No folder is selected"); + SLogs.d("----------No folder is selected"); //clear local storage SettingsManager.instance().saveBackupPaths(""); @@ -161,7 +163,7 @@ public void saveFolderConfig() { if ((TextUtils.isEmpty(originalBackupPaths) && !TextUtils.isEmpty(strJsonPath)) || !originalBackupPaths.equals(strJsonPath)) { mBackupService.startFolderMonitor(selectFolderPaths); - Utils.utilsLogInfo(false, "----------Restart monitoring FolderMonitor"); + SLogs.d("----------Restart monitoring FolderMonitor"); } if (!TextUtils.isEmpty(originalBackupPaths) && TextUtils.isEmpty(strJsonPath)) { diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupSelectedPathActivity.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupSelectedPathActivity.java index 652eb01cc..faf03b255 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupSelectedPathActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupSelectedPathActivity.java @@ -2,7 +2,7 @@ import static com.seafile.seadroid2.folderbackup.FolderBackupConfigActivity.BACKUP_SELECT_PATHS; import static com.seafile.seadroid2.folderbackup.FolderBackupConfigActivity.BACKUP_SELECT_PATHS_SWITCH; -import static com.seafile.seadroid2.ui.fragment.SettingsFragment.FOLDER_BACKUP_REMOTE_PATH; +import static com.seafile.seadroid2.ui.settings.SettingsFragment.FOLDER_BACKUP_REMOTE_PATH; import android.content.DialogInterface; import android.content.Intent; @@ -22,7 +22,7 @@ import com.seafile.seadroid2.R; import com.seafile.seadroid2.SettingsManager; import com.seafile.seadroid2.folderbackup.selectfolder.StringTools; -import com.seafile.seadroid2.ui.activity.BaseActivity; +import com.seafile.seadroid2.ui.BaseActivity; import com.seafile.seadroid2.ui.bottomsheet.BottomSheetTextFragment; import org.jetbrains.annotations.NotNull; diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupService.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupService.java index e2d040904..f0c1dd732 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupService.java +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupService.java @@ -29,6 +29,7 @@ import com.seafile.seadroid2.transfer.UploadTaskManager; import com.seafile.seadroid2.util.CameraSyncStatus; import com.seafile.seadroid2.util.ConcurrentAsyncTask; +import com.seafile.seadroid2.util.SLogs; import com.seafile.seadroid2.util.Utils; import org.apache.commons.io.monitor.FileAlterationListener; @@ -175,7 +176,7 @@ protected String doInBackground(Void... params) { @Override protected void onPostExecute(String FilePath) { - Utils.utilsLogInfo(false, "----------" + FilePath); + SLogs.d("----------" + FilePath); } } @@ -211,8 +212,8 @@ private void startBackupFolder(String parentPath, String filePath) { continue; } - Utils.utilsLogInfo(false, "parentPath ->" + parentPath); - Utils.utilsLogInfo(false, "localPath ->" + fb.getFilePath()); + SLogs.d("parentPath ->" + parentPath); + SLogs.d("localPath ->" + fb.getFilePath()); FolderBackupInfo fileInfo = databaseHelper.getBackupFileInfo( repoConfig.getRepoID(), @@ -220,13 +221,13 @@ private void startBackupFolder(String parentPath, String filePath) { String.valueOf(fb.getSimpleSize())); // if (fileInfo != null && !TextUtils.isEmpty(fileInfo.filePath)) { - Utils.utilsLogInfo(false, "db exists ->" + fileInfo.filePath); - Utils.utilsLogInfo(false, "---------------"); + SLogs.d("db exists ->" + fileInfo.filePath); + SLogs.d("---------------"); continue; } - Utils.utilsLogInfo(false, "uploadFile ->" + fb.getFilePath()); - Utils.utilsLogInfo(false, "---------------"); + SLogs.d("uploadFile ->" + fb.getFilePath()); + SLogs.d("---------------"); int taskID = txService.addTaskToSourceQue(Utils.TRANSFER_FOLDER_TAG, currentAccount, repoConfig.getRepoID(), repoConfig.getRepoName(), parentPath, diff --git a/app/src/main/java/com/seafile/seadroid2/gesturelock/DefaultAppLock.java b/app/src/main/java/com/seafile/seadroid2/gesturelock/DefaultAppLock.java index aa9553bbf..4cf7b0d91 100644 --- a/app/src/main/java/com/seafile/seadroid2/gesturelock/DefaultAppLock.java +++ b/app/src/main/java/com/seafile/seadroid2/gesturelock/DefaultAppLock.java @@ -13,7 +13,7 @@ import com.google.common.collect.MapMaker; import com.seafile.seadroid2.SettingsManager; -import com.seafile.seadroid2.ui.activity.UnlockGesturePasswordActivity; +import com.seafile.seadroid2.ui.gesture.UnlockGesturePasswordActivity; import java.util.concurrent.ConcurrentMap; diff --git a/app/src/main/java/com/seafile/seadroid2/notification/BaseNotificationProvider.java b/app/src/main/java/com/seafile/seadroid2/notification/BaseNotificationProvider.java index 4fdd38366..1da7abec9 100644 --- a/app/src/main/java/com/seafile/seadroid2/notification/BaseNotificationProvider.java +++ b/app/src/main/java/com/seafile/seadroid2/notification/BaseNotificationProvider.java @@ -36,13 +36,13 @@ public abstract class BaseNotificationProvider { public static final String NOTIFICATION_MESSAGE_KEY = "notification message key"; /** - * Creates an explicit flag for opening @{link com.seafile.seadroid2.ui.fragment.DownloadTaskFragment} - * in @{link com.seafile.seadroid2.ui.activity.TransferActivity} + * Creates an explicit flag for opening @{link com.seafile.seadroid2.ui.transfer.DownloadTaskFragment} + * in @{link com.seafile.seadroid2.ui.transfer.TransferActivity} */ public static final String NOTIFICATION_OPEN_DOWNLOAD_TAB = "open download tab notification"; /** - * Creates an explicit flag for opening @{link com.seafile.seadroid2.ui.fragment.UploadTaskFragment} - * in @{link com.seafile.seadroid2.ui.activity.TransferActivity} + * Creates an explicit flag for opening @{link com.seafile.seadroid2.ui.transfer.UploadTaskFragment} + * in @{link com.seafile.seadroid2.ui.transfer.TransferActivity} */ public static final String NOTIFICATION_OPEN_UPLOAD_TAB = "open upload tab notification"; diff --git a/app/src/main/java/com/seafile/seadroid2/notification/DownloadNotificationProvider.java b/app/src/main/java/com/seafile/seadroid2/notification/DownloadNotificationProvider.java index 536549e58..49ce4249c 100644 --- a/app/src/main/java/com/seafile/seadroid2/notification/DownloadNotificationProvider.java +++ b/app/src/main/java/com/seafile/seadroid2/notification/DownloadNotificationProvider.java @@ -11,7 +11,7 @@ import com.seafile.seadroid2.transfer.TaskState; import com.seafile.seadroid2.transfer.TransferService; import com.seafile.seadroid2.ui.CustomNotificationBuilder; -import com.seafile.seadroid2.ui.activity.TransferActivity; +import com.seafile.seadroid2.ui.transfer.TransferActivity; import java.util.List; diff --git a/app/src/main/java/com/seafile/seadroid2/notification/UploadNotificationProvider.java b/app/src/main/java/com/seafile/seadroid2/notification/UploadNotificationProvider.java index b6dd8e8af..18d54e352 100644 --- a/app/src/main/java/com/seafile/seadroid2/notification/UploadNotificationProvider.java +++ b/app/src/main/java/com/seafile/seadroid2/notification/UploadNotificationProvider.java @@ -11,7 +11,7 @@ import com.seafile.seadroid2.transfer.UploadTaskInfo; import com.seafile.seadroid2.transfer.UploadTaskManager; import com.seafile.seadroid2.ui.CustomNotificationBuilder; -import com.seafile.seadroid2.ui.activity.TransferActivity; +import com.seafile.seadroid2.ui.transfer.TransferActivity; import java.util.List; diff --git a/app/src/main/java/com/seafile/seadroid2/play/exoplayer/CustomExoVideoPlayerActivity.java b/app/src/main/java/com/seafile/seadroid2/play/exoplayer/CustomExoVideoPlayerActivity.java index 47753e89b..71f6f06f7 100644 --- a/app/src/main/java/com/seafile/seadroid2/play/exoplayer/CustomExoVideoPlayerActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/play/exoplayer/CustomExoVideoPlayerActivity.java @@ -24,7 +24,6 @@ import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.ProgressiveMediaSource; import androidx.media3.ui.DefaultTimeBar; -import androidx.media3.ui.PlayerView; import androidx.media3.ui.TimeBar; import com.blankj.utilcode.util.AppUtils; @@ -33,7 +32,7 @@ import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.play.VideoLinkStateListener; import com.seafile.seadroid2.play.VideoLinkTask; -import com.seafile.seadroid2.ui.activity.BaseActivity; +import com.seafile.seadroid2.ui.BaseActivity; import com.seafile.seadroid2.util.ConcurrentAsyncTask; import com.seafile.seadroid2.util.Utils; diff --git a/app/src/main/java/com/seafile/seadroid2/task/StarItemsTask.java b/app/src/main/java/com/seafile/seadroid2/task/StarItemsTask.java index 5fdcf929a..985a9af11 100644 --- a/app/src/main/java/com/seafile/seadroid2/task/StarItemsTask.java +++ b/app/src/main/java/com/seafile/seadroid2/task/StarItemsTask.java @@ -3,7 +3,7 @@ import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeafException; import com.seafile.seadroid2.listener.OnCallback; -import com.seafile.seadroid2.ui.activity.BrowserActivity; +import com.seafile.seadroid2.ui.BrowserActivity; import com.seafile.seadroid2.util.SupportAsyncTask; /** diff --git a/app/src/main/java/com/seafile/seadroid2/ui/AnimateFirstDisplayListener.java b/app/src/main/java/com/seafile/seadroid2/ui/AnimateFirstDisplayListener.java deleted file mode 100644 index fb89d2842..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/AnimateFirstDisplayListener.java +++ /dev/null @@ -1,30 +0,0 @@ -//package com.seafile.seadroid2.ui; -// -//import android.graphics.Bitmap; -//import android.view.View; -//import android.widget.ImageView; -//import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; -//import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; -// -//import java.util.Collections; -//import java.util.LinkedList; -//import java.util.List; -// -///** -// * Do a fancy fade-in of thumbnails. -// */ -//public class AnimateFirstDisplayListener extends SimpleImageLoadingListener { -// static final List displayedImages = Collections.synchronizedList(new LinkedList()); -// -// @Override -// public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { -// if (loadedImage != null) { -// ImageView imageView = (ImageView) view; -// boolean firstDisplay = !displayedImages.contains(imageUri); -// if (firstDisplay) { -// FadeInBitmapDisplayer.animate(imageView, 1000); -// displayedImages.add(imageUri); -// } -// } -// } -//} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/BaseActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/BaseActivity.java similarity index 99% rename from app/src/main/java/com/seafile/seadroid2/ui/activity/BaseActivity.java rename to app/src/main/java/com/seafile/seadroid2/ui/BaseActivity.java index 917cf4bc0..e86bb44bc 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/BaseActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/BaseActivity.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.activity; +package com.seafile.seadroid2.ui; import android.app.Activity; import android.content.Intent; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/BrowserActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/BrowserActivity.java similarity index 99% rename from app/src/main/java/com/seafile/seadroid2/ui/activity/BrowserActivity.java rename to app/src/main/java/com/seafile/seadroid2/ui/BrowserActivity.java index e2af485cb..ee8d36e4d 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/BrowserActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/BrowserActivity.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.activity; +package com.seafile.seadroid2.ui; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -44,10 +44,11 @@ import com.seafile.seadroid2.SettingsManager; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.AccountManager; -import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.cameraupload.CameraUploadManager; import com.seafile.seadroid2.cameraupload.MediaObserverService; import com.seafile.seadroid2.config.Constants; +import com.seafile.seadroid2.context.CopyMoveContext; +import com.seafile.seadroid2.context.NavContext; import com.seafile.seadroid2.data.CheckUploadServiceEvent; import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.data.DatabaseHelper; @@ -71,11 +72,14 @@ import com.seafile.seadroid2.transfer.TransferService.TransferBinder; import com.seafile.seadroid2.transfer.UploadTaskInfo; import com.seafile.seadroid2.transfer.UploadTaskManager; -import com.seafile.seadroid2.ui.CopyMoveContext; -import com.seafile.seadroid2.ui.NavContext; -import com.seafile.seadroid2.ui.WidgetUtils; -import com.seafile.seadroid2.ui.activity.search.Search2Activity; -import com.seafile.seadroid2.ui.activity.webview.SeaWebViewActivity; +import com.seafile.seadroid2.ui.account.AccountsActivity; +import com.seafile.seadroid2.ui.activity.FileActivity; +import com.seafile.seadroid2.ui.activity.SeafilePathChooserActivity; +import com.seafile.seadroid2.ui.settings.SettingsActivity; +import com.seafile.seadroid2.ui.gesture.UnlockGesturePasswordActivity; +import com.seafile.seadroid2.ui.search.Search2Activity; +import com.seafile.seadroid2.ui.transfer.TransferActivity; +import com.seafile.seadroid2.ui.webview.SeaWebViewActivity; import com.seafile.seadroid2.ui.adapter.SeafItemAdapter; import com.seafile.seadroid2.ui.dialog.AppChoiceDialog; import com.seafile.seadroid2.ui.dialog.AppChoiceDialog.CustomAction; @@ -92,12 +96,12 @@ import com.seafile.seadroid2.ui.dialog.SortFilesDialogFragment; import com.seafile.seadroid2.ui.dialog.SslConfirmDialog; import com.seafile.seadroid2.ui.dialog.TaskDialog; -import com.seafile.seadroid2.ui.fragment.ActivitiesFragment; -import com.seafile.seadroid2.ui.fragment.ReposFragment; -import com.seafile.seadroid2.ui.fragment.StarredFragment; +import com.seafile.seadroid2.ui.activities.ActivitiesFragment; +import com.seafile.seadroid2.ui.repo.ReposFragment; +import com.seafile.seadroid2.ui.star.StarredFragment; import com.seafile.seadroid2.util.ConcurrentAsyncTask; import com.seafile.seadroid2.util.PermissionUtil; -import com.seafile.seadroid2.util.SeafileLog; +import com.seafile.seadroid2.util.SLogs; import com.seafile.seadroid2.util.Utils; import com.seafile.seadroid2.util.UtilsJellyBean; @@ -597,7 +601,7 @@ public void onRequestPermissionsResult(int requestCode, String permissions[], in // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // permission was granted - SeafileLog.i(this.getClass().getName(), "PERMISSIONS_EXTERNAL_STORAGE has been granted"); + SLogs.i("PERMISSIONS_EXTERNAL_STORAGE has been granted"); } break; } @@ -884,7 +888,7 @@ protected void onNewIntent(Intent intent) { String repoName = intent.getStringExtra("repoName"); String path = intent.getStringExtra("path"); - if (TextUtils.isEmpty(repoId) || TextUtils.isEmpty(path)){ + if (TextUtils.isEmpty(repoId) || TextUtils.isEmpty(path)) { return; } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/WidgetUtils.java b/app/src/main/java/com/seafile/seadroid2/ui/WidgetUtils.java index f70d1adc1..06d518556 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/WidgetUtils.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/WidgetUtils.java @@ -22,17 +22,14 @@ import android.text.ClipboardManager; import android.webkit.MimeTypeMap; -import android.widget.Toast; import com.blankj.utilcode.util.ToastUtils; import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeadroidApplication; import com.seafile.seadroid2.SeafException; import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.ui.activity.BaseActivity; -import com.seafile.seadroid2.ui.activity.BrowserActivity; import com.seafile.seadroid2.ui.activity.GalleryActivity; -import com.seafile.seadroid2.ui.activity.MarkdownActivity; +import com.seafile.seadroid2.ui.markdown.MarkdownActivity; import com.seafile.seadroid2.ui.dialog.AppChoiceDialog; import com.seafile.seadroid2.ui.dialog.GetShareLinkDialog; import com.seafile.seadroid2.ui.dialog.GetShareLinkEncryptDialog; diff --git a/app/src/main/java/com/seafile/seadroid2/account/ui/AccountDetailActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/account/AccountDetailActivity.java similarity index 99% rename from app/src/main/java/com/seafile/seadroid2/account/ui/AccountDetailActivity.java rename to app/src/main/java/com/seafile/seadroid2/ui/account/AccountDetailActivity.java index d9d31e319..d16f1cb63 100644 --- a/app/src/main/java/com/seafile/seadroid2/account/ui/AccountDetailActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/account/AccountDetailActivity.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.account.ui; +package com.seafile.seadroid2.ui.account; import android.app.ProgressDialog; import android.content.Context; @@ -36,8 +36,7 @@ import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.ssl.CertsManager; import com.seafile.seadroid2.ui.EmailAutoCompleteTextView; -import com.seafile.seadroid2.ui.activity.AccountsActivity; -import com.seafile.seadroid2.ui.activity.BaseActivity; +import com.seafile.seadroid2.ui.BaseActivity; import com.seafile.seadroid2.ui.dialog.SslConfirmDialog; import com.seafile.seadroid2.util.ConcurrentAsyncTask; import com.seafile.seadroid2.util.Utils; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/AccountsActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/account/AccountsActivity.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/ui/activity/AccountsActivity.java rename to app/src/main/java/com/seafile/seadroid2/ui/account/AccountsActivity.java index d3004f371..9a4d3df02 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/AccountsActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/account/AccountsActivity.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.activity; +package com.seafile.seadroid2.ui.account; import android.accounts.AccountManagerCallback; import android.accounts.AccountManagerFuture; @@ -38,8 +38,10 @@ import com.seafile.seadroid2.avatar.Avatar; import com.seafile.seadroid2.avatar.AvatarManager; import com.seafile.seadroid2.monitor.FileMonitorService; -import com.seafile.seadroid2.ui.adapter.AccountAdapter; -import com.seafile.seadroid2.ui.adapter.SeafAccountAdapter; +import com.seafile.seadroid2.ui.BaseActivity; +import com.seafile.seadroid2.ui.BrowserActivity; +import com.seafile.seadroid2.ui.account.adapter.AccountAdapter; +import com.seafile.seadroid2.ui.account.adapter.SeafAccountAdapter; import com.seafile.seadroid2.ui.dialog.PolicyDialog; import com.seafile.seadroid2.util.ConcurrentAsyncTask; import com.seafile.seadroid2.util.Utils; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/BaseAuthenticatorActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/account/BaseAuthenticatorActivity.java similarity index 96% rename from app/src/main/java/com/seafile/seadroid2/ui/BaseAuthenticatorActivity.java rename to app/src/main/java/com/seafile/seadroid2/ui/account/BaseAuthenticatorActivity.java index 017b478bf..be33822a1 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/BaseAuthenticatorActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/account/BaseAuthenticatorActivity.java @@ -1,10 +1,10 @@ -package com.seafile.seadroid2.ui; +package com.seafile.seadroid2.ui.account; import android.accounts.AccountAuthenticatorResponse; import android.accounts.AccountManager; import android.os.Bundle; -import com.seafile.seadroid2.ui.activity.BaseActivity; +import com.seafile.seadroid2.ui.BaseActivity; /** * Base class for implementing an Activity that is used to help implement an diff --git a/app/src/main/java/com/seafile/seadroid2/account/ui/SeafileAuthenticatorActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/account/SeafileAuthenticatorActivity.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/account/ui/SeafileAuthenticatorActivity.java rename to app/src/main/java/com/seafile/seadroid2/ui/account/SeafileAuthenticatorActivity.java index ef5c2ecae..db5386122 100644 --- a/app/src/main/java/com/seafile/seadroid2/account/ui/SeafileAuthenticatorActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/account/SeafileAuthenticatorActivity.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.account.ui; +package com.seafile.seadroid2.ui.account; import android.accounts.Account; import android.accounts.AccountManager; @@ -15,7 +15,6 @@ import com.seafile.seadroid2.R; import com.seafile.seadroid2.account.Authenticator; import com.seafile.seadroid2.cameraupload.CameraUploadManager; -import com.seafile.seadroid2.ui.BaseAuthenticatorActivity; /** * The Authenticator activity. diff --git a/app/src/main/java/com/seafile/seadroid2/account/ui/SingleSignOnActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnActivity.java similarity index 97% rename from app/src/main/java/com/seafile/seadroid2/account/ui/SingleSignOnActivity.java rename to app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnActivity.java index fe2288718..fb64efd56 100644 --- a/app/src/main/java/com/seafile/seadroid2/account/ui/SingleSignOnActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnActivity.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.account.ui; +package com.seafile.seadroid2.ui.account; import android.content.Intent; import android.os.Bundle; @@ -10,7 +10,7 @@ import android.widget.EditText; import com.seafile.seadroid2.R; -import com.seafile.seadroid2.ui.activity.BaseActivity; +import com.seafile.seadroid2.ui.BaseActivity; /** * Single Sign-On welcome page diff --git a/app/src/main/java/com/seafile/seadroid2/account/ui/SingleSignOnAuthorizeActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnAuthorizeActivity.java similarity index 99% rename from app/src/main/java/com/seafile/seadroid2/account/ui/SingleSignOnAuthorizeActivity.java rename to app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnAuthorizeActivity.java index 6628fbb5e..950280299 100644 --- a/app/src/main/java/com/seafile/seadroid2/account/ui/SingleSignOnAuthorizeActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnAuthorizeActivity.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.account.ui; +package com.seafile.seadroid2.ui.account; import android.content.Context; import android.content.Intent; @@ -28,7 +28,7 @@ import com.seafile.seadroid2.account.AccountInfo; import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.ssl.CertsManager; -import com.seafile.seadroid2.ui.activity.BaseActivity; +import com.seafile.seadroid2.ui.BaseActivity; import com.seafile.seadroid2.ui.dialog.SslConfirmDialog; import com.seafile.seadroid2.util.ConcurrentAsyncTask; import com.seafile.seadroid2.util.DeviceIdManager; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/adapter/AccountAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/account/adapter/AccountAdapter.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/ui/adapter/AccountAdapter.java rename to app/src/main/java/com/seafile/seadroid2/ui/account/adapter/AccountAdapter.java index 4b0bb89fd..63c68c6fc 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/adapter/AccountAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/account/adapter/AccountAdapter.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.adapter; +package com.seafile.seadroid2.ui.account.adapter; import android.content.Context; import android.view.LayoutInflater; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafAccountAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/account/adapter/SeafAccountAdapter.java similarity index 93% rename from app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafAccountAdapter.java rename to app/src/main/java/com/seafile/seadroid2/ui/account/adapter/SeafAccountAdapter.java index b43377da5..816c25dfd 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafAccountAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/account/adapter/SeafAccountAdapter.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.adapter; +package com.seafile.seadroid2.ui.account.adapter; import android.content.Context; import com.seafile.seadroid2.R; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/fragment/ActivitiesFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivitiesFragment.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/ui/fragment/ActivitiesFragment.java rename to app/src/main/java/com/seafile/seadroid2/ui/activities/ActivitiesFragment.java index 76dc1df5c..04c63396f 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/fragment/ActivitiesFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivitiesFragment.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.fragment; +package com.seafile.seadroid2.ui.activities; import android.app.Activity; import android.content.Intent; @@ -34,10 +34,9 @@ import com.seafile.seadroid2.data.SeafEvent; import com.seafile.seadroid2.data.SeafRepo; import com.seafile.seadroid2.data.ServerInfo; -import com.seafile.seadroid2.ui.NavContext; -import com.seafile.seadroid2.ui.activity.BrowserActivity; +import com.seafile.seadroid2.context.NavContext; +import com.seafile.seadroid2.ui.BrowserActivity; import com.seafile.seadroid2.ui.activity.FileActivity; -import com.seafile.seadroid2.ui.adapter.ActivitiesItemAdapter; import com.seafile.seadroid2.ui.adapter.BottomSheetAdapter; import com.seafile.seadroid2.ui.bottomsheet.BottomSheetListFragment; import com.seafile.seadroid2.ui.dialog.TaskDialog; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/adapter/ActivitiesItemAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivitiesItemAdapter.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/ui/adapter/ActivitiesItemAdapter.java rename to app/src/main/java/com/seafile/seadroid2/ui/activities/ActivitiesItemAdapter.java index 461e8c904..0d281fe67 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/adapter/ActivitiesItemAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivitiesItemAdapter.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.adapter; +package com.seafile.seadroid2.ui.activities; import androidx.annotation.NonNull; import android.text.TextUtils; @@ -17,8 +17,8 @@ import com.seafile.seadroid2.config.GlideLoadConfig; import com.seafile.seadroid2.data.SeafEvent; import com.seafile.seadroid2.data.SeafItem; -import com.seafile.seadroid2.ui.activity.BrowserActivity; -import com.seafile.seadroid2.ui.widget.CircleImageView; +import com.seafile.seadroid2.ui.BrowserActivity; +import com.seafile.seadroid2.view.CircleImageView; import com.seafile.seadroid2.util.GlideApp; import com.seafile.seadroid2.util.SystemSwitchUtils; import com.seafile.seadroid2.util.Utils; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/FileActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/activity/FileActivity.java index 9df0380c2..d89772269 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/FileActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/activity/FileActivity.java @@ -27,6 +27,7 @@ import com.seafile.seadroid2.transfer.TaskState; import com.seafile.seadroid2.transfer.TransferService; import com.seafile.seadroid2.transfer.TransferService.TransferBinder; +import com.seafile.seadroid2.ui.BaseActivity; import com.seafile.seadroid2.ui.dialog.PasswordDialog; import com.seafile.seadroid2.ui.dialog.TaskDialog; import com.seafile.seadroid2.util.Utils; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/GalleryActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/activity/GalleryActivity.java index c8585d58e..88e12f88f 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/GalleryActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/activity/GalleryActivity.java @@ -20,13 +20,15 @@ import com.seafile.seadroid2.data.SeafPhoto; import com.seafile.seadroid2.transfer.DownloadStateListener; import com.seafile.seadroid2.transfer.DownloadTask; -import com.seafile.seadroid2.ui.HackyViewPager; +import com.seafile.seadroid2.ui.BaseActivity; +import com.seafile.seadroid2.view.HackyViewPager; import com.seafile.seadroid2.ui.WidgetUtils; -import com.seafile.seadroid2.ui.ZoomOutPageTransformer; +import com.seafile.seadroid2.view.ZoomOutPageTransformer; import com.seafile.seadroid2.ui.adapter.GalleryAdapter; import com.seafile.seadroid2.ui.adapter.SeafItemAdapter; import com.seafile.seadroid2.ui.dialog.DeleteFileDialog; import com.seafile.seadroid2.ui.dialog.TaskDialog; +import com.seafile.seadroid2.ui.repo.ReposFragment; import com.seafile.seadroid2.util.ConcurrentAsyncTask; import com.seafile.seadroid2.util.Utils; @@ -175,7 +177,7 @@ protected void onRestoreInstanceState(Bundle savedInstanceState) { * If caches are not available, load them asynchronously. * * NOTE: When user browsing files in "LIBRARY" tab, he has to navigate into a repo in order to open gallery. - * Method which get called is {@link com.seafile.seadroid2.ui.fragment.ReposFragment#navToReposView(boolean)} or {@link com.seafile.seadroid2.ui.fragment.ReposFragment#navToDirectory(boolean)}, + * Method which get called is {@link ReposFragment#navToReposView(boolean)} or {@link ReposFragment#navToDirectory(boolean)}, * so seafDirents were already cached and it will always use them to calculate thumbnail urls for displaying photos in gallery. * But for browsing "STARRED" tab, caches of starred files may or may not cached, that is where the asynchronous loading code segment comes into use. * @param repoID diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/SeafilePathChooserActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/activity/SeafilePathChooserActivity.java index 1c7f1bb2a..40bd3ead2 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/SeafilePathChooserActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/activity/SeafilePathChooserActivity.java @@ -27,15 +27,17 @@ import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.data.SeafDirent; import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.ui.NavContext; -import com.seafile.seadroid2.ui.adapter.AccountAdapter; -import com.seafile.seadroid2.ui.adapter.DirentsAdapter; -import com.seafile.seadroid2.ui.adapter.SeafAccountAdapter; -import com.seafile.seadroid2.ui.adapter.SeafReposAdapter; +import com.seafile.seadroid2.ui.BaseActivity; +import com.seafile.seadroid2.context.NavContext; +import com.seafile.seadroid2.ui.account.adapter.AccountAdapter; +import com.seafile.seadroid2.ui.repo.DirentsAdapter; +import com.seafile.seadroid2.ui.account.adapter.SeafAccountAdapter; +import com.seafile.seadroid2.ui.repo.SeafReposAdapter; import com.seafile.seadroid2.ui.dialog.NewDirDialog; import com.seafile.seadroid2.ui.dialog.PasswordDialog; import com.seafile.seadroid2.ui.dialog.TaskDialog; -import com.seafile.seadroid2.ui.fragment.SettingsFragment; +import com.seafile.seadroid2.ui.gesture.UnlockGesturePasswordActivity; +import com.seafile.seadroid2.ui.settings.SettingsFragment; import com.seafile.seadroid2.util.ConcurrentAsyncTask; import com.seafile.seadroid2.util.Utils; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/ShareToSeafileActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/activity/ShareToSeafileActivity.java index 15f7235dd..224512fcc 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/ShareToSeafileActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/activity/ShareToSeafileActivity.java @@ -25,6 +25,7 @@ import com.seafile.seadroid2.notification.UploadNotificationProvider; import com.seafile.seadroid2.transfer.TransferService; import com.seafile.seadroid2.transfer.TransferService.TransferBinder; +import com.seafile.seadroid2.ui.BaseActivity; import com.seafile.seadroid2.util.ConcurrentAsyncTask; import com.seafile.seadroid2.util.Utils; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafItemAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafItemAdapter.java index 23eece933..740203672 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafItemAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafItemAdapter.java @@ -20,8 +20,9 @@ import com.seafile.seadroid2.data.SeafItem; import com.seafile.seadroid2.data.SeafRepo; import com.seafile.seadroid2.transfer.DownloadTaskInfo; -import com.seafile.seadroid2.ui.NavContext; -import com.seafile.seadroid2.ui.activity.BrowserActivity; +import com.seafile.seadroid2.context.NavContext; +import com.seafile.seadroid2.ui.BrowserActivity; +import com.seafile.seadroid2.ui.repo.ReposFragment; import com.seafile.seadroid2.util.GlideApp; import com.seafile.seadroid2.util.Utils; @@ -85,7 +86,7 @@ public boolean isEmpty() { } /** - * To refresh downloading status of {@link com.seafile.seadroid2.ui.fragment.ReposFragment#mListView}, + * To refresh downloading status of {@link ReposFragment#mListView}, * use this method to update data set. *

* This method should be called after the "Download folder" menu was clicked. @@ -334,7 +335,7 @@ public void onClick(View v) { } /** - * use to refresh view of {@link com.seafile.seadroid2.ui.fragment.ReposFragment #mPullRefreshListView} + * use to refresh view of {@link ReposFragment #mPullRefreshListView} *

*

when to show download status icons
* if the dirent is a file and already cached, show cached icon.
diff --git a/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafItemCheckableAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafItemCheckableAdapter.java index 498b9d881..90cdcf0c3 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafItemCheckableAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafItemCheckableAdapter.java @@ -15,8 +15,8 @@ import com.seafile.seadroid2.data.SeafCachedFile; import com.seafile.seadroid2.data.SeafDirent; import com.seafile.seadroid2.data.SeafItem; -import com.seafile.seadroid2.ui.NavContext; -import com.seafile.seadroid2.ui.activity.BrowserActivity; +import com.seafile.seadroid2.context.NavContext; +import com.seafile.seadroid2.ui.BrowserActivity; import com.seafile.seadroid2.util.Utils; import java.io.File; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/CopyMoveDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/CopyMoveDialog.java index d3d7b0ebf..ddb7cecd4 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/CopyMoveDialog.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog/CopyMoveDialog.java @@ -10,7 +10,7 @@ import com.seafile.seadroid2.account.AccountManager; import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.ui.CopyMoveContext; +import com.seafile.seadroid2.context.CopyMoveContext; import com.seafile.seadroid2.util.Utils; public class CopyMoveDialog extends TaskDialog { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/CopyMoveTask.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/CopyMoveTask.java index 5ffbfa5ec..e7d5baade 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/CopyMoveTask.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog/CopyMoveTask.java @@ -3,7 +3,7 @@ import com.seafile.seadroid2.SeafException; import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.data.SeafDirent; -import com.seafile.seadroid2.ui.CopyMoveContext; +import com.seafile.seadroid2.context.CopyMoveContext; /** * AsyncTask for copying/moving files diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/FetchFileDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/FetchFileDialog.java index 7a15f653f..4fa1d997f 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/FetchFileDialog.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog/FetchFileDialog.java @@ -21,7 +21,7 @@ import com.seafile.seadroid2.SeafException; import com.seafile.seadroid2.transfer.DownloadTaskInfo; import com.seafile.seadroid2.transfer.TransferService; -import com.seafile.seadroid2.ui.activity.BrowserActivity; +import com.seafile.seadroid2.ui.BrowserActivity; import com.seafile.seadroid2.util.Utils; /** diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/PolicyDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/PolicyDialog.java index 6117b609e..ef86ad33c 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/PolicyDialog.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog/PolicyDialog.java @@ -16,7 +16,7 @@ import android.widget.TextView; import com.seafile.seadroid2.R; -import com.seafile.seadroid2.ui.activity.PrivacyPolicyActivity; +import com.seafile.seadroid2.ui.settings.PrivacyPolicyActivity; import com.seafile.seadroid2.util.Utils; public class PolicyDialog extends Dialog implements View.OnClickListener { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/UploadChoiceDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/UploadChoiceDialog.java index aecdb820a..9fbdc7d63 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/UploadChoiceDialog.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog/UploadChoiceDialog.java @@ -12,7 +12,7 @@ import com.seafile.seadroid2.SeadroidApplication; import com.seafile.seadroid2.fileschooser.MultiFileChooserActivity; import com.seafile.seadroid2.gallery.MultipleImageSelectionActivity; -import com.seafile.seadroid2.ui.activity.BrowserActivity; +import com.seafile.seadroid2.ui.BrowserActivity; import com.seafile.seadroid2.util.Utils; public class UploadChoiceDialog extends DialogFragment { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/CreateGesturePasswordActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/gesture/CreateGesturePasswordActivity.java similarity index 99% rename from app/src/main/java/com/seafile/seadroid2/ui/activity/CreateGesturePasswordActivity.java rename to app/src/main/java/com/seafile/seadroid2/ui/gesture/CreateGesturePasswordActivity.java index 493a43e75..5a2cd94ff 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/CreateGesturePasswordActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/gesture/CreateGesturePasswordActivity.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.activity; +package com.seafile.seadroid2.ui.gesture; import android.os.Bundle; import androidx.appcompat.widget.Toolbar; @@ -17,6 +17,7 @@ import com.seafile.seadroid2.gesturelock.LockPatternView; import com.seafile.seadroid2.gesturelock.LockPatternView.Cell; import com.seafile.seadroid2.gesturelock.LockPatternView.DisplayMode; +import com.seafile.seadroid2.ui.BaseActivity; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/UnlockGesturePasswordActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/gesture/UnlockGesturePasswordActivity.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/ui/activity/UnlockGesturePasswordActivity.java rename to app/src/main/java/com/seafile/seadroid2/ui/gesture/UnlockGesturePasswordActivity.java index 86fb373c3..be06515d9 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/UnlockGesturePasswordActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/gesture/UnlockGesturePasswordActivity.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.activity; +package com.seafile.seadroid2.ui.gesture; import android.content.Intent; import android.graphics.Color; @@ -14,13 +14,14 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.TextView; -import android.widget.Toast; import com.seafile.seadroid2.R; import com.seafile.seadroid2.SettingsManager; import com.seafile.seadroid2.gesturelock.LockPatternUtils; import com.seafile.seadroid2.gesturelock.LockPatternView; import com.seafile.seadroid2.gesturelock.LockPatternView.Cell; +import com.seafile.seadroid2.ui.BaseActivity; +import com.seafile.seadroid2.ui.BrowserActivity; import java.util.List; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/MarkdownActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/markdown/MarkdownActivity.java similarity index 97% rename from app/src/main/java/com/seafile/seadroid2/ui/activity/MarkdownActivity.java rename to app/src/main/java/com/seafile/seadroid2/ui/markdown/MarkdownActivity.java index 55251f2c0..730229c7f 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/MarkdownActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/markdown/MarkdownActivity.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.activity; +package com.seafile.seadroid2.ui.markdown; import android.content.ActivityNotFoundException; import android.content.Intent; @@ -12,6 +12,7 @@ import com.seafile.seadroid2.R; import com.seafile.seadroid2.editor.EditorActivity; +import com.seafile.seadroid2.ui.BaseActivity; import com.seafile.seadroid2.util.FileMimeUtils; import java.io.File; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/adapter/DirentsAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/DirentsAdapter.java similarity index 99% rename from app/src/main/java/com/seafile/seadroid2/ui/adapter/DirentsAdapter.java rename to app/src/main/java/com/seafile/seadroid2/ui/repo/DirentsAdapter.java index 7c212b7bf..e55b06139 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/adapter/DirentsAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/DirentsAdapter.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.adapter; +package com.seafile.seadroid2.ui.repo; import android.graphics.Color; import android.view.LayoutInflater; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/adapter/ReposAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/ReposAdapter.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/ui/adapter/ReposAdapter.java rename to app/src/main/java/com/seafile/seadroid2/ui/repo/ReposAdapter.java index f00d27be5..aa14864ea 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/adapter/ReposAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/ReposAdapter.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.adapter; +package com.seafile.seadroid2.ui.repo; import android.view.LayoutInflater; import android.view.View; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/fragment/ReposFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/ReposFragment.java similarity index 99% rename from app/src/main/java/com/seafile/seadroid2/ui/fragment/ReposFragment.java rename to app/src/main/java/com/seafile/seadroid2/ui/repo/ReposFragment.java index 6f1f2911d..259c309ad 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/fragment/ReposFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/ReposFragment.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.fragment; +package com.seafile.seadroid2.ui.repo; import android.annotation.SuppressLint; import android.app.Activity; @@ -40,9 +40,9 @@ import com.seafile.seadroid2.ssl.CertsManager; import com.seafile.seadroid2.task.StarItemsTask; import com.seafile.seadroid2.transfer.TransferService; -import com.seafile.seadroid2.ui.CopyMoveContext; -import com.seafile.seadroid2.ui.NavContext; -import com.seafile.seadroid2.ui.activity.BrowserActivity; +import com.seafile.seadroid2.context.CopyMoveContext; +import com.seafile.seadroid2.context.NavContext; +import com.seafile.seadroid2.ui.BrowserActivity; import com.seafile.seadroid2.ui.adapter.SeafItemAdapter; import com.seafile.seadroid2.ui.dialog.SslConfirmDialog; import com.seafile.seadroid2.ui.dialog.TaskDialog; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafReposAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/SeafReposAdapter.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafReposAdapter.java rename to app/src/main/java/com/seafile/seadroid2/ui/repo/SeafReposAdapter.java index be5c127dd..a43a4a5a2 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafReposAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/SeafReposAdapter.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.adapter; +package com.seafile.seadroid2.ui.repo; import android.widget.ImageView; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/search/Search2Activity.java b/app/src/main/java/com/seafile/seadroid2/ui/search/Search2Activity.java similarity index 99% rename from app/src/main/java/com/seafile/seadroid2/ui/activity/search/Search2Activity.java rename to app/src/main/java/com/seafile/seadroid2/ui/search/Search2Activity.java index 1327504ef..166b2169f 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/search/Search2Activity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/search/Search2Activity.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.activity.search; +package com.seafile.seadroid2.ui.search; import android.content.ComponentName; import android.content.Context; @@ -41,7 +41,7 @@ import com.seafile.seadroid2.play.exoplayer.CustomExoVideoPlayerActivity; import com.seafile.seadroid2.transfer.TransferService; import com.seafile.seadroid2.ui.WidgetUtils; -import com.seafile.seadroid2.ui.activity.BaseActivity; +import com.seafile.seadroid2.ui.BaseActivity; import com.seafile.seadroid2.ui.activity.FileActivity; import com.seafile.seadroid2.ui.base.adapter.CustomLoadMoreAdapter; import com.seafile.seadroid2.util.ConcurrentAsyncTask; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/search/SearchRecyclerViewAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/search/SearchRecyclerViewAdapter.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/ui/activity/search/SearchRecyclerViewAdapter.java rename to app/src/main/java/com/seafile/seadroid2/ui/search/SearchRecyclerViewAdapter.java index 3b34b6355..20cad7f72 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/search/SearchRecyclerViewAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/search/SearchRecyclerViewAdapter.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.activity.search; +package com.seafile.seadroid2.ui.search; import android.content.Context; import android.view.LayoutInflater; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/PrivacyPolicyActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/settings/PrivacyPolicyActivity.java similarity index 95% rename from app/src/main/java/com/seafile/seadroid2/ui/activity/PrivacyPolicyActivity.java rename to app/src/main/java/com/seafile/seadroid2/ui/settings/PrivacyPolicyActivity.java index 363cb00dc..9df5ae4bc 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/PrivacyPolicyActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/settings/PrivacyPolicyActivity.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.activity; +package com.seafile.seadroid2.ui.settings; import android.os.Bundle; import androidx.appcompat.widget.Toolbar; @@ -8,6 +8,7 @@ import android.webkit.WebViewClient; import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.BaseActivity; public class PrivacyPolicyActivity extends BaseActivity implements Toolbar.OnMenuItemClickListener { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/SettingsActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsActivity.java similarity index 93% rename from app/src/main/java/com/seafile/seadroid2/ui/activity/SettingsActivity.java rename to app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsActivity.java index c3a0e8983..54980277d 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/SettingsActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsActivity.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.activity; +package com.seafile.seadroid2.ui.settings; import android.os.Bundle; import android.view.MenuItem; @@ -10,7 +10,8 @@ import androidx.preference.PreferenceFragmentCompat; import com.seafile.seadroid2.R; -import com.seafile.seadroid2.ui.fragment.SettingsFragment; +import com.seafile.seadroid2.ui.BaseActivity; +import com.seafile.seadroid2.ui.settings.SettingsFragment; public class SettingsActivity extends BaseActivity implements Toolbar.OnMenuItemClickListener, PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/fragment/SettingsCameraBackupAdvanceFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsCameraBackupAdvanceFragment.java similarity index 97% rename from app/src/main/java/com/seafile/seadroid2/ui/fragment/SettingsCameraBackupAdvanceFragment.java rename to app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsCameraBackupAdvanceFragment.java index 85b07d827..5bdbddb56 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/fragment/SettingsCameraBackupAdvanceFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsCameraBackupAdvanceFragment.java @@ -1,7 +1,6 @@ -package com.seafile.seadroid2.ui.fragment; +package com.seafile.seadroid2.ui.settings; import android.content.Intent; -import android.hardware.camera2.CameraManager; import android.os.Bundle; import android.text.TextUtils; import android.view.View; @@ -12,7 +11,6 @@ import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.preference.CheckBoxPreference; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.SwitchPreferenceCompat; @@ -26,7 +24,6 @@ import com.seafile.seadroid2.ui.activity.SeafilePathChooserActivity; import java.util.ArrayList; -import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/fragment/SettingsFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsFragment.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/ui/fragment/SettingsFragment.java rename to app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsFragment.java index 0586a3f0f..a6e121c3f 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/fragment/SettingsFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsFragment.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.fragment; +package com.seafile.seadroid2.ui.settings; import android.app.Activity; import android.content.Context; @@ -48,17 +48,16 @@ import com.seafile.seadroid2.folderbackup.RepoConfig; import com.seafile.seadroid2.folderbackup.selectfolder.StringTools; import com.seafile.seadroid2.gesturelock.LockPatternUtils; -import com.seafile.seadroid2.ui.activity.BrowserActivity; -import com.seafile.seadroid2.ui.activity.CreateGesturePasswordActivity; -import com.seafile.seadroid2.ui.activity.PrivacyPolicyActivity; +import com.seafile.seadroid2.ui.BrowserActivity; +import com.seafile.seadroid2.ui.gesture.CreateGesturePasswordActivity; import com.seafile.seadroid2.ui.activity.SeafilePathChooserActivity; -import com.seafile.seadroid2.ui.activity.SettingsActivity; import com.seafile.seadroid2.ui.dialog.ClearCacheTaskDialog; import com.seafile.seadroid2.ui.dialog.ClearPasswordTaskDialog; import com.seafile.seadroid2.ui.dialog.SwitchStorageTaskDialog; import com.seafile.seadroid2.ui.dialog.TaskDialog.TaskDialogListener; import com.seafile.seadroid2.util.CameraSyncStatus; import com.seafile.seadroid2.util.ConcurrentAsyncTask; +import com.seafile.seadroid2.util.SLogs; import com.seafile.seadroid2.util.Utils; import org.apache.commons.io.FileUtils; @@ -608,7 +607,7 @@ private void refreshCameraUploadView() { try { selectRepoConfig = databaseHelper.getRepoConfig(backupEmail); } catch (Exception e) { - Utils.utilsLogInfo(true, "=refreshCameraUploadView=======================" + e); + SLogs.d("=refreshCameraUploadView=======================" + e); } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/fragment/StarredFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/star/StarredFragment.java similarity index 99% rename from app/src/main/java/com/seafile/seadroid2/ui/fragment/StarredFragment.java rename to app/src/main/java/com/seafile/seadroid2/ui/star/StarredFragment.java index 9747a3909..67eb853ce 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/fragment/StarredFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/star/StarredFragment.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.fragment; +package com.seafile.seadroid2.ui.star; import android.app.Activity; import android.os.Bundle; @@ -28,8 +28,7 @@ import com.seafile.seadroid2.listener.OnCallback; import com.seafile.seadroid2.task.StarItemsTask; import com.seafile.seadroid2.ui.WidgetUtils; -import com.seafile.seadroid2.ui.activity.BrowserActivity; -import com.seafile.seadroid2.ui.adapter.StarredItemAdapter; +import com.seafile.seadroid2.ui.BrowserActivity; import com.seafile.seadroid2.ui.dialog.PasswordDialog; import com.seafile.seadroid2.ui.dialog.TaskDialog; import com.seafile.seadroid2.util.ConcurrentAsyncTask; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/adapter/StarredItemAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/star/StarredItemAdapter.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/ui/adapter/StarredItemAdapter.java rename to app/src/main/java/com/seafile/seadroid2/ui/star/StarredItemAdapter.java index 59eb8f24d..be329c8ec 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/adapter/StarredItemAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/star/StarredItemAdapter.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.adapter; +package com.seafile.seadroid2.ui.star; import android.util.SparseBooleanArray; import android.view.LayoutInflater; @@ -15,7 +15,7 @@ import com.seafile.seadroid2.data.SeafItem; import com.seafile.seadroid2.data.SeafStarredFile; import com.seafile.seadroid2.ui.WidgetUtils; -import com.seafile.seadroid2.ui.activity.BrowserActivity; +import com.seafile.seadroid2.ui.BrowserActivity; import com.seafile.seadroid2.util.GlideApp; import com.seafile.seadroid2.util.Utils; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/fragment/DownloadTaskFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/transfer/DownloadTaskFragment.java similarity index 97% rename from app/src/main/java/com/seafile/seadroid2/ui/fragment/DownloadTaskFragment.java rename to app/src/main/java/com/seafile/seadroid2/ui/transfer/DownloadTaskFragment.java index 6dc616edc..433eccfe8 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/fragment/DownloadTaskFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/transfer/DownloadTaskFragment.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.fragment; +package com.seafile.seadroid2.ui.transfer; import android.os.Bundle; import android.util.Log; @@ -6,7 +6,6 @@ import com.seafile.seadroid2.transfer.DownloadTaskInfo; import com.seafile.seadroid2.transfer.TaskState; import com.seafile.seadroid2.transfer.TransferTaskInfo; -import com.seafile.seadroid2.ui.adapter.TransferTaskAdapter; import java.util.List; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/TransferActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/transfer/TransferActivity.java similarity index 97% rename from app/src/main/java/com/seafile/seadroid2/ui/activity/TransferActivity.java rename to app/src/main/java/com/seafile/seadroid2/ui/transfer/TransferActivity.java index aa8dc1b0f..99f4e96eb 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/TransferActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/transfer/TransferActivity.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.activity; +package com.seafile.seadroid2.ui.transfer; import android.content.Intent; import android.os.Bundle; @@ -17,9 +17,7 @@ import com.seafile.seadroid2.R; import com.seafile.seadroid2.notification.BaseNotificationProvider; import com.seafile.seadroid2.notification.DownloadNotificationProvider; -import com.seafile.seadroid2.ui.adapter.TransferTaskAdapter; -import com.seafile.seadroid2.ui.fragment.DownloadTaskFragment; -import com.seafile.seadroid2.ui.fragment.UploadTaskFragment; +import com.seafile.seadroid2.ui.BaseActivity; public class TransferActivity extends BaseActivity implements Toolbar.OnMenuItemClickListener { private static final String DEBUG_TAG = "TransferActivity"; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/adapter/TransferTaskAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/transfer/TransferTaskAdapter.java similarity index 99% rename from app/src/main/java/com/seafile/seadroid2/ui/adapter/TransferTaskAdapter.java rename to app/src/main/java/com/seafile/seadroid2/ui/transfer/TransferTaskAdapter.java index 46aeb430b..316602bd8 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/adapter/TransferTaskAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/transfer/TransferTaskAdapter.java @@ -1,7 +1,6 @@ -package com.seafile.seadroid2.ui.adapter; +package com.seafile.seadroid2.ui.transfer; import android.graphics.Color; -import android.util.Log; import android.util.SparseBooleanArray; import android.view.LayoutInflater; import android.view.View; @@ -16,7 +15,6 @@ import com.seafile.seadroid2.transfer.DownloadTaskInfo; import com.seafile.seadroid2.transfer.TransferTaskInfo; import com.seafile.seadroid2.transfer.UploadTaskInfo; -import com.seafile.seadroid2.ui.activity.TransferActivity; import com.seafile.seadroid2.util.Utils; import java.util.Collections; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/fragment/TransferTaskFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/transfer/TransferTaskFragment.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/ui/fragment/TransferTaskFragment.java rename to app/src/main/java/com/seafile/seadroid2/ui/transfer/TransferTaskFragment.java index 24e710411..65f13886f 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/fragment/TransferTaskFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/transfer/TransferTaskFragment.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.fragment; +package com.seafile.seadroid2.ui.transfer; import android.app.Activity; import android.content.ComponentName; @@ -26,8 +26,6 @@ import com.seafile.seadroid2.R; import com.seafile.seadroid2.transfer.TransferService; import com.seafile.seadroid2.transfer.TransferTaskInfo; -import com.seafile.seadroid2.ui.activity.TransferActivity; -import com.seafile.seadroid2.ui.adapter.TransferTaskAdapter; import java.util.List; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/fragment/UploadTaskFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/transfer/UploadTaskFragment.java similarity index 97% rename from app/src/main/java/com/seafile/seadroid2/ui/fragment/UploadTaskFragment.java rename to app/src/main/java/com/seafile/seadroid2/ui/transfer/UploadTaskFragment.java index 6861dbbed..09a03bdbd 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/fragment/UploadTaskFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/transfer/UploadTaskFragment.java @@ -1,11 +1,10 @@ -package com.seafile.seadroid2.ui.fragment; +package com.seafile.seadroid2.ui.transfer; import android.os.Bundle; import com.seafile.seadroid2.R; import com.seafile.seadroid2.transfer.TaskState; import com.seafile.seadroid2.transfer.TransferTaskInfo; import com.seafile.seadroid2.transfer.UploadTaskInfo; -import com.seafile.seadroid2.ui.adapter.TransferTaskAdapter; import java.util.List; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/webview/SeaWebViewActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/webview/SeaWebViewActivity.java similarity index 95% rename from app/src/main/java/com/seafile/seadroid2/ui/activity/webview/SeaWebViewActivity.java rename to app/src/main/java/com/seafile/seadroid2/ui/webview/SeaWebViewActivity.java index e0c843b74..f6f41c0ac 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/webview/SeaWebViewActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/webview/SeaWebViewActivity.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.activity.webview; +package com.seafile.seadroid2.ui.webview; import android.content.ComponentName; import android.content.Context; @@ -12,8 +12,6 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.webkit.CookieManager; -import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebView; import android.widget.LinearLayout; @@ -24,21 +22,16 @@ import androidx.core.content.ContextCompat; import com.blankj.utilcode.util.ActivityUtils; -import com.blankj.utilcode.util.ToastUtils; import com.seafile.seadroid2.R; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.config.Constants; import com.seafile.seadroid2.notification.DownloadNotificationProvider; import com.seafile.seadroid2.transfer.TransferService; -import com.seafile.seadroid2.ui.activity.BaseActivity; -import com.seafile.seadroid2.ui.activity.FileActivity; -import com.seafile.seadroid2.util.URLs; +import com.seafile.seadroid2.ui.BaseActivity; import com.seafile.seadroid2.view.webview.PreloadWebView; import com.seafile.seadroid2.view.webview.SeaWebView; -import okhttp3.internal.http2.Http2; - public class SeaWebViewActivity extends BaseActivity { private TransferService txService = null; diff --git a/app/src/main/java/com/seafile/seadroid2/util/ContactsDialog.java b/app/src/main/java/com/seafile/seadroid2/util/ContactsDialog.java index 5a75d3456..fb7a26ec8 100755 --- a/app/src/main/java/com/seafile/seadroid2/util/ContactsDialog.java +++ b/app/src/main/java/com/seafile/seadroid2/util/ContactsDialog.java @@ -24,7 +24,7 @@ //import com.seafile.seadroid2.data.DataManager; //import com.seafile.seadroid2.data.SeafDirent; //import com.seafile.seadroid2.data.ContactsData; -//import com.seafile.seadroid2.ui.activity.SettingsActivity; +//import com.seafile.seadroid2.ui.settings.SettingsActivity; //import com.seafile.seadroid2.ui.dialog.TaskDialog; // //import java.io.BufferedReader; diff --git a/app/src/main/java/com/seafile/seadroid2/util/CrashHandler.java b/app/src/main/java/com/seafile/seadroid2/util/CrashHandler.java index 898c9243f..96698ffb2 100644 --- a/app/src/main/java/com/seafile/seadroid2/util/CrashHandler.java +++ b/app/src/main/java/com/seafile/seadroid2/util/CrashHandler.java @@ -48,11 +48,11 @@ public void init(Context context) { @Override public void uncaughtException(Thread thread, Throwable ex) { if (myHandleException(ex) && mDefaultHandler != null) { - Utils.utilsLogInfo(false, "=============uncaughtException"); + SLogs.e("=============uncaughtException"); Toast.makeText(mContext, mContext.getString(R.string.UncaughtExceptionHandler_err), Toast.LENGTH_SHORT).show(); mDefaultHandler.uncaughtException(thread, ex); } else { - Utils.utilsLogInfo(false, "===else==========uncaughtException"); + SLogs.e("===else==========uncaughtException"); try { Thread.sleep(3000); } catch (InterruptedException e) { @@ -81,7 +81,7 @@ public void collectDeviceInfo(Context ctx) { String versionCode = pi.versionCode + ""; infos.put("versionName", versionName); infos.put("versionCode", versionCode); - infos.put("phoneModelInfo", "phoneModelInfo-------" + SeafileLog.getDeviceBrand() + "/" + SeafileLog.getSystemModel() + "/" + SeafileLog.getSystemVersion()); + infos.put("phoneModelInfo", "phoneModelInfo-------" + SLogs.getDeviceBrand() + "/" + SLogs.getSystemModel() + "/" + SLogs.getSystemVersion()); } } catch (NameNotFoundException e) { Log.e(TAG, "an error occured when collect package info", e); diff --git a/app/src/main/java/com/seafile/seadroid2/util/Logs.java b/app/src/main/java/com/seafile/seadroid2/util/Logs.java new file mode 100644 index 000000000..51b03fe88 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/util/Logs.java @@ -0,0 +1,171 @@ +package com.seafile.seadroid2.util; + +import com.blankj.utilcode.util.FileUtils; +import com.blankj.utilcode.util.PathUtils; +import com.elvishew.xlog.LogConfiguration; +import com.elvishew.xlog.LogLevel; +import com.elvishew.xlog.XLog; +import com.elvishew.xlog.flattener.ClassicFlattener; +import com.elvishew.xlog.printer.AndroidPrinter; +import com.elvishew.xlog.printer.ConsolePrinter; +import com.elvishew.xlog.printer.Printer; +import com.elvishew.xlog.printer.file.FilePrinter; +import com.elvishew.xlog.printer.file.backup.FileSizeBackupStrategy2; +import com.elvishew.xlog.printer.file.clean.FileLastModifiedCleanStrategy; +import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator; +import com.seafile.seadroid2.BuildConfig; + +/** + *

+ *
DevEnv: VERBOSE、DEBUG + *
ProductEnv: INFO、WARN、ERROR + *

+ * + *

+ *
see {@link com.elvishew.xlog.XLog} + *
see https://github.com/elvishew/xLog + *

+ */ +public class Logs { + + private static final String LOG_TAG = "Sea-Log"; + /** + * will delete log files that have not been modified for a period of time + */ + private static final long MAX_TIME = 30L * 24 * 60 * 60 * 1000; + + public static void init() { + LogConfiguration config = new LogConfiguration.Builder() + .logLevel(BuildConfig.DEBUG ? LogLevel.ALL + : LogLevel.NONE) + .tag(LOG_TAG) +// .enableThreadInfo() +// .enableStackTrace(2) +// .enableBorder() + .build(); + Printer androidPrinter = new AndroidPrinter(true); + + // /storage/emulated/0/Android/data/package/files + String p = PathUtils.getExternalAppFilesPath(); + String logPath = p + "/logs"; + FileUtils.createOrExistsDir(logPath); + + Printer filePrinter = new FilePrinter + .Builder(logPath) + .fileNameGenerator(new DateFileNameGenerator()) + .flattener(new ClassicFlattener()) + .backupStrategy(new FileSizeBackupStrategy2(1024 * 1024, 30)) + .cleanStrategy(new FileLastModifiedCleanStrategy(MAX_TIME)) + .build(); + + XLog.init(config, androidPrinter, filePrinter); + } + + //VERBOSE + public static void v(Object object) { + XLog.v(object); + } + + public static void v(Object[] array) { + XLog.v(array); + } + + public static void v(String format, Object... args) { + XLog.v(format, args); + } + + public static void v(String msg) { + XLog.v(msg); + } + + public static void v(String msg, Throwable tr) { + XLog.v(msg, tr); + } + + //DEBUG + public static void d(Object object) { + XLog.d(object); + } + + public static void d(Object[] array) { + XLog.d(array); + } + + public static void d(String format, Object... args) { + XLog.d(format, args); + } + + public static void d(String msg) { + XLog.d(msg); + } + + public static void d(String msg, Throwable tr) { + XLog.d(msg, tr); + } + + //info + public static void i(Object object) { + XLog.i(object); + } + + public static void i(Object[] array) { + XLog.i(array); + } + + public static void i(String format, Object... args) { + XLog.i(format, args); + } + + public static void i(String msg) { + XLog.i(msg); + } + + public static void i(String msg, Throwable tr) { + XLog.i(msg, tr); + } + + + //warn + public static void w(Object object) { + XLog.w(object); + } + + + public static void w(Object[] array) { + XLog.w(array); + } + + + public static void w(String format, Object... args) { + XLog.w(format, args); + } + + public static void w(String msg) { + XLog.w(msg); + } + + public static void w(String msg, Throwable tr) { + XLog.w(msg, tr); + } + + //error + public static void e(Object object) { + XLog.e(object); + } + + public static void e(Object[] array) { + XLog.e(array); + } + + public static void e(String format, Object... args) { + XLog.e(format, args); + } + + public static void e(String msg) { + XLog.e(msg); + } + + public static void e(String msg, Throwable tr) { + XLog.e(msg, tr); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/util/PermissionUtil.java b/app/src/main/java/com/seafile/seadroid2/util/PermissionUtil.java index 93d7b8019..b08d6fbe0 100644 --- a/app/src/main/java/com/seafile/seadroid2/util/PermissionUtil.java +++ b/app/src/main/java/com/seafile/seadroid2/util/PermissionUtil.java @@ -19,13 +19,10 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; -import androidx.core.content.PackageManagerCompat; import com.blankj.utilcode.util.CollectionUtils; -import com.blankj.utilcode.util.StringUtils; import com.google.android.material.snackbar.Snackbar; import com.seafile.seadroid2.R; -import com.seafile.seadroid2.ui.activity.BrowserActivity; import java.util.Arrays; import java.util.List; diff --git a/app/src/main/java/com/seafile/seadroid2/util/SLogs.java b/app/src/main/java/com/seafile/seadroid2/util/SLogs.java new file mode 100644 index 000000000..c9366ccda --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/util/SLogs.java @@ -0,0 +1,33 @@ +package com.seafile.seadroid2.util; + +import com.blankj.utilcode.util.AppUtils; + +public class SLogs extends Logs { + + public static void printAppEnvInfo() { + String brand = android.os.Build.BRAND; + String model = android.os.Build.MODEL; + String release = android.os.Build.VERSION.RELEASE; + AppUtils.AppInfo appInfo = AppUtils.getAppInfo(); + + d("App Env Info:"); + d("{" + + "\n Brand: " + brand + + "\n Model: " + model + + "\n Release: " + release + + "\n}"); + d(appInfo.toString()); + } + + public static String getDeviceBrand() { + return android.os.Build.BRAND; + } + + public static String getSystemModel() { + return android.os.Build.MODEL; + } + + public static String getSystemVersion() { + return android.os.Build.VERSION.RELEASE; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/util/SeafileLog.java b/app/src/main/java/com/seafile/seadroid2/util/SeafileLog.java deleted file mode 100644 index a73ac2dcb..000000000 --- a/app/src/main/java/com/seafile/seadroid2/util/SeafileLog.java +++ /dev/null @@ -1,163 +0,0 @@ -package com.seafile.seadroid2.util; - -import android.content.Context; -import android.util.Log; - -import com.seafile.seadroid2.SeadroidApplication; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; - -public class SeafileLog { - - private static Boolean MYLOG_SWITCH = true; // Main switch - private static Boolean MYLOG_WRITE_TO_FILE = true;// log switch - private static char MYLOG_TYPE = 'v'; - private static int SDCARD_LOG_FILE_SAVE_DAYS = 0; - private static String MYLOGFILENAME = "Log.txt"; - private static SimpleDateFormat myLogSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// The output format of the log - private static SimpleDateFormat logfile = new SimpleDateFormat("yyyy-MM-dd");// Log file format - public Context context; - - public static void w(String tag, Object msg) { // Warning message - log(tag, msg.toString(), 'w'); - } - - public static void e(String tag, Object msg) { // The error message - log(tag, msg.toString(), 'e'); - } - - public static void d(String tag, Object msg) {// Debugging information - log(tag, msg.toString(), 'd'); - } - - public static void i(String tag, Object msg) {// - log(tag, msg.toString(), 'i'); - } - - public static void v(String tag, Object msg) { - log(tag, msg.toString(), 'v'); - } - - public static void w(String tag, String text) { - log(tag, text, 'w'); - } - - public static void e(String tag, String text) { - log(tag, text, 'e'); - } - - public static void d(String tag, String text) { - log(tag, text, 'd'); - } - - public static void i(String tag, String text) { - log(tag, text, 'i'); - } - - public static void v(String tag, String text) { - log(tag, text, 'v'); - } - - private static void log(String tag, String msg, char level) { - if (MYLOG_SWITCH) {//Log file master switch - if ('e' == level && ('e' == MYLOG_TYPE || 'v' == MYLOG_TYPE)) { - Log.e(tag, msg); - } else if ('w' == level && ('w' == MYLOG_TYPE || 'v' == MYLOG_TYPE)) { - Log.w(tag, msg); - } else if ('d' == level && ('d' == MYLOG_TYPE || 'v' == MYLOG_TYPE)) { - Log.d(tag, msg); - } else if ('i' == level && ('d' == MYLOG_TYPE || 'v' == MYLOG_TYPE)) { - Log.i(tag, msg); - } else { - Log.v(tag, msg); - } - if (MYLOG_WRITE_TO_FILE)//Log write file switch - writeLogtoFile(String.valueOf(level), tag, msg); - } - } - - /** - * Open the log file and write to the log - * - * @param mylogtype - * @param tag - * @param text - */ - private static void writeLogtoFile(String mylogtype, String tag, String text) { - Date nowtime = new Date(); - String needWriteFile = logfile.format(nowtime); - String needWriteMessage = myLogSdf.format(nowtime) + " " + mylogtype + " " + tag + " " + text; -// File dirsFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Seafile/"); -// String rootPath = SeadroidApplication.getAppContext().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath(); - File[] externalMediaDirs = SeadroidApplication.getAppContext().getExternalMediaDirs(); - String rootPath = externalMediaDirs[0].getAbsolutePath(); - File dirsFile = new File(rootPath + "/Seafile/"); - if (!dirsFile.exists()) { - dirsFile.mkdirs(); - } - File file = new File(dirsFile.toString(), needWriteFile + MYLOGFILENAME);// MYLOG_PATH_SDCARD_DIR - if (!file.exists()) { - try { - file.createNewFile(); - } catch (Exception e) { - } - } - - try { - FileWriter filerWriter = new FileWriter(file, true); - BufferedWriter bufWriter = new BufferedWriter(filerWriter); - bufWriter.write(needWriteMessage); - bufWriter.newLine(); - bufWriter.close(); - filerWriter.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * Delete the specified log file - */ - public static void delFile() { - String needDelFiel = logfile.format(getDateBefore()); - File[] externalMediaDirs = SeadroidApplication.getAppContext().getExternalMediaDirs(); - String rootPath = externalMediaDirs[0].getAbsolutePath(); -// String rootPath = SeadroidApplication.getAppContext().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath(); -// File dirPath = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Seafile/"); - File dirPath = new File(rootPath + "/Seafile/"); - File file = new File(dirPath, needDelFiel + MYLOGFILENAME);// MYLOG_PATH_SDCARD_DIR - if (file.exists()) { - file.delete(); - } - } - - /** - * Use to get the file name of the log to delete - */ - private static Date getDateBefore() { - Date nowtime = new Date(); - Calendar now = Calendar.getInstance(); - now.setTime(nowtime); - now.set(Calendar.DATE, now.get(Calendar.DATE) - SDCARD_LOG_FILE_SAVE_DAYS); - return now.getTime(); - } - - public static String getDeviceBrand() { - return android.os.Build.BRAND; - } - - public static String getSystemModel() { - return android.os.Build.MODEL; - } - - public static String getSystemVersion() { - return android.os.Build.VERSION.RELEASE; - } - -} diff --git a/app/src/main/java/com/seafile/seadroid2/util/Utils.java b/app/src/main/java/com/seafile/seadroid2/util/Utils.java index a4cb866d8..4ae42eaee 100644 --- a/app/src/main/java/com/seafile/seadroid2/util/Utils.java +++ b/app/src/main/java/com/seafile/seadroid2/util/Utils.java @@ -1008,18 +1008,6 @@ public static String getRealPathFromURI(Context context, Uri contentUri, String } } - public static void logPhoneModelInfo() { - SeafileLog.d(DEBUG_TAG, "phoneModelInfo-------" + SeafileLog.getDeviceBrand() + "/" + SeafileLog.getSystemModel() + "/" + SeafileLog.getSystemVersion()); - } - - public static void utilsLogInfo(boolean b, String info) { - if (b) { - SeafileLog.d(DEBUG_TAG, info); - } else { - Log.d(DEBUG_TAG, info); - } - } - public static final String EXCEPTION_TYPE_CRASH = "crash_exception"; } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/widget/CircleImageView.java b/app/src/main/java/com/seafile/seadroid2/view/CircleImageView.java similarity index 99% rename from app/src/main/java/com/seafile/seadroid2/ui/widget/CircleImageView.java rename to app/src/main/java/com/seafile/seadroid2/view/CircleImageView.java index ed7c31c80..40be2c913 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/widget/CircleImageView.java +++ b/app/src/main/java/com/seafile/seadroid2/view/CircleImageView.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.widget; +package com.seafile.seadroid2.view; import android.content.Context; import android.content.res.TypedArray; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/CustomClearableEditText.java b/app/src/main/java/com/seafile/seadroid2/view/CustomClearableEditText.java similarity index 99% rename from app/src/main/java/com/seafile/seadroid2/ui/CustomClearableEditText.java rename to app/src/main/java/com/seafile/seadroid2/view/CustomClearableEditText.java index c09fda183..d233723ff 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/CustomClearableEditText.java +++ b/app/src/main/java/com/seafile/seadroid2/view/CustomClearableEditText.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui; +package com.seafile.seadroid2.view; import android.content.Context; import android.text.Editable; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/HackyViewPager.java b/app/src/main/java/com/seafile/seadroid2/view/HackyViewPager.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/ui/HackyViewPager.java rename to app/src/main/java/com/seafile/seadroid2/view/HackyViewPager.java index e6da1de20..88f9b392e 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/HackyViewPager.java +++ b/app/src/main/java/com/seafile/seadroid2/view/HackyViewPager.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui; +package com.seafile.seadroid2.view; import android.content.Context; import androidx.viewpager.widget.ViewPager; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/widget/PerformEdit.java b/app/src/main/java/com/seafile/seadroid2/view/PerformEdit.java similarity index 99% rename from app/src/main/java/com/seafile/seadroid2/ui/widget/PerformEdit.java rename to app/src/main/java/com/seafile/seadroid2/view/PerformEdit.java index 460a70f5f..2c668134b 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/widget/PerformEdit.java +++ b/app/src/main/java/com/seafile/seadroid2/view/PerformEdit.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.seafile.seadroid2.ui.widget; +package com.seafile.seadroid2.view; import android.text.Editable; import android.text.TextWatcher; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/ZoomOutPageTransformer.java b/app/src/main/java/com/seafile/seadroid2/view/ZoomOutPageTransformer.java similarity index 97% rename from app/src/main/java/com/seafile/seadroid2/ui/ZoomOutPageTransformer.java rename to app/src/main/java/com/seafile/seadroid2/view/ZoomOutPageTransformer.java index 68651f6f5..9a2c77d0a 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/ZoomOutPageTransformer.java +++ b/app/src/main/java/com/seafile/seadroid2/view/ZoomOutPageTransformer.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui; +package com.seafile.seadroid2.view; import androidx.viewpager.widget.ViewPager; import android.view.View; diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebView.java b/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebView.java index b9217ef13..bb80fec58 100644 --- a/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebView.java +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebView.java @@ -2,7 +2,6 @@ import android.annotation.SuppressLint; import android.content.Context; -import android.text.TextUtils; import android.util.AttributeSet; import android.webkit.CookieManager; import android.webkit.WebSettings; @@ -13,8 +12,6 @@ import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; -import com.seafile.seadroid2.util.SeafileLog; -import com.seafile.seadroid2.util.Token2SessionConverts; public class SeaWebView extends WebView { public static final String PATH_ACCOUNT_LOGIN = "accounts/login/"; diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java b/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java index 436505183..ec03c53f8 100644 --- a/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java @@ -6,20 +6,12 @@ import android.net.Uri; import android.text.TextUtils; import android.util.Log; -import android.webkit.CookieManager; -import android.webkit.URLUtil; import android.webkit.WebResourceRequest; -import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; -import androidx.annotation.Nullable; - -import com.blankj.utilcode.util.ToastUtils; import com.seafile.seadroid2.account.SupportAccountManager; -import com.seafile.seadroid2.util.SeafileLog; import com.seafile.seadroid2.util.Token2SessionConverts; -import com.seafile.seadroid2.util.URLs; import java.util.HashMap; import java.util.Map; diff --git a/app/src/main/res/layout/gallery_activity_layout.xml b/app/src/main/res/layout/gallery_activity_layout.xml index 0d7696961..a5a3f937f 100644 --- a/app/src/main/res/layout/gallery_activity_layout.xml +++ b/app/src/main/res/layout/gallery_activity_layout.xml @@ -5,7 +5,7 @@ android:layout_height="fill_parent" android:layout_above="@+id/gallery_pager"> - - - - \ No newline at end of file + + From a08623ef2a8bafda7fa6a7d66baca0805bc7fd4e Mon Sep 17 00:00:00 2001 From: zhwanng <48609908+zhwanng@users.noreply.github.com> Date: Fri, 2 Feb 2024 18:07:26 +0800 Subject: [PATCH 2/5] v3.0.0 --- app/build.gradle | 56 +- app/src/main/AndroidManifest.xml | 89 +- .../main/java/com/seafile/seadroid2/App.java | 19 + .../com/seafile/seadroid2/BootAutostart.java | 12 +- .../seadroid2/SeadroidApplication.java | 33 +- .../seafile/seadroid2/SeadroidViewModel.java | 50 + .../com/seafile/seadroid2/SeafConnection.java | 4 +- .../seafile/seadroid2/account/Account.java | 50 +- .../seadroid2/account/AccountDBHelper.java | 260 -- .../seadroid2/account/AccountInfo.java | 33 +- .../seadroid2/account/AccountManager.java | 89 - .../seadroid2/account/Authenticator.java | 37 +- .../account/SupportAccountManager.java | 133 +- .../seadroid2/account/SupportDataManager.java | 28 + .../seadroid2/avatar/AuthImageDownloader.java | 51 - .../com/seafile/seadroid2/avatar/Avatar.java | 79 - .../seadroid2/avatar/AvatarDBHelper.java | 182 -- .../seadroid2/avatar/AvatarManager.java | 85 - .../seadroid2/bottomsheetmenu/ActionMenu.java | 341 +++ .../bottomsheetmenu/ActionMenuItem.java | 342 +++ .../bottomsheetmenu/BottomSheetHelper.java | 39 + .../BottomSheetMenuAdapter.java | 71 + .../BottomSheetMenuFragment.java | 189 ++ .../bottomsheetmenu/OnMenuClickListener.java | 7 + .../BucketsSelectionFragment.java | 183 -- .../CameraUploadConfigActivity.java | 213 -- .../CloudLibraryAccountAdapter.java | 35 - .../cameraupload/CloudLibraryAdapter.java | 95 - .../cameraupload/CloudLibraryFragment.java | 61 - .../CloudLibrarySelectionFragment.java | 811 ------ .../cameraupload/HowToUploadFragment.java | 60 - .../cameraupload/MediaSchedulerService.java | 84 - .../cameraupload/ReadyToScanFragment.java | 35 - .../cameraupload/WhatToUploadFragment.java | 59 - .../seadroid2/config/AbsLayoutItemType.java | 18 + .../seafile/seadroid2/config/Constants.java | 83 +- .../seadroid2/config/GlideLoadConfig.java | 7 + .../seadroid2/context/CopyMoveContext.java | 52 +- .../seafile/seadroid2/context/NavContext.java | 229 +- .../seafile/seadroid2/data/ContactsData.java | 122 - .../seadroid2/data/DatabaseHelper.java | 1 + .../seadroid2/data/EventDetailsFileItem.java | 4 +- .../com/seafile/seadroid2/data/SeafGroup.java | 59 - .../com/seafile/seadroid2/data/SeafRepo.java | 5 +- .../seadroid2/data/SeafRepoEncrypt.java | 4 +- .../seafile/seadroid2/data/ServerInfo.java | 3 +- .../seadroid2/data/StorageManager.java | 119 +- .../seadroid2/data/db/AppDatabase.java | 88 + .../seadroid2/data/db/dao/CertCacheDAO.java | 24 + .../seadroid2/data/db/dao/DirentDAO.java | 37 + .../data/db/dao/DirentsCacheDAO.java | 23 + .../seadroid2/data/db/dao/EncKeyCacheDAO.java | 24 + .../seadroid2/data/db/dao/FileCacheDAO.java | 23 + .../data/db/dao/FolderBackupCacheDAO.java | 26 + .../data/db/dao/FolderBackupMonitorDAO.java | 24 + .../seadroid2/data/db/dao/ObjsDAO.java | 29 + .../seadroid2/data/db/dao/PhotoCacheDAO.java | 26 + .../seadroid2/data/db/dao/RepoDAO.java | 31 + .../seadroid2/data/db/dao/RepoDirDAO.java | 24 + .../data/db/dao/StarredFileCacheDAO.java | 24 + .../data/db/entities/CertEntity.java | 21 + .../data/db/entities/DirentModel.java | 90 + .../data/db/entities/DirentsCacheEntity.java | 25 + .../data/db/entities/EncKeyCacheEntity.java | 25 + .../data/db/entities/FileCacheEntity.java | 28 + .../db/entities/FolderBackupCacheEntity.java | 31 + .../entities/FolderBackupMonitorEntity.java | 31 + .../seadroid2/data/db/entities/ObjsModel.java | 30 + .../data/db/entities/PhotoCacheEntity.java | 24 + .../data/db/entities/RepoDirEntity.java | 25 + .../seadroid2/data/db/entities/RepoModel.java | 72 + .../db/entities/StarredFileCacheEntity.java | 22 + .../seadroid2/data/model/BaseModel.java | 8 + .../seadroid2/data/model/GroupItemModel.java | 22 + .../seadroid2/data/model/ResultModel.java | 6 + .../data/model/activities/ActivityModel.java | 47 + .../activities/ActivityWrapperModel.java | 7 + .../data/model/dirents/DeleteDirentModel.java | 16 + .../data/model/dirents/FileCreateModel.java | 16 + .../data/model/repo/DirentMiniModel.java | 16 + .../data/model/repo/DirentWrapperModel.java | 11 + .../data/model/repo/RepoWrapperModel.java | 9 + .../deserializer/EncryptFieldJsonAdapter.java | 47 + .../data/model/server/ServerInfoModel.java | 21 + .../data/model/star/StarredModel.java | 51 + .../data/model/star/StarredWrapperModel.java | 7 + .../data/remote/api/AccountService.java | 11 + .../data/remote/api/ActivityService.java | 13 + .../data/remote/api/MainService.java | 13 + .../data/remote/api/RepoService.java | 36 + .../data/remote/api/StarredService.java | 19 + .../data/repository/DirentsRepository.java | 67 + .../com/seafile/seadroid2/enums/FileType.java | 5 + .../com/seafile/seadroid2/enums/OpType.java | 12 + .../fileschooser/FileFooterFragment.java | 65 - .../fileschooser/FileListAdapter.java | 129 - .../fileschooser/FileListFragment.java | 95 - .../seadroid2/fileschooser/FileLoader.java | 102 - .../MultiFileChooserActivity.java | 234 -- .../fileschooser/SelectableFile.java | 106 - .../CloudLibraryChooserFragment.java | 737 ----- .../FolderBackupConfigActivity.java | 266 -- .../FolderBackupSelectedPathActivity.java | 144 - .../selectfolder/FileListAdapter.java | 89 - .../selectfolder/OnFileItemClickListener.java | 8 - .../SelectBackupFolderFragment.java | 198 -- .../selectfolder/SelectOptions.java | 119 - .../selectfolder/StringTools.java | 56 - .../com/seafile/seadroid2/gallery/Image.java | 15 +- .../seadroid2/gesturelock/DefaultAppLock.java | 8 +- .../seadroid2/httputils/RequestManager.java | 2 - .../com/seafile/seadroid2/io/http/BaseIO.java | 140 + .../com/seafile/seadroid2/io/http/IO.java | 115 + .../io/http/converter/ConverterFactory.java | 80 + .../converter/GsonRequestBodyConverter.java | 38 + .../converter/SupportResponseConverter.java | 36 + .../interceptor/AddCookiesInterceptor.java | 23 + .../http/interceptor/HeaderInterceptor.java | 35 + .../http/interceptor/ParamsInterceptor.java | 119 + .../ReceivedCookiesInterceptor.java | 21 + .../listener/OnFileItemChangeListener.java | 7 + .../seadroid2/monitor/AutoUpdateInfo.java | 4 +- .../seadroid2/monitor/MonitorDBHelper.java | 18 +- .../seadroid2/monitor/SeafileMonitor.java | 17 +- .../CustomExoVideoPlayerActivity.java | 12 +- .../seadroid2/provider/DocumentIdParser.java | 11 +- .../seadroid2/provider/SeafileProvider.java | 10 +- .../seafile/seadroid2/task/StarItemsTask.java | 75 - .../seadroid2/transfer/TransferManager.java | 4 +- .../seadroid2/transfer/UploadTask.java | 4 +- .../seafile/seadroid2/ui/BaseActivity.java | 222 +- .../seafile/seadroid2/ui/BrowserActivity.java | 2528 ----------------- .../ui/CustomNotificationBuilder.java | 25 +- .../ui/EmailAutoCompleteTextView.java | 81 - .../seafile/seadroid2/ui/SplashActivity.java | 60 + .../com/seafile/seadroid2/ui/WidgetUtils.java | 109 +- .../ui/account/AccountDetailActivity.java | 87 +- .../ui/account/AccountsActivity.java | 226 +- .../account/SeafileAuthenticatorActivity.java | 127 +- .../ui/account/SingleSignOnActivity.java | 5 +- .../SingleSignOnAuthorizeActivity.java | 21 +- .../ui/account/adapter/AccountAdapter.java | 78 +- .../account/adapter/SeafAccountAdapter.java | 34 - .../ui/activities/ActivitiesFragment.java | 495 ---- .../ui/activities/ActivitiesItemAdapter.java | 242 -- .../ui/activities/ActivityAdapter.java | 103 + .../activities/ActivityContainerFragment.java | 69 + .../ui/activities/ActivityViewHolder.java | 15 + .../ui/activities/ActivityViewModel.java | 103 + .../ui/activities/AllActivitiesFragment.java | 214 ++ .../ui/activities/MineActivitiesFragment.java | 104 + .../ActivityBottomSheetAdapter.java | 59 + .../ActivityBottomSheetDialogFragment.java | 64 + .../seadroid2/ui/activity/FileActivity.java | 34 +- .../ui/activity/GalleryActivity.java | 95 +- .../activity/SeafilePathChooserActivity.java | 843 ------ .../ui/activity/ShareToSeafileActivity.java | 85 +- .../ui/adapter/BottomSheetAdapter.java | 86 - .../seadroid2/ui/adapter/SeafItemAdapter.java | 606 ---- .../ui/adapter/SeafItemCheckableAdapter.java | 238 -- .../ui/adapter/ViewPager2Adapter.java | 68 + .../ui/adapter/ViewPagerAdapter.java | 31 + .../seadroid2/ui/base/BaseQuickActivity.java | 44 + .../ui/base/adapter/BaseAdapter.java | 13 + .../ui/base/adapter/BaseMultiAdapter.java | 10 + .../base/adapter/CustomLoadMoreAdapter.java | 4 +- .../ui/base/adapter/ParentAdapter.java | 11 - .../ui/base/adapter/ParentMultiAdapter.java | 11 - .../fragment/BaseDialogFragmentWithVM.java | 46 + .../ui/base/fragment/BaseFragment.java | 11 + .../ui/base/fragment/BaseFragmentWithVM.java | 54 + .../base/fragment/CustomDialogFragment.java | 157 + .../RequestCustomDialogFragmentWithVM.java | 237 ++ .../BaseViewHolder.java | 4 +- .../ui/base/viewmodel/BaseViewModel.java | 207 ++ .../BaseBottomSheetDialogFragment.java | 100 - .../bottomsheet/BottomSheetListFragment.java | 82 - .../bottomsheet/BottomSheetTextFragment.java | 56 - .../camera_upload}/CameraSyncAdapter.java | 20 +- .../camera_upload}/CameraSyncService.java | 8 +- .../CameraUploadConfigActivity.java | 188 ++ .../camera_upload}/CameraUploadDBHelper.java | 2 +- .../camera_upload}/CameraUploadManager.java | 27 +- .../camera_upload}/GalleryBucketUtils.java | 102 +- .../camera_upload}/MediaObserverService.java | 56 +- .../camera_upload/MediaSchedulerService.java | 32 + .../camera_upload}/StubContentProvider.java | 2 +- .../config_fragment}/BucketsFragment.java | 110 +- .../ConfigWelcomeFragment.java | 6 +- .../config_fragment/HowToUploadFragment.java | 43 + .../config_fragment/ReadyToScanFragment.java | 20 + .../config_fragment/WhatToUploadFragment.java | 38 + .../data_migrate/DataMigrationActivity.java | 760 +++++ .../seadroid2/ui/dialog/AppChoiceDialog.java | 9 +- .../ui/dialog/ClearCacheTaskDialog.java | 47 - .../ui/dialog/ClearPasswordTaskDialog.java | 43 - .../seadroid2/ui/dialog/CopyMoveDialog.java | 42 +- .../seadroid2/ui/dialog/CopyMoveTask.java | 60 +- .../seadroid2/ui/dialog/DeleteFileDialog.java | 182 -- .../seadroid2/ui/dialog/DeleteRepoDialog.java | 85 - .../seadroid2/ui/dialog/FetchFileDialog.java | 86 +- .../ui/dialog/GetShareLinkEncryptDialog.java | 144 - .../seadroid2/ui/dialog/NewDirDialog.java | 129 - .../seadroid2/ui/dialog/NewFileDialog.java | 126 - .../seadroid2/ui/dialog/NewRepoDialog.java | 163 -- .../seadroid2/ui/dialog/PasswordDialog.java | 239 -- .../seadroid2/ui/dialog/PolicyDialog.java | 149 - .../seadroid2/ui/dialog/RenameFileDialog.java | 145 - .../seadroid2/ui/dialog/RenameRepoDialog.java | 129 - .../ui/dialog/SortFilesDialogFragment.java | 77 - .../ui/dialog/UploadChoiceDialog.java | 54 +- .../ClearCacheDialogFragment.java | 53 + .../ClearPasswordDialogFragment.java | 50 + .../CopyMoveDialogFragment.java | 137 + .../DeleteFileDialogFragment.java | 80 + .../DeleteRepoDialogFragment.java | 92 + .../ui/dialog_fragment/DialogService.java | 66 + .../GetShareLinkPasswordDialogFragment.java | 92 + .../NewDirFileDialogFragment.java | 136 + .../NewRepoDialogFragment.java | 124 + .../PasswordDialogFragment.java | 143 + .../dialog_fragment/PolicyDialogFragment.java | 95 + .../dialog_fragment/RenameDialogFragment.java | 136 + .../SignOutDialogFragment.java | 53 + .../SortFilesDialogFragment.java | 40 + .../SwitchStorageDialogFragment.java} | 124 +- .../listener/OnRefreshDataListener.java | 5 + .../listener/OnSortItemClickListener.java | 12 + .../viewmodel/ClearCacheViewModel.java | 50 + .../viewmodel/CopyMoveViewModel.java | 69 + .../viewmodel/DeleteDirentsViewModel.java | 97 + .../viewmodel/DeleteRepoViewModel.java | 53 + .../GetShareLinkPasswordViewModel.java | 6 + .../viewmodel/NewDirViewModel.java | 103 + .../viewmodel/NewRepoViewModel.java | 55 + .../viewmodel/PasswordViewModel.java | 98 + .../viewmodel/RenameRepoViewModel.java | 115 + .../FolderBackupConfigActivity.java | 236 ++ .../folder_backup}/FolderBackupDBHelper.java | 2 +- .../folder_backup}/FolderBackupEvent.java | 2 +- .../folder_backup}/FolderBackupInfo.java | 2 +- .../FolderBackupSelectedPathActivity.java | 132 + .../FolderBackupSelectedPathAdapter.java} | 12 +- .../folder_backup}/FolderBackupService.java | 36 +- .../folder_backup}/RepoConfig.java | 3 +- .../CreateGesturePasswordActivity.java | 7 +- .../UnlockGesturePasswordActivity.java | 13 +- .../seadroid2/ui/main/MainActivity.java | 1137 ++++++++ .../seadroid2/ui/main/MainViewModel.java | 227 ++ .../ui/markdown/MarkdownActivity.java | 6 +- .../seadroid2/ui/repo/AccountViewHolder.java | 16 + .../seadroid2/ui/repo/DirentViewHolder.java | 16 + .../seadroid2/ui/repo/DirentsAdapter.java | 15 +- .../seadroid2/ui/repo/RepoQuickAdapter.java | 507 ++++ .../seadroid2/ui/repo/RepoQuickFragment.java | 993 +++++++ .../seadroid2/ui/repo/RepoViewHolder.java | 16 + .../seadroid2/ui/repo/RepoViewModel.java | 521 ++++ .../seadroid2/ui/repo/ReposAdapter.java | 1 + .../seadroid2/ui/repo/ReposFragment.java | 1243 -------- .../seadroid2/ui/repo/ScrollState.java | 11 + .../seadroid2/ui/repo/SeafReposAdapter.java | 31 +- .../ui/repo/UnsupportedViewHolder.java | 16 + .../seadroid2/ui/search/Search2Activity.java | 39 +- .../ui/search/SearchRecyclerViewAdapter.java | 6 +- .../ui/selector/ObjSelectorActivity.java | 291 ++ .../ui/selector/ObjSelectorFragment.java | 211 ++ .../ui/selector/ObjSelectorViewModel.java | 403 +++ .../folder_selector}/BeanListManager.java | 86 +- .../selector/folder_selector}/Constants.java | 2 +- .../selector/folder_selector}/FileBean.java | 13 +- .../folder_selector/FileListAdapter.java | 68 + .../folder_selector}/FileListViewHolder.java | 9 +- .../FolderSelectorFragment.java | 243 ++ .../FolderSelectorViewModel.java | 196 ++ .../selector/folder_selector/StringTools.java | 31 + .../folder_selector}/TabBarFileBean.java | 4 +- .../TabBarFileListAdapter.java | 18 +- .../TabbarFileViewHolder.java | 2 +- .../ui/settings/PrivacyPolicyActivity.java | 54 - .../ui/settings/SettingsActivity.java | 26 +- .../settings/SettingsActivityViewModel.java | 6 + .../SettingsCameraBackupAdvanceFragment.java | 31 +- .../ui/settings/SettingsFragment.java | 631 ++-- .../settings/SettingsFragmentViewModel.java | 92 + .../seadroid2/ui/star/StarredAdapter.java | 143 + .../seadroid2/ui/star/StarredFragment.java | 519 ---- .../seadroid2/ui/star/StarredItemAdapter.java | 223 -- .../ui/star/StarredQuickFragment.java | 192 ++ .../seadroid2/ui/star/StarredViewHolder.java | 15 + .../seadroid2/ui/star/StarredViewModel.java | 67 + .../ui/transfer/TransferActivity.java | 264 +- .../ui/transfer/TransferTaskFragment.java | 7 +- .../ui/viewholder/GroupItemViewHolder.java | 15 + .../ui/webview/SeaWebViewActivity.java | 55 +- .../WindowPreferencesManager.java | 91 + .../seadroid2/util/ContactsDialog.java | 2 +- .../seafile/seadroid2/util/FileExports.java | 30 +- .../selectfolder => util}/FileTools.java | 19 +- .../seadroid2/util/SystemSwitchUtils.java | 9 +- .../com/seafile/seadroid2/util/TUtil.java | 33 + .../seafile/seadroid2/util/TakeCameras.java | 76 + .../com/seafile/seadroid2/util/Times.java | 13 + .../com/seafile/seadroid2/util/Utils.java | 69 +- .../com/seafile/seadroid2/util/sp/AppSPs.java | 15 + .../util/sp/FolderBackupConfigSPs.java | 77 + .../com/seafile/seadroid2/util/sp/SPs.java | 66 + .../{ => util/sp}/SettingsManager.java | 148 +- .../com/seafile/seadroid2/util/sp/Sorts.java | 91 + .../view/CustomClearableEditText.java | 131 - .../seadroid2/view/LinearRecyclerView.java | 31 + .../seadroid2/view/ListPreferenceCompat.java | 61 + .../com/seafile/seadroid2/view/TipsViews.java | 15 + .../seadroid2/view/webview/SeaWebView.java | 12 +- .../view/webview/SeaWebViewClient.java | 32 +- .../main/res/anim/bs_list_layout_fade_in.xml | 6 - .../anim/pull_to_refresh_header_loading.xml | 23 - .../main/res/drawable-hdpi/action_more.png | Bin 243 -> 0 bytes .../res/drawable-hdpi/action_remove_cache.png | Bin 617 -> 0 bytes .../drawable-hdpi/action_share_password.png | Bin 1540 -> 0 bytes .../main/res/drawable-hdpi/action_star.png | Bin 637 -> 0 bytes .../drawable-hdpi/actionbar_background.9.png | Bin 198 -> 0 bytes .../res/drawable-hdpi/activity_normal.png | Bin 1017 -> 0 bytes .../res/drawable-hdpi/activity_selected.png | Bin 1328 -> 0 bytes .../btn_code_lock_default_holo.png | Bin 625 -> 0 bytes .../btn_code_lock_touched_holo.png | Bin 549 -> 0 bytes .../res/drawable-hdpi/btn_search_icon.png | Bin 15537 -> 0 bytes .../main/res/drawable-hdpi/card_avatar.9.png | Bin 970 -> 0 bytes .../custom_tab_indicator_divider.9.png | Bin 76 -> 0 bytes .../custom_tab_indicator_selected.9.png | Bin 90 -> 0 bytes ...ustom_tab_indicator_selected_focused.9.png | Bin 194 -> 0 bytes ...ustom_tab_indicator_selected_pressed.9.png | Bin 190 -> 0 bytes .../custom_tab_indicator_unselected.9.png | Bin 187 -> 0 bytes ...tom_tab_indicator_unselected_focused.9.png | Bin 230 -> 0 bytes ...tom_tab_indicator_unselected_pressed.9.png | Bin 189 -> 0 bytes ...ialog_textfield_activated_holo_light.9.png | Bin 196 -> 0 bytes .../dialog_textfield_default_holo_light.9.png | Bin 227 -> 0 bytes ...extfield_disabled_focused_holo_light.9.png | Bin 1208 -> 0 bytes ...dialog_textfield_disabled_holo_light.9.png | Bin 1116 -> 0 bytes .../dialog_textfield_focused_holo_light.9.png | Bin 290 -> 0 bytes .../main/res/drawable-hdpi/et_normal.9.png | Bin 425 -> 0 bytes .../main/res/drawable-hdpi/et_pressed.9.png | Bin 425 -> 0 bytes .../fancy_orange_text_select_handle_left.png | Bin 1244 -> 0 bytes ...fancy_orange_text_select_handle_middle.png | Bin 1164 -> 0 bytes .../fancy_orange_text_select_handle_right.png | Bin 1221 -> 0 bytes .../drawable-hdpi/gallery_loading_failed.png | Bin 7071 -> 0 bytes ...icator_code_lock_drag_direction_red_up.png | Bin 298 -> 0 bytes ...ator_code_lock_point_area_default_holo.png | Bin 625 -> 0 bytes ...icator_code_lock_point_area_green_holo.png | Bin 7367 -> 0 bytes ...ndicator_code_lock_point_area_red_holo.png | Bin 7343 -> 0 bytes .../main/res/drawable-hdpi/library_normal.png | Bin 484 -> 0 bytes .../res/drawable-hdpi/library_selected.png | Bin 656 -> 0 bytes .../list_item_download_waiting.png | Bin 393 -> 0 bytes app/src/main/res/drawable-hdpi/loading.png | Bin 394 -> 0 bytes app/src/main/res/drawable-hdpi/loading_1.png | Bin 1252 -> 0 bytes app/src/main/res/drawable-hdpi/loading_2.png | Bin 1445 -> 0 bytes app/src/main/res/drawable-hdpi/loading_3.png | Bin 1461 -> 0 bytes app/src/main/res/drawable-hdpi/loading_4.png | Bin 1436 -> 0 bytes .../drawable-hdpi/operation_button_cancel.png | Bin 278 -> 0 bytes .../drawable-hdpi/operation_button_delete.png | Bin 3214 -> 0 bytes .../operation_button_selectall.png | Bin 3317 -> 0 bytes .../res/drawable-hdpi/pulltorefresh_arrow.png | Bin 324 -> 0 bytes .../main/res/drawable-hdpi/search_button.png | Bin 2922 -> 0 bytes .../drawable-hdpi/search_button_clicked.png | Bin 2921 -> 0 bytes app/src/main/res/drawable-hdpi/select_all.png | Bin 661 -> 0 bytes .../main/res/drawable-hdpi/star_selected.png | Bin 768 -> 0 bytes app/src/main/res/drawable-hdpi/upload.png | Bin 429 -> 0 bytes .../res/drawable-hdpi/upload_disabled.png | Bin 334 -> 0 bytes .../main/res/drawable-ldpi/action_more.png | Bin 156 -> 0 bytes .../res/drawable-ldpi/action_remove_cache.png | Bin 617 -> 0 bytes .../drawable-ldpi/action_share_password.png | Bin 830 -> 0 bytes .../main/res/drawable-ldpi/action_star.png | Bin 323 -> 0 bytes .../res/drawable-ldpi/activity_normal.png | Bin 564 -> 0 bytes .../res/drawable-ldpi/activity_selected.png | Bin 740 -> 0 bytes .../main/res/drawable-ldpi/library_normal.png | Bin 103 -> 0 bytes .../res/drawable-ldpi/library_selected.png | Bin 111 -> 0 bytes app/src/main/res/drawable-ldpi/loading_1.png | Bin 612 -> 0 bytes app/src/main/res/drawable-ldpi/loading_2.png | Bin 698 -> 0 bytes app/src/main/res/drawable-ldpi/loading_3.png | Bin 723 -> 0 bytes app/src/main/res/drawable-ldpi/loading_4.png | Bin 756 -> 0 bytes .../res/drawable-ldpi/pulltorefresh_arrow.png | Bin 257 -> 0 bytes app/src/main/res/drawable-ldpi/select_all.png | Bin 360 -> 0 bytes .../main/res/drawable-ldpi/star_selected.png | Bin 474 -> 0 bytes app/src/main/res/drawable-ldpi/upload.png | Bin 232 -> 0 bytes .../res/drawable-ldpi/upload_disabled.png | Bin 185 -> 0 bytes app/src/main/res/drawable-mdpi/account.png | Bin 1400 -> 0 bytes .../main/res/drawable-mdpi/action_more.png | Bin 197 -> 0 bytes .../res/drawable-mdpi/action_remove_cache.png | Bin 490 -> 0 bytes .../drawable-mdpi/action_share_password.png | Bin 1006 -> 0 bytes .../main/res/drawable-mdpi/action_star.png | Bin 399 -> 0 bytes .../drawable-mdpi/actionbar_background.9.png | Bin 158 -> 0 bytes .../res/drawable-mdpi/activity_normal.png | Bin 720 -> 0 bytes .../res/drawable-mdpi/activity_selected.png | Bin 944 -> 0 bytes ...ialog_textfield_activated_holo_light.9.png | Bin 164 -> 0 bytes .../dialog_textfield_default_holo_light.9.png | Bin 182 -> 0 bytes ...extfield_disabled_focused_holo_light.9.png | Bin 1133 -> 0 bytes ...dialog_textfield_disabled_holo_light.9.png | Bin 1094 -> 0 bytes .../dialog_textfield_focused_holo_light.9.png | Bin 256 -> 0 bytes .../res/drawable-mdpi/drop_down_button.png | Bin 346 -> 0 bytes .../fancy_orange_text_select_handle_left.png | Bin 788 -> 0 bytes ...fancy_orange_text_select_handle_middle.png | Bin 689 -> 0 bytes .../fancy_orange_text_select_handle_right.png | Bin 791 -> 0 bytes .../main/res/drawable-mdpi/library_normal.png | Bin 269 -> 0 bytes .../res/drawable-mdpi/library_selected.png | Bin 342 -> 0 bytes .../list_item_download_waiting.png | Bin 305 -> 0 bytes app/src/main/res/drawable-mdpi/loading_1.png | Bin 893 -> 0 bytes app/src/main/res/drawable-mdpi/loading_2.png | Bin 926 -> 0 bytes app/src/main/res/drawable-mdpi/loading_3.png | Bin 996 -> 0 bytes app/src/main/res/drawable-mdpi/loading_4.png | Bin 981 -> 0 bytes .../drawable-mdpi/operation_button_cancel.png | Bin 226 -> 0 bytes .../res/drawable-mdpi/pulltorefresh_arrow.png | Bin 241 -> 0 bytes app/src/main/res/drawable-mdpi/select_all.png | Bin 482 -> 0 bytes .../main/res/drawable-mdpi/star_selected.png | Bin 563 -> 0 bytes app/src/main/res/drawable-mdpi/upload.png | Bin 291 -> 0 bytes .../res/drawable-mdpi/upload_disabled.png | Bin 234 -> 0 bytes .../drawable-nodpi/action_remove_cache.png | Bin 617 -> 0 bytes .../drawable-nodpi/action_share_password.png | Bin 1540 -> 0 bytes .../main/res/drawable-xhdpi/action_more.png | Bin 485 -> 0 bytes .../drawable-xhdpi/action_remove_cache.png | Bin 796 -> 0 bytes .../drawable-xhdpi/action_share_password.png | Bin 2021 -> 0 bytes .../main/res/drawable-xhdpi/action_star.png | Bin 746 -> 0 bytes .../drawable-xhdpi/actionbar_background.9.png | Bin 266 -> 0 bytes .../res/drawable-xhdpi/activity_normal.png | Bin 1299 -> 0 bytes .../res/drawable-xhdpi/activity_selected.png | Bin 1644 -> 0 bytes ..._radio_off_disabled_focused_holo_light.png | Bin 1985 -> 0 bytes .../btn_radio_off_disabled_holo_light.png | Bin 919 -> 0 bytes .../btn_radio_off_focused_holo_light.png | Bin 2143 -> 0 bytes .../btn_radio_off_holo_light.png | Bin 1043 -> 0 bytes .../btn_radio_off_pressed_holo_light.png | Bin 3302 -> 0 bytes ...n_radio_on_disabled_focused_holo_light.png | Bin 3408 -> 0 bytes .../btn_radio_on_disabled_holo_light.png | Bin 1643 -> 0 bytes .../btn_radio_on_focused_holo_light.png | Bin 3527 -> 0 bytes .../btn_radio_on_holo_light.png | Bin 2426 -> 0 bytes .../btn_radio_on_pressed_holo_light.png | Bin 3690 -> 0 bytes ...ialog_textfield_activated_holo_light.9.png | Bin 231 -> 0 bytes .../dialog_textfield_default_holo_light.9.png | Bin 220 -> 0 bytes ...extfield_disabled_focused_holo_light.9.png | Bin 1176 -> 0 bytes ...dialog_textfield_disabled_holo_light.9.png | Bin 1116 -> 0 bytes .../dialog_textfield_focused_holo_light.9.png | Bin 426 -> 0 bytes .../fancy_orange_text_select_handle_left.png | Bin 1734 -> 0 bytes ...fancy_orange_text_select_handle_middle.png | Bin 1563 -> 0 bytes .../fancy_orange_text_select_handle_right.png | Bin 1769 -> 0 bytes .../main/res/drawable-xhdpi/home_up_btn.png | Bin 1754 -> 0 bytes .../res/drawable-xhdpi/library_normal.png | Bin 494 -> 0 bytes .../res/drawable-xhdpi/library_selected.png | Bin 659 -> 0 bytes .../list_item_download_waiting.png | Bin 554 -> 0 bytes app/src/main/res/drawable-xhdpi/loading_1.png | Bin 1717 -> 0 bytes app/src/main/res/drawable-xhdpi/loading_2.png | Bin 1699 -> 0 bytes app/src/main/res/drawable-xhdpi/loading_3.png | Bin 1937 -> 0 bytes app/src/main/res/drawable-xhdpi/loading_4.png | Bin 1863 -> 0 bytes .../operation_button_cancel.png | Bin 343 -> 0 bytes .../drawable-xhdpi/pulltorefresh_arrow.png | Bin 321 -> 0 bytes .../scrollbar_handle_vertical.9.png | Bin 132 -> 0 bytes .../main/res/drawable-xhdpi/select_all.png | Bin 988 -> 0 bytes .../main/res/drawable-xhdpi/star_selected.png | Bin 1013 -> 0 bytes app/src/main/res/drawable-xhdpi/upload.png | Bin 539 -> 0 bytes .../res/drawable-xhdpi/upload_disabled.png | Bin 419 -> 0 bytes .../main/res/drawable-xxhdpi/action_more.png | Bin 505 -> 0 bytes .../drawable-xxhdpi/action_remove_cache.png | Bin 924 -> 0 bytes .../drawable-xxhdpi/action_share_password.png | Bin 2220 -> 0 bytes .../main/res/drawable-xxhdpi/action_star.png | Bin 729 -> 0 bytes .../actionbar_background.9.png | Bin 358 -> 0 bytes .../res/drawable-xxhdpi/activity_normal.png | Bin 1271 -> 0 bytes .../res/drawable-xxhdpi/activity_selected.png | Bin 1593 -> 0 bytes .../res/drawable-xxhdpi/default_avatar.png | Bin 7642 -> 1292 bytes ...ialog_textfield_activated_holo_light.9.png | Bin 331 -> 0 bytes .../dialog_textfield_default_holo_light.9.png | Bin 325 -> 0 bytes ...extfield_disabled_focused_holo_light.9.png | Bin 464 -> 0 bytes ...dialog_textfield_disabled_holo_light.9.png | Bin 315 -> 0 bytes .../dialog_textfield_focused_holo_light.9.png | Bin 504 -> 0 bytes .../fancy_orange_text_select_handle_left.png | Bin 2650 -> 0 bytes ...fancy_orange_text_select_handle_middle.png | Bin 2611 -> 0 bytes .../fancy_orange_text_select_handle_right.png | Bin 2459 -> 0 bytes .../main/res/drawable-xxhdpi/home_up_btn.png | Bin 1968 -> 0 bytes .../res/drawable-xxhdpi/library_normal.png | Bin 478 -> 0 bytes .../res/drawable-xxhdpi/library_selected.png | Bin 561 -> 0 bytes .../list_item_download_waiting.png | Bin 726 -> 0 bytes .../main/res/drawable-xxhdpi/loading_1.png | Bin 1885 -> 0 bytes .../main/res/drawable-xxhdpi/loading_2.png | Bin 1969 -> 0 bytes .../main/res/drawable-xxhdpi/loading_3.png | Bin 2176 -> 0 bytes .../main/res/drawable-xxhdpi/loading_4.png | Bin 2083 -> 0 bytes .../operation_button_cancel.png | Bin 493 -> 0 bytes .../drawable-xxhdpi/pulltorefresh_arrow.png | Bin 401 -> 0 bytes .../main/res/drawable-xxhdpi/select_all.png | Bin 1113 -> 0 bytes .../res/drawable-xxhdpi/star_selected.png | Bin 1168 -> 0 bytes app/src/main/res/drawable-xxhdpi/upload.png | Bin 604 -> 0 bytes .../res/drawable-xxhdpi/upload_disabled.png | Bin 460 -> 0 bytes .../res/drawable-xxxhdpi/default_account.png | Bin 13822 -> 0 bytes .../main/res/drawable-xxxhdpi/home_up_btn.png | Bin 2411 -> 0 bytes .../drawable-xxxhdpi/pulltorefresh_arrow.png | Bin 452 -> 0 bytes .../main/res/drawable-xxxhdpi/select_all.png | Bin 1302 -> 0 bytes app/src/main/res/drawable-xxxhdpi/upload.png | Bin 725 -> 0 bytes .../res/drawable-xxxhdpi/upload_disabled.png | Bin 561 -> 0 bytes app/src/main/res/drawable/ab_upload.xml | 6 - ...actionbar_space_between_icon_and_title.xml | 7 - .../main/res/drawable/activities_text_bg.xml | 15 - .../main/res/drawable/baseline_star_24.xml | 5 + app/src/main/res/drawable/bottom_line.xml | 5 - .../drawable/btn_bg_selector_holo_dark.xml | 31 - .../res/drawable/btn_radio_holo_light.xml | 44 - .../main/res/drawable/btn_search_selector.xml | 7 - .../res/drawable/camera_bucket_selected.xml | 5 - ...ml => cat_tabs_rounded_line_indicator.xml} | 39 +- .../res/drawable/custom_tab_indicator.xml | 34 - .../res/drawable/edit_text_holo_light.xml | 25 - app/src/main/res/drawable/et_selector.xml | 8 - .../main/res/drawable/indicator_selector.xml | 13 + .../res/drawable/list_item_background.xml | 5 - app/src/main/res/drawable/list_selector.xml | 16 - .../res/drawable/lv_expandable_divider.xml | 5 - .../main/res/drawable/policy_dialog_style.xml | 5 - app/src/main/res/drawable/shape_divider.xml | 14 - .../shape_radius_solid_orange_dark.xml | 8 - app/src/main/res/drawable/tab_activity.xml | 6 - .../res/drawable/tab_background_drawable.xml | 18 + app/src/main/res/drawable/tab_bg_selector.xml | 5 - app/src/main/res/drawable/tab_library.xml | 6 - app/src/main/res/drawable/tab_starred.xml | 5 - .../list_item_account_entry.xml | 56 +- .../layout-sw600dp/gesturepassword_unlock.xml | 6 - .../list_item_account_entry.xml | 87 +- .../layout-xlarge/list_item_account_entry.xml | 95 +- app/src/main/res/layout-xlarge/start.xml | 48 +- app/src/main/res/layout/account_detail.xml | 94 +- .../main/res/layout/account_list_footer.xml | 13 +- .../main/res/layout/activities_fragment.xml | 64 - .../res/layout/activity_data_migration.xml | 19 + app/src/main/res/layout/activity_main.xml | 34 + app/src/main/res/layout/activity_markdown.xml | 7 +- .../main/res/layout/activity_selector_obj.xml | 67 + app/src/main/res/layout/activity_splash.xml | 11 + .../res/layout/bg_settings_section_about.xml | 10 +- .../layout/bg_settings_section_account.xml | 10 +- .../res/layout/bg_settings_section_cache.xml | 6 +- .../bg_settings_section_camera_advance.xml | 6 +- .../bg_settings_section_camera_upload.xml | 6 +- .../bg_settings_section_contacts_upload.xml | 16 - .../bg_settings_section_folder_backup.xml | 6 +- .../layout/bg_settings_section_security.xml | 6 +- .../res/layout/bottom_sheet_item_grid.xml | 32 + .../res/layout/bottom_sheet_item_list.xml | 30 + app/src/main/res/layout/bucket_item.xml | 26 +- .../main/res/layout/clearable_edit_text.xml | 23 - .../main/res/layout/cuc_activity_layout.xml | 44 +- .../res/layout/cuc_directory_list_item.xml | 73 - .../layout/cuc_local_directory_fragment.xml | 94 +- .../res/layout/cuc_multi_selection_layout.xml | 127 - .../res/layout/cuc_ready_to_scan_fragment.xml | 24 +- .../layout/cuc_remote_library_fragment.xml | 46 - .../layout/custom_ssl_confirm_title_view.xml | 17 - .../layout/dialog_activity_bottom_sheet.xml | 25 + .../main/res/layout/dialog_delete_cache.xml | 18 - .../main/res/layout/dialog_delete_file.xml | 20 - .../res/layout/dialog_delete_password.xml | 18 - .../main/res/layout/dialog_delete_repo.xml | 21 - app/src/main/res/layout/dialog_file_save.xml | 20 - app/src/main/res/layout/dialog_new_dir.xml | 18 - app/src/main/res/layout/dialog_new_repo.xml | 79 - app/src/main/res/layout/dialog_password.xml | 49 - .../main/res/layout/dialog_share_password.xml | 37 - .../layout/folder_backup_activity_layout.xml | 32 +- .../res/layout/folder_selection_fragment.xml | 15 +- app/src/main/res/layout/footer_load_more.xml | 25 - app/src/main/res/layout/fragment_activity.xml | 22 + .../res/layout/fragment_folder_selector.xml | 32 + .../fragment_remote_library_fragment.xml | 72 + app/src/main/res/layout/gallery_item.xml | 18 - .../res/layout/gesturepassword_unlock.xml | 7 +- app/src/main/res/layout/group_item.xml | 332 --- app/src/main/res/layout/item_account.xml | 72 + app/src/main/res/layout/item_activity.xml | 109 + app/src/main/res/layout/item_dirent.xml | 124 + app/src/main/res/layout/item_file_picker.xml | 72 - app/src/main/res/layout/item_files_list.xml | 69 +- app/src/main/res/layout/item_group_item.xml | 24 + app/src/main/res/layout/item_repo.xml | 94 + app/src/main/res/layout/item_starred.xml | 80 + app/src/main/res/layout/item_text_more.xml | 16 +- app/src/main/res/layout/item_unsupported.xml | 16 + .../layout_bottom_sheet_recycler_menu.xml | 33 + ...ialog_button_positive_negative_loading.xml | 38 + .../res/layout/layout_dialog_container.xml | 16 + .../main/res/layout/layout_dialog_policy.xml | 46 + .../layout/layout_exo_play_control_view.xml | 63 - .../main/res/layout/layout_frame_swipe_rv.xml | 18 + .../res/layout/list_item_account_entry.xml | 86 +- .../main/res/layout/list_item_activities.xml | 164 -- app/src/main/res/layout/list_item_diff.xml | 2 +- .../res/layout/list_item_single_choice.xml | 10 - app/src/main/res/layout/policy_dialog.xml | 79 - .../main/res/layout/ppw_history_changes.xml | 33 - .../main/res/layout/pull_to_refresh_head.xml | 76 - app/src/main/res/layout/refresh.xml | 20 - app/src/main/res/layout/repos_fragment.xml | 69 - app/src/main/res/layout/seadroid_main.xml | 26 - .../res/layout/seafile_checkbox_layout.xml | 60 - .../res/layout/seafile_dialog_item_layout.xml | 15 - .../layout/seafile_dialog_items_divider.xml | 16 - .../main/res/layout/seafile_dialog_layout.xml | 1 - .../main/res/layout/seafile_path_chooser.xml | 120 - .../res/layout/settings_activity_layout.xml | 14 +- app/src/main/res/layout/share_to_seafile.xml | 122 - .../layout/single_sign_on_welcome_layout.xml | 12 +- app/src/main/res/layout/starred_fragment.xml | 69 - app/src/main/res/layout/starred_list_item.xml | 66 - .../res/layout/switch_compat_preference.xml | 9 - app/src/main/res/layout/tabs_main.xml | 39 - app/src/main/res/layout/task_dialog.xml | 29 - .../layout/toolbar_action_progress_bar.xml | 12 +- app/src/main/res/layout/toolbar_actionbar.xml | 29 +- .../main/res/layout/transfer_list_layout.xml | 8 +- .../layout/view_dialog_message_textview.xml | 10 + ..._new_file.xml => view_dialog_new_file.xml} | 14 +- .../main/res/layout/view_dialog_new_repo.xml | 57 + .../main/res/layout/view_dialog_password.xml | 28 + .../res/layout/view_dialog_share_password.xml | 45 + ...age.xml => view_dialog_switch_storage.xml} | 4 +- app/src/main/res/layout/view_load_more.xml | 2 +- app/src/main/res/layout/view_tip_textview.xml | 9 + app/src/main/res/menu/bottom_sheet_delete.xml | 7 - .../main/res/menu/bottom_sheet_unstarred.xml | 8 + app/src/main/res/menu/browser_menu.xml | 95 +- app/src/main/res/menu/menu_add_delete.xml | 23 - .../main/res/menu/starred_fragment_menu.xml | 14 - app/src/main/res/menu/transfer_list_menu.xml | 16 +- app/src/main/res/menu/transfer_task_menu.xml | 23 - app/src/main/res/values-de/styles.xml | 20 - app/src/main/res/values-ko/strings.xml | 1 + app/src/main/res/values-night/colors.xml | 68 + app/src/main/res/values-night/styles.xml | 147 + app/src/main/res/values-night/theme.xml | 92 + app/src/main/res/values-sw320dp/styles.xml | 25 - app/src/main/res/values-sw600dp/dimens.xml | 2 +- app/src/main/res/values-sw600dp/styles.xml | 25 - app/src/main/res/values-v21/styles.xml | 62 +- app/src/main/res/values-v23/styles.xml | 66 +- app/src/main/res/values/arrays.xml | 12 + app/src/main/res/values/attrs.xml | 5 - app/src/main/res/values/colors.xml | 32 +- app/src/main/res/values/dimens.xml | 36 +- app/src/main/res/values/strings.xml | 6 + app/src/main/res/values/styles.xml | 215 +- app/src/main/res/values/theme.xml | 105 + app/src/main/res/xml/file_paths.xml | 17 +- app/src/main/res/xml/settings.xml | 1 - build.gradle | 4 +- private_key.pepk | Bin 0 -> 1776 bytes 646 files changed, 18658 insertions(+), 19988 deletions(-) create mode 100644 app/src/main/java/com/seafile/seadroid2/App.java create mode 100644 app/src/main/java/com/seafile/seadroid2/SeadroidViewModel.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/account/AccountDBHelper.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/account/AccountManager.java create mode 100644 app/src/main/java/com/seafile/seadroid2/account/SupportDataManager.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/avatar/AuthImageDownloader.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/avatar/Avatar.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/avatar/AvatarDBHelper.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/avatar/AvatarManager.java create mode 100644 app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/ActionMenu.java create mode 100644 app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/ActionMenuItem.java create mode 100644 app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/BottomSheetHelper.java create mode 100644 app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/BottomSheetMenuAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/BottomSheetMenuFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/OnMenuClickListener.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/cameraupload/BucketsSelectionFragment.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadConfigActivity.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryAccountAdapter.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryAdapter.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryFragment.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibrarySelectionFragment.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/cameraupload/HowToUploadFragment.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/cameraupload/MediaSchedulerService.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/cameraupload/ReadyToScanFragment.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/cameraupload/WhatToUploadFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/config/AbsLayoutItemType.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/data/ContactsData.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/data/SeafGroup.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/AppDatabase.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/dao/CertCacheDAO.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/dao/DirentDAO.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/dao/DirentsCacheDAO.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/dao/EncKeyCacheDAO.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/dao/FileCacheDAO.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/dao/FolderBackupCacheDAO.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/dao/FolderBackupMonitorDAO.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/dao/ObjsDAO.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/dao/PhotoCacheDAO.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/dao/RepoDAO.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/dao/RepoDirDAO.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/dao/StarredFileCacheDAO.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/entities/CertEntity.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/entities/DirentModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/entities/DirentsCacheEntity.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/entities/EncKeyCacheEntity.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/entities/FileCacheEntity.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/entities/FolderBackupCacheEntity.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/entities/FolderBackupMonitorEntity.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/entities/ObjsModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/entities/PhotoCacheEntity.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/entities/RepoDirEntity.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/entities/RepoModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/db/entities/StarredFileCacheEntity.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/model/BaseModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/model/GroupItemModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/model/ResultModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/model/activities/ActivityModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/model/activities/ActivityWrapperModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/model/dirents/DeleteDirentModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/model/dirents/FileCreateModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/model/repo/DirentMiniModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/model/repo/DirentWrapperModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/model/repo/RepoWrapperModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/model/repo/deserializer/EncryptFieldJsonAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/model/server/ServerInfoModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/model/star/StarredModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/model/star/StarredWrapperModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/remote/api/AccountService.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/remote/api/ActivityService.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/remote/api/MainService.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/remote/api/RepoService.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/remote/api/StarredService.java create mode 100644 app/src/main/java/com/seafile/seadroid2/data/repository/DirentsRepository.java create mode 100644 app/src/main/java/com/seafile/seadroid2/enums/FileType.java create mode 100644 app/src/main/java/com/seafile/seadroid2/enums/OpType.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/fileschooser/FileFooterFragment.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/fileschooser/FileListAdapter.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/fileschooser/FileListFragment.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/fileschooser/FileLoader.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/fileschooser/MultiFileChooserActivity.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/fileschooser/SelectableFile.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/folderbackup/CloudLibraryChooserFragment.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupConfigActivity.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupSelectedPathActivity.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileListAdapter.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/OnFileItemClickListener.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/SelectBackupFolderFragment.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/SelectOptions.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/StringTools.java create mode 100644 app/src/main/java/com/seafile/seadroid2/io/http/BaseIO.java create mode 100644 app/src/main/java/com/seafile/seadroid2/io/http/IO.java create mode 100644 app/src/main/java/com/seafile/seadroid2/io/http/converter/ConverterFactory.java create mode 100644 app/src/main/java/com/seafile/seadroid2/io/http/converter/GsonRequestBodyConverter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/io/http/converter/SupportResponseConverter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/io/http/interceptor/AddCookiesInterceptor.java create mode 100644 app/src/main/java/com/seafile/seadroid2/io/http/interceptor/HeaderInterceptor.java create mode 100644 app/src/main/java/com/seafile/seadroid2/io/http/interceptor/ParamsInterceptor.java create mode 100644 app/src/main/java/com/seafile/seadroid2/io/http/interceptor/ReceivedCookiesInterceptor.java create mode 100644 app/src/main/java/com/seafile/seadroid2/listener/OnFileItemChangeListener.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/task/StarItemsTask.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/BrowserActivity.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/EmailAutoCompleteTextView.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/SplashActivity.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/account/adapter/SeafAccountAdapter.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/activities/ActivitiesFragment.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/activities/ActivitiesItemAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/activities/ActivityAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/activities/ActivityContainerFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/activities/ActivityViewHolder.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/activities/ActivityViewModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/activities/AllActivitiesFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/activities/MineActivitiesFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/activities/bottomsheet/ActivityBottomSheetAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/activities/bottomsheet/ActivityBottomSheetDialogFragment.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/activity/SeafilePathChooserActivity.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/adapter/BottomSheetAdapter.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafItemAdapter.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafItemCheckableAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/adapter/ViewPager2Adapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/adapter/ViewPagerAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/base/BaseQuickActivity.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/base/adapter/BaseAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/base/adapter/BaseMultiAdapter.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/base/adapter/ParentAdapter.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/base/adapter/ParentMultiAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/base/fragment/BaseDialogFragmentWithVM.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/base/fragment/BaseFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/base/fragment/BaseFragmentWithVM.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/base/fragment/CustomDialogFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/base/fragment/RequestCustomDialogFragmentWithVM.java rename app/src/main/java/com/seafile/seadroid2/ui/base/{adapter => viewholder}/BaseViewHolder.java (62%) create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/base/viewmodel/BaseViewModel.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/bottomsheet/BaseBottomSheetDialogFragment.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/bottomsheet/BottomSheetListFragment.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/bottomsheet/BottomSheetTextFragment.java rename app/src/main/java/com/seafile/seadroid2/{cameraupload => ui/camera_upload}/CameraSyncAdapter.java (98%) rename app/src/main/java/com/seafile/seadroid2/{cameraupload => ui/camera_upload}/CameraSyncService.java (86%) create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadConfigActivity.java rename app/src/main/java/com/seafile/seadroid2/{cameraupload => ui/camera_upload}/CameraUploadDBHelper.java (98%) rename app/src/main/java/com/seafile/seadroid2/{cameraupload => ui/camera_upload}/CameraUploadManager.java (83%) rename app/src/main/java/com/seafile/seadroid2/{cameraupload => ui/camera_upload}/GalleryBucketUtils.java (77%) rename app/src/main/java/com/seafile/seadroid2/{cameraupload => ui/camera_upload}/MediaObserverService.java (71%) create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/camera_upload/MediaSchedulerService.java rename app/src/main/java/com/seafile/seadroid2/{cameraupload => ui/camera_upload}/StubContentProvider.java (96%) rename app/src/main/java/com/seafile/seadroid2/{cameraupload => ui/camera_upload/config_fragment}/BucketsFragment.java (62%) rename app/src/main/java/com/seafile/seadroid2/{cameraupload => ui/camera_upload/config_fragment}/ConfigWelcomeFragment.java (71%) create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/HowToUploadFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/ReadyToScanFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/WhatToUploadFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/data_migrate/DataMigrationActivity.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog/ClearCacheTaskDialog.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog/ClearPasswordTaskDialog.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog/DeleteFileDialog.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog/DeleteRepoDialog.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog/GetShareLinkEncryptDialog.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog/NewDirDialog.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog/NewFileDialog.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog/NewRepoDialog.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog/PasswordDialog.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog/PolicyDialog.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog/RenameFileDialog.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog/RenameRepoDialog.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog/SortFilesDialogFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/ClearCacheDialogFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/ClearPasswordDialogFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/CopyMoveDialogFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/DeleteFileDialogFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/DeleteRepoDialogFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/DialogService.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/GetShareLinkPasswordDialogFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/NewDirFileDialogFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/NewRepoDialogFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/PasswordDialogFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/PolicyDialogFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/RenameDialogFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SignOutDialogFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SortFilesDialogFragment.java rename app/src/main/java/com/seafile/seadroid2/ui/{dialog/SwitchStorageTaskDialog.java => dialog_fragment/SwitchStorageDialogFragment.java} (51%) create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/listener/OnRefreshDataListener.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/listener/OnSortItemClickListener.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/ClearCacheViewModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/CopyMoveViewModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/DeleteDirentsViewModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/DeleteRepoViewModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/GetShareLinkPasswordViewModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/NewDirViewModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/NewRepoViewModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/PasswordViewModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/RenameRepoViewModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupConfigActivity.java rename app/src/main/java/com/seafile/seadroid2/{folderbackup => ui/folder_backup}/FolderBackupDBHelper.java (99%) rename app/src/main/java/com/seafile/seadroid2/{folderbackup => ui/folder_backup}/FolderBackupEvent.java (82%) rename app/src/main/java/com/seafile/seadroid2/{folderbackup => ui/folder_backup}/FolderBackupInfo.java (97%) create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupSelectedPathActivity.java rename app/src/main/java/com/seafile/seadroid2/{folderbackup/FolderBackSelectedPathRecyclerViewAdapter.java => ui/folder_backup/FolderBackupSelectedPathAdapter.java} (69%) rename app/src/main/java/com/seafile/seadroid2/{folderbackup => ui/folder_backup}/FolderBackupService.java (91%) rename app/src/main/java/com/seafile/seadroid2/{folderbackup => ui/folder_backup}/RepoConfig.java (94%) create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/main/MainActivity.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/main/MainViewModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/repo/AccountViewHolder.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/repo/DirentViewHolder.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewHolder.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/repo/ReposFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/repo/ScrollState.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/repo/UnsupportedViewHolder.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorActivity.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorViewModel.java rename app/src/main/java/com/seafile/seadroid2/{folderbackup/selectfolder => ui/selector/folder_selector}/BeanListManager.java (62%) rename app/src/main/java/com/seafile/seadroid2/{folderbackup/selectfolder => ui/selector/folder_selector}/Constants.java (98%) rename app/src/main/java/com/seafile/seadroid2/{folderbackup/selectfolder => ui/selector/folder_selector}/FileBean.java (90%) create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FileListAdapter.java rename app/src/main/java/com/seafile/seadroid2/{folderbackup/selectfolder => ui/selector/folder_selector}/FileListViewHolder.java (68%) create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FolderSelectorFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FolderSelectorViewModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/StringTools.java rename app/src/main/java/com/seafile/seadroid2/{folderbackup/selectfolder => ui/selector/folder_selector}/TabBarFileBean.java (91%) rename app/src/main/java/com/seafile/seadroid2/{folderbackup/selectfolder => ui/selector/folder_selector}/TabBarFileListAdapter.java (75%) rename app/src/main/java/com/seafile/seadroid2/{folderbackup/selectfolder => ui/selector/folder_selector}/TabbarFileViewHolder.java (90%) delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/settings/PrivacyPolicyActivity.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsActivityViewModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsFragmentViewModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/star/StarredAdapter.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/star/StarredFragment.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/star/StarredItemAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/star/StarredQuickFragment.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/star/StarredViewHolder.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/star/StarredViewModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/viewholder/GroupItemViewHolder.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/windowpreferences/WindowPreferencesManager.java rename app/src/main/java/com/seafile/seadroid2/{folderbackup/selectfolder => util}/FileTools.java (93%) create mode 100644 app/src/main/java/com/seafile/seadroid2/util/TUtil.java create mode 100644 app/src/main/java/com/seafile/seadroid2/util/TakeCameras.java create mode 100644 app/src/main/java/com/seafile/seadroid2/util/Times.java create mode 100644 app/src/main/java/com/seafile/seadroid2/util/sp/AppSPs.java create mode 100644 app/src/main/java/com/seafile/seadroid2/util/sp/FolderBackupConfigSPs.java create mode 100644 app/src/main/java/com/seafile/seadroid2/util/sp/SPs.java rename app/src/main/java/com/seafile/seadroid2/{ => util/sp}/SettingsManager.java (68%) create mode 100644 app/src/main/java/com/seafile/seadroid2/util/sp/Sorts.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/view/CustomClearableEditText.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/LinearRecyclerView.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/ListPreferenceCompat.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/TipsViews.java delete mode 100644 app/src/main/res/anim/bs_list_layout_fade_in.xml delete mode 100644 app/src/main/res/anim/pull_to_refresh_header_loading.xml delete mode 100644 app/src/main/res/drawable-hdpi/action_more.png delete mode 100644 app/src/main/res/drawable-hdpi/action_remove_cache.png delete mode 100644 app/src/main/res/drawable-hdpi/action_share_password.png delete mode 100644 app/src/main/res/drawable-hdpi/action_star.png delete mode 100755 app/src/main/res/drawable-hdpi/actionbar_background.9.png delete mode 100644 app/src/main/res/drawable-hdpi/activity_normal.png delete mode 100644 app/src/main/res/drawable-hdpi/activity_selected.png delete mode 100644 app/src/main/res/drawable-hdpi/btn_code_lock_default_holo.png delete mode 100644 app/src/main/res/drawable-hdpi/btn_code_lock_touched_holo.png delete mode 100644 app/src/main/res/drawable-hdpi/btn_search_icon.png delete mode 100644 app/src/main/res/drawable-hdpi/card_avatar.9.png delete mode 100644 app/src/main/res/drawable-hdpi/custom_tab_indicator_divider.9.png delete mode 100644 app/src/main/res/drawable-hdpi/custom_tab_indicator_selected.9.png delete mode 100644 app/src/main/res/drawable-hdpi/custom_tab_indicator_selected_focused.9.png delete mode 100644 app/src/main/res/drawable-hdpi/custom_tab_indicator_selected_pressed.9.png delete mode 100644 app/src/main/res/drawable-hdpi/custom_tab_indicator_unselected.9.png delete mode 100644 app/src/main/res/drawable-hdpi/custom_tab_indicator_unselected_focused.9.png delete mode 100644 app/src/main/res/drawable-hdpi/custom_tab_indicator_unselected_pressed.9.png delete mode 100755 app/src/main/res/drawable-hdpi/dialog_textfield_activated_holo_light.9.png delete mode 100755 app/src/main/res/drawable-hdpi/dialog_textfield_default_holo_light.9.png delete mode 100755 app/src/main/res/drawable-hdpi/dialog_textfield_disabled_focused_holo_light.9.png delete mode 100755 app/src/main/res/drawable-hdpi/dialog_textfield_disabled_holo_light.9.png delete mode 100755 app/src/main/res/drawable-hdpi/dialog_textfield_focused_holo_light.9.png delete mode 100644 app/src/main/res/drawable-hdpi/et_normal.9.png delete mode 100644 app/src/main/res/drawable-hdpi/et_pressed.9.png delete mode 100755 app/src/main/res/drawable-hdpi/fancy_orange_text_select_handle_left.png delete mode 100755 app/src/main/res/drawable-hdpi/fancy_orange_text_select_handle_middle.png delete mode 100755 app/src/main/res/drawable-hdpi/fancy_orange_text_select_handle_right.png delete mode 100644 app/src/main/res/drawable-hdpi/gallery_loading_failed.png delete mode 100644 app/src/main/res/drawable-hdpi/indicator_code_lock_drag_direction_red_up.png delete mode 100644 app/src/main/res/drawable-hdpi/indicator_code_lock_point_area_default_holo.png delete mode 100644 app/src/main/res/drawable-hdpi/indicator_code_lock_point_area_green_holo.png delete mode 100644 app/src/main/res/drawable-hdpi/indicator_code_lock_point_area_red_holo.png delete mode 100644 app/src/main/res/drawable-hdpi/library_normal.png delete mode 100644 app/src/main/res/drawable-hdpi/library_selected.png delete mode 100644 app/src/main/res/drawable-hdpi/list_item_download_waiting.png delete mode 100644 app/src/main/res/drawable-hdpi/loading.png delete mode 100644 app/src/main/res/drawable-hdpi/loading_1.png delete mode 100644 app/src/main/res/drawable-hdpi/loading_2.png delete mode 100644 app/src/main/res/drawable-hdpi/loading_3.png delete mode 100644 app/src/main/res/drawable-hdpi/loading_4.png delete mode 100644 app/src/main/res/drawable-hdpi/operation_button_cancel.png delete mode 100755 app/src/main/res/drawable-hdpi/operation_button_delete.png delete mode 100755 app/src/main/res/drawable-hdpi/operation_button_selectall.png delete mode 100644 app/src/main/res/drawable-hdpi/pulltorefresh_arrow.png delete mode 100644 app/src/main/res/drawable-hdpi/search_button.png delete mode 100644 app/src/main/res/drawable-hdpi/search_button_clicked.png delete mode 100644 app/src/main/res/drawable-hdpi/select_all.png delete mode 100644 app/src/main/res/drawable-hdpi/star_selected.png delete mode 100644 app/src/main/res/drawable-hdpi/upload.png delete mode 100644 app/src/main/res/drawable-hdpi/upload_disabled.png delete mode 100644 app/src/main/res/drawable-ldpi/action_more.png delete mode 100644 app/src/main/res/drawable-ldpi/action_remove_cache.png delete mode 100644 app/src/main/res/drawable-ldpi/action_share_password.png delete mode 100644 app/src/main/res/drawable-ldpi/action_star.png delete mode 100644 app/src/main/res/drawable-ldpi/activity_normal.png delete mode 100644 app/src/main/res/drawable-ldpi/activity_selected.png delete mode 100644 app/src/main/res/drawable-ldpi/library_normal.png delete mode 100644 app/src/main/res/drawable-ldpi/library_selected.png delete mode 100644 app/src/main/res/drawable-ldpi/loading_1.png delete mode 100644 app/src/main/res/drawable-ldpi/loading_2.png delete mode 100644 app/src/main/res/drawable-ldpi/loading_3.png delete mode 100644 app/src/main/res/drawable-ldpi/loading_4.png delete mode 100644 app/src/main/res/drawable-ldpi/pulltorefresh_arrow.png delete mode 100644 app/src/main/res/drawable-ldpi/select_all.png delete mode 100644 app/src/main/res/drawable-ldpi/star_selected.png delete mode 100644 app/src/main/res/drawable-ldpi/upload.png delete mode 100644 app/src/main/res/drawable-ldpi/upload_disabled.png delete mode 100644 app/src/main/res/drawable-mdpi/account.png delete mode 100644 app/src/main/res/drawable-mdpi/action_more.png delete mode 100644 app/src/main/res/drawable-mdpi/action_remove_cache.png delete mode 100644 app/src/main/res/drawable-mdpi/action_share_password.png delete mode 100644 app/src/main/res/drawable-mdpi/action_star.png delete mode 100755 app/src/main/res/drawable-mdpi/actionbar_background.9.png delete mode 100644 app/src/main/res/drawable-mdpi/activity_normal.png delete mode 100644 app/src/main/res/drawable-mdpi/activity_selected.png delete mode 100755 app/src/main/res/drawable-mdpi/dialog_textfield_activated_holo_light.9.png delete mode 100755 app/src/main/res/drawable-mdpi/dialog_textfield_default_holo_light.9.png delete mode 100755 app/src/main/res/drawable-mdpi/dialog_textfield_disabled_focused_holo_light.9.png delete mode 100755 app/src/main/res/drawable-mdpi/dialog_textfield_disabled_holo_light.9.png delete mode 100755 app/src/main/res/drawable-mdpi/dialog_textfield_focused_holo_light.9.png delete mode 100644 app/src/main/res/drawable-mdpi/drop_down_button.png delete mode 100755 app/src/main/res/drawable-mdpi/fancy_orange_text_select_handle_left.png delete mode 100755 app/src/main/res/drawable-mdpi/fancy_orange_text_select_handle_middle.png delete mode 100755 app/src/main/res/drawable-mdpi/fancy_orange_text_select_handle_right.png delete mode 100644 app/src/main/res/drawable-mdpi/library_normal.png delete mode 100644 app/src/main/res/drawable-mdpi/library_selected.png delete mode 100644 app/src/main/res/drawable-mdpi/list_item_download_waiting.png delete mode 100644 app/src/main/res/drawable-mdpi/loading_1.png delete mode 100644 app/src/main/res/drawable-mdpi/loading_2.png delete mode 100644 app/src/main/res/drawable-mdpi/loading_3.png delete mode 100644 app/src/main/res/drawable-mdpi/loading_4.png delete mode 100644 app/src/main/res/drawable-mdpi/operation_button_cancel.png delete mode 100644 app/src/main/res/drawable-mdpi/pulltorefresh_arrow.png delete mode 100644 app/src/main/res/drawable-mdpi/select_all.png delete mode 100644 app/src/main/res/drawable-mdpi/star_selected.png delete mode 100644 app/src/main/res/drawable-mdpi/upload.png delete mode 100644 app/src/main/res/drawable-mdpi/upload_disabled.png delete mode 100644 app/src/main/res/drawable-nodpi/action_remove_cache.png delete mode 100644 app/src/main/res/drawable-nodpi/action_share_password.png delete mode 100644 app/src/main/res/drawable-xhdpi/action_more.png delete mode 100644 app/src/main/res/drawable-xhdpi/action_remove_cache.png delete mode 100644 app/src/main/res/drawable-xhdpi/action_share_password.png delete mode 100644 app/src/main/res/drawable-xhdpi/action_star.png delete mode 100755 app/src/main/res/drawable-xhdpi/actionbar_background.9.png delete mode 100644 app/src/main/res/drawable-xhdpi/activity_normal.png delete mode 100644 app/src/main/res/drawable-xhdpi/activity_selected.png delete mode 100755 app/src/main/res/drawable-xhdpi/btn_radio_off_disabled_focused_holo_light.png delete mode 100755 app/src/main/res/drawable-xhdpi/btn_radio_off_disabled_holo_light.png delete mode 100755 app/src/main/res/drawable-xhdpi/btn_radio_off_focused_holo_light.png delete mode 100755 app/src/main/res/drawable-xhdpi/btn_radio_off_holo_light.png delete mode 100755 app/src/main/res/drawable-xhdpi/btn_radio_off_pressed_holo_light.png delete mode 100755 app/src/main/res/drawable-xhdpi/btn_radio_on_disabled_focused_holo_light.png delete mode 100755 app/src/main/res/drawable-xhdpi/btn_radio_on_disabled_holo_light.png delete mode 100755 app/src/main/res/drawable-xhdpi/btn_radio_on_focused_holo_light.png delete mode 100755 app/src/main/res/drawable-xhdpi/btn_radio_on_holo_light.png delete mode 100755 app/src/main/res/drawable-xhdpi/btn_radio_on_pressed_holo_light.png delete mode 100755 app/src/main/res/drawable-xhdpi/dialog_textfield_activated_holo_light.9.png delete mode 100755 app/src/main/res/drawable-xhdpi/dialog_textfield_default_holo_light.9.png delete mode 100755 app/src/main/res/drawable-xhdpi/dialog_textfield_disabled_focused_holo_light.9.png delete mode 100755 app/src/main/res/drawable-xhdpi/dialog_textfield_disabled_holo_light.9.png delete mode 100755 app/src/main/res/drawable-xhdpi/dialog_textfield_focused_holo_light.9.png delete mode 100755 app/src/main/res/drawable-xhdpi/fancy_orange_text_select_handle_left.png delete mode 100755 app/src/main/res/drawable-xhdpi/fancy_orange_text_select_handle_middle.png delete mode 100755 app/src/main/res/drawable-xhdpi/fancy_orange_text_select_handle_right.png delete mode 100644 app/src/main/res/drawable-xhdpi/home_up_btn.png delete mode 100644 app/src/main/res/drawable-xhdpi/library_normal.png delete mode 100644 app/src/main/res/drawable-xhdpi/library_selected.png delete mode 100644 app/src/main/res/drawable-xhdpi/list_item_download_waiting.png delete mode 100644 app/src/main/res/drawable-xhdpi/loading_1.png delete mode 100644 app/src/main/res/drawable-xhdpi/loading_2.png delete mode 100644 app/src/main/res/drawable-xhdpi/loading_3.png delete mode 100644 app/src/main/res/drawable-xhdpi/loading_4.png delete mode 100644 app/src/main/res/drawable-xhdpi/operation_button_cancel.png delete mode 100644 app/src/main/res/drawable-xhdpi/pulltorefresh_arrow.png delete mode 100644 app/src/main/res/drawable-xhdpi/scrollbar_handle_vertical.9.png delete mode 100644 app/src/main/res/drawable-xhdpi/select_all.png delete mode 100644 app/src/main/res/drawable-xhdpi/star_selected.png delete mode 100644 app/src/main/res/drawable-xhdpi/upload.png delete mode 100644 app/src/main/res/drawable-xhdpi/upload_disabled.png delete mode 100644 app/src/main/res/drawable-xxhdpi/action_more.png delete mode 100644 app/src/main/res/drawable-xxhdpi/action_remove_cache.png delete mode 100644 app/src/main/res/drawable-xxhdpi/action_share_password.png delete mode 100644 app/src/main/res/drawable-xxhdpi/action_star.png delete mode 100755 app/src/main/res/drawable-xxhdpi/actionbar_background.9.png delete mode 100644 app/src/main/res/drawable-xxhdpi/activity_normal.png delete mode 100644 app/src/main/res/drawable-xxhdpi/activity_selected.png delete mode 100755 app/src/main/res/drawable-xxhdpi/dialog_textfield_activated_holo_light.9.png delete mode 100755 app/src/main/res/drawable-xxhdpi/dialog_textfield_default_holo_light.9.png delete mode 100755 app/src/main/res/drawable-xxhdpi/dialog_textfield_disabled_focused_holo_light.9.png delete mode 100755 app/src/main/res/drawable-xxhdpi/dialog_textfield_disabled_holo_light.9.png delete mode 100755 app/src/main/res/drawable-xxhdpi/dialog_textfield_focused_holo_light.9.png delete mode 100755 app/src/main/res/drawable-xxhdpi/fancy_orange_text_select_handle_left.png delete mode 100755 app/src/main/res/drawable-xxhdpi/fancy_orange_text_select_handle_middle.png delete mode 100755 app/src/main/res/drawable-xxhdpi/fancy_orange_text_select_handle_right.png delete mode 100644 app/src/main/res/drawable-xxhdpi/home_up_btn.png delete mode 100644 app/src/main/res/drawable-xxhdpi/library_normal.png delete mode 100644 app/src/main/res/drawable-xxhdpi/library_selected.png delete mode 100644 app/src/main/res/drawable-xxhdpi/list_item_download_waiting.png delete mode 100644 app/src/main/res/drawable-xxhdpi/loading_1.png delete mode 100644 app/src/main/res/drawable-xxhdpi/loading_2.png delete mode 100644 app/src/main/res/drawable-xxhdpi/loading_3.png delete mode 100644 app/src/main/res/drawable-xxhdpi/loading_4.png delete mode 100644 app/src/main/res/drawable-xxhdpi/operation_button_cancel.png delete mode 100644 app/src/main/res/drawable-xxhdpi/pulltorefresh_arrow.png delete mode 100644 app/src/main/res/drawable-xxhdpi/select_all.png delete mode 100644 app/src/main/res/drawable-xxhdpi/star_selected.png delete mode 100644 app/src/main/res/drawable-xxhdpi/upload.png delete mode 100644 app/src/main/res/drawable-xxhdpi/upload_disabled.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/default_account.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/home_up_btn.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/pulltorefresh_arrow.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/select_all.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/upload.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/upload_disabled.png delete mode 100644 app/src/main/res/drawable/ab_upload.xml delete mode 100644 app/src/main/res/drawable/actionbar_space_between_icon_and_title.xml delete mode 100644 app/src/main/res/drawable/activities_text_bg.xml create mode 100644 app/src/main/res/drawable/baseline_star_24.xml delete mode 100644 app/src/main/res/drawable/bottom_line.xml delete mode 100644 app/src/main/res/drawable/btn_bg_selector_holo_dark.xml delete mode 100755 app/src/main/res/drawable/btn_radio_holo_light.xml delete mode 100644 app/src/main/res/drawable/btn_search_selector.xml delete mode 100644 app/src/main/res/drawable/camera_bucket_selected.xml rename app/src/main/res/drawable/{progressloading.xml => cat_tabs_rounded_line_indicator.xml} (53%) delete mode 100644 app/src/main/res/drawable/custom_tab_indicator.xml delete mode 100644 app/src/main/res/drawable/edit_text_holo_light.xml delete mode 100644 app/src/main/res/drawable/et_selector.xml create mode 100644 app/src/main/res/drawable/indicator_selector.xml delete mode 100644 app/src/main/res/drawable/list_item_background.xml delete mode 100644 app/src/main/res/drawable/list_selector.xml delete mode 100644 app/src/main/res/drawable/lv_expandable_divider.xml delete mode 100644 app/src/main/res/drawable/policy_dialog_style.xml delete mode 100644 app/src/main/res/drawable/shape_divider.xml delete mode 100644 app/src/main/res/drawable/shape_radius_solid_orange_dark.xml delete mode 100644 app/src/main/res/drawable/tab_activity.xml create mode 100644 app/src/main/res/drawable/tab_background_drawable.xml delete mode 100644 app/src/main/res/drawable/tab_bg_selector.xml delete mode 100644 app/src/main/res/drawable/tab_library.xml delete mode 100644 app/src/main/res/drawable/tab_starred.xml delete mode 100644 app/src/main/res/layout/activities_fragment.xml create mode 100644 app/src/main/res/layout/activity_data_migration.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/activity_selector_obj.xml create mode 100644 app/src/main/res/layout/activity_splash.xml delete mode 100644 app/src/main/res/layout/bg_settings_section_contacts_upload.xml create mode 100644 app/src/main/res/layout/bottom_sheet_item_grid.xml create mode 100644 app/src/main/res/layout/bottom_sheet_item_list.xml delete mode 100644 app/src/main/res/layout/clearable_edit_text.xml delete mode 100644 app/src/main/res/layout/cuc_directory_list_item.xml delete mode 100644 app/src/main/res/layout/cuc_multi_selection_layout.xml delete mode 100644 app/src/main/res/layout/cuc_remote_library_fragment.xml delete mode 100644 app/src/main/res/layout/custom_ssl_confirm_title_view.xml create mode 100644 app/src/main/res/layout/dialog_activity_bottom_sheet.xml delete mode 100644 app/src/main/res/layout/dialog_delete_cache.xml delete mode 100644 app/src/main/res/layout/dialog_delete_file.xml delete mode 100644 app/src/main/res/layout/dialog_delete_password.xml delete mode 100644 app/src/main/res/layout/dialog_delete_repo.xml delete mode 100644 app/src/main/res/layout/dialog_file_save.xml delete mode 100644 app/src/main/res/layout/dialog_new_dir.xml delete mode 100644 app/src/main/res/layout/dialog_new_repo.xml delete mode 100644 app/src/main/res/layout/dialog_password.xml delete mode 100644 app/src/main/res/layout/dialog_share_password.xml delete mode 100644 app/src/main/res/layout/footer_load_more.xml create mode 100644 app/src/main/res/layout/fragment_activity.xml create mode 100644 app/src/main/res/layout/fragment_folder_selector.xml create mode 100644 app/src/main/res/layout/fragment_remote_library_fragment.xml delete mode 100644 app/src/main/res/layout/gallery_item.xml delete mode 100644 app/src/main/res/layout/group_item.xml create mode 100644 app/src/main/res/layout/item_account.xml create mode 100644 app/src/main/res/layout/item_activity.xml create mode 100644 app/src/main/res/layout/item_dirent.xml delete mode 100644 app/src/main/res/layout/item_file_picker.xml create mode 100644 app/src/main/res/layout/item_group_item.xml create mode 100644 app/src/main/res/layout/item_repo.xml create mode 100644 app/src/main/res/layout/item_starred.xml create mode 100644 app/src/main/res/layout/item_unsupported.xml create mode 100644 app/src/main/res/layout/layout_bottom_sheet_recycler_menu.xml create mode 100644 app/src/main/res/layout/layout_dialog_button_positive_negative_loading.xml create mode 100644 app/src/main/res/layout/layout_dialog_container.xml create mode 100644 app/src/main/res/layout/layout_dialog_policy.xml delete mode 100644 app/src/main/res/layout/layout_exo_play_control_view.xml create mode 100644 app/src/main/res/layout/layout_frame_swipe_rv.xml delete mode 100644 app/src/main/res/layout/list_item_activities.xml delete mode 100644 app/src/main/res/layout/list_item_single_choice.xml delete mode 100644 app/src/main/res/layout/policy_dialog.xml delete mode 100644 app/src/main/res/layout/ppw_history_changes.xml delete mode 100644 app/src/main/res/layout/pull_to_refresh_head.xml delete mode 100644 app/src/main/res/layout/refresh.xml delete mode 100644 app/src/main/res/layout/repos_fragment.xml delete mode 100644 app/src/main/res/layout/seadroid_main.xml delete mode 100644 app/src/main/res/layout/seafile_checkbox_layout.xml delete mode 100644 app/src/main/res/layout/seafile_dialog_item_layout.xml delete mode 100644 app/src/main/res/layout/seafile_dialog_items_divider.xml delete mode 100644 app/src/main/res/layout/seafile_path_chooser.xml delete mode 100644 app/src/main/res/layout/share_to_seafile.xml delete mode 100644 app/src/main/res/layout/starred_fragment.xml delete mode 100644 app/src/main/res/layout/starred_list_item.xml delete mode 100644 app/src/main/res/layout/switch_compat_preference.xml delete mode 100644 app/src/main/res/layout/tabs_main.xml delete mode 100644 app/src/main/res/layout/task_dialog.xml create mode 100644 app/src/main/res/layout/view_dialog_message_textview.xml rename app/src/main/res/layout/{dialog_new_file.xml => view_dialog_new_file.xml} (52%) create mode 100644 app/src/main/res/layout/view_dialog_new_repo.xml create mode 100644 app/src/main/res/layout/view_dialog_password.xml create mode 100644 app/src/main/res/layout/view_dialog_share_password.xml rename app/src/main/res/layout/{dialog_switch_storage.xml => view_dialog_switch_storage.xml} (86%) create mode 100644 app/src/main/res/layout/view_tip_textview.xml delete mode 100644 app/src/main/res/menu/bottom_sheet_delete.xml create mode 100644 app/src/main/res/menu/bottom_sheet_unstarred.xml delete mode 100644 app/src/main/res/menu/menu_add_delete.xml delete mode 100644 app/src/main/res/menu/starred_fragment_menu.xml delete mode 100644 app/src/main/res/menu/transfer_task_menu.xml create mode 100644 app/src/main/res/values-night/colors.xml create mode 100644 app/src/main/res/values-night/styles.xml create mode 100644 app/src/main/res/values-night/theme.xml create mode 100644 app/src/main/res/values/theme.xml create mode 100644 private_key.pepk diff --git a/app/build.gradle b/app/build.gradle index dc70f2b47..055c733d7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,12 +7,14 @@ android { applicationId 'com.seafile.seadroid2' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 140 - versionName "2.3.7" + versionCode 150 + versionName "3.0.0" multiDexEnabled true resValue "string", "authorities", applicationId + '.cameraupload.provider' - resValue "string", "account_type", "com.seafile.seadroid2.account.api2" + resValue "string", "account_type", applicationId + ".account.api2" buildConfigField "String", "ACCOUNT_TYPE", '"com.seafile.seadroid2.account.api2"' + buildConfigField "String", "FILE_PROVIDER_AUTHORITIES", '"com.seafile.seadroid2.fileprovider"' + } lintOptions { @@ -62,8 +64,10 @@ android { debuggable true applicationIdSuffix ".debug" resValue "string", "authorities", defaultConfig.applicationId + '.debug.cameraupload.provider' - resValue "string", "account_type", "com.seafile.seadroid2.debug.account.api2" - buildConfigField "String", "ACCOUNT_TYPE", '"com.seafile.seadroid2.debug.account.api2"' + resValue "string", "account_type", defaultConfig.applicationId + ".debug.account.api2" + buildConfigField "String", "ACCOUNT_TYPE", '"' + defaultConfig.applicationId + '.debug.account.api2"' + buildConfigField "String", "FILE_PROVIDER_AUTHORITIES", '"' + defaultConfig.applicationId + '.debug.fileprovider"' + signingConfig signingConfigs.debug minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' @@ -104,23 +108,31 @@ android { } } + buildFeatures { + viewBinding true + } + allprojects { repositories { maven { url 'https://jitpack.io' } } } + dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.blankj:utilcode:1.30.7' - implementation "io.github.cymchad:BaseRecyclerViewAdapterHelper:4.0.0-beta14" + //https://github.com/CymChad/BaseRecyclerViewAdapterHelper + implementation "io.github.cymchad:BaseRecyclerViewAdapterHelper4:4.1.2" + //media3 final def media3_version = '1.1.0' implementation "androidx.media3:media3-exoplayer:$media3_version" implementation "androidx.media3:media3-ui:$media3_version" - implementation "androidx.appcompat:appcompat:1.3.0" + implementation 'androidx.core:core-splashscreen:1.0.1' + implementation "androidx.appcompat:appcompat:1.6.1" implementation "androidx.activity:activity:1.7.2" implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' @@ -143,29 +155,39 @@ android { implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit_version" implementation 'com.squareup.okhttp3:okhttp:3.9.1' + implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1' + //room + def room_version = "2.5.2" + implementation "androidx.room:room-runtime:$room_version" + implementation "androidx.room:room-rxjava2:$room_version" + annotationProcessor "androidx.room:room-compiler:$room_version" + + //https://github.com/google/guava + implementation "com.google.guava:guava:32.1.2-android" //gson - implementation 'com.google.code.gson:gson:2.10' + implementation 'com.google.code.gson:gson:2.10.1' + //https://github.com/apache/commons-io + implementation 'commons-io:commons-io:2.13.0' + //https://github.com/apache/commons-lang + implementation 'com.github.apache:commons-lang:rel~commons-lang-3.12.0' - //rxjava - implementation 'io.reactivex.rxjava3:rxjava:3.1.5' - implementation 'io.reactivex.rxjava3:rxandroid:3.0.2' + //Rxjava + implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' + implementation 'io.reactivex.rxjava2:rxjava:2.2.10' //https://github.com/elvishew/xLog implementation 'com.elvishew:xlog:1.10.1' +// //https://github.com/wasabeef/recyclerview-animators +// implementation 'jp.wasabeef:recyclerview-animators:4.0.2' + //live event bus // implementation 'io.github.jeremyliao:live-event-bus-x:1.8.0' - //https://github.com/ongakuer/CircleIndicator - implementation 'me.relex:circleindicator:2.1.6' - implementation 'com.github.kevinsawicki:http-request:6.0' implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.3' - //https://github.com/google/guava - implementation "com.google.guava:guava:32.1.2-android" - implementation 'commons-io:commons-io:2.13.0' //https://github.com/Baseflow/PhotoView implementation 'com.github.Chrisbanes:PhotoView:2.3.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2f1ea8b1b..42620d727 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,9 +5,15 @@ + + + + @@ -49,51 +55,53 @@ + android:theme="@style/AppTheme.Material3"> + + + + + + + - - - + - - - - - + android:exported="false" + android:launchMode="singleTop" /> - + @@ -121,9 +129,7 @@ - - - + + + - - - - - - - - - + + - + + + - - - - + + + + @@ -224,11 +227,11 @@ diff --git a/app/src/main/java/com/seafile/seadroid2/App.java b/app/src/main/java/com/seafile/seadroid2/App.java new file mode 100644 index 000000000..1666dbe85 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/App.java @@ -0,0 +1,19 @@ +package com.seafile.seadroid2; + +import com.seafile.seadroid2.util.SLogs; +import com.seafile.seadroid2.util.sp.Sorts; + +public class App { + public static void init() { + + // + Sorts.init(); + + + //init slogs + SLogs.init(); + + + SLogs.printAppEnvInfo(); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/BootAutostart.java b/app/src/main/java/com/seafile/seadroid2/BootAutostart.java index bef3a8c4b..5155b77b5 100644 --- a/app/src/main/java/com/seafile/seadroid2/BootAutostart.java +++ b/app/src/main/java/com/seafile/seadroid2/BootAutostart.java @@ -3,6 +3,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.text.TextUtils; import com.seafile.seadroid2.util.Utils; @@ -21,10 +22,15 @@ public class BootAutostart extends BroadcastReceiver { * - upgrade of the Seadroid package */ public void onReceive(Context context, Intent intent) { - - Utils.startCameraSyncJob(context); + if (intent == null || intent.getAction() == null) { + return; + } + + if (TextUtils.equals(Intent.ACTION_BOOT_COMPLETED, intent.getAction()) + || TextUtils.equals(Intent.ACTION_MY_PACKAGE_REPLACED, intent.getAction())) { + Utils.startCameraSyncJob(context); + } } - } diff --git a/app/src/main/java/com/seafile/seadroid2/SeadroidApplication.java b/app/src/main/java/com/seafile/seadroid2/SeadroidApplication.java index 67c094891..8eacdb57d 100644 --- a/app/src/main/java/com/seafile/seadroid2/SeadroidApplication.java +++ b/app/src/main/java/com/seafile/seadroid2/SeadroidApplication.java @@ -5,31 +5,52 @@ import android.app.NotificationManager; import android.content.Context; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.lifecycle.ViewModelProvider; + import com.joanzapata.iconify.Iconify; import com.joanzapata.iconify.fonts.MaterialCommunityModule; import com.seafile.seadroid2.gesturelock.AppLockManager; import com.seafile.seadroid2.monitor.ActivityMonitor; import com.seafile.seadroid2.ui.CustomNotificationBuilder; import com.seafile.seadroid2.util.CrashHandler; -import com.seafile.seadroid2.util.SLogs; public class SeadroidApplication extends Application { private static Context context; + private static SeadroidApplication instance; + private int waitingNumber; private int totalNumber; private int scanUploadStatus; - private static SeadroidApplication instance; private int totalBackup; private int waitingBackup; + + //Global View Model + private SeadroidViewModel seaDroidViewModel; + + public SeadroidViewModel getSeaDroidViewModel() { + return seaDroidViewModel; + } + + @Override public void onCreate() { super.onCreate(); - Iconify.with(new MaterialCommunityModule()); + instance = this; + + seaDroidViewModel = new ViewModelProvider.AndroidViewModelFactory(this).create(SeadroidViewModel.class); + + //night mode: no + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); + + + //iconify + Iconify.with(new MaterialCommunityModule()); + // initImageLoader(getApplicationContext()); - //init slogs - SLogs.init(); + App.init(); // set gesture lock if available AppLockManager.getInstance().enableDefaultAppLockIfAvailable(this); @@ -38,8 +59,6 @@ public void onCreate() { CrashHandler crashHandler = CrashHandler.getInstance(); crashHandler.init(this); - SLogs.printAppEnvInfo(); - //This feature can be extended registerActivityLifecycleCallbacks(new ActivityMonitor()); } diff --git a/app/src/main/java/com/seafile/seadroid2/SeadroidViewModel.java b/app/src/main/java/com/seafile/seadroid2/SeadroidViewModel.java new file mode 100644 index 000000000..fd5c24121 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/SeadroidViewModel.java @@ -0,0 +1,50 @@ +package com.seafile.seadroid2; + +import android.app.Application; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; + +import androidx.lifecycle.AndroidViewModel; + +import com.seafile.seadroid2.transfer.TransferService; + +public class SeadroidViewModel extends AndroidViewModel { + @Override + protected void onCleared() { + super.onCleared(); + + } + + private TransferService txService; + private ServiceConnection serviceConnection; + + public SeadroidViewModel(Application application) { + super(application); + + serviceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + TransferService.TransferBinder binder = (TransferService.TransferBinder) service; + txService = binder.getService(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + txService = null; + } + }; + } + + public void bindService() { + Intent intent = new Intent(getApplication(), TransferService.class); + getApplication().startService(intent); + getApplication().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); + } + + public void unbindService() { + getApplication().unbindService(serviceConnection); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/SeafConnection.java b/app/src/main/java/com/seafile/seadroid2/SeafConnection.java index b728e9953..016abf0b6 100644 --- a/app/src/main/java/com/seafile/seadroid2/SeafConnection.java +++ b/app/src/main/java/com/seafile/seadroid2/SeafConnection.java @@ -22,6 +22,7 @@ import com.seafile.seadroid2.data.ProgressMonitor; import com.seafile.seadroid2.httputils.RequestManager; import com.seafile.seadroid2.ssl.SSLTrustManager; +import com.seafile.seadroid2.util.DeviceIdManager; import com.seafile.seadroid2.util.Utils; import org.json.JSONArray; @@ -197,7 +198,8 @@ private boolean realLogin(String passwd, String authToken, boolean rememberDevic // ignore } - String deviceId = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID); + //TODO Secure.getString(context.getContentResolver(), Secure.ANDROID_ID); ? + String deviceId = DeviceIdManager.getInstance().getOrSet(); req.form("platform", "android"); req.form("device_id", deviceId); diff --git a/app/src/main/java/com/seafile/seadroid2/account/Account.java b/app/src/main/java/com/seafile/seadroid2/account/Account.java index ee6441282..a30f316cf 100644 --- a/app/src/main/java/com/seafile/seadroid2/account/Account.java +++ b/app/src/main/java/com/seafile/seadroid2/account/Account.java @@ -7,17 +7,12 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import com.seafile.seadroid2.BuildConfig; +import com.seafile.seadroid2.config.Constants; +import com.seafile.seadroid2.data.model.BaseModel; import com.seafile.seadroid2.util.URLs; import com.seafile.seadroid2.util.Utils; -public class Account implements Parcelable, Comparable { - private static final String DEBUG_TAG = "Account"; - - /** - * Type of the account (currently there is only one type) - */ - public final static String ACCOUNT_TYPE = BuildConfig.ACCOUNT_TYPE; - +public class Account extends BaseModel implements Parcelable, Comparable { // The full URL of the server, like 'http://gonggeng.org/seahub/' or 'http://gonggeng.org/' public final String server; public final String name; @@ -28,19 +23,22 @@ public class Account implements Parcelable, Comparable { public String token; public String sessionKey; + public String avatar_url; - public Account(String server, String email, String name, String token, Boolean is_shib) { + public Account(String server, String email, String name, String avatar_url, String token, Boolean is_shib) { this.name = name; + this.avatar_url = avatar_url; this.server = server; this.email = email; this.token = token; this.is_shib = is_shib; } - public Account(String name, String server, String email, String token, Boolean is_shib, String sessionKey) { + public Account(String name, String server, String email, String avatar_url, String token, Boolean is_shib, String sessionKey) { this.server = server; this.name = name; this.email = email; + this.avatar_url = avatar_url; this.token = token; this.sessionKey = sessionKey; this.is_shib = is_shib; @@ -64,16 +62,21 @@ public String getServerDomainName() { * https://dev.xxx.com/dev/ => https://dev.xxx.com */ public String getProtocolHost() { - return URLs.getProtocolHost(server); + return URLs.getProtocolHost(server); } public String getEmail() { return email; } - public String getName(){ + public String getAvatarUrl() { + return avatar_url; + } + + public String getName() { return name; } + public String getServer() { return server; } @@ -118,7 +121,7 @@ public boolean equals(Object obj) { return false; Account a = (Account) obj; - if (a.server == null || a.email == null || a.token == null ) + if (a.server == null || a.email == null || a.token == null) return false; return a.server.equals(this.server) && a.email.equals(this.email); @@ -134,7 +137,7 @@ public String getDisplayName() { } public android.accounts.Account getAndroidAccount() { - return new android.accounts.Account(getSignature(), ACCOUNT_TYPE); + return new android.accounts.Account(getSignature(), Constants.Account.ACCOUNT_TYPE); } public boolean hasValidToken() { @@ -153,6 +156,7 @@ public void writeToParcel(Parcel out, int flags) { out.writeString(this.email); out.writeString(this.token); out.writeString(this.sessionKey); + out.writeString(this.avatar_url); out.writeValue(this.is_shib); } @@ -174,19 +178,23 @@ protected Account(Parcel in) { this.email = in.readString(); this.token = in.readString(); this.sessionKey = in.readString(); + this.avatar_url = in.readString(); this.is_shib = (Boolean) in.readValue(Boolean.class.getClassLoader()); - // Log.d(DEBUG_TAG, String.format("%s %s %s %b", server, email, token ,is_shib)); + // Log.d(DEBUG_TAG, String.format("%s %s %s %b", server, email, token ,is_shib)); } @Override public String toString() { - return MoreObjects.toStringHelper(this) - .add("server", server) - .add("user", email) - .add("name", name) - .add("sessionKey", sessionKey) - .toString(); + return "Account{" + + "server='" + server + '\'' + + ", name='" + name + '\'' + + ", email='" + email + '\'' + + ", is_shib=" + is_shib + + ", token='" + token + '\'' + + ", sessionKey='" + sessionKey + '\'' + + ", avatar_url='" + avatar_url + '\'' + + '}'; } @Override diff --git a/app/src/main/java/com/seafile/seadroid2/account/AccountDBHelper.java b/app/src/main/java/com/seafile/seadroid2/account/AccountDBHelper.java deleted file mode 100644 index 8189a3b4e..000000000 --- a/app/src/main/java/com/seafile/seadroid2/account/AccountDBHelper.java +++ /dev/null @@ -1,260 +0,0 @@ -package com.seafile.seadroid2.account; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.preference.PreferenceManager; -import android.util.Log; - -import com.google.common.collect.Lists; -import com.seafile.seadroid2.SettingsManager; -import com.seafile.seadroid2.cameraupload.CameraUploadManager; -import com.seafile.seadroid2.data.ServerInfo; - -import java.util.List; - -/** - * Legacy code. Only for migrating old account settings to the new android account store. - * - * A helper class to manage {@link #DATABASE_NAME} database creation and version management. - */ -public class AccountDBHelper extends SQLiteOpenHelper { - public static final String DEBUG_TAG = "AccountDBHelper"; - - // NOTE: carefully update onUpgrade() when bumping database versions to make - // sure user data is saved. - - /** version of adding a new table @{link #ACCOUNT_TABLE_NAME} */ - private static final int VER_ADD_NEW_TABLE_ACCOUNT = 1; - /** version of adding a new table @{link #SERVER_INFO_TABLE_NAME} */ - private static final int VER_ADD_NEW_TABLE_SERVER_INFO = 2; - /** version of adding a new table @{link #SERVER_INFO_TABLE_NAME} without losing data */ - private static final int VER_ADD_NEW_TABLE_SERVER_INFO_MIGRATION = 3; - /** version of moving all accounts to the android account store */ - private static final int VER_MIGRATE_TO_ANDROID_ACCOUNT = 4; - - // If you change the database schema, you must increment the database version. - private static final int DATABASE_VERSION = VER_MIGRATE_TO_ANDROID_ACCOUNT; - - private static final String DATABASE_NAME = "account.db"; - - private static final String ACCOUNT_TABLE_NAME = "Account"; - private static final String SERVER_INFO_TABLE_NAME = "ServerInfo"; - - // Account - private static final String ACCOUNT_COLUMN_SERVER = "server"; - private static final String ACCOUNT_COLUMN_EMAIL = "email"; - private static final String ACCOUNT_COLUMN_NAME = "name"; - private static final String ACCOUNT_COLUMN_TOKEN = "token"; - - // Server info - private static final String SERVER_INFO_COLUMN_URL = "url"; - private static final String SERVER_INFO_COLUMN_VERSION = "version"; - private static final String SERVER_INFO_COLUMN_FEATURE = "feature"; - - private static final String SQL_CREATE_SERVER_INFO_TABLE = - "CREATE TABLE " + SERVER_INFO_TABLE_NAME + " (" - + SERVER_INFO_COLUMN_URL + " VARCHAR(255) PRIMARY KEY, " - + SERVER_INFO_COLUMN_VERSION + " TEXT NOT NULL, " - + SERVER_INFO_COLUMN_FEATURE + " TEXT NOT NULL" + ")"; - - private static final String SQL_CREATE_ACCOUNT_TABLE = - "CREATE TABLE " + ACCOUNT_TABLE_NAME + " (" - + ACCOUNT_COLUMN_SERVER + " TEXT NOT NULL, " - + ACCOUNT_COLUMN_EMAIL + " TEXT NOT NULL, " - + ACCOUNT_COLUMN_NAME + " TEXT NOT NULL, " - + ACCOUNT_COLUMN_TOKEN + " TEXT NOT NULL);"; - - private android.accounts.AccountManager mAccountManager; - private Context context; - - public static void migrateAccounts(Context context) { - AccountDBHelper db = null; - try { - db = new AccountDBHelper(context); - db.getWritableDatabase().close(); // this forces an onUpgrade() - } catch (Exception e) { - e.printStackTrace(); - } - } - - private AccountDBHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - this.context = context; - mAccountManager = android.accounts.AccountManager.get(context); - } - - public void onCreate(SQLiteDatabase db) { - db.execSQL(SQL_CREATE_ACCOUNT_TABLE); - db.execSQL(SQL_CREATE_SERVER_INFO_TABLE); - } - - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - Log.d(DEBUG_TAG, "onUpgrade() from " + oldVersion + " to " + newVersion); - - // NOTE: This switch statement is designed to handle cascading database - // updates, starting at the current version and falling through to all - // future upgrade cases. Only use "break;" when you want to drop and - // recreate the entire database. - - // Current DB version. We update this variable as we perform upgrades to reflect - // the current version we are in. - int version = oldVersion; - - switch (version) { - case VER_ADD_NEW_TABLE_ACCOUNT: - // Version 2 added new table "ServerInfo" - db.execSQL("DROP TABLE IF EXISTS " + SERVER_INFO_TABLE_NAME + ";"); - db.execSQL(SQL_CREATE_SERVER_INFO_TABLE); - version = VER_ADD_NEW_TABLE_SERVER_INFO; - - case VER_ADD_NEW_TABLE_SERVER_INFO: - // Version 3 added new table "ServerInfo" without losing user data - db.execSQL("DROP TABLE IF EXISTS " + SERVER_INFO_TABLE_NAME + ";"); - db.execSQL(SQL_CREATE_SERVER_INFO_TABLE); - version = VER_ADD_NEW_TABLE_SERVER_INFO_MIGRATION; - case VER_ADD_NEW_TABLE_SERVER_INFO_MIGRATION: - migrateToAndroidAccount(db); - db.execSQL("DROP TABLE IF EXISTS " + ACCOUNT_TABLE_NAME + ";"); - db.execSQL("DROP TABLE IF EXISTS " + SERVER_INFO_TABLE_NAME + ";"); - version = VER_MIGRATE_TO_ANDROID_ACCOUNT; - } - - Log.d(DEBUG_TAG, "after upgrade logic, at version " + version); - - // at this point, we ran out of upgrade logic, so if we are still at the wrong - // version, we have no choice but to delete everything and create everything again. - if (version != DATABASE_VERSION) { - Log.w(DEBUG_TAG, "Destroying old data during upgrade"); - db.execSQL("DROP TABLE IF EXISTS " + ACCOUNT_TABLE_NAME + ";"); - db.execSQL("DROP TABLE IF EXISTS " + SERVER_INFO_TABLE_NAME + ";"); - onCreate(db); - version = DATABASE_VERSION; - } - } - - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { - onUpgrade(db, oldVersion, newVersion); - } - - private void migrateToAndroidAccount(SQLiteDatabase db) { - - Log.i(DEBUG_TAG, "Migrating seafile accounts into Android account store (upgrade)"); - - SharedPreferences sharedPref = context.getSharedPreferences(SupportAccountManager.SHARED_PREF_NAME, Context.MODE_PRIVATE); - SharedPreferences settingsSharedPref = PreferenceManager.getDefaultSharedPreferences(context); - - Account cameraAccount = null; - String cameraName = sharedPref.getString(SettingsManager.SHARED_PREF_CAMERA_UPLOAD_ACCOUNT_NAME, null); - String cameraServer = sharedPref.getString(SettingsManager.SHARED_PREF_CAMERA_UPLOAD_ACCOUNT_SERVER, null); - String cameraEmail = sharedPref.getString(SettingsManager.SHARED_PREF_CAMERA_UPLOAD_ACCOUNT_EMAIL, null); - String cameraToken = sharedPref.getString(SettingsManager.SHARED_PREF_CAMERA_UPLOAD_ACCOUNT_TOKEN, null); - if (settingsSharedPref.getBoolean(SettingsManager.CAMERA_UPLOAD_SWITCH_KEY, false) && cameraEmail != null - && cameraServer != null && cameraToken != null ) { - - // on this account camera upload was done previously - cameraAccount = new Account(cameraName, cameraServer, cameraEmail, cameraToken, false); - } - - for (Account account: getAccountList(db)) { - Log.d(DEBUG_TAG, "Migrating seafile account: " + account); - - // MIGRATE account - Log.d(DEBUG_TAG, "adding account: " + account); - mAccountManager.addAccountExplicitly(account.getAndroidAccount(), null, null); - mAccountManager.setAuthToken(account.getAndroidAccount(), Authenticator.AUTHTOKEN_TYPE, account.getToken()); - mAccountManager.setUserData(account.getAndroidAccount(), Authenticator.KEY_SERVER_URI, account.getServer()); - mAccountManager.setUserData(account.getAndroidAccount(), Authenticator.KEY_EMAIL, account.getEmail()); - mAccountManager.setUserData(account.getAndroidAccount(), Authenticator.KEY_NAME, account.getName()); - - // MIGRATE ServerInfo - ServerInfo info = getServerInfo(db, account.getServer()); - if (info != null) { - Log.d(DEBUG_TAG, "setting server info: " + info); - mAccountManager.setUserData(account.getAndroidAccount(), Authenticator.KEY_SERVER_FEATURES, info.getFeatures()); - mAccountManager.setUserData(account.getAndroidAccount(), Authenticator.KEY_SERVER_VERSION, info.getVersion()); - } - - // MIGRATE camera sync settings - if (cameraAccount != null && cameraAccount.equals(account)) { - Log.d(DEBUG_TAG, "enabling camera sync"); - ContentResolver.setIsSyncable(account.getAndroidAccount(), CameraUploadManager.AUTHORITY, 1); - ContentResolver.setSyncAutomatically(account.getAndroidAccount(), CameraUploadManager.AUTHORITY, true); - } else { - ContentResolver.setIsSyncable(account.getAndroidAccount(), CameraUploadManager.AUTHORITY, 0); - } - Log.d(DEBUG_TAG, "Finished migrating seafile account: " + account); - } - - Log.i(DEBUG_TAG, "Finished migration of seafile accounts"); - } - - private List getAccountList(SQLiteDatabase database) { - List accounts = Lists.newArrayList(); - - String[] projection = { - AccountDBHelper.ACCOUNT_COLUMN_SERVER, - AccountDBHelper.ACCOUNT_COLUMN_EMAIL, - AccountDBHelper.ACCOUNT_COLUMN_NAME, - AccountDBHelper.ACCOUNT_COLUMN_TOKEN - }; - - Cursor c = database.query( - AccountDBHelper.ACCOUNT_TABLE_NAME, - projection, - null, - null, - null, // don't group the rows - null, // don't filter by row groups - null // The sort order - ); - - c.moveToFirst(); - while (!c.isAfterLast()) { - Account account = cursorToAccount(c); - accounts.add(account); - c.moveToNext(); - } - - c.close(); - return accounts; - } - - private Account cursorToAccount(Cursor cursor) { - return new Account(cursor.getString(0), cursor.getString(1), cursor.getString(2), cursor.getString(3), false); - } - - private ServerInfo getServerInfo(SQLiteDatabase database, String url) { - String[] projection = {SERVER_INFO_COLUMN_URL, SERVER_INFO_COLUMN_VERSION, SERVER_INFO_COLUMN_FEATURE}; - - Cursor c = database.query(SERVER_INFO_TABLE_NAME, - projection, - "url=?", - new String[] {url}, - null, // don't group the rows - null, // don't filter by row groups - null); // The sort order - - if (!c.moveToFirst()) { - c.close(); - return null; - } - - ServerInfo serverInfo = cursorToServerInfo(c); - - c.close(); - return serverInfo; - } - - private ServerInfo cursorToServerInfo(Cursor cursor) { - String url = cursor.getString(0); - String version = cursor.getString(1); - String features = cursor.getString(2); - ServerInfo serverInfo = new ServerInfo(url, version, features); - return serverInfo; - } - -} diff --git a/app/src/main/java/com/seafile/seadroid2/account/AccountInfo.java b/app/src/main/java/com/seafile/seadroid2/account/AccountInfo.java index 075004fa6..c563f2dde 100644 --- a/app/src/main/java/com/seafile/seadroid2/account/AccountInfo.java +++ b/app/src/main/java/com/seafile/seadroid2/account/AccountInfo.java @@ -1,33 +1,42 @@ package com.seafile.seadroid2.account; import com.seafile.seadroid2.util.Utils; + import org.json.JSONException; import org.json.JSONObject; /** * This class used to manage Account information - * */ public class AccountInfo { - private static final String DEBUG_TAG = "AccountInfo"; - public static final String SPACE_USAGE_SEPERATOR = " / "; private long usage; private long total; private String email; private String server; + private String avatar_url; private String name; + private String space_usage;// "6.0382327%" +// private String login_id; +// private String department; +// private String contact_email; +// private String institution; +// private boolean is_staff; +// private int file_updates_email_interval; +// private int collaborate_email_interval; - private AccountInfo() {} + private AccountInfo() { + } - public static AccountInfo fromJson(JSONObject accountInfo, String server) throws JSONException { + public static AccountInfo fromJson(JSONObject accountInfo, String server) throws JSONException { AccountInfo info = new AccountInfo(); info.server = server; info.usage = accountInfo.getLong("usage"); info.total = accountInfo.getLong("total"); info.email = accountInfo.getString("email"); info.name = accountInfo.optString("name"); - + info.avatar_url = accountInfo.optString("avatar_url"); + info.space_usage = accountInfo.optString("space_usage"); return info; } @@ -51,10 +60,22 @@ public String getName() { return name; } + public String getDisplayName() { + String server = Utils.stripSlashes(getServer()); + return Utils.assembleUserName(name, email, server); + } + + public void setServer(String server) { + this.server = server; + } + public String getSpaceUsed() { String strUsage = Utils.readableFileSize(usage); String strTotal = Utils.readableFileSize(total); return strUsage + SPACE_USAGE_SEPERATOR + strTotal; } + public String getAvatarUrl() { + return avatar_url; + } } diff --git a/app/src/main/java/com/seafile/seadroid2/account/AccountManager.java b/app/src/main/java/com/seafile/seadroid2/account/AccountManager.java deleted file mode 100644 index 8744ec353..000000000 --- a/app/src/main/java/com/seafile/seadroid2/account/AccountManager.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.seafile.seadroid2.account; - -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.text.TextUtils; - -import androidx.annotation.Nullable; - -import com.google.common.collect.Lists; -import com.seafile.seadroid2.cameraupload.CameraUploadManager; -import com.seafile.seadroid2.data.ServerInfo; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; - -/** - * 2023-3-28 Convert to a proxy class - *

- * Account Manager.
- * note the differences between {@link Account} and {@link AccountInfo}
- */ -public class AccountManager { - WeakReference contextWeakReference = null; - - public AccountManager(Context context) { - contextWeakReference = new WeakReference<>(context); - } - - public List getAccountList() { - return SupportAccountManager.getInstance().getAccountList(); - } - - public List getSignedInAccountList() { - return SupportAccountManager.getInstance().getSignedInAccountList(); - } - - @Nullable - public Account getCurrentAccount() { - return SupportAccountManager.getInstance().getCurrentAccount(); - } - - public Account getSeafileAccount(android.accounts.Account androidAccount) { - - return SupportAccountManager.getInstance().getSeafileAccount(androidAccount); - } - - public void setServerInfo(Account account, ServerInfo serverInfo) { - SupportAccountManager.getInstance().setServerInfo(account, serverInfo); - } - - /** - * Return cached ServerInfo - * - * @param account - * @return ServerInfo. Will never be null. - */ - public ServerInfo getServerInfo(Account account) { - return SupportAccountManager.getInstance().getServerInfo(account); - } - - /** - * save current Account info to SharedPreference
- * current means the Account is now in using at the foreground if has multiple accounts - * - * @param accountName - */ - public void saveCurrentAccount(String accountName) { - SupportAccountManager.getInstance().saveCurrentAccount(accountName); - } - - /** - * when user sign out, delete authorized information of the current Account instance.
- * If Camera Upload Service is running under the Account, stop the service. - */ - public void signOutAccount(Account account) { - SupportAccountManager.getInstance().signOutAccount(account); - } - - /** - * get all email texts from database in order to auto complete email address - * - * @return - */ - public ArrayList getAccountAutoCompleteTexts() { - return SupportAccountManager.getInstance().getAccountAutoCompleteTexts(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/account/Authenticator.java b/app/src/main/java/com/seafile/seadroid2/account/Authenticator.java index 1c0605d32..78178f92c 100644 --- a/app/src/main/java/com/seafile/seadroid2/account/Authenticator.java +++ b/app/src/main/java/com/seafile/seadroid2/account/Authenticator.java @@ -18,15 +18,12 @@ /* * Seafile Authenticator. - * */ public class Authenticator extends AbstractAccountAuthenticator { private String DEBUG_TAG = "SeafileAuthenticator"; private final Context context; - private com.seafile.seadroid2.account.AccountManager manager; - /** * Type of the auth token used (there is only one type) */ @@ -35,49 +32,51 @@ public class Authenticator extends AbstractAccountAuthenticator { /** * Key of Server URI in userData */ - public final static String KEY_SERVER_URI = "server"; + public static final String KEY_SERVER_URI = "server"; /** * Key of email in userData */ - public final static String KEY_EMAIL = "email"; + public static final String KEY_EMAIL = "email"; /** * Key of name in userData */ - public final static String KEY_NAME = "name"; + public static final String KEY_NAME = "name"; + /** + * Key of avatar_url in userData + */ + public static final String KEY_AVATAR_URL = "avatar_url"; /** * Key of Server version in userData */ - public final static String KEY_SERVER_VERSION = "version"; + public static final String KEY_SERVER_VERSION = "version"; /** * Key of Server Feature-list in userData */ - public final static String KEY_SERVER_FEATURES = "features"; + public static final String KEY_SERVER_FEATURES = "features"; /** * Key of shib_setting in userData */ - public final static String KEY_SHIB = "shib"; + public static final String KEY_SHIB = "shib"; /** * Two Factor Auth in userData */ - public final static String SESSION_KEY = "sessionKey"; + public static final String SESSION_KEY = "sessionKey"; public Authenticator(Context context) { super(context); Log.d(DEBUG_TAG, "SeafileAuthenticator created."); this.context = context; - this.manager = new com.seafile.seadroid2.account.AccountManager(context); } /** * We have no properties. */ @Override - public Bundle editProperties( - AccountAuthenticatorResponse r, String s) { + public Bundle editProperties(AccountAuthenticatorResponse r, String s) { Log.d(DEBUG_TAG, "editProperties"); throw new UnsupportedOperationException(); @@ -90,7 +89,7 @@ public Bundle addAccount(AccountAuthenticatorResponse response, String[] requiredFeatures, Bundle options) throws NetworkErrorException { - Log.d(DEBUG_TAG, "addAccount of type "+accountType); + Log.d(DEBUG_TAG, "addAccount of type " + accountType); if (authTokenType != null && !authTokenType.equals(Authenticator.AUTHTOKEN_TYPE)) { Bundle result = new Bundle(); @@ -116,7 +115,7 @@ public Bundle confirmCredentials( Bundle bundle) throws NetworkErrorException { Log.d(DEBUG_TAG, "confirmCredentials"); - Account a = manager.getSeafileAccount(account); + Account a = SupportAccountManager.getInstance().getSeafileAccount(account); DataManager manager = new DataManager(a); try { @@ -185,8 +184,8 @@ public String getAuthTokenLabel(String authTokenType) { @Override public Bundle updateCredentials(AccountAuthenticatorResponse response, - android.accounts.Account account, - String authTokenType, Bundle options) throws NetworkErrorException { + android.accounts.Account account, + String authTokenType, Bundle options) throws NetworkErrorException { Log.d(DEBUG_TAG, "updateCredentials"); if (authTokenType != null && !authTokenType.equals(Authenticator.AUTHTOKEN_TYPE)) { @@ -202,7 +201,7 @@ public Bundle updateCredentials(AccountAuthenticatorResponse response, intent.putExtra(SeafileAuthenticatorActivity.ARG_ACCOUNT_NAME, account.name); // will be overridden intent.putExtra(SeafileAuthenticatorActivity.ARG_EDIT_OLD_ACCOUNT_NAME, account.name); intent.putExtra(SeafileAuthenticatorActivity.ARG_IS_EDITING, true); - boolean is_shib = manager.getSeafileAccount(account).isShib(); + boolean is_shib = SupportAccountManager.getInstance().getSeafileAccount(account).isShib(); intent.putExtra(SeafileAuthenticatorActivity.ARG_SHIB, is_shib); final Bundle bundle = new Bundle(); @@ -212,7 +211,7 @@ public Bundle updateCredentials(AccountAuthenticatorResponse response, @Override public Bundle hasFeatures(AccountAuthenticatorResponse r, - android.accounts.Account account, String[] strings) throws NetworkErrorException { + android.accounts.Account account, String[] strings) throws NetworkErrorException { Log.d(DEBUG_TAG, "hasFeatures"); final Bundle result = new Bundle(); diff --git a/app/src/main/java/com/seafile/seadroid2/account/SupportAccountManager.java b/app/src/main/java/com/seafile/seadroid2/account/SupportAccountManager.java index 661ef2e5f..8f7be92d8 100644 --- a/app/src/main/java/com/seafile/seadroid2/account/SupportAccountManager.java +++ b/app/src/main/java/com/seafile/seadroid2/account/SupportAccountManager.java @@ -1,15 +1,20 @@ package com.seafile.seadroid2.account; -import android.content.Context; -import android.content.SharedPreferences; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.os.Bundle; +import android.os.Handler; import android.text.TextUtils; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.google.common.collect.Lists; import com.seafile.seadroid2.SeadroidApplication; -import com.seafile.seadroid2.cameraupload.CameraUploadManager; +import com.seafile.seadroid2.ui.camera_upload.CameraUploadManager; +import com.seafile.seadroid2.config.Constants; import com.seafile.seadroid2.data.ServerInfo; +import com.seafile.seadroid2.io.http.IO; +import com.seafile.seadroid2.util.sp.SPs; import java.util.ArrayList; import java.util.List; @@ -29,43 +34,35 @@ public static SupportAccountManager getInstance() { return singleton; } - @SuppressWarnings("unused") - private static String DEBUG_TAG = "AccountManager"; - public static final String SHARED_PREF_NAME = "latest_account"; - public static final String SHARED_PREF_ACCOUNT_NAME = "com.seafile.seadroid.account_name"; + private final android.accounts.AccountManager accountManager; - /** - * used to manage multi Accounts when user switch between different Accounts - */ - private SharedPreferences actMangeSharedPref; - private SharedPreferences.Editor editor; - - private android.accounts.AccountManager accountManager; - - public SupportAccountManager() { + private SupportAccountManager() { accountManager = android.accounts.AccountManager.get(SeadroidApplication.getAppContext()); - // used to manage multi Accounts when user switch between different Accounts - actMangeSharedPref = SeadroidApplication.getAppContext().getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); - editor = actMangeSharedPref.edit(); - - // migrate old accounts - AccountDBHelper.migrateAccounts(SeadroidApplication.getAppContext()); } + @NonNull public List getAccountList() { List list = new ArrayList(); - android.accounts.Account[] availableAccounts = accountManager.getAccountsByType(Account.ACCOUNT_TYPE); + String currentAccountName = getCurrentAccountName(); + + android.accounts.Account[] availableAccounts = accountManager.getAccountsByType(Constants.Account.ACCOUNT_TYPE); for (android.accounts.Account availableAccount : availableAccounts) { Account a = getSeafileAccount(availableAccount); + if (!TextUtils.isEmpty(currentAccountName) && a.getSignature().equals(currentAccountName)) { + a.is_selected = true; + } + list.add(a); } + return list; } + @NonNull public List getSignedInAccountList() { - List list = new ArrayList(); - android.accounts.Account[] availableAccounts = accountManager.getAccountsByType(Account.ACCOUNT_TYPE); + List list = new ArrayList<>(); + android.accounts.Account[] availableAccounts = accountManager.getAccountsByType(Constants.Account.ACCOUNT_TYPE); for (android.accounts.Account availableAccount : availableAccounts) { Account a = getSeafileAccount(availableAccount); if (a.hasValidToken()) @@ -74,11 +71,23 @@ public List getSignedInAccountList() { return list; } + /** + * save current Account info to SharedPreference
+ * current means the Account is now in using at the foreground if has multiple accounts + */ + public void saveCurrentAccount(String accountName) { + SPs.put(Constants.SP.ACCOUNT_CURRENT, accountName); + + //IMPORTANT + //reset IO Singleton + IO.resetSingleton(); + } + @Nullable public Account getCurrentAccount() { - String name = actMangeSharedPref.getString(SHARED_PREF_ACCOUNT_NAME, null); + String name = SPs.getString(Constants.SP.ACCOUNT_CURRENT); - if (name != null) { + if (!TextUtils.isEmpty(name)) { List list = getAccountList(); for (Account a : list) { if (a.hasValidToken() && a.getSignature().equals(name)) { @@ -90,14 +99,20 @@ public Account getCurrentAccount() { return null; } + public String getCurrentAccountName() { + return SPs.getString(Constants.SP.ACCOUNT_CURRENT); + } + + @NonNull public Account getSeafileAccount(android.accounts.Account androidAccount) { String server = accountManager.getUserData(androidAccount, Authenticator.KEY_SERVER_URI); String email = accountManager.getUserData(androidAccount, Authenticator.KEY_EMAIL); String name = accountManager.getUserData(androidAccount, Authenticator.KEY_NAME); + String avatarUrl = accountManager.getUserData(androidAccount, Authenticator.KEY_AVATAR_URL); boolean is_shib = accountManager.getUserData(androidAccount, Authenticator.KEY_SHIB) != null; String token = accountManager.peekAuthToken(androidAccount, Authenticator.AUTHTOKEN_TYPE); String session_key = accountManager.getUserData(androidAccount, Authenticator.SESSION_KEY); - return new Account(name, server, email, token, is_shib, session_key); + return new Account(name, server, email, avatarUrl, token, is_shib, session_key); } public void setServerInfo(Account account, ServerInfo serverInfo) { @@ -106,31 +121,40 @@ public void setServerInfo(Account account, ServerInfo serverInfo) { accountManager.setUserData(account.getAndroidAccount(), Authenticator.KEY_SERVER_FEATURES, serverInfo.getFeatures()); } + public void setUserData(final android.accounts.Account account, final String key, final String value) { + accountManager.setUserData(account, key, value); + } + + public boolean addAccountExplicitly(android.accounts.Account account, String password, Bundle userdata) { + return accountManager.addAccountExplicitly(account, password, userdata); + } + + public AccountManagerFuture removeAccount(final android.accounts.Account account, + AccountManagerCallback callback, Handler handler) { + return accountManager.removeAccount(account, callback, handler); + } + + public void setAuthToken(android.accounts.Account account, final String authTokenType, final String authToken) { + accountManager.setAuthToken(account, authTokenType, authToken); + } + + public String getUserData(final android.accounts.Account account, final String key) { + return accountManager.getUserData(account, key); + } + /** * Return cached ServerInfo * - * @param account * @return ServerInfo. Will never be null. */ + @NonNull public ServerInfo getServerInfo(Account account) { String server = accountManager.getUserData(account.getAndroidAccount(), Authenticator.KEY_SERVER_URI); String version = accountManager.getUserData(account.getAndroidAccount(), Authenticator.KEY_SERVER_VERSION); String features = accountManager.getUserData(account.getAndroidAccount(), Authenticator.KEY_SERVER_FEATURES); - ServerInfo info = new ServerInfo(server, version, features); - return info; + return new ServerInfo(server, version, features); } - /** - * save current Account info to SharedPreference
- * current means the Account is now in using at the foreground if has multiple accounts - * - * @param accountName - */ - public void saveCurrentAccount(String accountName) { - - editor.putString(SHARED_PREF_ACCOUNT_NAME, accountName); - editor.commit(); - } /** * when user sign out, delete authorized information of the current Account instance.
@@ -141,10 +165,11 @@ public void signOutAccount(Account account) { return; } - CameraUploadManager cameraManager = new CameraUploadManager(SeadroidApplication.getAppContext()); + saveCurrentAccount(null); - accountManager.invalidateAuthToken(Account.ACCOUNT_TYPE, account.getToken()); + accountManager.invalidateAuthToken(Constants.Account.ACCOUNT_TYPE, account.getToken()); + CameraUploadManager cameraManager = new CameraUploadManager(); // disable camera upload if on this account Account camAccount = cameraManager.getCameraAccount(); if (camAccount != null && camAccount.equals(account)) { @@ -152,22 +177,4 @@ public void signOutAccount(Account account) { } } - /** - * get all email texts from database in order to auto complete email address - * - * @return - */ - public ArrayList getAccountAutoCompleteTexts() { - ArrayList autoCompleteTexts = Lists.newArrayList(); - - List accounts = getAccountList(); - - if (accounts == null) return null; - for (Account act : accounts) { - if (!autoCompleteTexts.contains(act.getEmail())) - autoCompleteTexts.add(act.getEmail()); - } - return autoCompleteTexts; - } - } diff --git a/app/src/main/java/com/seafile/seadroid2/account/SupportDataManager.java b/app/src/main/java/com/seafile/seadroid2/account/SupportDataManager.java new file mode 100644 index 000000000..0e2b2608d --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/account/SupportDataManager.java @@ -0,0 +1,28 @@ +package com.seafile.seadroid2.account; + +import com.seafile.seadroid2.data.DataManager; + +public class SupportDataManager { + private DataManager dataManager; + + private static volatile SupportDataManager singleton = null; + + public static SupportDataManager getInstance() { + if (singleton == null) { + synchronized (SupportDataManager.class) { + if (singleton == null) { + singleton = new SupportDataManager(); + } + } + } + return singleton; + } + + public SupportDataManager() { + dataManager = new DataManager(SupportAccountManager.getInstance().getCurrentAccount()); + } + + public DataManager getDataManager() { + return dataManager; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/avatar/AuthImageDownloader.java b/app/src/main/java/com/seafile/seadroid2/avatar/AuthImageDownloader.java deleted file mode 100644 index 6f4991a96..000000000 --- a/app/src/main/java/com/seafile/seadroid2/avatar/AuthImageDownloader.java +++ /dev/null @@ -1,51 +0,0 @@ - -package com.seafile.seadroid2.avatar; - -import android.content.Context; - -import com.github.kevinsawicki.http.HttpRequest; -import com.nostra13.universalimageloader.core.assist.FlushedInputStream; -import com.nostra13.universalimageloader.core.download.BaseImageDownloader; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.ssl.SSLTrustManager; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; - -import javax.net.ssl.HttpsURLConnection; - -public class AuthImageDownloader extends BaseImageDownloader { - public static final String TAG = AuthImageDownloader.class.getName(); - - public AuthImageDownloader(Context context, int connectTimeout, - int readTimeout) { - super(context, connectTimeout, readTimeout); - } - - @Override - protected InputStream getStreamFromNetwork(String imageUri, Object extra) - throws IOException { - HttpRequest req = HttpRequest.get(imageUri, null, false) - .readTimeout(readTimeout) - .connectTimeout(connectTimeout) - .followRedirects(true) - .header("Authorization", "Token " + ((Account) extra).token); - - HttpURLConnection conn = req.getConnection(); - - if (conn instanceof HttpsURLConnection) { - // Tell HttpRequest to trust all hosts, and then the user will get a dialog - // where he needs to confirm the SSL certificate for the account, - // and the accepted certificate will be stored, so he is not prompted to accept later on. - // This is handled by SSLTrustManager and CertsManager - req.trustAllHosts(); - HttpsURLConnection sconn = (HttpsURLConnection) conn; - sconn.setSSLSocketFactory(SSLTrustManager.instance().getSSLSocketFactory((Account) extra)); - } - - return new FlushedInputStream(new BufferedInputStream( - req.stream())); - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/avatar/Avatar.java b/app/src/main/java/com/seafile/seadroid2/avatar/Avatar.java deleted file mode 100644 index 582f19526..000000000 --- a/app/src/main/java/com/seafile/seadroid2/avatar/Avatar.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.seafile.seadroid2.avatar; - -import org.json.JSONException; -import org.json.JSONObject; - -import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; - -import android.util.Log; - -public class Avatar { - private static final String DEBUG_TAG = "Avatar"; - - private String signature; // Account Signature - private String url; - private long mtime; - // private boolean is_default; - - static Avatar fromJson(JSONObject obj) { - Avatar avatar = new Avatar(); - try { - avatar.url = obj.getString("url"); - avatar.mtime = obj.getLong("mtime"); - // avatar.is_default = obj.getBoolean("is_default"); - - return avatar; - } catch (JSONException e) { - Log.d(DEBUG_TAG, e.getMessage()); - return null; - } - } - - @Override - public int hashCode() { - return Objects.hashCode(url, mtime); - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public long getMtime() { - return mtime; - } - - public void setMtime(long mtime) { - this.mtime = mtime; - } - - /*public boolean isIs_default() { - return is_default; - } - - public void setIs_default(boolean is_default) { - this.is_default = is_default; - }*/ - - public String getSignature() { - return signature; - } - - public void setSignature(String signature) { - this.signature = signature; - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("signature", signature) - .add("url", url) - .add("mtime", mtime) - /*.add("is_default", is_default)*/ - .toString(); - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/avatar/AvatarDBHelper.java b/app/src/main/java/com/seafile/seadroid2/avatar/AvatarDBHelper.java deleted file mode 100644 index e0ba35d84..000000000 --- a/app/src/main/java/com/seafile/seadroid2/avatar/AvatarDBHelper.java +++ /dev/null @@ -1,182 +0,0 @@ -package com.seafile.seadroid2.avatar; - -import java.util.ArrayList; -import java.util.List; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; - -import com.google.common.collect.Lists; -import com.seafile.seadroid2.SeadroidApplication; -import com.seafile.seadroid2.account.Account; - -public class AvatarDBHelper extends SQLiteOpenHelper { - private static final String DEBUG_TAG = "AvatarDBHelper"; - - public static final int DATABASE_VERSION = 1; - public static final String DATABASE_NAME = "avatar.db"; - private static final String AVATAR_TABLE_NAME = "Avatar"; - - private static final String AVATAR_COLUMN_ID = "id"; - private static final String AVATAR_COLUMN_SIGNATURE = "signature"; - private static final String AVATAR_COLUMN_URL = "url"; - private static final String AVATAR_COLUMN_MTIME = "mtime"; - /*private static final String AVATAR_COLUMN_IS_DEFAULT = "is_default";*/ - - private static final String SQL_CREATE_AVATAR_TABLE = - "CREATE TABLE " + AVATAR_TABLE_NAME + " (" - + AVATAR_COLUMN_ID + " INTEGER PRIMARY KEY, " - + AVATAR_COLUMN_SIGNATURE + " TEXT NOT NULL, " - + AVATAR_COLUMN_URL + " TEXT NOT NULL, " - + AVATAR_COLUMN_MTIME + " INTEGER NOT NULL);"; - - private static final String[] projection = { - //AVATAR_COLUMN_ID, - AVATAR_COLUMN_SIGNATURE, - AVATAR_COLUMN_URL, - AVATAR_COLUMN_MTIME - /*AVATAR_COLUMN_IS_DEFAULT*/ - }; - - public static final String [] hasAvatarProjection = { - AVATAR_COLUMN_SIGNATURE, - AVATAR_COLUMN_URL, - AVATAR_COLUMN_MTIME - }; - - private static AvatarDBHelper dbHelper = null; - private SQLiteDatabase database = null; - - public static synchronized AvatarDBHelper getAvatarDbHelper() { - if (dbHelper != null) - return dbHelper; - dbHelper = new AvatarDBHelper(SeadroidApplication.getAppContext()); - dbHelper.database = dbHelper.getWritableDatabase(); - return dbHelper; - } - - private AvatarDBHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - public boolean hasAvatar(Account account) { - - if (account == null) - return false; - if (account.getSignature() == null || account.getSignature().isEmpty()) - return false; - - String selection = AVATAR_COLUMN_SIGNATURE + "=?"; - - Cursor cursor = database.query( - AVATAR_TABLE_NAME, - hasAvatarProjection, - selection, - new String[]{account.getSignature()}, - null, - null, - null - ); - - boolean hasAvatar = false; - - if (cursor.moveToFirst()) - hasAvatar = true; - - cursor.close(); - return hasAvatar; - - } - - public List getAvatarList() { - Cursor cursor = database.query( - AVATAR_TABLE_NAME, - projection, - null, - null, - null, // don't group the rows - null, // don't filter by row groups - null // The sort order - ); - - List avatars = new ArrayList(); - - if (!cursor.moveToFirst()) - return avatars; - - do { - Avatar avatar = new Avatar(); - avatar.setSignature(cursor.getString(0)); - avatar.setUrl(cursor.getString(1)); - avatar.setMtime(cursor.getInt(2)); - /*avatar.setIs_default(cursor.getInt(3) == 1);*/ - avatars.add(avatar); - } while (cursor.moveToNext()); - - cursor.close(); - return avatars; - } - - public void saveAvatars(List avatars) { - - List validAvatars = Lists.newArrayList(); - - // query database in case insert duplicate rows - for (Avatar avatar : avatars) { - if (!isRowDuplicate(avatar)) { - validAvatars.add(avatar); - } - } - - for (Avatar avatar : validAvatars) { - ContentValues values = new ContentValues(); - values.put(AVATAR_COLUMN_SIGNATURE, avatar.getSignature()); - values.put(AVATAR_COLUMN_URL, avatar.getUrl()); - values.put(AVATAR_COLUMN_MTIME, avatar.getMtime()); - /*values.put(AVATAR_COLUMN_IS_DEFAULT, (avatar.isIs_default() ? 1 : 0));*/ - database.insert(AVATAR_TABLE_NAME, null, values); - } - } - - // avoid duplicate inserting request - private boolean isRowDuplicate(Avatar avatar) { - - long count = DatabaseUtils.queryNumEntries( - database, - AVATAR_TABLE_NAME, - AVATAR_COLUMN_SIGNATURE + "=? and " + - AVATAR_COLUMN_URL + "=?", - new String[]{avatar.getSignature(), avatar.getUrl()}); - return count > 0; - - } - - @Override - public void onCreate(SQLiteDatabase db) { - createAvatarTable(db); - } - - private void createAvatarTable(SQLiteDatabase db) { - db.execSQL(SQL_CREATE_AVATAR_TABLE); - db.execSQL("CREATE INDEX account_signature_index ON " + AVATAR_TABLE_NAME - + " (" + AVATAR_COLUMN_SIGNATURE + ");"); - db.execSQL("CREATE INDEX avatar_url_index ON " + AVATAR_TABLE_NAME - + " (" + AVATAR_COLUMN_URL + ");"); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - db.execSQL("DROP TABLE IF EXISTS " + AVATAR_TABLE_NAME + ";"); - onCreate(db); - } - - @Override - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { - onUpgrade(db, oldVersion, newVersion); - } - -} diff --git a/app/src/main/java/com/seafile/seadroid2/avatar/AvatarManager.java b/app/src/main/java/com/seafile/seadroid2/avatar/AvatarManager.java deleted file mode 100644 index 32c7077e3..000000000 --- a/app/src/main/java/com/seafile/seadroid2/avatar/AvatarManager.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.seafile.seadroid2.avatar; - -import java.util.*; - -import com.seafile.seadroid2.SeadroidApplication; -import com.seafile.seadroid2.account.AccountManager; -import org.json.JSONObject; - -import com.google.common.collect.Lists; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.util.Utils; - -/** - * load, cache, update avatars - * - */ -public class AvatarManager { - private static final String DEBUG_TAG = "AvatarManager"; - - private final AvatarDBHelper dbHelper = AvatarDBHelper.getAvatarDbHelper(); - private List avatars; - private AccountManager accountMgr; - - public AvatarManager() { - this.avatars = Lists.newArrayList(); - this.accountMgr = new AccountManager(SeadroidApplication.getAppContext()); - } - - /** - * get accounts who don`t have avatars yet - * - * @return ArrayList - */ - public ArrayList getAccountsWithoutAvatars() { - List accounts = accountMgr.getAccountList(); - - if (accounts == null) return null; - - ArrayList accountsWithoutAvatar = Lists.newArrayList(); - - for (Account account : accounts) { - if (!hasAvatar(account)) { - accountsWithoutAvatar.add(account); - } - } - - return accountsWithoutAvatar; - } - - private boolean hasAvatar(Account account) { - return dbHelper.hasAvatar(account); - } - - public boolean isNeedToLoadNewAvatars() { - ArrayList accounts = getAccountsWithoutAvatars(); - if (accounts == null || accounts.size() ==0) return false; - else - return true; - } - - public List getAvatarList() { - return dbHelper.getAvatarList(); - } - - public void saveAvatarList(List avatars) { - dbHelper.saveAvatars(avatars); - } - - public Avatar parseAvatar(String json) { - if (json == null) return null; - - JSONObject obj = Utils.parseJsonObject(json); - if (obj == null) - return null; - Avatar avatar = Avatar.fromJson(obj); - if (avatar == null) - return null; - - return avatar; - } - - - - -} diff --git a/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/ActionMenu.java b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/ActionMenu.java new file mode 100644 index 000000000..eea46dd88 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/ActionMenu.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.seafile.seadroid2.bottomsheetmenu; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.view.KeyEvent; +import android.view.MenuItem; +import android.view.SubMenu; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +class ActionMenu implements android.view.Menu { + private static final int[] sCategoryToOrder = new int[]{ + 1, /* No category */ + 4, /* CONTAINER */ + 5, /* SYSTEM */ + 3, /* SECONDARY */ + 2, /* ALTERNATIVE */ + 0, /* SELECTED_ALTERNATIVE */ + }; + /** + * This is the part of an order integer that the user can provide. + */ + private static final int USER_MASK = 0x0000ffff; + /** + * Bit shift of the user portion of the order integer. + */ + private static final int USER_SHIFT = 0; + /** + * This is the part of an order integer that supplies the category of the item. + */ + private static final int CATEGORY_MASK = 0xffff0000; + /** + * Bit shift of the category portion of the order integer. + */ + private static final int CATEGORY_SHIFT = 16; + private Context mContext; + private boolean mIsQwerty; + private ArrayList mItems; + + ActionMenu(Context context) { + mContext = context; + mItems = new ArrayList<>(); + } + + private static int findInsertIndex(ArrayList items, int ordering) { + for (int i = items.size() - 1; i >= 0; i--) { + ActionMenuItem item = items.get(i); + if (item.getOrder() <= ordering) { + return i + 1; + } + } + return 0; + } + + /** + * Returns the ordering across all items. This will grab the category from + * the upper bits, find out how to order the category with respect to other + * categories, and combine it with the lower bits. + * + * @param categoryOrder The category order for a particular item (if it has + * not been or/add with a category, the default category is + * assumed). + * @return An ordering integer that can be used to order this item across + * all the items (even from other categories). + */ + private static int getOrdering(int categoryOrder) { + final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT; + + if (index < 0 || index >= sCategoryToOrder.length) { + throw new IllegalArgumentException("order does not contain a valid category."); + } + + return (sCategoryToOrder[index] << CATEGORY_SHIFT) | (categoryOrder & USER_MASK); + } + + public Context getContext() { + return mContext; + } + + public MenuItem add(CharSequence title) { + return add(0, 0, 0, title); + } + + public MenuItem add(int titleRes) { + return add(0, 0, 0, titleRes); + } + + public MenuItem add(int groupId, int itemId, int order, int titleRes) { + return add(groupId, itemId, order, mContext.getResources().getString(titleRes)); + } + + public MenuItem add(int groupId, int itemId, int order, CharSequence title) { + ActionMenuItem item = new ActionMenuItem(getContext(), + groupId, itemId, 0, order, title); + mItems.add(findInsertIndex(mItems, getOrdering(order)), item); + return item; + } + + MenuItem add(ActionMenuItem item) { + mItems.add(findInsertIndex(mItems, getOrdering(item.getOrder())), item); + return item; + } + + public int addIntentOptions(int groupId, int itemId, int order, + ComponentName caller, Intent[] specifics, Intent intent, int flags, + MenuItem[] outSpecificItems) { + PackageManager pm = mContext.getPackageManager(); + final List lri = + pm.queryIntentActivityOptions(caller, specifics, intent, 0); + final int N = lri != null ? lri.size() : 0; + + if ((flags & FLAG_APPEND_TO_GROUP) == 0) { + removeGroup(groupId); + } + + for (int i = 0; i < N; i++) { + final ResolveInfo ri = lri.get(i); + Intent rintent = new Intent( + ri.specificIndex < 0 ? intent : specifics[ri.specificIndex]); + rintent.setComponent(new ComponentName( + ri.activityInfo.applicationInfo.packageName, + ri.activityInfo.name)); + final MenuItem item = add(groupId, itemId, order, ri.loadLabel(pm)) + .setIcon(ri.loadIcon(pm)) + .setIntent(rintent); + if (outSpecificItems != null && ri.specificIndex >= 0) { + outSpecificItems[ri.specificIndex] = item; + } + } + + return N; + } + + public SubMenu addSubMenu(CharSequence title) { + // TODO Implement submenus + return null; + } + + public SubMenu addSubMenu(int titleRes) { + // TODO Implement submenus + return null; + } + + public SubMenu addSubMenu(int groupId, int itemId, int order, + CharSequence title) { + // TODO Implement submenus + return null; + } + + public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) { + // TODO Implement submenus + return null; + } + + public void clear() { + mItems.clear(); + } + + public void close() { + } + + private int findItemIndex(int id) { + final ArrayList items = mItems; + final int itemCount = items.size(); + for (int i = 0; i < itemCount; i++) { + if (items.get(i).getItemId() == id) { + return i; + } + } + return -1; + } + + public MenuItem findItem(int id) { + final int index = findItemIndex(id); + if (index < 0) { + return null; + } + + return mItems.get(index); + } + + public MenuItem getItem(int index) { + return mItems.get(index); + } + + public boolean hasVisibleItems() { + final ArrayList items = mItems; + final int itemCount = items.size(); + + for (int i = 0; i < itemCount; i++) { + if (items.get(i).isVisible()) { + return true; + } + } + + return false; + } + + private ActionMenuItem findItemWithShortcut(int keyCode, KeyEvent event) { + // TODO Make this smarter. + final boolean qwerty = mIsQwerty; + final ArrayList items = mItems; + final int itemCount = items.size(); + + for (int i = 0; i < itemCount; i++) { + ActionMenuItem item = items.get(i); + final char shortcut = qwerty ? item.getAlphabeticShortcut() : + item.getNumericShortcut(); + if (keyCode == shortcut) { + return item; + } + } + return null; + } + + public boolean isShortcutKey(int keyCode, KeyEvent event) { + return findItemWithShortcut(keyCode, event) != null; + } + + public boolean performIdentifierAction(int id, int flags) { + final int index = findItemIndex(id); + if (index < 0) { + return false; + } + + return mItems.get(index).invoke(); + } + + public boolean performShortcut(int keyCode, KeyEvent event, int flags) { + ActionMenuItem item = findItemWithShortcut(keyCode, event); + if (item == null) { + return false; + } + + return item.invoke(); + } + + public void removeGroup(int groupId) { + final ArrayList items = mItems; + int itemCount = items.size(); + int i = 0; + while (i < itemCount) { + if (items.get(i).getGroupId() == groupId) { + items.remove(i); + itemCount--; + } else { + i++; + } + } + } + + public void removeItem(int id) { + final int index = findItemIndex(id); + if (index < 0) { + return; + } + + mItems.remove(index); + } + + public void setGroupCheckable(int group, boolean checkable, + boolean exclusive) { + final ArrayList items = mItems; + final int itemCount = items.size(); + + for (int i = 0; i < itemCount; i++) { + ActionMenuItem item = items.get(i); + if (item.getGroupId() == group) { + item.setCheckable(checkable); + item.setExclusiveCheckable(exclusive); + } + } + } + + public void setGroupEnabled(int group, boolean enabled) { + final ArrayList items = mItems; + final int itemCount = items.size(); + + for (int i = 0; i < itemCount; i++) { + ActionMenuItem item = items.get(i); + if (item.getGroupId() == group) { + item.setEnabled(enabled); + } + } + } + + public void setGroupVisible(int group, boolean visible) { + final ArrayList items = mItems; + final int itemCount = items.size(); + + for (int i = 0; i < itemCount; i++) { + ActionMenuItem item = items.get(i); + if (item.getGroupId() == group) { + item.setVisible(visible); + } + } + } + + public void setQwertyMode(boolean isQwerty) { + mIsQwerty = isQwerty; + } + + public int size() { + return mItems.size(); + } + + ActionMenu clone(int size) { + ActionMenu out = new ActionMenu(getContext()); + out.mItems = new ArrayList<>(this.mItems.subList(0, size)); + return out; + } + + void removeInvisible() { + Iterator iter = mItems.iterator(); + while (iter.hasNext()) { + ActionMenuItem item = iter.next(); + if (!item.isVisible()) iter.remove(); + } + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/ActionMenuItem.java b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/ActionMenuItem.java new file mode 100644 index 000000000..47f28f68c --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/ActionMenuItem.java @@ -0,0 +1,342 @@ +package com.seafile.seadroid2.bottomsheetmenu; + +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.content.Context; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.view.ContextMenu; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; + +import androidx.annotation.DrawableRes; +import androidx.core.content.ContextCompat; + + +class ActionMenuItem implements MenuItem { + + + private static final int NO_ICON = 0; + private static final int CHECKABLE = 0x00000001; + private static final int CHECKED = 0x00000002; + private static final int EXCLUSIVE = 0x00000004; + private static final int HIDDEN = 0x00000008; + private static final int ENABLED = 0x00000010; + private final int mId; + private final int mGroup; + private final int mCategoryOrder; + private final int mOrdering; + private CharSequence mTitle; + private CharSequence mTitleCondensed; + private Intent mIntent; + private char mShortcutNumericChar; + private char mShortcutAlphabeticChar; + private Drawable mIconDrawable; + private int mIconResId = NO_ICON; + private Context mContext; + private OnMenuItemClickListener mClickListener; + private int mFlags = ENABLED; + + public ActionMenuItem(Context context, int group, int id, int categoryOrder, int ordering, + CharSequence title) { + mContext = context; + mId = id; + mGroup = group; + mCategoryOrder = categoryOrder; + mOrdering = ordering; + mTitle = title; + } + + public char getAlphabeticShortcut() { + return mShortcutAlphabeticChar; + } + + public int getGroupId() { + return mGroup; + } + + public Drawable getIcon() { + return mIconDrawable; + } + + public Intent getIntent() { + return mIntent; + } + + public int getItemId() { + return mId; + } + + public ContextMenu.ContextMenuInfo getMenuInfo() { + return null; + } + + public char getNumericShortcut() { + return mShortcutNumericChar; + } + + public int getOrder() { + return mOrdering; + } + + public SubMenu getSubMenu() { + return null; + } + + public CharSequence getTitle() { + return mTitle; + } + + public CharSequence getTitleCondensed() { + return mTitleCondensed != null ? mTitleCondensed : mTitle; + } + + public boolean hasSubMenu() { + return false; + } + + public boolean isCheckable() { + return (mFlags & CHECKABLE) != 0; + } + + public boolean isChecked() { + return (mFlags & CHECKED) != 0; + } + + public boolean isEnabled() { + return (mFlags & ENABLED) != 0; + } + + public boolean isVisible() { + return (mFlags & HIDDEN) == 0; + } + + public MenuItem setAlphabeticShortcut(char alphaChar) { + mShortcutAlphabeticChar = alphaChar; + return this; + } + + public MenuItem setCheckable(boolean checkable) { + mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0); + return this; + } + + public ActionMenuItem setExclusiveCheckable(boolean exclusive) { + mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0); + return this; + } + + public MenuItem setChecked(boolean checked) { + mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0); + return this; + } + + public MenuItem setEnabled(boolean enabled) { + mFlags = (mFlags & ~ENABLED) | (enabled ? ENABLED : 0); + return this; + } + + public MenuItem setIcon(Drawable icon) { + mIconDrawable = icon; + mIconResId = NO_ICON; + return this; + } + + public MenuItem setIcon(@DrawableRes int iconRes) { + mIconResId = iconRes; + if (iconRes != 0) { + mIconDrawable = ContextCompat.getDrawable(mContext, iconRes); + } + return this; + } + + public MenuItem setIntent(Intent intent) { + mIntent = intent; + return this; + } + + public MenuItem setNumericShortcut(char numericChar) { + mShortcutNumericChar = numericChar; + return this; + } + + public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) { + mClickListener = menuItemClickListener; + return this; + } + + public MenuItem setShortcut(char numericChar, char alphaChar) { + mShortcutNumericChar = numericChar; + mShortcutAlphabeticChar = alphaChar; + return this; + } + + public MenuItem setTitle(CharSequence title) { + mTitle = title; + return this; + } + + public MenuItem setTitle(int title) { + mTitle = mContext.getResources().getString(title); + return this; + } + + public MenuItem setTitleCondensed(CharSequence title) { + mTitleCondensed = title; + return this; + } + + public MenuItem setVisible(boolean visible) { + mFlags = (mFlags & ~HIDDEN) | (visible ? 0 : HIDDEN); + return this; + } + + public boolean invoke() { + if (mClickListener != null && mClickListener.onMenuItemClick(this)) { + return true; + } + + if (mIntent != null) { + mContext.startActivity(mIntent); + return true; + } + + return false; + } + + public void setShowAsAction(int show) { + // Do nothing. ActionMenuItems always show as action buttons. + } + + public MenuItem setActionView(View actionView) { + throw new UnsupportedOperationException(); + } + + public View getActionView() { + return null; + } + + @Override + public MenuItem setActionProvider(android.view.ActionProvider actionProvider) { + throw new UnsupportedOperationException(); + } + + @Override + public android.view.ActionProvider getActionProvider() { + throw new UnsupportedOperationException(); + } + + @Override + public MenuItem setActionView(int resId) { + throw new UnsupportedOperationException(); + } + + + @Override + public MenuItem setShowAsActionFlags(int actionEnum) { + setShowAsAction(actionEnum); + return this; + } + + @Override + public boolean expandActionView() { + return false; + } + + @Override + public boolean collapseActionView() { + return false; + } + + @Override + public boolean isActionViewExpanded() { + return false; + } + + @Override + public MenuItem setOnActionExpandListener(OnActionExpandListener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public MenuItem setContentDescription(CharSequence contentDescription) { + return this; + } + + @Override + public CharSequence getContentDescription() { + return null; + } + + @Override + public MenuItem setTooltipText(CharSequence tooltipText) { + return this; + } + + @Override + public CharSequence getTooltipText() { + return null; + } + + @Override + public MenuItem setShortcut(char numericChar, char alphaChar, int numericModifiers, int alphaModifiers) { + return this; + } + + @Override + public MenuItem setNumericShortcut(char numericChar, int numericModifiers) { + return this; + } + + @Override + public int getNumericModifiers() { + return 0; + } + + @Override + public MenuItem setAlphabeticShortcut(char alphaChar, int alphaModifiers) { + return this; + } + + @Override + public int getAlphabeticModifiers() { + return 0; + } + + @Override + public MenuItem setIconTintList(ColorStateList tint) { + return this; + } + + @Override + public ColorStateList getIconTintList() { + return null; + } + + @Override + public MenuItem setIconTintMode(PorterDuff.Mode tintMode) { + return this; + } + + @Override + public PorterDuff.Mode getIconTintMode() { + return null; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/BottomSheetHelper.java b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/BottomSheetHelper.java new file mode 100644 index 000000000..77ca716dd --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/BottomSheetHelper.java @@ -0,0 +1,39 @@ +package com.seafile.seadroid2.bottomsheetmenu; + +import android.view.MenuItem; + +import androidx.fragment.app.FragmentActivity; + +import java.util.List; + +public class BottomSheetHelper { + + public static BottomSheetMenuFragment.Builder buildSheet(FragmentActivity activity, int menuSheetId, OnMenuClickListener onMenuClickListener) { + return new BottomSheetMenuFragment.Builder(activity) + .setMenuSheetId(menuSheetId) + .setColumnCount(1) + .setCancelable(true) + .setOnMenuClickListener(onMenuClickListener); + } + + public static void showSheet(FragmentActivity activity, int menuSheetId, OnMenuClickListener onMenuClickListener) { + new BottomSheetMenuFragment.Builder(activity) + .setMenuSheetId(menuSheetId) + .setColumnCount(1) + .setCancelable(true) + .setOnMenuClickListener(onMenuClickListener) + .show(activity.getSupportFragmentManager()); + } + + public static void showSheet(FragmentActivity activity, List menuItems, OnMenuClickListener onMenuClickListener) { + if (menuItems == null || menuItems.isEmpty()) { + return; + } + new BottomSheetMenuFragment.Builder(activity) + .setMenuItems(menuItems) + .setColumnCount(1) + .setCancelable(true) + .setOnMenuClickListener(onMenuClickListener) + .show(activity.getSupportFragmentManager()); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/BottomSheetMenuAdapter.java b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/BottomSheetMenuAdapter.java new file mode 100644 index 000000000..ae88e6671 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/BottomSheetMenuAdapter.java @@ -0,0 +1,71 @@ +package com.seafile.seadroid2.bottomsheetmenu; + +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.seafile.seadroid2.R; + +import java.util.ArrayList; +import java.util.List; + +public class BottomSheetMenuAdapter extends RecyclerView.Adapter { + private List dataList = new ArrayList<>(); + private int columnCount = 1; + private OnMenuClickListener onMenuClickListener; + + public BottomSheetMenuAdapter(List dataList, int columnCount) { + this.dataList = dataList; + this.columnCount = columnCount; + } + + public void setOnMenuClickListener(OnMenuClickListener onMenuClickListener) { + this.onMenuClickListener = onMenuClickListener; + } + + @NonNull + @Override + public BottomSheetViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + int layoutId = columnCount == 1 ? R.layout.bottom_sheet_item_list : R.layout.bottom_sheet_item_grid; + View view = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false); + return new BottomSheetViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull BottomSheetViewHolder holder, int position) { + final int p = position; + holder.icon.setImageDrawable(dataList.get(p).getIcon()); + holder.name.setText(dataList.get(p).getTitle()); + if (onMenuClickListener != null) { + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onMenuClickListener.onMenuClick(dataList.get(p)); + } + }); + } + } + + @Override + public int getItemCount() { + return dataList.size(); + } + + public static class BottomSheetViewHolder extends RecyclerView.ViewHolder { + public ImageView icon; + public TextView name; + + public BottomSheetViewHolder(@NonNull View itemView) { + super(itemView); + icon = itemView.findViewById(R.id.icon); + name = itemView.findViewById(R.id.name); + } + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/BottomSheetMenuFragment.java b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/BottomSheetMenuFragment.java new file mode 100644 index 000000000..af28dff79 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/BottomSheetMenuFragment.java @@ -0,0 +1,189 @@ +package com.seafile.seadroid2.bottomsheetmenu; + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.MenuRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.fragment.app.FragmentManager; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.blankj.utilcode.util.CollectionUtils; +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.seafile.seadroid2.R; + +import java.util.ArrayList; +import java.util.List; + +public class BottomSheetMenuFragment extends BottomSheetDialogFragment { + private final Builder builder; + + public BottomSheetMenuFragment(Builder builder) { + this.builder = builder; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (builder == null) { + throw new IllegalArgumentException("need to create Builder"); + } + + if (builder.columnCount < 0) { + throw new IllegalArgumentException("columnCount should not less than 0"); + } + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(requireContext(), R.style.ThemeOverlay_Catalog_BottomSheetDialog_Scrollable); + bottomSheetDialog.setContentView(R.layout.layout_bottom_sheet_recycler_menu); + bottomSheetDialog.setDismissWithAnimation(true); + + View parentView = bottomSheetDialog.findViewById(R.id.bottom_sheet_container); + setView(parentView); + return bottomSheetDialog; + } + + public void setView(View rootView) { + if (TextUtils.isEmpty(builder.title)) { + rootView.findViewById(R.id.title).setVisibility(View.GONE); + } else { + TextView title = rootView.findViewById(R.id.title); + title.setText(builder.title); + } + + if (!CollectionUtils.isEmpty(builder.menuItems)) { + RecyclerView rv = rootView.findViewById(R.id.rv); + if (builder.columnCount == 1) { + rv.setLayoutManager(new LinearLayoutManager(requireContext())); + } else { + rv.setLayoutManager(new GridLayoutManager(requireContext(), builder.columnCount)); + } + + BottomSheetMenuAdapter adapter = new BottomSheetMenuAdapter(builder.menuItems, builder.columnCount); + adapter.setOnMenuClickListener(new OnMenuClickListener() { + @Override + public void onMenuClick(MenuItem menuItem) { + dismiss(); + if (builder.onMenuClickListener != null) { + builder.onMenuClickListener.onMenuClick(menuItem); + } + } + }); + + rv.setAdapter(adapter); + } + + } + + + public static class Builder { + public Context context; + + public int columnCount = 1; + @MenuRes + public int menuSheetId = -1; + + public boolean cancelable; + + public String title; + + public OnMenuClickListener onMenuClickListener; + public List menuItems; + + public Builder(Context context) { + this.context = context; + } + + public Builder setColumnCount(int columnCount) { + this.columnCount = columnCount; + return this; + } + + public Builder setMenuSheetId(int menuSheetId) { + this.menuSheetId = menuSheetId; + + ActionMenu menu = new ActionMenu(context); + + MenuInflater inflater = new MenuInflater(context); + inflater.inflate(menuSheetId, menu); + + List items = new ArrayList(menu.size()); + + for (int i = 0; i < menu.size(); i++) { + items.add(menu.getItem(i)); + } + + return setMenuItems(items); + } + + public Builder setMenuItems(List menuItems) { + if (menuItems != null && !menuItems.isEmpty()) { + if (this.menuItems == null) { + this.menuItems = new ArrayList<>(menuItems.size()); + } + this.menuItems.addAll(menuItems); + } + + return this; + } + + public Builder setCancelable(boolean cancelable) { + this.cancelable = cancelable; + return this; + } + + public Builder setTitle(@StringRes int title) { + this.title = context.getString(title); + return this; + } + + public Builder setTitle(String title) { + this.title = title; + return this; + } + + public Builder removeMenu(int menuId) { + if (CollectionUtils.isEmpty(this.menuItems)) { + return this; + } + int index = -1; + for (int i = 0; i < menuItems.size(); i++) { + if (menuId == menuItems.get(i).getItemId()) { + index = i; + break; + } + } + if (index > -1) { + this.menuItems.remove(index); + } + return this; + } + + public Builder setOnMenuClickListener(OnMenuClickListener onMenuClickListener) { + this.onMenuClickListener = onMenuClickListener; + return this; + } + + public BottomSheetMenuFragment build() { + return new BottomSheetMenuFragment(this); + } + + public void show(FragmentManager fragmentManager) { + build().show(fragmentManager, BottomSheetMenuFragment.class.getSimpleName()); + } + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/OnMenuClickListener.java b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/OnMenuClickListener.java new file mode 100644 index 000000000..e6bae6e4f --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/bottomsheetmenu/OnMenuClickListener.java @@ -0,0 +1,7 @@ +package com.seafile.seadroid2.bottomsheetmenu; + +import android.view.MenuItem; + +public interface OnMenuClickListener { + void onMenuClick(MenuItem menuItem); +} diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/BucketsSelectionFragment.java b/app/src/main/java/com/seafile/seadroid2/cameraupload/BucketsSelectionFragment.java deleted file mode 100644 index f92e98247..000000000 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/BucketsSelectionFragment.java +++ /dev/null @@ -1,183 +0,0 @@ -package com.seafile.seadroid2.cameraupload; - -import android.content.Context; -import android.graphics.Bitmap; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.provider.MediaStore; -import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.GridView; -import android.widget.ImageView; -import android.widget.TextView; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeadroidApplication; -import com.seafile.seadroid2.SettingsManager; -import com.seafile.seadroid2.util.GlideApp; -import com.seafile.seadroid2.util.Utils; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -/** - * Choose gallery buckets to upload - */ -public class BucketsSelectionFragment extends Fragment { - - private List buckets; - private boolean[] selectedBuckets; - private ImageAdapter imageAdapter; - private Bitmap[] thumbnails; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - - View rootView = getActivity().getLayoutInflater().inflate(R.layout.cuc_bucket_selection_layout, null); - SettingsManager settingsManager = SettingsManager.instance(); - List currentBucketList = settingsManager.getCameraUploadBucketList(); - buckets = GalleryBucketUtils.getMediaBuckets(getActivity().getApplicationContext()); - selectedBuckets = new boolean[buckets.size()]; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - thumbnails = new Bitmap[buckets.size()]; - for (int i = 0; i < buckets.size(); i++) { - GalleryBucketUtils.Bucket b = buckets.get(i); - if (b.image_id > 0) { - thumbnails[i] = MediaStore.Images.Thumbnails.getThumbnail( - getActivity().getApplicationContext().getContentResolver(), b.image_id, - MediaStore.Images.Thumbnails.MINI_KIND, null); - } - if (currentBucketList.size() > 0) - selectedBuckets[i] = currentBucketList.contains(b.id); - else - selectedBuckets[i] = b.isCameraBucket; - } - } else { - for (int i = 0; i < this.buckets.size(); i++) { - GalleryBucketUtils.Bucket b = this.buckets.get(i); - if (b.isImages != null && b.isImages.equals(GalleryBucketUtils.IMAGES)) { - Uri image_uri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, b.imageId); - String image_path = Utils.getRealPathFromURI(SeadroidApplication.getAppContext(), image_uri, "images"); - b.imagePath = image_path; - } else { - Uri video_uri = Uri.withAppendedPath(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, b.videoId); - String videoPath = Utils.getRealPathFromURI(SeadroidApplication.getAppContext(), video_uri, "video"); - b.videoPath = videoPath; - } - - // if the user has previously selected buckets, mark these. - // otherwise, select the ones that will be auto-guessed. - if (currentBucketList.size() > 0) - selectedBuckets[i] = currentBucketList.contains(b.id); - else - selectedBuckets[i] = b.isCameraBucket; - } - } - - GridView imagegrid = (GridView) rootView.findViewById(R.id.cuc_bucket_selection_grid); - imageAdapter = new ImageAdapter(); - imagegrid.setAdapter(imageAdapter); - - return rootView; - } - - @Override - public void onDestroy() { - super.onDestroy(); - getActivity().finish(); - } - - @Override - public void onPause() { - super.onPause(); - getActivity().finish(); - } - - public List getSelectedBuckets() { - List ret = new ArrayList<>(); - for (int i = 0; i < buckets.size(); i++) { - if (selectedBuckets[i]) { - ret.add(buckets.get(i).id); - } - } - - return ret; - } - - public class ImageAdapter extends BaseAdapter { - private LayoutInflater mInflater; - - public ImageAdapter() { - mInflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - public int getCount() { - return buckets.size(); - } - - public Object getItem(int position) { - return position; - } - - public long getItemId(int position) { - return position; - } - - public View getView(int position, View convertView, ViewGroup parent) { - final ViewHolder holder; - if (convertView == null) { - holder = new ViewHolder(); - convertView = mInflater.inflate(R.layout.bucket_item, null); - holder.imageview = (ImageView) convertView.findViewById(R.id.bucket_item_thumbImage); - holder.text = (TextView) convertView.findViewById(R.id.bucket_item_name); - holder.marking = (ImageView) convertView.findViewById(R.id.bucket_item_marking); - - convertView.setTag(holder); - } - else { - holder = (ViewHolder) convertView.getTag(); - } - holder.imageview.setId(position); - holder.text.setText(buckets.get(position).name); - holder.imageview.setOnClickListener(new View.OnClickListener() { - - public void onClick(View v) { - int id = v.getId(); - selectedBuckets[id] = !selectedBuckets[id]; - if (selectedBuckets[id]) - holder.marking.setBackgroundResource(R.drawable.checkbox_checked); - else - holder.marking.setBackgroundResource(R.drawable.checkbox_unchecked); - } - }); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - holder.imageview.setImageBitmap(thumbnails[position]); - } else { - if (buckets.get(position).isImages != null && buckets.get(position).isImages.equals(GalleryBucketUtils.IMAGES)) { - GlideApp.with(getActivity()).load(buckets.get(position).imagePath).into(holder.imageview); - } else { - GlideApp.with(getActivity()).load(Uri.fromFile(new File(buckets.get(position).videoPath))).into(holder.imageview); - } - } - if (selectedBuckets[position]) - holder.marking.setBackgroundResource(R.drawable.checkbox_checked); - else - holder.marking.setBackgroundResource(R.drawable.checkbox_unchecked); - holder.id = position; - return convertView; - } - } - - static class ViewHolder { - ImageView imageview; - ImageView marking; - TextView text; - int id; - } -} - diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadConfigActivity.java b/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadConfigActivity.java deleted file mode 100644 index b224dbeae..000000000 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadConfigActivity.java +++ /dev/null @@ -1,213 +0,0 @@ -package com.seafile.seadroid2.cameraupload; - -import android.content.Intent; -import android.os.Bundle; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentStatePagerAdapter; -import androidx.viewpager.widget.ViewPager; -import androidx.viewpager.widget.ViewPager.OnPageChangeListener; -import android.view.View; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SettingsManager; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.ui.BaseActivity; -import com.seafile.seadroid2.ui.activity.SeafilePathChooserActivity; -import com.seafile.seadroid2.ui.settings.SettingsFragment; -import com.seafile.seadroid2.util.SystemSwitchUtils; - -import java.util.List; - -import me.relex.circleindicator.CircleIndicator; - - -/** - * Camera upload configuration helper - */ -public class CameraUploadConfigActivity extends BaseActivity { - public String DEBUG_TAG = "CameraUploadConfigActivity"; - - private ViewPager mViewPager; - - private CircleIndicator magicIndicator; - private BucketsFragment mBucketsFragment; - private CloudLibraryFragment mCloudLibFragment; - private WhatToUploadFragment whatToUploadFragment; - private SettingsManager sm; - private SeafRepo mSeafRepo; - private Account mAccount; - /** handling data from configuration helper */ - private boolean isChooseBothPages; - /** handling data from cloud library page */ - private boolean isChooseLibPage; - /** handling data from local directory page */ - private boolean isChooseDirPage; - private int mCurrentPosition; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - overridePendingTransition(R.anim.fade_in, R.anim.fade_out); - - setContentView(R.layout.cuc_activity_layout); - - if (getSupportActionBar() != null) - getSupportActionBar().hide(); - - isChooseBothPages = getIntent().getBooleanExtra(SettingsFragment.CAMERA_UPLOAD_BOTH_PAGES, false); - isChooseLibPage = getIntent().getBooleanExtra(SettingsFragment.CAMERA_UPLOAD_REMOTE_LIBRARY, false); - isChooseDirPage = getIntent().getBooleanExtra(SettingsFragment.CAMERA_UPLOAD_LOCAL_DIRECTORIES, false); - - mViewPager = (ViewPager) findViewById(R.id.cuc_pager); - - FragmentManager fm = getSupportFragmentManager(); - mViewPager.setAdapter(new CameraUploadConfigAdapter(fm)); - mViewPager.setOffscreenPageLimit(6); - mViewPager.addOnPageChangeListener(pageChangeListener); - - magicIndicator = findViewById(R.id.cuc_indicator); - magicIndicator.setViewPager(mViewPager); - - sm = SettingsManager.instance(); - - if (isChooseLibPage || isChooseDirPage) { - magicIndicator.setVisibility(View.GONE); - } - } - - /** - * Page scroll listener. - */ - private OnPageChangeListener pageChangeListener = new OnPageChangeListener() { - - @Override - public void onPageScrollStateChanged(int scrollState) {} - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - mCurrentPosition = position; - } - - @Override - public void onPageSelected(int page){} - }; - - public void saveCameraUploadInfo(Account account, SeafRepo seafRepo) { - mSeafRepo = seafRepo; - mAccount = account; - } - - public void saveSettings() { - SystemSwitchUtils.getInstance(this).syncSwitchUtils(); - if (isChooseBothPages || isChooseDirPage) { - - SettingsManager settingsManager = SettingsManager.instance(); - List selectedBuckets = mBucketsFragment.getSelectedBuckets(); - if (mBucketsFragment.isAutoScanSelected()) { - selectedBuckets.clear(); - } - // this is the only setting that is safed here. all other are returned to caller - // and safed there... - settingsManager.setCameraUploadBucketList(selectedBuckets); - } - - Intent intent = new Intent(); - // update cloud library data - if (mSeafRepo != null && mAccount != null) { - intent.putExtra(SeafilePathChooserActivity.DATA_REPO_NAME, mSeafRepo.repo_name); - intent.putExtra(SeafilePathChooserActivity.DATA_REPO_ID, mSeafRepo.repo_id); - intent.putExtra(SeafilePathChooserActivity.DATA_ACCOUNT, mAccount); - } - - setResult(RESULT_OK, intent); - - } - - - @Override - public void onBackPressed() { - if (mCurrentPosition == 0) { - setResult(RESULT_CANCELED); - super.onBackPressed(); - } else { - // navigate to previous page when press back button - mCurrentPosition -= 1; - mViewPager.setCurrentItem(mCurrentPosition); - } - } - - public boolean isChooseLibPage() { - return isChooseLibPage; - } - - public boolean isChooseDirPage() { - return isChooseDirPage; - } - - public void saveDataPlanAllowed(boolean isAllowed) { - sm.saveDataPlanAllowed(isAllowed); - } - - public void saveVideosAllowed(boolean isAllowed) { - sm.saveVideosAllowed(isAllowed); - } - - class CameraUploadConfigAdapter extends FragmentStatePagerAdapter { - - public CameraUploadConfigAdapter(FragmentManager fm) { - super(fm); - } - - // This method controls which fragment should be shown on a specific screen. - @Override - public Fragment getItem(int position) { - - if (isChooseLibPage) { - return position == 0 ? new CloudLibraryFragment() : null; - } - - if (isChooseDirPage) { - switch (position) { - case 0: - mBucketsFragment = new BucketsFragment(); - return mBucketsFragment; - default: - return null; - } - - } - - // Assign the appropriate screen to the fragment object, based on which screen is displayed. - switch (position) { - case 0: - return new ConfigWelcomeFragment(); - case 1: - return new HowToUploadFragment(); - case 2: - whatToUploadFragment = new WhatToUploadFragment(); - return whatToUploadFragment; - case 3: - mBucketsFragment = new BucketsFragment(); - return mBucketsFragment; - case 4: - mCloudLibFragment = new CloudLibraryFragment(); - return mCloudLibFragment; - case 5: - return new ReadyToScanFragment(); - default: - return null; - } - } - - @Override - public int getCount() { - if (isChooseLibPage || isChooseDirPage) - return 1; - else - return 6; - } - - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryAccountAdapter.java b/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryAccountAdapter.java deleted file mode 100644 index d6caaca67..000000000 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryAccountAdapter.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.seafile.seadroid2.cameraupload; - -import android.content.Context; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.ui.account.adapter.AccountAdapter; - -/** - * Adapter for choosing an cloud account - */ -public class CloudLibraryAccountAdapter extends AccountAdapter { - - public CloudLibraryAccountAdapter(Context context) { - super(context); - } - - @Override - protected int getChildLayout() { - return R.layout.cuc_account_list_item; - } - - @Override - protected int getChildTitleId() { - return R.id.cuc_account_list_item_account_title; - } - - @Override - protected int getChildSubTitleId() { - return R.id.cuc_account_list_item_account_subtitle; - } - - @Override - protected int getChildIconId() { - return R.id.cuc_account_list_item_account_icon; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryAdapter.java b/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryAdapter.java deleted file mode 100644 index 066341119..000000000 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryAdapter.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.seafile.seadroid2.cameraupload; - -import android.view.View; -import android.widget.ImageView; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.ui.repo.ReposAdapter; - -/** - * Cloud library adapter - */ -public class CloudLibraryAdapter extends ReposAdapter { - - /** mark the checked repo */ - public SeafRepo selectedRepo; - - public CloudLibraryAdapter(boolean onlyShowWritableRepos, String encryptedRepoId) { - super(onlyShowWritableRepos, encryptedRepoId); - } - - @Override - protected int getChildLayout() { - return R.layout.cuc_repo_list_item; - } - - @Override - protected int getChildTitleId() { - return R.id.cuc_repo_list_item_title; - } - - @Override - protected int getChildSubTitleId() { - return R.id.cuc_repo_list_item_subtitle; - } - - @Override - protected int getChildIconId() { - return R.id.cuc_repo_list_item_icon; - } - - @Override - protected int getChildActionId() { - return R.id.cuc_repo_list_item_action; - } - - @Override - protected SeafRepo getChildSeafRepo(int position) { - return repos.get(position); - } - - @Override - public boolean isEnabled(int position) { - // if repo is encrypted, disable it - // because camera upload service doesn`t support it - return !(repos.get(position).encrypted); - } - - @Override - protected void showRepoSelectedIcon(int position, ImageView imageView) { - if (selectedRepo == null) { - imageView.setVisibility(View.INVISIBLE); - return; - } - - if (selectedRepo.equals(repos.get(position))) - imageView.setVisibility(View.VISIBLE); - else - imageView.setVisibility(View.INVISIBLE); - } - - public void setSelectedRepo(SeafRepo repo) { - selectedRepo = repo; - } - - @Override - public int getCount() { - return repos.size(); - } - - @Override - public boolean isEmpty() { - return repos.isEmpty(); - } - - @Override - public SeafRepo getItem(int position) { - return repos.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - -} diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryFragment.java b/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryFragment.java deleted file mode 100644 index 6f3abe872..000000000 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibraryFragment.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.seafile.seadroid2.cameraupload; - -import android.os.Bundle; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import com.seafile.seadroid2.R; -/** - * Cloud Library fragment - */ -public class CloudLibraryFragment extends Fragment { - - private CameraUploadConfigActivity mActivity; - private FragmentManager fm; - private Button mDoneBtn; - private CloudLibrarySelectionFragment mSelectionFragment; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - - mActivity = (CameraUploadConfigActivity) getActivity(); - View rootView = mActivity.getLayoutInflater().inflate(R.layout.cuc_remote_library_fragment, null); - - fm = getChildFragmentManager(); - fm.beginTransaction() - .add(R.id.cuc_remote_library_list_container, getAccountOrReposSelectionFragment()) - .commit(); - - mDoneBtn = (Button) rootView.findViewById(R.id.cuc_remote_library_btn); - mDoneBtn.setOnClickListener(onClickListener); - if (mActivity.isChooseLibPage()) - mDoneBtn.setVisibility(View.VISIBLE); - - return rootView; - } - - private View.OnClickListener onClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - mActivity.saveSettings(); - mActivity.finish(); - } - }; - - /** - * Instantiates a new fragment if mSelectionFragment is null. - * Returns the current fragment, otherwise. - */ - public CloudLibrarySelectionFragment getAccountOrReposSelectionFragment() { - if (mSelectionFragment == null) { - mSelectionFragment = new CloudLibrarySelectionFragment(); - } - - return mSelectionFragment; - } - -} - diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibrarySelectionFragment.java b/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibrarySelectionFragment.java deleted file mode 100644 index c0231e044..000000000 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/CloudLibrarySelectionFragment.java +++ /dev/null @@ -1,811 +0,0 @@ -package com.seafile.seadroid2.cameraupload; - -import android.content.Intent; -import android.database.Cursor; -import android.os.AsyncTask; -import android.os.Bundle; -import androidx.fragment.app.Fragment; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import com.google.common.collect.Lists; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeafConnection; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.AccountManager; -import com.seafile.seadroid2.avatar.Avatar; -import com.seafile.seadroid2.avatar.AvatarManager; -import com.seafile.seadroid2.data.DataManager; -import com.seafile.seadroid2.data.SeafDirent; -import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.context.NavContext; -import com.seafile.seadroid2.ui.repo.DirentsAdapter; -import com.seafile.seadroid2.ui.dialog.PasswordDialog; -import com.seafile.seadroid2.ui.dialog.TaskDialog; -import com.seafile.seadroid2.util.ConcurrentAsyncTask; -import com.seafile.seadroid2.util.Utils; - -import java.net.HttpURLConnection; -import java.util.ArrayList; -import java.util.List; - -/** - * Choose account and library for camera upload - */ -public class CloudLibrarySelectionFragment extends Fragment { - public static final String DEBUG_TAG = "CloudLibrarySelectionFragment"; - - public static final String PASSWORD_DIALOG_FRAGMENT_TAG = "passwordDialogFragmentTag"; - public static final String ONLY_SHOW_WRITABLE_REPOS = "onlyShowWritableRepos"; - public static final String SHOW_ENCRYPTED_REPOS = "showEncryptedRepos"; - public static final String ENCRYPTED_REPO_ID = "encryptedRepoId"; - - public static final String DATA_REPO_ID = "repoID"; - public static final String DATA_REPO_NAME = "repoNAME"; - public static final String DATA_DIR = "dir"; - public static final String DATA_ACCOUNT = "account"; - - private static final int STEP_CHOOSE_ACCOUNT = 1; - private static final int STEP_CHOOSE_REPO = 2; - private static final int STEP_CHOOSE_DIR = 3; - private int mStep = 1; - - private CameraUploadConfigActivity mActivity; - private CloudLibraryAccountAdapter mAccountAdapter; - private CloudLibraryAdapter mReposAdapter; - private DirentsAdapter mDirentsAdapter; - private AccountManager mAccountManager; - private DataManager mDataManager; - private NavContext mNavContext; - private Account mAccount; - private LoadAccountsTask mLoadAccountsTask; - private LoadReposTask mLoadReposTask; - private LoadDirTask mLoadDirTask; - private AvatarManager avatarManager; - - private RelativeLayout mUpLayout; - private TextView mCurrentFolderText; - private TextView mEmptyText, mErrorText; - private ImageView mRefreshBtn; - private View mProgressContainer, mListContainer; - private ListView mFoldersListView; - private Cursor mCursor; - private String mCurrentDir; - - private boolean canChooseAccount; - private boolean onlyShowWritableRepos; - private String encryptedRepoId; - - /** only show repo list for camera upload */ - private boolean isOnlyChooseRepo; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - mActivity = (CameraUploadConfigActivity) getActivity(); - Intent intent = mActivity.getIntent(); - avatarManager = new AvatarManager(); - Account account = intent.getParcelableExtra("account"); - if (account == null) { - canChooseAccount = true; - } else { - mAccount = account; - } - onlyShowWritableRepos = intent.getBooleanExtra(ONLY_SHOW_WRITABLE_REPOS, true); - encryptedRepoId = intent.getStringExtra(ENCRYPTED_REPO_ID); - isOnlyChooseRepo = true; - - View rootView = getActivity().getLayoutInflater().inflate(R.layout.cuc_multi_selection_layout, null); - mFoldersListView = (ListView) rootView.findViewById(R.id.cuc_multi_selection_lv); - mFoldersListView.setFastScrollEnabled(true); - mUpLayout = (RelativeLayout) rootView.findViewById(R.id.cuc_multi_selection_up_layout); - mCurrentFolderText = (TextView) rootView.findViewById(R.id.cuc_multi_selection_current_directory_txt); - mEmptyText = (TextView) rootView.findViewById(R.id.cuc_multi_selection_empty_msg); - mErrorText = (TextView) rootView.findViewById(R.id.cuc_multi_selection_error_msg); - mRefreshBtn = (ImageView) rootView.findViewById(R.id.cuc_multi_selection_refresh_iv); - mProgressContainer = rootView.findViewById(R.id.cuc_multi_selection_progress_container); - mListContainer = rootView.findViewById(R.id.cuc_multi_selection_list_container); - mRefreshBtn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - refreshList(true); - } - }); - - mUpLayout.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - try { - stepBack(); - } catch (Exception e) { - e.printStackTrace(); - } - - } - - }); - - mFoldersListView.setOnItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - onListItemClick(parent, position, id); - } - }); - - if (canChooseAccount) { - chooseAccount(); - } else { - chooseRepo(); - } - - return rootView; - } - - @Override - public void onResume() { - super.onResume(); - loadAvatarUrls(48); - } - - private void refreshList(final boolean forceRefresh) { - switch (mStep) { - case STEP_CHOOSE_ACCOUNT: - if (mLoadAccountsTask != null && mLoadAccountsTask.getStatus() != AsyncTask.Status.FINISHED) { - return; - } else { - chooseAccount(false); - break; - } - case STEP_CHOOSE_REPO: - if (mLoadReposTask != null && mLoadReposTask.getStatus() != AsyncTask.Status.FINISHED) { - return; - } else { - chooseRepo(forceRefresh); - break; - } - case STEP_CHOOSE_DIR: - if (mLoadDirTask != null && mLoadDirTask.getStatus() != AsyncTask.Status.FINISHED) { - return; - } else { - SeafRepo repo = getDataManager().getCachedRepoByID(getNavContext().getRepoID()); - if (repo.encrypted && !getDataManager().getRepoPasswordSet(repo.repo_id)) { - String password = getDataManager().getRepoPassword(repo.repo_id); - showPasswordDialog(repo.repo_name, repo.repo_id, - new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - chooseRepo(forceRefresh); - } - } , password); - } - chooseDir(forceRefresh); - break; - } - } - } - - public void onListItemClick(final View v, final int position, final long id) { - NavContext nav = getNavContext(); - SeafRepo repo = null; - - if (mStep == STEP_CHOOSE_REPO) { - repo = getReposAdapter().getItem(position); - //mCurrentFolderText.setText(nav.getRepoName()); - } else if (mStep == STEP_CHOOSE_DIR) { - repo = getDataManager().getCachedRepoByID(nav.getRepoID()); - - } - - if (repo != null) { - if (repo.encrypted && !getDataManager().getRepoPasswordSet(repo.repo_id)) { - String password = getDataManager().getRepoPassword(repo.repo_id); - showPasswordDialog(repo.repo_name, repo.repo_id, new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - onListItemClick(v, position, id); - } - }, password); - - return; - } - } - - switch (mStep) { - case STEP_CHOOSE_ACCOUNT: - setAccount(getAccountAdapter().getItem(position)); - mCurrentDir = mAccount.getDisplayName(); - setCurrentDirText(mCurrentDir); - chooseRepo(); - break; - case STEP_CHOOSE_REPO: - if (!isOnlyChooseRepo) { - nav.setRepoName(repo.repo_name); - nav.setRepoID(repo.repo_id); - nav.setDirPath("/"); - chooseDir(); - } - mCurrentDir = getString(R.string.settings_cuc_remote_lib_repo, repo.repo_name); - setCurrentDirText(mCurrentDir); - SeafRepo seafRepo = getReposAdapter().getItem(position); - onRepoSelected(mAccount, seafRepo); - break; - case STEP_CHOOSE_DIR: - SeafDirent dirent = getDirentsAdapter().getItem(position); - mCurrentDir += "/" + dirent.name; - setCurrentDirText(mCurrentDir); - - if (dirent.type == SeafDirent.DirentType.FILE) { - return; - } - - String path = Utils.pathJoin(nav.getDirPath(), dirent.name); - nav.setDirPath(path); - refreshDir(); - break; - } - } - - private void onRepoSelected(Account account, SeafRepo seafRepo) { - mActivity.saveCameraUploadInfo(account, seafRepo); - getReposAdapter().setSelectedRepo(seafRepo); - getReposAdapter().notifyDataSetChanged(); - } - - private void stepBack() { - stepBack(false); - } - - private void stepBack(boolean cancelIfFirstStep) { - switch (mStep) { - case STEP_CHOOSE_ACCOUNT: - if (cancelIfFirstStep) { - mActivity.finish(); - } - mUpLayout.setVisibility(View.INVISIBLE); - break; - case STEP_CHOOSE_REPO: - if (canChooseAccount) { - mCurrentDir = getString(R.string.settings_cuc_remote_lib_account); - setCurrentDirText(mCurrentDir); - chooseAccount(false); - } else if (cancelIfFirstStep) { - mActivity.finish(); - } - break; - case STEP_CHOOSE_DIR: - if (getNavContext().isRepoRoot()) { - mCurrentDir = getAccountManager().getCurrentAccount().getEmail(); - setCurrentDirText(mCurrentDir); - chooseRepo(); - } else { - String path = getNavContext().getDirPath(); - mCurrentDir = getNavContext().getRepoName() + Utils.getParentPath(path); - setCurrentDirText(mCurrentDir); - getNavContext().setDirPath(Utils.getParentPath(path)); - refreshDir(); - } - break; - } - } - - private void showPasswordDialog() { - NavContext nav = getNavContext(); - String repoName = nav.getRepoName(); - String repoID = nav.getRepoID(); - - showPasswordDialog(repoName, repoID, new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - refreshDir(); - } - }, null); - } - - public void showPasswordDialog(String repoName, String repoID, - TaskDialog.TaskDialogListener listener, String password) { - PasswordDialog passwordDialog = new PasswordDialog(); - passwordDialog.setRepo(repoName, repoID, mAccount); - if (password != null) { - passwordDialog.setPassword(password); - } - passwordDialog.setTaskDialogLisenter(listener); - passwordDialog.show(mActivity.getSupportFragmentManager(), PASSWORD_DIALOG_FRAGMENT_TAG); - } - - private void chooseDir() { - chooseDir(false); - } - - private void chooseDir(boolean forceRefresh) { - mStep = STEP_CHOOSE_DIR; - mUpLayout.setVisibility(View.VISIBLE); - mEmptyText.setText(R.string.dir_empty); - - setListAdapter(getDirentsAdapter()); - refreshDir(forceRefresh); - } - - private void chooseAccount() { - chooseAccount(true); - } - - /** - * List all accounts - */ - private void chooseAccount(boolean forwardIfOnlyOneAccount) { - mStep = STEP_CHOOSE_ACCOUNT; - mUpLayout.setVisibility(View.INVISIBLE); - mEmptyText.setText(R.string.no_account); - mCurrentDir = getString(R.string.settings_cuc_remote_lib_account); - setCurrentDirText(mCurrentDir); - - mLoadAccountsTask = new LoadAccountsTask(getAccountManager(), forwardIfOnlyOneAccount); - - ConcurrentAsyncTask.execute(mLoadAccountsTask); - setListAdapter(getAccountAdapter()); - } - - /** - * List all repos - */ - private void chooseRepo() { - chooseRepo(false); - } - - private void chooseRepo(boolean forceRefresh) { - mStep = STEP_CHOOSE_REPO; - mUpLayout.setVisibility(View.VISIBLE); - mCurrentDir = mAccount.getDisplayName(); - setCurrentDirText(mCurrentDir); - - setListAdapter(getReposAdapter()); - - getNavContext().setRepoID(null); - - if (!Utils.isNetworkOn() || !forceRefresh) { - List repos = getDataManager().getReposFromCache(); - if (repos != null) { - updateAdapterWithRepos(repos); - return; - } - } - - mLoadReposTask = new LoadReposTask(getDataManager()); - ConcurrentAsyncTask.execute(mLoadReposTask); - } - - private void refreshDir() { - refreshDir(false); - } - - private void refreshDir(boolean forceRefresh) { - String repoID = getNavContext().getRepoID(); - String dirPath = getNavContext().getDirPath(); - - if (!Utils.isNetworkOn() || !forceRefresh) { - List dirents = getDataManager().getCachedDirents( - getNavContext().getRepoID(), getNavContext().getDirPath()); - if (dirents != null) { - updateAdapterWithDirents(dirents); - return; - } - } - - mLoadDirTask = new LoadDirTask(repoID, dirPath, getDataManager()); - ConcurrentAsyncTask.execute(mLoadDirTask); - } - - private void updateAdapterWithDirents(List dirents) { - getDirentsAdapter().setDirents(dirents); - showListOrEmptyText(dirents.size()); - } - - private void updateAdapterWithRepos(List repos) { - // remove encrypted repos in order to "hide" them in selection list - List filteredRepos = Lists.newArrayList(); - for (SeafRepo repo : repos) { - if (!repo.encrypted) - filteredRepos.add(repo); - } - getReposAdapter().setRepos(filteredRepos); - showListOrEmptyText(filteredRepos.size()); - } - - private CloudLibraryAccountAdapter getAccountAdapter() { - if (mAccountAdapter == null) { - mAccountAdapter = new CloudLibraryAccountAdapter(mActivity); - } - - return mAccountAdapter; - } - - private CloudLibraryAdapter getReposAdapter() { - if (mReposAdapter == null) { - mReposAdapter = new CloudLibraryAdapter(onlyShowWritableRepos, encryptedRepoId); - } - - return mReposAdapter; - } - - private DirentsAdapter getDirentsAdapter() { - if (mDirentsAdapter == null) { - mDirentsAdapter = new DirentsAdapter(); - } - - return mDirentsAdapter; - } - - private void showListOrEmptyText(int listSize) { - if (listSize == 0) { - mFoldersListView.setVisibility(View.GONE); - mEmptyText.setVisibility(View.VISIBLE); - } else { - mFoldersListView.setVisibility(View.VISIBLE); - mEmptyText.setVisibility(View.GONE); - } - } - - private void setListAdapter(BaseAdapter adapter) { - mFoldersListView.setAdapter(adapter); - } - - private DataManager getDataManager() { - if (mDataManager == null) { - mDataManager = new DataManager(mAccount); - } - - return mDataManager; - } - - private void setAccount(Account account) { - mAccount = account; - mDataManager = new DataManager(account); - } - - private AccountManager getAccountManager() { - if (mAccountManager == null) { - mAccountManager = new AccountManager(getActivity()); - } - - return mAccountManager; - } - - private NavContext getNavContext() { - if (mNavContext == null) { - mNavContext = new NavContext(); - } - - return mNavContext; - } - - /** - * Sets the current directory's text. - */ - private void setCurrentDirText(String text) { - mCurrentFolderText.setText(text); - } - - @Override - public void onDestroy() { - super.onDestroy(); - getActivity().finish(); - } - - @Override - public void onPause() { - super.onPause(); - getActivity().finish(); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - - if (isRemoving()) { - mCursor.close(); - mCursor = null; - } - - } - - private class LoadAccountsTask extends AsyncTask { - private List accounts; - private Exception err; - private AccountManager accountManager; - private boolean forwardIfOnlyOneAccount; - - public LoadAccountsTask(AccountManager accountManager, boolean forwardIfOnlyOneAccount) { - this.accountManager = accountManager; - this.forwardIfOnlyOneAccount = forwardIfOnlyOneAccount; - } - - @Override - protected void onPreExecute() { - showLoading(true); - } - - @Override - protected Void doInBackground(Void... params) { - try { - accounts = accountManager.getAccountList(); - } catch (Exception e) { - err = e; - } - - return null; - } - - @Override - protected void onPostExecute(Void v) { - showLoading(false); - if (err != null || accounts == null) { - setErrorMessage(R.string.load_accounts_fail); - if (err != null) { - Log.d(DEBUG_TAG, "failed to load accounts: " + err.getMessage()); - } - return; - } - - if (accounts.size() == 1 && forwardIfOnlyOneAccount) { - // Only 1 account. Go to next step. - setAccount(accounts.get(0)); - chooseRepo(); - return; - } - - CloudLibraryAccountAdapter adapter = getAccountAdapter(); - adapter.clear(); - for (Account account: accounts) { - adapter.add(account); - } - adapter.notifyDataSetChanged(); - showListOrEmptyText(accounts.size()); - } - } - - private class LoadReposTask extends AsyncTask { - private List repos; - private SeafException err; - private DataManager dataManager; - - public LoadReposTask(DataManager dataManager) { - this.dataManager = dataManager; - } - - @Override - protected Void doInBackground(Void... params) { - try { - repos = dataManager.getReposFromServer(); - } catch (SeafException e) { - err = e; - } - - return null; - } - - @Override - protected void onPreExecute() { - showLoading(true); - } - - @Override - protected void onPostExecute(Void v) { - if (mStep != STEP_CHOOSE_REPO) { - return; - } - - showLoading(false); - if (err != null || repos == null) { - setErrorMessage(R.string.load_libraries_fail); - Log.d(DEBUG_TAG, "failed to load repos: " + (err != null ? err.getMessage() : " no error present")); - return; - } - - updateAdapterWithRepos(repos); - } - } - - private class LoadDirTask extends AsyncTask { - private String repoID, dirPath; - private SeafException err; - private DataManager dataManager; - private List dirents; - - public LoadDirTask(String repoID, String dirPath, DataManager dataManager) { - this.repoID = repoID; - this.dirPath = dirPath; - this.dataManager = dataManager; - } - - @Override - protected void onPreExecute() { - showLoading(true); - } - - @Override - protected Void doInBackground(Void... params) { - try { - dirents = dataManager.getDirentsFromServer(repoID, dirPath); - } catch (SeafException e) { - err = e; - } - - return null; - } - - @Override - protected void onPostExecute(Void v) { - if (mStep != STEP_CHOOSE_DIR) { - return; - } - - getDirentsAdapter().clearDirents(); - showLoading(false); - if (err != null) { - int retCode = err.getCode(); - if (retCode == SeafConnection.HTTP_STATUS_REPO_PASSWORD_REQUIRED) { - showPasswordDialog(); - } else if (retCode == HttpURLConnection.HTTP_NOT_FOUND) { - final String message = String.format(getString(R.string.op_exception_folder_deleted), dirPath); - mActivity.showShortToast(mActivity, message); - } else { - Log.d(DEBUG_TAG, "failed to load dirents: " + err.getMessage()); - err.printStackTrace(); - setErrorMessage(R.string.load_dir_fail); - } - return; - } - - if (dirents == null) { - Log.d(DEBUG_TAG, "failed to load dirents: no error present"); - setErrorMessage(R.string.load_dir_fail); - return; - } - - updateAdapterWithDirents(dirents); - } - } - - /** - * asynchronously load avatars - * - * @param avatarSize set a avatar size in one of 24*24, 32*32, 48*48, 64*64, 72*72, 96*96 - */ - public void loadAvatarUrls(int avatarSize) { - List avatars; - - if (!Utils.isNetworkOn() || !avatarManager.isNeedToLoadNewAvatars()) { - // Toast.makeText(AccountsActivity.this, getString(R.string.network_down), Toast.LENGTH_SHORT).show(); - - // use cached avatars - avatars = avatarManager.getAvatarList(); - - if (avatars == null) { - return; - } - - // set avatars url to adapter - mAccountAdapter.setAvatars((ArrayList) avatars); - - // notify adapter data changed - mAccountAdapter.notifyDataSetChanged(); - - return; - } - - LoadAvatarUrlsTask task = new LoadAvatarUrlsTask(avatarSize); - - ConcurrentAsyncTask.execute(task); - - } - - private class LoadAvatarUrlsTask extends AsyncTask> { - - private List avatars; - private int avatarSize; - private SeafConnection httpConnection; - - public LoadAvatarUrlsTask(int avatarSize) { - this.avatarSize = avatarSize; - this.avatars = Lists.newArrayList(); - } - - @Override - protected List doInBackground(Void... params) { - // reuse cached avatars - avatars = avatarManager.getAvatarList(); - - // contains accounts who don`t have avatars yet - List acts = avatarManager.getAccountsWithoutAvatars(); - - // contains new avatars in order to persist them to database - List newAvatars = new ArrayList(acts.size()); - - // load avatars from server - for (Account account : acts) { - httpConnection = new SeafConnection(account); - - String avatarRawData = null; - try { - avatarRawData = httpConnection.getAvatar(account.getEmail(), avatarSize); - } catch (SeafException e) { - e.printStackTrace(); - return avatars; - } - - Avatar avatar = avatarManager.parseAvatar(avatarRawData); - if (avatar == null) - continue; - - avatar.setSignature(account.getSignature()); - - avatars.add(avatar); - - newAvatars.add(avatar); - } - - // save new added avatars to database - avatarManager.saveAvatarList(newAvatars); - - return avatars; - } - - @Override - protected void onPostExecute(List avatars) { - if (avatars == null) { - return; - } - - // set avatars url to adapter - mAccountAdapter.setAvatars((ArrayList) avatars); - - // notify adapter data changed - mAccountAdapter.notifyDataSetChanged(); - } - } - - private void setErrorMessage(int resID) { - //mContentArea.setVisibility(View.GONE); - mErrorText.setVisibility(View.VISIBLE); - mErrorText.setText(getString(resID)); - } - - private void clearError() { - mErrorText.setVisibility(View.GONE); - //mContentArea.setVisibility(View.VISIBLE); - } - - private void showLoading(boolean loading) { - clearError(); - if (loading) { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation( - mActivity, android.R.anim.fade_in)); - mListContainer.startAnimation(AnimationUtils.loadAnimation( - mActivity, android.R.anim.fade_out)); - - mProgressContainer.setVisibility(View.VISIBLE); - mListContainer.setVisibility(View.INVISIBLE); - } else { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation( - mActivity, android.R.anim.fade_out)); - mListContainer.startAnimation(AnimationUtils.loadAnimation( - mActivity, android.R.anim.fade_in)); - - mProgressContainer.setVisibility(View.GONE); - mListContainer.setVisibility(View.VISIBLE); - } - } - -} - diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/HowToUploadFragment.java b/app/src/main/java/com/seafile/seadroid2/cameraupload/HowToUploadFragment.java deleted file mode 100644 index 4828f1797..000000000 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/HowToUploadFragment.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.seafile.seadroid2.cameraupload; - -import android.os.Bundle; -import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.RadioGroup.OnCheckedChangeListener; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SettingsManager; - -/** - * How to upload fragment - */ -public class HowToUploadFragment extends Fragment { - - private RadioButton mDataPlanRadioBtn; - private RadioGroup mRadioGroup; - - private CameraUploadConfigActivity mActivity; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - - mActivity = (CameraUploadConfigActivity) getActivity(); - View rootView = mActivity.getLayoutInflater().inflate(R.layout.cuc_how_to_upload_fragment, null); - - mRadioGroup = (RadioGroup) rootView.findViewById(R.id.cuc_wifi_radio_group); - mDataPlanRadioBtn = (RadioButton) rootView.findViewById(R.id.cuc_wifi_or_data_plan_rb); - - if (SettingsManager.instance().isDataPlanAllowed()) { - mDataPlanRadioBtn.setChecked(true); - } - - mRadioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() { - - @Override - public void onCheckedChanged(RadioGroup group, int checkedId) { - switch (checkedId) { - case R.id.cuc_wifi_only_rb: - // WiFi only - mActivity.saveDataPlanAllowed(false); - break; - case R.id.cuc_wifi_or_data_plan_rb: - // WiFi and data plan - mActivity.saveDataPlanAllowed(true); - break; - } - - } - - }); - - return rootView; - } - -} - diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/MediaSchedulerService.java b/app/src/main/java/com/seafile/seadroid2/cameraupload/MediaSchedulerService.java deleted file mode 100644 index 1b2b7284e..000000000 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/MediaSchedulerService.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.seafile.seadroid2.cameraupload; - - -import android.app.job.JobParameters; -import android.app.job.JobService; -import android.content.SharedPreferences; - -import androidx.annotation.RequiresApi; -import android.util.Log; - -import com.seafile.seadroid2.SettingsManager; - -/** - * This service monitors the media provider content provider for new images/videos. - */ -public class MediaSchedulerService extends JobService { - private SettingsManager mSettingsManager; - private CameraUploadManager mCameraManager; - - - @Override - public boolean onStartJob(JobParameters jobParameters) { - Log.i(MediaSchedulerService.class.getName(),"MediaSchedulerService exec job"); - mSettingsManager = SettingsManager.instance(); - mSettingsManager.registerSharedPreferencesListener(settingsListener); - mCameraManager = new CameraUploadManager(getApplicationContext()); - if (mCameraManager.isCameraUploadEnabled() && mSettingsManager.isVideosUploadAllowed()) { - mCameraManager.performFullSync(); - } - jobFinished(jobParameters, true); - return true; - } - - @Override - public boolean onStopJob(JobParameters jobParameters) { - if (mSettingsManager != null) { - mSettingsManager.unregisterSharedPreferencesListener(settingsListener); - } - return false; - } - - - /** - * If camera upload settings have changed, we might have to trigger a full resync. - * This listener takes care of that. - */ - private SharedPreferences.OnSharedPreferenceChangeListener settingsListener = - new SharedPreferences.OnSharedPreferenceChangeListener() { - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - - boolean doFullResync = false; - - // here we have to catch *all* the cases that might make a full resync to the repository - // necessary. - - switch (key) { - - // if video upload has been switched on, do a full sync, to upload - // any older videos already recorded. - case SettingsManager.CAMERA_UPLOAD_ALLOW_VIDEOS_SWITCH_KEY: - if (mSettingsManager != null && mSettingsManager.isVideosUploadAllowed()) - doFullResync = true; - break; - - // same goes for if the list of selected buckets has been changed - case SettingsManager.SHARED_PREF_CAMERA_UPLOAD_BUCKETS: - doFullResync = true; - break; - - // the repo changed, also do a full resync - case SettingsManager.SHARED_PREF_CAMERA_UPLOAD_REPO_ID: - doFullResync = true; - break; - } - - if (mCameraManager != null && mCameraManager.isCameraUploadEnabled() && doFullResync) { - // Log.i(DEBUG_TAG, "Doing a full resync of all media content."); - mCameraManager.performFullSync(); - } - } - }; -} diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/ReadyToScanFragment.java b/app/src/main/java/com/seafile/seadroid2/cameraupload/ReadyToScanFragment.java deleted file mode 100644 index db44a3ec5..000000000 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/ReadyToScanFragment.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.seafile.seadroid2.cameraupload; - -import android.os.Bundle; -import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import com.seafile.seadroid2.R; - -public class ReadyToScanFragment extends Fragment { - - private Button continueBtn; - private CameraUploadConfigActivity mActivity; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - - mActivity = (CameraUploadConfigActivity) getActivity(); - View rootView = mActivity.getLayoutInflater().inflate(R.layout.cuc_ready_to_scan_fragment, null); - - continueBtn = (Button) rootView.findViewById(R.id.cuc_click_to_finish_btn); - continueBtn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mActivity.saveSettings(); - mActivity.finish(); - } - }); - - return rootView; - } - -} - diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/WhatToUploadFragment.java b/app/src/main/java/com/seafile/seadroid2/cameraupload/WhatToUploadFragment.java deleted file mode 100644 index d50a3ed8e..000000000 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/WhatToUploadFragment.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.seafile.seadroid2.cameraupload; - -import android.os.Bundle; -import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.RadioGroup.OnCheckedChangeListener; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SettingsManager; - -/** - * What to upload fragment for camera upload configuration helper - */ -public class WhatToUploadFragment extends Fragment { - - private CameraUploadConfigActivity mActivity; - private RadioGroup mRadioGroup; - private RadioButton mVideoUploadRadioBtn; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - - mActivity = (CameraUploadConfigActivity) getActivity(); - View rootView = mActivity.getLayoutInflater().inflate(R.layout.cuc_what_to_upload_fragment, null); - mRadioGroup = (RadioGroup) rootView.findViewById(R.id.cuc_upload_radio_group); - mVideoUploadRadioBtn = (RadioButton) rootView.findViewById(R.id.cuc_upload_photos_and_videos_rb); - if (SettingsManager.instance().isVideosUploadAllowed()) { - mRadioGroup.check(R.id.cuc_upload_photos_and_videos_rb); - //mVideoUploadRadioBtn.setChecked(true); - } - - mRadioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() { - - @Override - public void onCheckedChanged(RadioGroup group, int checkedId) { - switch (checkedId) { - case R.id.cuc_upload_photos_rb: - // only upload photos - mActivity.saveVideosAllowed(false); - break; - case R.id.cuc_upload_photos_and_videos_rb: - // upload photos and videos - mActivity.saveVideosAllowed(true); - break; - } - - } - - }); -// mActivity.saveSettings(); - return rootView; - } - -} - diff --git a/app/src/main/java/com/seafile/seadroid2/config/AbsLayoutItemType.java b/app/src/main/java/com/seafile/seadroid2/config/AbsLayoutItemType.java new file mode 100644 index 000000000..790ba00b5 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/config/AbsLayoutItemType.java @@ -0,0 +1,18 @@ +package com.seafile.seadroid2.config; + +public class AbsLayoutItemType { + /** + * unsupported layout + */ + public static final int UNSUPPORTED = -1; + + public static final int REPO = 10; + public static final int DIRENT = 11; + + public static final int ACTIVITY = 20; + + public static final int GROUP_ITEM = 30; + + public static final int ACCOUNT = 40; + +} diff --git a/app/src/main/java/com/seafile/seadroid2/config/Constants.java b/app/src/main/java/com/seafile/seadroid2/config/Constants.java index 4fb763dc3..f46650c0b 100644 --- a/app/src/main/java/com/seafile/seadroid2/config/Constants.java +++ b/app/src/main/java/com/seafile/seadroid2/config/Constants.java @@ -1,11 +1,92 @@ package com.seafile.seadroid2.config; +import com.blankj.utilcode.util.SizeUtils; +import com.seafile.seadroid2.BuildConfig; + public class Constants { + private Constants() { + throw new IllegalStateException("Constants class"); //NON-NLS(1) + } + + public static final int PASSWORD_MINIMUM_LENGTH = 4; + public static final String URL_PRIVACY = "https://www.seafile.com/privacy/"; + + public static class App { + private App() { + throw new IllegalStateException("Don't instantiate this class"); + } + + /** + * When the app version is upgraded to v3.0.0(or v3x), some data must be migrated, + * such as. CameraUploadDBHelper/FolderBackupDBHelper. + * This field is used to check if it has been migrated. + *

+ * 0 no + * 1 yes + *

+ */ + public static final String DATA_IS_MIGRATION = "data_is_migrated_when_app_version_is_v3x"; + } - public static final String LIVE_BUS_REPO_NAV_TO = "liveBusRepoNavTo"; + + public static class ObjType { + private ObjType() { + throw new IllegalStateException("Don't instantiate this class"); + } + + public static final String REPO = "repo"; + public static final String DIR = "dir"; + public static final String FILE = "file"; + } + + public static class Account { + private Account() { + throw new IllegalStateException("Don't instantiate this class"); + } + + /** + * Type of the account (currently there is only one type) + */ + public final static String ACCOUNT_TYPE = BuildConfig.ACCOUNT_TYPE; + } + + public static class SP { + private SP() { + throw new IllegalStateException("Don't instantiate this class"); + } + + public static final String ACCOUNT_CURRENT = "latest_account"; + public static final String ACCOUNT_NAME = "com.seafile.seadroid..account_name"; + + } + + public static class Protocol { + private Protocol() { + throw new IllegalStateException("Don't instantiate this class"); + } + + public static final String HTTPS = "https://"; + public static final String HTTP = "http://"; + } public static class Format { + private Format() { + throw new IllegalStateException("Don't instantiate this class"); + } + public static final String SDOC = "sdoc"; public static final String DOT_SDOC = ".sdoc"; } + + public static class DP { + private DP() { + throw new IllegalStateException("Don't instantiate this class"); + } + + public static final int DP_2 = SizeUtils.dp2px(2); + public static final int DP_4 = SizeUtils.dp2px(4); + public static final int DP_8 = SizeUtils.dp2px(8); + public static final int DP_16 = SizeUtils.dp2px(16); + public static final int DP_32 = SizeUtils.dp2px(32); + } } diff --git a/app/src/main/java/com/seafile/seadroid2/config/GlideLoadConfig.java b/app/src/main/java/com/seafile/seadroid2/config/GlideLoadConfig.java index 939a680ec..72b5c9499 100644 --- a/app/src/main/java/com/seafile/seadroid2/config/GlideLoadConfig.java +++ b/app/src/main/java/com/seafile/seadroid2/config/GlideLoadConfig.java @@ -23,6 +23,13 @@ public static GlideUrl getGlideUrl(String url) { return glideUrl; } + public static RequestOptions getAvatarOptions() { + return new RequestOptions() + .fallback(R.drawable.default_avatar) + .placeholder(R.drawable.default_avatar) + .override(WidgetUtils.getThumbnailWidth(), WidgetUtils.getThumbnailWidth()); + } + public static RequestOptions getOptions() { return new RequestOptions() .fallback(R.drawable.file_image) diff --git a/app/src/main/java/com/seafile/seadroid2/context/CopyMoveContext.java b/app/src/main/java/com/seafile/seadroid2/context/CopyMoveContext.java index 6c10b3b2d..e5ba93f4a 100644 --- a/app/src/main/java/com/seafile/seadroid2/context/CopyMoveContext.java +++ b/app/src/main/java/com/seafile/seadroid2/context/CopyMoveContext.java @@ -1,7 +1,6 @@ package com.seafile.seadroid2.context; -import com.seafile.seadroid2.data.SeafDirent; -import com.seafile.seadroid2.util.Utils; +import com.seafile.seadroid2.data.db.entities.DirentModel; import java.util.List; @@ -10,61 +9,35 @@ public enum OP { COPY, MOVE } + public OP op; - public List dirents; + public List dirents; public String srcRepoId; public String srcRepoName; public String srcDir; - public String srcFn; + public boolean isdir; - /** flag to mark multiple selection & operations */ - public boolean batch; public String dstRepoId; + public String dstRepoName; public String dstDir; - /** - * Constructor for a single file operations - * - * @param srcRepoId - * @param srcRepoName - * @param srcDir - * @param srcFn - * @param isdir - * @param op - */ - public CopyMoveContext(String srcRepoId, String srcRepoName, String srcDir, String srcFn, boolean isdir, OP op) { - this.srcRepoId = srcRepoId; - this.srcRepoName = srcRepoName; - this.srcDir = srcDir; - this.srcFn = srcFn; - this.isdir = isdir; - this.op = op; - this.batch = false; - } - /** * Constructor for multiple files operations - * - * @param srcRepoId - * @param srcRepoName - * @param srcDir - * @param dirents - * @param op */ - public CopyMoveContext(String srcRepoId, String srcRepoName, String srcDir, List dirents, OP op) { + public CopyMoveContext(String srcRepoId, String srcRepoName, String srcDir, List dirents, OP op) { this.srcRepoId = srcRepoId; this.srcRepoName = srcRepoName; this.srcDir = srcDir; this.dirents = dirents; - this.batch = true; this.op = op; } - public void setDest(String dstRepoId, String dstDir) { + public void setDest(String dstRepoId, String dstDir, String dstRepoName) { this.dstRepoId = dstRepoId; this.dstDir = dstDir; + this.dstRepoName = dstRepoName; } public boolean isCopy() { @@ -77,16 +50,13 @@ public boolean isMove() { /** * Avoid copy/move a folder into its subfolder E.g. situations like: - * - * srcDir: / - * srcFn: dirX + *

+ * srcDir: /dirX * dstDir: /dirX/dirY - * */ public boolean checkCopyMoveToSubfolder() { if (isdir && srcRepoId.equals(dstRepoId)) { - String srcFolder = Utils.pathJoin(srcDir, srcFn); - return !dstDir.startsWith(srcFolder); + return !dstDir.startsWith(srcDir); } return true; } diff --git a/app/src/main/java/com/seafile/seadroid2/context/NavContext.java b/app/src/main/java/com/seafile/seadroid2/context/NavContext.java index abd479a46..2364ec346 100644 --- a/app/src/main/java/com/seafile/seadroid2/context/NavContext.java +++ b/app/src/main/java/com/seafile/seadroid2/context/NavContext.java @@ -1,13 +1,26 @@ package com.seafile.seadroid2.context; -import com.seafile.seadroid2.ui.BrowserActivity; +import android.text.TextUtils; + +import com.blankj.utilcode.util.EncryptUtils; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.data.model.BaseModel; +import com.seafile.seadroid2.data.db.entities.DirentModel; +import com.seafile.seadroid2.data.db.entities.RepoModel; +import com.seafile.seadroid2.util.StringUtils; + +import java.util.Stack; public class NavContext { - String repoID = null; - String repoName = null; // for display - String dirPath = null; -// String dirID = null; - String dirPermission = null; + private final Stack navStack = new Stack<>(); + + private String repoID = null; + private String repoName = null; // for display + private String dirPath = null; + private String dirID = null; + + //(repo or dir)'s permission, recommend + private String permission = null; public NavContext() { } @@ -20,18 +33,10 @@ public void setRepoName(String repoName) { this.repoName = repoName; } -// public void setDir(String path, String dirID) { -// this.dirPath = path; -//// this.dirID = dirID; -// } - - public void setDirPath(String path){ + public void setDirPath(String path) { this.dirPath = path; } - public boolean inRepo() { - return repoID != null; - } public String getRepoID() { return repoID; @@ -50,14 +55,198 @@ public String getDirPath() { } public String getDirPathName() { - return dirPath.substring(dirPath.lastIndexOf(BrowserActivity.ACTIONBAR_PARENT_PATH) + 1); + return dirPath.substring(dirPath.lastIndexOf("/") + 1); } - public String getDirPermission() { - return dirPermission; + public String getPermission() { + return permission; } - public void setDirPermission(String dirPermission) { - this.dirPermission = dirPermission; + public void setPermission(String permission) { + this.permission = permission; } + + public boolean inRepo() { + return repoID != null; + } + + ////////////////////////////new//////////////////////// + + //repoId = xxx, path = / + public boolean isInRepoRoot() { + return navStack.size() == 1; + } + + //repo list [age + public boolean isInRepoList() { + return navStack.size() == 0; + } + + /** + * Not on the Repo list page + */ + public boolean isInRepo() { + return navStack.size() >= 1; + } + + public void clear() { + if (!navStack.empty()) + navStack.clear(); + } + + public void push(BaseModel model) { + if (model instanceof RepoModel) { + //There is only one RepoModel + + //clear + navStack.clear(); + + //push + navStack.push(model); + } else if (model instanceof DirentModel) { + //stack + navStack.push(model); + } else { + throw new IllegalArgumentException("model must be RepoMode or DirentsModel."); + } + } + + public void pop() { + if (navStack.empty()) { + return; + } + + //stack + navStack.pop(); + } + + public void navToPath(RepoModel repoModel, String full_path) { + navStack.clear(); + + navStack.push(repoModel); + + Stack stack = new Stack<>(); + String[] slash = full_path.split("/"); +// boolean isDir = full_path.endsWith("/"); + + if (slash.length > 1) { + StringBuilder stringBuilder = new StringBuilder(); + for (String s : slash) { + if (TextUtils.isEmpty(s)) { + continue; + } + + stringBuilder.append("/").append(s); + DirentModel dm = new DirentModel(); + dm.name = s; +// dm.type = + dm.full_path = stringBuilder.toString(); + dm.hash_path = EncryptUtils.encryptMD5ToString(dm.full_path); + + stack.push(dm); + } + } + + for (DirentModel model : stack) { + navStack.push(model); + } + } + + /** + * Get the dirent model at the top of the stack + */ + public DirentModel getTopDirentModel() { + if (navStack.empty()) { + return null; + } + return (DirentModel) navStack.peek(); + } + + public RepoModel getRepoModel() { + if (navStack.empty()) { + return null; + } + return (RepoModel) navStack.get(0); + } + + /** + * Get the model at the top of the stack + */ + public BaseModel getTopModel() { + if (navStack.empty()) { + return null; + } + return navStack.peek(); + } + + public String getNavPath() { + if (navStack.empty()) { + return null; + } + + if (navStack.size() == 1) { + return "/"; + } + + String fullPath = getTopDirentModel().full_path; + if (!fullPath.endsWith("/")) { + fullPath += "/"; + } + return fullPath; + } + + public String getLastPathName() { + String fullPath = getNavPath(); + if (TextUtils.isEmpty(fullPath)) { + return null; + } + + if (!fullPath.contains("/")) { + return fullPath; + } + + String[] slash = fullPath.split("/"); + if (slash.length == 0) { + return null; + } + + return slash[slash.length - 1]; + } + + public String getNameInCurPath() { + BaseModel baseModel = getTopModel(); + if (baseModel == null) { + return null; + } + + if (baseModel instanceof RepoModel) { + return ((RepoModel) baseModel).repo_name; + } + + if (baseModel instanceof DirentModel) { + return ((DirentModel) baseModel).name; + } + + return null; + } + + // + public boolean hasWritePermissionWithRepo() { + +// BaseModel baseModel = getTopModel(); +// if (baseModel == null) { +// return false; +// } +// +// if (baseModel instanceof RepoModel) { +// return ((RepoModel) baseModel).hasWritePermission(); +// } +// +// if (baseModel instanceof DirentModel) { +// return ((DirentModel) baseModel).hasWritePermission(); +// } + + return getRepoModel().hasWritePermission(); + } + } diff --git a/app/src/main/java/com/seafile/seadroid2/data/ContactsData.java b/app/src/main/java/com/seafile/seadroid2/data/ContactsData.java deleted file mode 100644 index b1bb02ac6..000000000 --- a/app/src/main/java/com/seafile/seadroid2/data/ContactsData.java +++ /dev/null @@ -1,122 +0,0 @@ -//package com.seafile.seadroid2.data; -// -//import java.util.ArrayList; -//import java.util.List; -// -///** -// * Function: -// * Author: Saud -// * Create: 2016/11/12 -// * Modtime: 2016/11/12 -// */ -//public class ContactsData { -// private String userid; -// private String name; -// private List phoneList = new ArrayList(); // PhoneNumber -// private List email = new ArrayList(); // Email -// -// -// public String getUserid() { -// return userid; -// } -// -// public void setUserid(String userid) { -// this.userid = userid; -// } -// -// public String getName() { -// return name; -// } -// -// public void setName(String name) { -// this.name = name; -// } -// -// -// public List getPhoneList() { -// return phoneList; -// } -// -// public void setPhoneList(List phoneList) { -// this.phoneList = phoneList; -// } -// -// public List getEmail() { -// return email; -// } -// -// public void setEmail(List email) { -// this.email = email; -// } -// -// -// public static class PhoneInfo { -// private int type; -// private String number; -// -// public int getType() { -// return type; -// } -// -// public void setType(int type) { -// this.type = type; -// } -// -// public String getNumber() { -// return number; -// } -// -// public void setNumber(String number) { -// this.number = number; -// } -// -// @Override -// public String toString() { -// return "PhoneInfo{" + -// "type=" + type + -// ", number='" + number + '\'' + -// '}'; -// } -// } -// -// -// public static class EmailInfo { -// private int type; -// private String email; -// -// -// public String getEmail() { -// return email; -// } -// -// public void setEmail(String email) { -// this.email = email; -// } -// -// public int getType() { -// return type; -// } -// -// public void setType(int type) { -// this.type = type; -// } -// -// @Override -// public String toString() { -// return "EmailInfo{" + -// "type=" + type + -// ", email='" + email + '\'' + -// '}'; -// } -// } -// -// @Override -// public String toString() { -// return "UserData{" + -// "userid='" + userid + '\'' + -// ", name='" + name + '\'' + -// ", phoneList=" + phoneList + -// ", email=" + email + -// '}'; -// } -//} diff --git a/app/src/main/java/com/seafile/seadroid2/data/DatabaseHelper.java b/app/src/main/java/com/seafile/seadroid2/data/DatabaseHelper.java index 114557bdf..82876f7e6 100644 --- a/app/src/main/java/com/seafile/seadroid2/data/DatabaseHelper.java +++ b/app/src/main/java/com/seafile/seadroid2/data/DatabaseHelper.java @@ -204,6 +204,7 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } } + // 删除表时为什么不备份??? db.execSQL("DROP TABLE IF EXISTS " + FILECACHE_TABLE_NAME + ";"); db.execSQL("DROP TABLE IF EXISTS " + REPODIR_TABLE_NAME + ";"); db.execSQL("DROP TABLE IF EXISTS " + DIRENTS_CACHE_TABLE_NAME + ";"); diff --git a/app/src/main/java/com/seafile/seadroid2/data/EventDetailsFileItem.java b/app/src/main/java/com/seafile/seadroid2/data/EventDetailsFileItem.java index 65429d41d..e2f80d8de 100644 --- a/app/src/main/java/com/seafile/seadroid2/data/EventDetailsFileItem.java +++ b/app/src/main/java/com/seafile/seadroid2/data/EventDetailsFileItem.java @@ -1,6 +1,8 @@ package com.seafile.seadroid2.data; -public class EventDetailsFileItem { +import com.seafile.seadroid2.data.model.BaseModel; + +public class EventDetailsFileItem extends BaseModel { public enum EType { FILE_ADDED, diff --git a/app/src/main/java/com/seafile/seadroid2/data/SeafGroup.java b/app/src/main/java/com/seafile/seadroid2/data/SeafGroup.java deleted file mode 100644 index ea17b723a..000000000 --- a/app/src/main/java/com/seafile/seadroid2/data/SeafGroup.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.seafile.seadroid2.data; - -import com.google.common.collect.Lists; -import com.seafile.seadroid2.ui.adapter.SeafItemAdapter; - -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -public class SeafGroup implements SeafItem { - private String name; - private List repos = Lists.newArrayList(); - - public SeafGroup(String name) { - this.name = name; - } - - @Override - public String getTitle() { - return name; - } - - @Override - public String getSubtitle() { - return null; - } - - @Override - public int getIcon() { - return 0; - } - - public List getRepos() { - return repos; - } - - public void addIfAbsent(SeafRepo repo) { - if (!repos.contains(repo)) - this.repos.add(repo); - } - - /** - * sort collections by repository name or last modified time - */ - public void sortByType(int type, int order) { - if (type == SeafItemAdapter.SORT_BY_NAME) { - Collections.sort(repos, new SeafRepo.RepoNameComparator()); - if (order == SeafItemAdapter.SORT_ORDER_DESCENDING) { - Collections.reverse(repos); - } - } else if (type == SeafItemAdapter.SORT_BY_LAST_MODIFIED_TIME) { - Collections.sort(repos, new SeafRepo.RepoLastMTimeComparator()); - if (order == SeafItemAdapter.SORT_ORDER_DESCENDING) { - Collections.reverse(repos); - } - } - } - -} diff --git a/app/src/main/java/com/seafile/seadroid2/data/SeafRepo.java b/app/src/main/java/com/seafile/seadroid2/data/SeafRepo.java index 6b308e29d..1aa00fbd0 100644 --- a/app/src/main/java/com/seafile/seadroid2/data/SeafRepo.java +++ b/app/src/main/java/com/seafile/seadroid2/data/SeafRepo.java @@ -5,7 +5,7 @@ import com.blankj.utilcode.util.TimeUtils; import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeadroidApplication; -import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.util.sp.SettingsManager; import com.seafile.seadroid2.config.DateFormatType; import com.seafile.seadroid2.util.PinyinUtils; import com.seafile.seadroid2.util.Utils; @@ -15,7 +15,6 @@ import java.util.Comparator; import java.util.Date; -import java.util.Objects; /** * SeafRepo: A Seafile library @@ -148,7 +147,7 @@ public void setStarred(boolean starred) { } public boolean canLocalDecrypt() { - return encrypted && SettingsManager.instance().isEncryptEnabled(); + return encrypted && SettingsManager.getInstance().isEncryptEnabled(); } public boolean hasWritePermission() { diff --git a/app/src/main/java/com/seafile/seadroid2/data/SeafRepoEncrypt.java b/app/src/main/java/com/seafile/seadroid2/data/SeafRepoEncrypt.java index 813a12ec2..56749fed6 100644 --- a/app/src/main/java/com/seafile/seadroid2/data/SeafRepoEncrypt.java +++ b/app/src/main/java/com/seafile/seadroid2/data/SeafRepoEncrypt.java @@ -2,7 +2,7 @@ import android.text.TextUtils; -import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.util.sp.SettingsManager; import org.json.JSONException; import org.json.JSONObject; @@ -50,7 +50,7 @@ public boolean canLocalDecrypt() { return encrypted && encVersion == 2 && !TextUtils.isEmpty(magic) - && SettingsManager.instance().isEncryptEnabled(); + && SettingsManager.getInstance().isEncryptEnabled(); } } diff --git a/app/src/main/java/com/seafile/seadroid2/data/ServerInfo.java b/app/src/main/java/com/seafile/seadroid2/data/ServerInfo.java index d19a88bfc..34a9703d8 100644 --- a/app/src/main/java/com/seafile/seadroid2/data/ServerInfo.java +++ b/app/src/main/java/com/seafile/seadroid2/data/ServerInfo.java @@ -68,8 +68,7 @@ public boolean isSearchEnabled() { } public boolean canLocalDecrypt() { - if (TextUtils.isEmpty(version) - || version.length() != 5) + if (TextUtils.isEmpty(version) || version.length() != 5) return false; final String realVersion = version.replaceAll("[.]", ""); diff --git a/app/src/main/java/com/seafile/seadroid2/data/StorageManager.java b/app/src/main/java/com/seafile/seadroid2/data/StorageManager.java index 2f602bd17..11a5ffe95 100644 --- a/app/src/main/java/com/seafile/seadroid2/data/StorageManager.java +++ b/app/src/main/java/com/seafile/seadroid2/data/StorageManager.java @@ -5,60 +5,62 @@ import android.net.Uri; import android.os.Build; import android.os.Environment; -import androidx.core.os.EnvironmentCompat; import android.text.format.Formatter; import android.util.Log; +import androidx.core.os.EnvironmentCompat; + import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeadroidApplication; -import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.util.sp.SettingsManager; import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.AccountManager; +import com.seafile.seadroid2.account.SupportAccountManager; import org.apache.commons.io.FileUtils; import java.io.File; import java.util.ArrayList; import java.util.Collection; +import java.util.List; /** * This class decides where to store Seadroid's data in the file system. - * + *

* Up till API 18, there was not much choice. Only CLASSIC_LOCATION did make any sense * to use. Starting with KitKat new options have appeared. API 19+ offers an API to get a list * of possible storage locations, which will include things like SD cards or USB connected flash * drives. - * + *

* This StorageManager allows the user to make the choice where to store its data. It remembers * this choice in the SettingsManager. - * + *

* This StorageManager also does an auto-selection of the best storage on first use of Seadroid. - * + *

* The following data falls into the scope of this StorageManager: - * + *

* - Downloaded repository files - * -> Indexed by Gallery - * -> synced server content, might be deleted locally - * -> persistent, as deletion would cause user inconvenience - * + * -> Indexed by Gallery + * -> synced server content, might be deleted locally + * -> persistent, as deletion would cause user inconvenience + *

* - Temp files (e.g. pending for upload, download in progress) - * -> NOT indexed by Gallery - * -> important content, not to be deleted prematurely - * -> only temporary - * -> might be moved to "Downloaded repository files", so should be same mount point - * + * -> NOT indexed by Gallery + * -> important content, not to be deleted prematurely + * -> only temporary + * -> might be moved to "Downloaded repository files", so should be same mount point + *

* - Image thumbnails - * -> NOT indexed by Gallery - * -> long term storage - * -> not important content, can be deleted if necessary - * + * -> NOT indexed by Gallery + * -> long term storage + * -> not important content, can be deleted if necessary + *

* - JSON Cache files - * -> NOT indexed by Gallery - * -> long term storage - * -> not important content, can be deleted if necessary - * + * -> NOT indexed by Gallery + * -> long term storage + * -> not important content, can be deleted if necessary + *

* This class offers a set of methods to retrieve the base dir for specific types of data. - * + *

* Useful links: * - https://developer.android.com/guide/topics/data/data-storage.html */ @@ -76,6 +78,7 @@ public StorageManager() { /** * Fetch instance of the StorageManager + * * @return */ public final static StorageManager getInstance() { @@ -116,12 +119,12 @@ private void fillLocationInfo(Location loc) { } if (loc.available) { loc.description = getContext().getString(R.string.storage_manager_storage_description, - label, - Formatter.formatFileSize(getContext(), getStorageFreeSpace(loc.mediaPath)), - Formatter.formatFileSize(getContext(), getStorageSize(loc.mediaPath))); + label, + Formatter.formatFileSize(getContext(), getStorageFreeSpace(loc.mediaPath)), + Formatter.formatFileSize(getContext(), getStorageSize(loc.mediaPath))); } else { loc.description = getContext().getString(R.string.storage_manager_storage_description_not_available, - label); + label); } } @@ -157,6 +160,7 @@ private void fillLocationInfo(Location loc) { /** * Allows this device multiple storage locations? + * * @return */ public abstract boolean supportsMultipleStorageLocations(); @@ -173,7 +177,7 @@ public void onScanCompleted(String path, Uri uri) { public final ArrayList getStorageLocations() { ArrayList retList = new ArrayList<>(); - int selectedDir = SettingsManager.instance().getStorageDir(); + int selectedDir = SettingsManager.getInstance().getStorageDir(); retList.add(CLASSIC_LOCATION); @@ -201,7 +205,7 @@ public final ArrayList getStorageLocations() { retList.add(location); } - for (Location loc: retList) { + for (Location loc : retList) { loc.currentSelection = (loc.id == selectedDir); fillLocationInfo(loc); // fill in size & description info } @@ -211,7 +215,7 @@ public final ArrayList getStorageLocations() { /** * Set the new storage directory. - * + *

* This will change the settings and move files from the old to the new location. * Therefore, this method might take a while to finish. * @@ -219,7 +223,7 @@ public final ArrayList getStorageLocations() { */ public final void setStorageDir(int id) { - int oldID = SettingsManager.instance().getStorageDir(); + int oldID = SettingsManager.getInstance().getStorageDir(); if (oldID == id) return; @@ -235,12 +239,11 @@ public final void setStorageDir(int id) { Location oldLocation = lookupStorageLocation(oldID); if (oldLocation != null) { - AccountManager manager = new AccountManager(getContext()); try { // move cached files from old location to new location (might take a while) - - for (Account account: manager.getAccountList()) { + List list = SupportAccountManager.getInstance().getAccountList(); + for (Account account : list) { DataManager dataManager = new DataManager(account); File oldAccountDir = new File(dataManager.getAccountDir()); @@ -260,24 +263,24 @@ public final void setStorageDir(int id) { } Log.i(DEBUG_TAG, "Setting storage directory to " + newMediaDir); - SettingsManager.instance().setStorageDir(id); + SettingsManager.getInstance().setStorageDir(id); } /** * Decide, which storage to use. This will be called only once, * on first start of Seadroid. After that, it's read from the Settings. - * - * -> API 19+ - * It selects the media with the most free space in it. - * - * -> API 1-18 - * On pre-KitKat, only CLASSIC_LOCATION is available. So there is no choice. - * + *

+ * -> API 19+ + * It selects the media with the most free space in it. + *

+ * -> API 1-18 + * On pre-KitKat, only CLASSIC_LOCATION is available. So there is no choice. + *

* This method does not change the Settings. It just evaluates what the best storage location * might be. * * @return storage ID with the most free space - */ + */ private Location getPreferredStorage() { /* Backwards compatibility on upgrade: @@ -292,7 +295,7 @@ private Location getPreferredStorage() { // auto-select the location with the most free space available Location best = null; - for (Location location: getStorageLocations()) { + for (Location location : getStorageLocations()) { if (!location.available) continue; @@ -309,7 +312,7 @@ private Location getPreferredStorage() { /** * Return the base directory for media storage to be used in Seadroid. - * + *

* It guaranties to always return a valid directory. * However, this directory might change at runtime, So it should never be cached. * @@ -320,7 +323,7 @@ public final File getMediaDir() { } private Location lookupStorageLocation(int id) { - for (Location location: getStorageLocations()) { + for (Location location : getStorageLocations()) { if (location.id == id) return location; } @@ -329,14 +332,14 @@ private Location lookupStorageLocation(int id) { /** * Return the storage location to be used in Seadroid. - * + *

* It guaranties to always return a valid one. * * @return Location info */ public Location getStorageLocation() { - int storageDirID = SettingsManager.instance().getStorageDir(); + int storageDirID = SettingsManager.getInstance().getStorageDir(); Location storageLocation = lookupStorageLocation(storageDirID); // if there is one configured but unavailable, use fallback @@ -351,7 +354,7 @@ public Location getStorageLocation() { storageLocation = getPreferredStorage(); Log.i(DEBUG_TAG, "First start of Seadroid, auto-setting storage directory to " + storageLocation.id); - SettingsManager.instance().setStorageDir(storageLocation.id); + SettingsManager.getInstance().setStorageDir(storageLocation.id); } if (storageLocation == null || !storageLocation.available) { @@ -378,7 +381,7 @@ protected final Context getContext() { /** * Store temp files in a subdirectory below the media directory. - * + *

* This should be a subdirectory of getMediaDir() so that temp files * can be efficiently moved into the media storage. * @@ -392,7 +395,7 @@ public final File getTempDir() { /** * Store JSON cache files in private internal cache. - * + *

* This cache directory will contain json files listing the repositories and directory listings. * this can be pretty private (especially the repository listing). So these should not be readable * by other apps. Therefore we save them in internal storage, where only Seadroid has access to. @@ -422,9 +425,9 @@ public final File getThumbnailsDir() { */ public final void notifyAndroidGalleryFileChange(File file) { MediaScannerConnection.scanFile(getContext(), - new String[]{file.toString()}, - null, - this); + new String[]{file.toString()}, + null, + this); } /** @@ -436,7 +439,7 @@ private final void notifyAndroidGalleryDirectoryChange(Collection fileList int count = 0; String[] list = new String[fileList.size()]; - for (File f: fileList) { + for (File f : fileList) { list[count++] = f.getAbsolutePath(); } diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/AppDatabase.java b/app/src/main/java/com/seafile/seadroid2/data/db/AppDatabase.java new file mode 100644 index 000000000..fe106e9a1 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/AppDatabase.java @@ -0,0 +1,88 @@ +package com.seafile.seadroid2.data.db; + +import androidx.room.Database; +import androidx.room.Room; +import androidx.room.RoomDatabase; + +import com.seafile.seadroid2.SeadroidApplication; +import com.seafile.seadroid2.data.db.dao.CertCacheDAO; +import com.seafile.seadroid2.data.db.dao.DirentDAO; +import com.seafile.seadroid2.data.db.dao.DirentsCacheDAO; +import com.seafile.seadroid2.data.db.dao.EncKeyCacheDAO; +import com.seafile.seadroid2.data.db.dao.FileCacheDAO; +import com.seafile.seadroid2.data.db.dao.FolderBackupCacheDAO; +import com.seafile.seadroid2.data.db.dao.FolderBackupMonitorDAO; +import com.seafile.seadroid2.data.db.dao.ObjsDAO; +import com.seafile.seadroid2.data.db.dao.PhotoCacheDAO; +import com.seafile.seadroid2.data.db.dao.RepoDAO; +import com.seafile.seadroid2.data.db.dao.RepoDirDAO; +import com.seafile.seadroid2.data.db.dao.StarredFileCacheDAO; +import com.seafile.seadroid2.data.db.entities.CertEntity; +import com.seafile.seadroid2.data.db.entities.DirentModel; +import com.seafile.seadroid2.data.db.entities.DirentsCacheEntity; +import com.seafile.seadroid2.data.db.entities.EncKeyCacheEntity; +import com.seafile.seadroid2.data.db.entities.FileCacheEntity; +import com.seafile.seadroid2.data.db.entities.FolderBackupCacheEntity; +import com.seafile.seadroid2.data.db.entities.FolderBackupMonitorEntity; +import com.seafile.seadroid2.data.db.entities.ObjsModel; +import com.seafile.seadroid2.data.db.entities.PhotoCacheEntity; +import com.seafile.seadroid2.data.db.entities.RepoDirEntity; +import com.seafile.seadroid2.data.db.entities.RepoModel; +import com.seafile.seadroid2.data.db.entities.StarredFileCacheEntity; + +@Database(entities = { + RepoModel.class, + DirentModel.class, + ObjsModel.class, + PhotoCacheEntity.class, + FolderBackupCacheEntity.class, + FolderBackupMonitorEntity.class, + FileCacheEntity.class, + StarredFileCacheEntity.class, + RepoDirEntity.class, + DirentsCacheEntity.class, + EncKeyCacheEntity.class, + CertEntity.class +}, version = 1, exportSchema = false) +public abstract class AppDatabase extends RoomDatabase { + private static final String DATABASE_NAME = "seafile_room.db"; + private static volatile AppDatabase _instance; + + public static AppDatabase getInstance() { + if (_instance == null) { + synchronized (AppDatabase.class) { + if (_instance == null) { + _instance = Room + .databaseBuilder(SeadroidApplication.getAppContext(), AppDatabase.class, DATABASE_NAME) + .build(); + } + } + } + return _instance; + } + + public abstract RepoDAO repoDao(); + + public abstract DirentDAO direntDao(); + + public abstract ObjsDAO objDao(); + + public abstract PhotoCacheDAO photoCacheDao(); + + public abstract FolderBackupCacheDAO folderBackupCacheDao(); + + public abstract FileCacheDAO fileCacheDAO(); + + public abstract StarredFileCacheDAO starredFileCacheDAO(); + + public abstract DirentsCacheDAO direntsCacheDAO(); + + public abstract EncKeyCacheDAO encKeyCacheDAO(); + + public abstract CertCacheDAO certDAO(); + + public abstract RepoDirDAO repoDirDAO(); + + public abstract FolderBackupMonitorDAO folderBackupMonitorDAO(); + +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/dao/CertCacheDAO.java b/app/src/main/java/com/seafile/seadroid2/data/db/dao/CertCacheDAO.java new file mode 100644 index 000000000..92cae5bdf --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/dao/CertCacheDAO.java @@ -0,0 +1,24 @@ +package com.seafile.seadroid2.data.db.dao; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; + +import com.seafile.seadroid2.data.db.entities.CertEntity; +import com.seafile.seadroid2.data.db.entities.EncKeyCacheEntity; + +import java.util.List; + +import io.reactivex.Completable; + +@Dao +public interface CertCacheDAO { + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insert(CertEntity entity); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insertAll(List entities); + +// @Query("select * from repo_config_cache where repo_id = :repoId limit 1") +// Single> getByRepoId(String repoId); +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/dao/DirentDAO.java b/app/src/main/java/com/seafile/seadroid2/data/db/dao/DirentDAO.java new file mode 100644 index 000000000..1ffa28696 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/dao/DirentDAO.java @@ -0,0 +1,37 @@ +package com.seafile.seadroid2.data.db.dao; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; + +import com.seafile.seadroid2.data.db.entities.DirentModel; + +import java.util.List; + +import io.reactivex.Completable; +import io.reactivex.Single; + +@Dao +public interface DirentDAO { + @Query("select * from dirents where related_account_email = :cur_account_email") + Single> getAllByAccount(String cur_account_email); + + @Query("select * from dirents where parent_dir = :parent_dir and repo_id = :repo_id") + Single> getAllByParentPath(String repo_id, String parent_dir); + + @Query("select * from dirents where id = :id") + Single getDirentById(String id); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insertAll(List list); + + @Query("DELETE FROM dirents where related_account_email = :cur_account_email") + Completable deleteAllByAccount(String cur_account_email); + + @Query("DELETE FROM dirents where parent_dir = :parent_dir and repo_id = :repo_id") + Completable deleteAllByPath(String repo_id, String parent_dir); + + @Query("DELETE FROM dirents") + Completable deleteAll(); +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/dao/DirentsCacheDAO.java b/app/src/main/java/com/seafile/seadroid2/data/db/dao/DirentsCacheDAO.java new file mode 100644 index 000000000..78cb53a47 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/dao/DirentsCacheDAO.java @@ -0,0 +1,23 @@ +package com.seafile.seadroid2.data.db.dao; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; + +import com.seafile.seadroid2.data.db.entities.DirentsCacheEntity; + +import java.util.List; + +import io.reactivex.Completable; + +@Dao +public interface DirentsCacheDAO { + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insert(DirentsCacheEntity entity); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insertAll(List entities); + +// @Query("select * from repo_config_cache where repo_id = :repoId limit 1") +// Single> getByRepoId(String repoId); +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/dao/EncKeyCacheDAO.java b/app/src/main/java/com/seafile/seadroid2/data/db/dao/EncKeyCacheDAO.java new file mode 100644 index 000000000..139264619 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/dao/EncKeyCacheDAO.java @@ -0,0 +1,24 @@ +package com.seafile.seadroid2.data.db.dao; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; + +import com.seafile.seadroid2.data.db.entities.DirentsCacheEntity; +import com.seafile.seadroid2.data.db.entities.EncKeyCacheEntity; + +import java.util.List; + +import io.reactivex.Completable; + +@Dao +public interface EncKeyCacheDAO { + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insert(EncKeyCacheEntity entity); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insertAll(List entities); + +// @Query("select * from repo_config_cache where repo_id = :repoId limit 1") +// Single> getByRepoId(String repoId); +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/dao/FileCacheDAO.java b/app/src/main/java/com/seafile/seadroid2/data/db/dao/FileCacheDAO.java new file mode 100644 index 000000000..7518296ab --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/dao/FileCacheDAO.java @@ -0,0 +1,23 @@ +package com.seafile.seadroid2.data.db.dao; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; + +import com.seafile.seadroid2.data.db.entities.FileCacheEntity; + +import java.util.List; + +import io.reactivex.Completable; + +@Dao +public interface FileCacheDAO { + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insert(FileCacheEntity entity); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insertAll(List entities); + +// @Query("select * from repo_config_cache where repo_id = :repoId limit 1") +// Single> getByRepoId(String repoId); +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/dao/FolderBackupCacheDAO.java b/app/src/main/java/com/seafile/seadroid2/data/db/dao/FolderBackupCacheDAO.java new file mode 100644 index 000000000..88113860a --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/dao/FolderBackupCacheDAO.java @@ -0,0 +1,26 @@ +package com.seafile.seadroid2.data.db.dao; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; + +import com.seafile.seadroid2.data.db.entities.FolderBackupCacheEntity; +import com.seafile.seadroid2.data.db.entities.PhotoCacheEntity; + +import java.util.List; + +import io.reactivex.Completable; +import io.reactivex.Single; + +@Dao +public interface FolderBackupCacheDAO { + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insert(FolderBackupCacheEntity entity); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insertAll(List entities); + + @Query("select * from folder_backup_cache where file_name = :fileName limit 1") + Single> getByFileName(String fileName); +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/dao/FolderBackupMonitorDAO.java b/app/src/main/java/com/seafile/seadroid2/data/db/dao/FolderBackupMonitorDAO.java new file mode 100644 index 000000000..190a9e290 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/dao/FolderBackupMonitorDAO.java @@ -0,0 +1,24 @@ +package com.seafile.seadroid2.data.db.dao; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; + +import com.seafile.seadroid2.data.db.entities.FolderBackupMonitorEntity; +import com.seafile.seadroid2.data.db.entities.StarredFileCacheEntity; + +import java.util.List; + +import io.reactivex.Completable; + +@Dao +public interface FolderBackupMonitorDAO { + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insert(FolderBackupMonitorEntity entity); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insertAll(List entities); + +// @Query("select * from repo_config_cache where repo_id = :repoId limit 1") +// Single> getByRepoId(String repoId); +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/dao/ObjsDAO.java b/app/src/main/java/com/seafile/seadroid2/data/db/dao/ObjsDAO.java new file mode 100644 index 000000000..419132106 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/dao/ObjsDAO.java @@ -0,0 +1,29 @@ +package com.seafile.seadroid2.data.db.dao; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; + +import com.seafile.seadroid2.data.db.entities.ObjsModel; + +import java.util.List; + +import io.reactivex.Completable; +import io.reactivex.Single; + +@Dao +public interface ObjsDAO { + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insert(ObjsModel dirModel); + + @Query("select * from objs where path = :path limit 1") + Single> getByPath(String path); + + @Query("select * from objs where path = :path and type = :type limit 1") + Single> getByPath(String path, String type); + + @Query("update objs set decrypt_expire_time_long = :timestamp where path = :path") + Completable updateDecryptExpireTimeByPath(String path, long timestamp); + +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/dao/PhotoCacheDAO.java b/app/src/main/java/com/seafile/seadroid2/data/db/dao/PhotoCacheDAO.java new file mode 100644 index 000000000..7de4a628a --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/dao/PhotoCacheDAO.java @@ -0,0 +1,26 @@ +package com.seafile.seadroid2.data.db.dao; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; + +import com.seafile.seadroid2.data.db.entities.ObjsModel; +import com.seafile.seadroid2.data.db.entities.PhotoCacheEntity; + +import java.util.List; + +import io.reactivex.Completable; +import io.reactivex.Single; + +@Dao +public interface PhotoCacheDAO { + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insert(PhotoCacheEntity entity); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insertAll(List entities); + + @Query("select * from photo_cache where file = :file limit 1") + Single> getByfile(String file); +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/dao/RepoDAO.java b/app/src/main/java/com/seafile/seadroid2/data/db/dao/RepoDAO.java new file mode 100644 index 000000000..c83eaa818 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/dao/RepoDAO.java @@ -0,0 +1,31 @@ +package com.seafile.seadroid2.data.db.dao; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; + +import com.seafile.seadroid2.data.db.entities.RepoModel; + +import java.util.List; + +import io.reactivex.Completable; +import io.reactivex.Single; + +@Dao +public interface RepoDAO { + @Query("select * from repos where related_account_email = :cur_account_email") + Single> getAllByAccount(String cur_account_email); + + @Query("select * from repos where repo_id = :repo_id limit 1") + Single> getRepoById(String repo_id); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insertAll(List list); + + @Query("DELETE FROM repos where related_account_email = :cur_account_email") + Completable deleteAllByAccount(String cur_account_email); + + @Query("DELETE FROM repos") + Completable deleteAll(); +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/dao/RepoDirDAO.java b/app/src/main/java/com/seafile/seadroid2/data/db/dao/RepoDirDAO.java new file mode 100644 index 000000000..c47ed1557 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/dao/RepoDirDAO.java @@ -0,0 +1,24 @@ +package com.seafile.seadroid2.data.db.dao; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; + +import com.seafile.seadroid2.data.db.entities.RepoDirEntity; +import com.seafile.seadroid2.data.db.entities.StarredFileCacheEntity; + +import java.util.List; + +import io.reactivex.Completable; + +@Dao +public interface RepoDirDAO { + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insert(RepoDirEntity entity); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insertAll(List entities); + +// @Query("select * from repo_config_cache where repo_id = :repoId limit 1") +// Single> getByRepoId(String repoId); +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/dao/StarredFileCacheDAO.java b/app/src/main/java/com/seafile/seadroid2/data/db/dao/StarredFileCacheDAO.java new file mode 100644 index 000000000..e3b5029e8 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/dao/StarredFileCacheDAO.java @@ -0,0 +1,24 @@ +package com.seafile.seadroid2.data.db.dao; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; + +import com.seafile.seadroid2.data.db.entities.FileCacheEntity; +import com.seafile.seadroid2.data.db.entities.StarredFileCacheEntity; + +import java.util.List; + +import io.reactivex.Completable; + +@Dao +public interface StarredFileCacheDAO { + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insert(StarredFileCacheEntity entity); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + Completable insertAll(List entities); + +// @Query("select * from repo_config_cache where repo_id = :repoId limit 1") +// Single> getByRepoId(String repoId); +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/entities/CertEntity.java b/app/src/main/java/com/seafile/seadroid2/data/db/entities/CertEntity.java new file mode 100644 index 000000000..22a557710 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/entities/CertEntity.java @@ -0,0 +1,21 @@ +package com.seafile.seadroid2.data.db.entities; + +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +@Entity(tableName = "cert_cache") +public class CertEntity { + @PrimaryKey(autoGenerate = true) + public long id; + + public String url; + public String cert; + + @Override + public String toString() { + return "CertEntity{" + + "url='" + url + '\'' + + ", cert='" + cert + '\'' + + '}'; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/entities/DirentModel.java b/app/src/main/java/com/seafile/seadroid2/data/db/entities/DirentModel.java new file mode 100644 index 000000000..194711721 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/entities/DirentModel.java @@ -0,0 +1,90 @@ +package com.seafile.seadroid2.data.db.entities; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.Ignore; +import androidx.room.PrimaryKey; + +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.data.model.BaseModel; +import com.seafile.seadroid2.util.Utils; + +@Entity(tableName = "dirents") +public class DirentModel extends BaseModel { + + // parent_dir + name + // /test.txt + // /Download + // /Download/test.txt + // /Download/test/test1.txt + @PrimaryKey + @NonNull + public String hash_path = ""; + public String full_path = ""; + + public String name; + public String parent_dir; + //net data id + public String id; + public String type; + public long mtime; // last modified timestamp + public String permission; + public String dir_id; + public boolean starred; + + public long size; // size of file, 0 if type is dir + + public String related_account_email; //related account + public String repo_id; + public String repo_name; + + //lock + public boolean is_locked; + public boolean is_freezed; + public boolean locked_by_me; + public long lock_time; + public String lock_owner; + public String lock_owner_name; + public String lock_owner_contact_email; + public String modifier_email; + public String modifier_name; + public String modifier_contact_email; + public String encoded_thumbnail_src; + + //last sync time + public long last_sync_time = 0; + + @Ignore + private String timestamp; + + public boolean isDir() { + return TextUtils.equals(type, "dir"); + } + + public String getSubtitle() { + if (TextUtils.isEmpty(timestamp)) { + timestamp = Utils.translateCommitTime(mtime * 1000); + } + if (isDir()) + return timestamp; + return Utils.readableFileSize(size) + ", " + timestamp; + } + + public int getIcon() { + if (isDir()) { + if (!hasWritePermission()) { + return R.drawable.folder_read_only; + } else { + return R.drawable.folder; + } + } + return Utils.getFileIcon(name); + } + + public boolean hasWritePermission() { + return !TextUtils.isEmpty(permission) && permission.contains("w"); + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/entities/DirentsCacheEntity.java b/app/src/main/java/com/seafile/seadroid2/data/db/entities/DirentsCacheEntity.java new file mode 100644 index 000000000..3a1da1d37 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/entities/DirentsCacheEntity.java @@ -0,0 +1,25 @@ +package com.seafile.seadroid2.data.db.entities; + +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +@Entity(tableName = "dirents_cache") +public class DirentsCacheEntity { + @PrimaryKey(autoGenerate = true) + public long id; + + public String repo_id; + public String path; + public String dir_id; + public String related_account; //related account + + @Override + public String toString() { + return "DirentsCacheEntity{" + + "id=" + id + + ", repo_id='" + repo_id + '\'' + + ", path='" + path + '\'' + + ", dir_id='" + dir_id + '\'' + + '}'; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/entities/EncKeyCacheEntity.java b/app/src/main/java/com/seafile/seadroid2/data/db/entities/EncKeyCacheEntity.java new file mode 100644 index 000000000..0afd0743a --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/entities/EncKeyCacheEntity.java @@ -0,0 +1,25 @@ +package com.seafile.seadroid2.data.db.entities; + +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +@Entity(tableName = "enc_key_cache") +public class EncKeyCacheEntity { + @PrimaryKey(autoGenerate = true) + public long id; + + public String enc_key; + public String enc_iv; + public String repo_id; + public String related_account; //related account + + @Override + public String toString() { + return "EncKeyCacheEntity{" + + "id=" + id + + ", enc_key='" + enc_key + '\'' + + ", enc_iv='" + enc_iv + '\'' + + ", repo_id='" + repo_id + '\'' + + '}'; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/entities/FileCacheEntity.java b/app/src/main/java/com/seafile/seadroid2/data/db/entities/FileCacheEntity.java new file mode 100644 index 000000000..07fdca329 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/entities/FileCacheEntity.java @@ -0,0 +1,28 @@ +package com.seafile.seadroid2.data.db.entities; + +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +@Entity(tableName = "file_cache") +public class FileCacheEntity { + @PrimaryKey(autoGenerate = true) + public long id; + + public String file_id; + public String path; + public String repo_id; + public String repo_name; + public String related_account; + + @Override + public String toString() { + return "FileCacheEntity{" + + "id=" + id + + ", file_id='" + file_id + '\'' + + ", path='" + path + '\'' + + ", repo_id='" + repo_id + '\'' + + ", repo_name='" + repo_name + '\'' + + ", related_account='" + related_account + '\'' + + '}'; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/entities/FolderBackupCacheEntity.java b/app/src/main/java/com/seafile/seadroid2/data/db/entities/FolderBackupCacheEntity.java new file mode 100644 index 000000000..0345452df --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/entities/FolderBackupCacheEntity.java @@ -0,0 +1,31 @@ +package com.seafile.seadroid2.data.db.entities; + +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +@Entity(tableName = "folder_backup_cache") +public class FolderBackupCacheEntity { + @PrimaryKey(autoGenerate = true) + public long id; + + public String repo_id; + public String repo_name; + public String parent_folder; + public String file_name; + public String file_path; + public long file_size; + public String related_account; + + @Override + public String toString() { + return "FolderBackupCacheEntity{" + + "id=" + id + + ", repo_id='" + repo_id + '\'' + + ", repo_name='" + repo_name + '\'' + + ", parent_folder='" + parent_folder + '\'' + + ", file_name='" + file_name + '\'' + + ", file_path='" + file_path + '\'' + + ", file_size=" + file_size + + '}'; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/entities/FolderBackupMonitorEntity.java b/app/src/main/java/com/seafile/seadroid2/data/db/entities/FolderBackupMonitorEntity.java new file mode 100644 index 000000000..00058367b --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/entities/FolderBackupMonitorEntity.java @@ -0,0 +1,31 @@ +package com.seafile.seadroid2.data.db.entities; + +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +@Entity(tableName = "folder_monitor_cache") +public class FolderBackupMonitorEntity { + @PrimaryKey(autoGenerate = true) + public long id; + + public String repo_id; + public String repo_name; + public String related_account; + public String parent_dir; + public String local_path; + public String version; + + @Override + public String toString() { + return "MonitorEntity{" + + "id=" + id + + ", repo_id='" + repo_id + '\'' + + ", repo_name='" + repo_name + '\'' + + ", related_account='" + related_account + '\'' + + ", parent_dir='" + parent_dir + '\'' + + ", local_path='" + local_path + '\'' + + ", version='" + version + '\'' + + '}'; + + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/entities/ObjsModel.java b/app/src/main/java/com/seafile/seadroid2/data/db/entities/ObjsModel.java new file mode 100644 index 000000000..d71c537c4 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/entities/ObjsModel.java @@ -0,0 +1,30 @@ +package com.seafile.seadroid2.data.db.entities; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +@Entity(tableName = "objs") +public class ObjsModel { + + /** + * when type is repo, this path is repo id. + * when type is dir/file, this path is full_path, like this: /1/2 or /1/2/3/4.md + */ + @PrimaryKey + @NonNull + public String path = ""; + + //repo/dir/file + public String type; + +// public long refresh_time_long; + + // The millisecond of the expiration timestamp. + // There is encryption, and there must be decryption. + // If it is 0, it means that there is no decryption. + public long decrypt_expire_time_long; + + + public String related_account_email; //related account +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/entities/PhotoCacheEntity.java b/app/src/main/java/com/seafile/seadroid2/data/db/entities/PhotoCacheEntity.java new file mode 100644 index 000000000..b24ae8ce0 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/entities/PhotoCacheEntity.java @@ -0,0 +1,24 @@ +package com.seafile.seadroid2.data.db.entities; + +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +@Entity(tableName = "photo_cache") +public class PhotoCacheEntity { + @PrimaryKey(autoGenerate = true) + public long id; + + public String file; + public long date_added; + public String related_account; + + + @Override + public String toString() { + return "PhotoCacheEntity{" + + "id=" + id + + ", file='" + file + '\'' + + ", date_added=" + date_added + + '}'; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/entities/RepoDirEntity.java b/app/src/main/java/com/seafile/seadroid2/data/db/entities/RepoDirEntity.java new file mode 100644 index 000000000..0df71cbdd --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/entities/RepoDirEntity.java @@ -0,0 +1,25 @@ +package com.seafile.seadroid2.data.db.entities; + +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +@Entity(tableName = "repo_dir") +public class RepoDirEntity { + @PrimaryKey(autoGenerate = true) + public long id; + + public String related_account; + public String repo_id; + public String repo_dir; + public String related_account_email; + + @Override + public String toString() { + return "RepoDirEntity{" + + "id=" + id + + ", related_account='" + related_account + '\'' + + ", repo_id='" + repo_id + '\'' + + ", repo_dir='" + repo_dir + '\'' + + '}'; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/entities/RepoModel.java b/app/src/main/java/com/seafile/seadroid2/data/db/entities/RepoModel.java new file mode 100644 index 000000000..196ae5137 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/entities/RepoModel.java @@ -0,0 +1,72 @@ +package com.seafile.seadroid2.data.db.entities; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +import com.google.gson.annotations.JsonAdapter; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.util.sp.SettingsManager; +import com.seafile.seadroid2.data.model.BaseModel; +import com.seafile.seadroid2.data.model.repo.deserializer.EncryptFieldJsonAdapter; +import com.seafile.seadroid2.util.Utils; + +@Entity(tableName = "repos") +public class RepoModel extends BaseModel { + @PrimaryKey + @NonNull + public String repo_id = ""; + public String repo_name; //repo_name + + public String type; //mine\group\shared + public long group_id; + public String group_name; + public String owner_name; //owner_name + public String owner_email; //owner_email + public String owner_contact_email; //owner_contact_email + public String modifier_email; + public String modifier_name; + public String modifier_contact_email; + + + public String related_account_email; //related account + + public String last_modified; + + @JsonAdapter(EncryptFieldJsonAdapter.class) + public boolean encrypted; + + public long size; + public boolean starred; + + public String permission; + public boolean monitored; + public boolean is_admin; + public String salt; + public String status; + + public long last_modified_long; + + public String getSubtitle() { + return Utils.readableFileSize(size) + " · " + Utils.translateCommitTime(last_modified_long); + } + + public int getIcon() { + if (encrypted) + return R.drawable.repo_encrypted; + if (!hasWritePermission()) + return R.drawable.repo_readonly; + + return R.drawable.repo; + } + + public boolean hasWritePermission() { + return !TextUtils.isEmpty(permission) && permission.contains("w"); + } + + public boolean canLocalDecrypt() { + return encrypted && SettingsManager.getInstance().isEncryptEnabled(); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/db/entities/StarredFileCacheEntity.java b/app/src/main/java/com/seafile/seadroid2/data/db/entities/StarredFileCacheEntity.java new file mode 100644 index 000000000..51854995a --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/db/entities/StarredFileCacheEntity.java @@ -0,0 +1,22 @@ +package com.seafile.seadroid2.data.db.entities; + +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +@Entity(tableName = "starred_file_cache") +public class StarredFileCacheEntity { + @PrimaryKey(autoGenerate = true) + public long id; + + public String content; + public String related_account; + + @Override + public String toString() { + return "StarredFileCacheEntity{" + + "id=" + id + + ", content='" + content + '\'' + + ", related_account='" + related_account + '\'' + + '}'; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/model/BaseModel.java b/app/src/main/java/com/seafile/seadroid2/data/model/BaseModel.java new file mode 100644 index 000000000..3c5be30ca --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/model/BaseModel.java @@ -0,0 +1,8 @@ +package com.seafile.seadroid2.data.model; + +import androidx.room.Ignore; + +public class BaseModel { + @Ignore + public boolean is_selected = false; +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/model/GroupItemModel.java b/app/src/main/java/com/seafile/seadroid2/data/model/GroupItemModel.java new file mode 100644 index 000000000..84c6af12a --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/model/GroupItemModel.java @@ -0,0 +1,22 @@ +package com.seafile.seadroid2.data.model; + +import androidx.annotation.StringRes; + +public class GroupItemModel extends BaseModel { + @StringRes + public int name; + + public String title; + + public GroupItemModel() { + + } + + public GroupItemModel(@StringRes int nameRes) { + this.name = nameRes; + } + + public GroupItemModel(String title) { + this.title = title; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/model/ResultModel.java b/app/src/main/java/com/seafile/seadroid2/data/model/ResultModel.java new file mode 100644 index 000000000..84d484d96 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/model/ResultModel.java @@ -0,0 +1,6 @@ +package com.seafile.seadroid2.data.model; + +public class ResultModel extends BaseModel{ + public boolean success; + public String error_msg; +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/model/activities/ActivityModel.java b/app/src/main/java/com/seafile/seadroid2/data/model/activities/ActivityModel.java new file mode 100644 index 000000000..c65756426 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/model/activities/ActivityModel.java @@ -0,0 +1,47 @@ +package com.seafile.seadroid2.data.model.activities; + +import android.text.TextUtils; + +import com.seafile.seadroid2.data.model.BaseModel; +import com.seafile.seadroid2.enums.OpType; +import com.seafile.seadroid2.util.Times; +import com.seafile.seadroid2.util.Utils; + +public class ActivityModel extends BaseModel { + public String op_type; + public String repo_id; + public String repo_name; + public String obj_type; + public String commit_id; + public String path; + public String name; + public String old_path; + public String old_name; + public String author_email; + public String author_name; + public String author_contact_email; + public String avatar_url; + public String time; + + private long mTimeLong; + public OpType opType; + + public String getTime() { + if (mTimeLong == 0){ + mTimeLong = Times.convertMtime2Long(time); + } + return Utils.translateCommitTime(mTimeLong); + } + + + public boolean isFileOpenable() { + return opType == OpType.CREATE || + opType == OpType.UPDATE || + opType == OpType.RENAME || + opType == OpType.EDIT; + } + + public boolean isDir() { + return TextUtils.equals(obj_type,"dir"); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/model/activities/ActivityWrapperModel.java b/app/src/main/java/com/seafile/seadroid2/data/model/activities/ActivityWrapperModel.java new file mode 100644 index 000000000..e53197769 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/model/activities/ActivityWrapperModel.java @@ -0,0 +1,7 @@ +package com.seafile.seadroid2.data.model.activities; + +import java.util.List; + +public class ActivityWrapperModel { + public List events; +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/model/dirents/DeleteDirentModel.java b/app/src/main/java/com/seafile/seadroid2/data/model/dirents/DeleteDirentModel.java new file mode 100644 index 000000000..81c72bc96 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/model/dirents/DeleteDirentModel.java @@ -0,0 +1,16 @@ +package com.seafile.seadroid2.data.model.dirents; + +import com.seafile.seadroid2.data.model.ResultModel; + +public class DeleteDirentModel extends ResultModel { + public String commit_id; + + @Override + public String toString() { + return "DeleteDirentModel{" + + "commit_id='" + commit_id + '\'' + + ", success=" + success + + ", error_msg='" + error_msg + '\'' + + '}'; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/model/dirents/FileCreateModel.java b/app/src/main/java/com/seafile/seadroid2/data/model/dirents/FileCreateModel.java new file mode 100644 index 000000000..dbf63b7dd --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/model/dirents/FileCreateModel.java @@ -0,0 +1,16 @@ +package com.seafile.seadroid2.data.model.dirents; + +public class FileCreateModel { + public String error_msg; + public boolean can_edit; + public boolean can_preview; + public boolean is_locked; + public String mtime; + public String obj_id; + public String obj_name; + public String parent_dir; + public String repo_id; + public long size; + public String type; + +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/model/repo/DirentMiniModel.java b/app/src/main/java/com/seafile/seadroid2/data/model/repo/DirentMiniModel.java new file mode 100644 index 000000000..a4c6ffa5b --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/model/repo/DirentMiniModel.java @@ -0,0 +1,16 @@ +package com.seafile.seadroid2.data.model.repo; + +import com.seafile.seadroid2.data.model.BaseModel; + +public class DirentMiniModel extends BaseModel { + public String repo_id; + public String repo_name; + public String path; + public String obj_name; + public String mtime; + public String user_email; + public String user_name; + public String user_contact_email; + public boolean repo_encrypted; + public boolean is_dir; +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/model/repo/DirentWrapperModel.java b/app/src/main/java/com/seafile/seadroid2/data/model/repo/DirentWrapperModel.java new file mode 100644 index 000000000..9ef9ccc5f --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/model/repo/DirentWrapperModel.java @@ -0,0 +1,11 @@ +package com.seafile.seadroid2.data.model.repo; + +import com.seafile.seadroid2.data.db.entities.DirentModel; + +import java.util.List; + +public class DirentWrapperModel { + public String user_perm; + public String dir_id; + public List dirent_list; +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/model/repo/RepoWrapperModel.java b/app/src/main/java/com/seafile/seadroid2/data/model/repo/RepoWrapperModel.java new file mode 100644 index 000000000..7e3c19500 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/model/repo/RepoWrapperModel.java @@ -0,0 +1,9 @@ +package com.seafile.seadroid2.data.model.repo; + +import com.seafile.seadroid2.data.db.entities.RepoModel; + +import java.util.List; + +public class RepoWrapperModel { + public List repos; +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/model/repo/deserializer/EncryptFieldJsonAdapter.java b/app/src/main/java/com/seafile/seadroid2/data/model/repo/deserializer/EncryptFieldJsonAdapter.java new file mode 100644 index 000000000..4004ad8fc --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/model/repo/deserializer/EncryptFieldJsonAdapter.java @@ -0,0 +1,47 @@ +package com.seafile.seadroid2.data.model.repo.deserializer; + +import android.text.TextUtils; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import java.lang.reflect.Type; + +public class EncryptFieldJsonAdapter implements JsonSerializer, JsonDeserializer { + @Override + public JsonElement serialize(Boolean src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src); + } + + @Override + public Boolean deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if (json == null || json.isJsonNull()) { + return false; + } + + if (TextUtils.equals(json.toString(), "")) { + return false; + } + + if (json.isJsonPrimitive()) { + JsonPrimitive jsonPrimitive = json.getAsJsonPrimitive(); + if (jsonPrimitive.isNumber()) { + int value = jsonPrimitive.getAsInt(); + return value == 1; + } else if (jsonPrimitive.isBoolean()) { + return jsonPrimitive.getAsBoolean(); + } else if (jsonPrimitive.isString()) { + return TextUtils.isEmpty(jsonPrimitive.getAsString()); + } + } + + return false; + } + + +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/model/server/ServerInfoModel.java b/app/src/main/java/com/seafile/seadroid2/data/model/server/ServerInfoModel.java new file mode 100644 index 000000000..4d42769bd --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/model/server/ServerInfoModel.java @@ -0,0 +1,21 @@ +package com.seafile.seadroid2.data.model.server; + +import com.blankj.utilcode.util.CollectionUtils; +import com.seafile.seadroid2.data.ServerInfo; + +import java.util.Collection; +import java.util.List; + +public class ServerInfoModel { + public String version; + public String encrypted_library_version; + public List features; + + public String getFeaturesString() { + if (CollectionUtils.isEmpty(features)){ + return null; + } + + return String.join(",",features); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/model/star/StarredModel.java b/app/src/main/java/com/seafile/seadroid2/data/model/star/StarredModel.java new file mode 100644 index 000000000..21593165a --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/model/star/StarredModel.java @@ -0,0 +1,51 @@ +package com.seafile.seadroid2.data.model.star; + +import android.text.TextUtils; + +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.data.model.BaseModel; +import com.seafile.seadroid2.util.Times; +import com.seafile.seadroid2.util.Utils; + +public class StarredModel extends BaseModel { + public String repo_id; + public String repo_name; + public boolean repo_encrypted; + public boolean is_dir; + public boolean deleted; + + public String mtime; + public String path; + public String obj_name; + public String user_email; + public String user_name; + public String user_contact_email; + + //image thumbnail url + public String encoded_thumbnail_src; + public long size; +// public FileType type; + + private long mTimeLong; + + public String getSubtitle() { + if (deleted) { + return repo_name; + } + + if (TextUtils.isEmpty(mtime)) { + return repo_name; + } + + if (mTimeLong == 0) { + mTimeLong = Times.convertMtime2Long(mtime); + } + + return repo_name + " " + Utils.translateCommitTime(mTimeLong); + } + + public boolean isRepo() { + return TextUtils.equals("/", path) && is_dir; + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/model/star/StarredWrapperModel.java b/app/src/main/java/com/seafile/seadroid2/data/model/star/StarredWrapperModel.java new file mode 100644 index 000000000..04269b807 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/model/star/StarredWrapperModel.java @@ -0,0 +1,7 @@ +package com.seafile.seadroid2.data.model.star; + +import java.util.List; + +public class StarredWrapperModel { + public List starred_item_list; +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/remote/api/AccountService.java b/app/src/main/java/com/seafile/seadroid2/data/remote/api/AccountService.java new file mode 100644 index 000000000..6abc0ee7a --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/remote/api/AccountService.java @@ -0,0 +1,11 @@ +package com.seafile.seadroid2.data.remote.api; + +import com.seafile.seadroid2.account.AccountInfo; + +import io.reactivex.Single; +import retrofit2.http.GET; + +public interface AccountService { + @GET("api2/account/info/") + Single getAccountInfo(); +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/remote/api/ActivityService.java b/app/src/main/java/com/seafile/seadroid2/data/remote/api/ActivityService.java new file mode 100644 index 000000000..5bc241b5b --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/remote/api/ActivityService.java @@ -0,0 +1,13 @@ +package com.seafile.seadroid2.data.remote.api; + +import com.seafile.seadroid2.data.model.activities.ActivityWrapperModel; + +import io.reactivex.Flowable; +import io.reactivex.Single; +import retrofit2.http.GET; +import retrofit2.http.Query; + +public interface ActivityService { + @GET("api/v2.1/activities/?avatar_size=72") + Single getActivities(@Query("page") int page); +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/remote/api/MainService.java b/app/src/main/java/com/seafile/seadroid2/data/remote/api/MainService.java new file mode 100644 index 000000000..935c619b5 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/remote/api/MainService.java @@ -0,0 +1,13 @@ +package com.seafile.seadroid2.data.remote.api; + +import com.seafile.seadroid2.data.model.server.ServerInfoModel; + +import io.reactivex.Single; +import retrofit2.http.GET; + +public interface MainService { + + @GET("api2/server-info/") + Single getServerInfo(); + +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/remote/api/RepoService.java b/app/src/main/java/com/seafile/seadroid2/data/remote/api/RepoService.java new file mode 100644 index 000000000..1a885f2eb --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/remote/api/RepoService.java @@ -0,0 +1,36 @@ +package com.seafile.seadroid2.data.remote.api; + +import com.seafile.seadroid2.data.model.ResultModel; +import com.seafile.seadroid2.data.model.repo.DirentMiniModel; +import com.seafile.seadroid2.data.model.repo.DirentWrapperModel; +import com.seafile.seadroid2.data.model.repo.RepoWrapperModel; + +import java.util.Map; + +import io.reactivex.Flowable; +import io.reactivex.Single; +import okhttp3.RequestBody; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.Multipart; +import retrofit2.http.POST; +import retrofit2.http.PartMap; +import retrofit2.http.Path; +import retrofit2.http.Query; + +public interface RepoService { + @GET("api/v2.1/repos/") + Single getRepos(); + + @GET("api/v2.1/repos/{repo_id}/dir/?with_thumbnail=true") + Single getDirents(@Path("repo_id") String repoId, @Query("p") String path); + + @Multipart + @POST("api/v2.1/starred-items/") + Single star(@PartMap Map map); + + //https://dev.seafile.com/seahub/api/v2.1/starred-items/?repo_id=a5354c34-118d-4d60-9ed6-a246c6d8148d&path=%2F%E8%BF%99%E6%98%AF%E4%B8%80%E4%B8%AA%E8%B6%85%E7%BA%A7%E8%B6%85%E7%BA%A7%E8%B6%85%E7%BA%A7%E8%B6%85%E7%BA%A7%E8%B6%85%E7%BA%A7%E8%B6%85%E7%BA%A7%E8%B6%85%E7%BA%A7%E8%B6%85%E7%BA%A7%E8%B6%85%E7%BA%A7%E8%B6%85%E7%BA%A7%E9%95%BF%E7%9A%84%E5%90%8D%E5%AD%97 + @DELETE("api/v2.1/starred-items/") + Single unStar(@Query("repo_id") String repoId, @Query("path") String path); + +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/remote/api/StarredService.java b/app/src/main/java/com/seafile/seadroid2/data/remote/api/StarredService.java new file mode 100644 index 000000000..1ba399816 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/remote/api/StarredService.java @@ -0,0 +1,19 @@ +package com.seafile.seadroid2.data.remote.api; + +import com.seafile.seadroid2.data.model.ResultModel; +import com.seafile.seadroid2.data.model.star.StarredWrapperModel; + +import io.reactivex.Flowable; +import io.reactivex.Single; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.Query; + +public interface StarredService { + @GET("api/v2.1/starred-items/") + Single getStarItems(); + + @DELETE("api/v2.1/starred-items/") + Single unStarItem(@Query("repo_id") String repoId, @Query("path") String path); + +} diff --git a/app/src/main/java/com/seafile/seadroid2/data/repository/DirentsRepository.java b/app/src/main/java/com/seafile/seadroid2/data/repository/DirentsRepository.java new file mode 100644 index 000000000..ae413f39c --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/data/repository/DirentsRepository.java @@ -0,0 +1,67 @@ +package com.seafile.seadroid2.data.repository; + +import com.seafile.seadroid2.data.db.dao.DirentDAO; +import com.seafile.seadroid2.data.db.entities.DirentModel; +import com.seafile.seadroid2.data.model.repo.DirentWrapperModel; +import com.seafile.seadroid2.data.remote.api.RepoService; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; + +import io.reactivex.Completable; +import io.reactivex.Single; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.functions.Consumer; +import io.reactivex.functions.Function; +import io.reactivex.schedulers.Schedulers; + +public class DirentsRepository { + private DirentDAO dao; + private RepoService service; + + public DirentsRepository(RepoService service, DirentDAO dao) { + this.service = service; + this.dao = dao; + } + + public Single> getDirents(String repoId, String parent_dir) { + // 先尝试从数据库中获取数据 + Single> localData = dao.getAllByParentPath(repoId, parent_dir) + .onErrorResumeNext(throwable -> Single.just(Collections.emptyList())); + + // 发起网络请求并保存到数据库 + Single> remoteData = service.getDirents(repoId, parent_dir) + .map(new Function>() { + @Override + public List apply(DirentWrapperModel direntWrapperModel) throws Exception { + return direntWrapperModel.dirent_list; + } + }) + .doOnSuccess(new Consumer>() { + @Override + public void accept(List direntModels) throws Exception { + +// dao.insertAll(dirents) + } + }); + + return Single.concat(localData, remoteData).first(Collections.emptyList()); + } + + public void insert(List dirents) { + Completable.fromCallable(new Callable() { + @Override + public Boolean call() throws Exception { + dao.insertAll(dirents); + return true; + } + }).subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(); + } + + public void deleteAll() { + dao.deleteAll(); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/enums/FileType.java b/app/src/main/java/com/seafile/seadroid2/enums/FileType.java new file mode 100644 index 000000000..8817e3369 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/enums/FileType.java @@ -0,0 +1,5 @@ +package com.seafile.seadroid2.enums; + +public enum FileType { + DIR, FILE +} diff --git a/app/src/main/java/com/seafile/seadroid2/enums/OpType.java b/app/src/main/java/com/seafile/seadroid2/enums/OpType.java new file mode 100644 index 000000000..b52fc7c27 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/enums/OpType.java @@ -0,0 +1,12 @@ +package com.seafile.seadroid2.enums; + +public enum OpType { + CREATE, + RENAME, + DELETE, + RESTORE, + EDIT, + MOVE, + UPDATE, + PUBLISH, +} diff --git a/app/src/main/java/com/seafile/seadroid2/fileschooser/FileFooterFragment.java b/app/src/main/java/com/seafile/seadroid2/fileschooser/FileFooterFragment.java deleted file mode 100644 index 28d436b3f..000000000 --- a/app/src/main/java/com/seafile/seadroid2/fileschooser/FileFooterFragment.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.seafile.seadroid2.fileschooser; - - -import com.seafile.seadroid2.R; - -import android.os.Bundle; -import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.TextView; - -public class FileFooterFragment extends Fragment { - - private TextView statusView; - private Button cancelButton; - private Button confirmButton; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View root = inflater.inflate(R.layout.file_list_footer, container, false); - statusView = (TextView)root.findViewById(R.id.upload_selection_status); - cancelButton = (Button)root.findViewById(R.id.button_cancel_upload); - confirmButton = (Button)root.findViewById(R.id.button_confirm_upload); - confirmButton.setEnabled(false); - - cancelButton.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - ((MultiFileChooserActivity)getActivity()).onCancelButtonClicked(); - - } - }); - - confirmButton.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - ((MultiFileChooserActivity)getActivity()).onConfirmButtonClicked(); - } - }); - - return root; - } - - public TextView getStatusView() { - return statusView; - } - - public Button getConfirmButton() { - return confirmButton; - } - - @Override - public void onPause() { - super.onPause(); - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/fileschooser/FileListAdapter.java b/app/src/main/java/com/seafile/seadroid2/fileschooser/FileListAdapter.java deleted file mode 100644 index cd209158e..000000000 --- a/app/src/main/java/com/seafile/seadroid2/fileschooser/FileListAdapter.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.seafile.seadroid2.fileschooser; - -import java.util.List; - -import com.google.common.collect.Lists; -import com.seafile.seadroid2.R; - -import android.app.Activity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.TextView; - -public class FileListAdapter extends BaseAdapter { - - private Activity mActivity; - private List mFiles; - - public FileListAdapter(Activity activity) { - this.mActivity = activity; - mFiles = Lists.newArrayList(); - } - - @Override - public int getCount() { - return mFiles.size(); - } - - @Override - public SelectableFile getItem(int position) { - return mFiles.get(position); - } - - public void addItem(SelectableFile file) { - mFiles.add(file); - } - - public void clear() { - mFiles.clear(); - } - - @Override - public long getItemId(int position) { - return position; - } - - public void setListItems(List files) { - this.mFiles = files; - notifyDataSetChanged(); - } - - public List getListItems() { - return mFiles; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - SelectableFile item = mFiles.get(position); - View view = convertView; - Viewholder viewHolder; - TextView title; - TextView subtitle; - ImageView icon; - CheckBox checkBox; - - if (convertView == null) { - view = LayoutInflater.from(mActivity).inflate(R.layout.list_item_entry_check, null); - title = (TextView) view.findViewById(R.id.list_item_title); - subtitle = (TextView) view.findViewById(R.id.list_item_subtitle); - icon = (ImageView) view.findViewById(R.id.list_item_icon); - checkBox = (CheckBox) view.findViewById(R.id.list_item_checkbox); - viewHolder = new Viewholder(title, subtitle, icon, checkBox); - view.setTag(viewHolder); - - viewHolder.checkBox.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - CheckBox cb = (CheckBox) v; - SelectableFile file = (SelectableFile) cb.getTag(); - file.setSelected(cb.isChecked()); - ((MultiFileChooserActivity)mActivity).onFileChecked(file); - - } - }); - - } else { - viewHolder = (Viewholder) convertView.getTag(); - title = viewHolder.title; - subtitle = viewHolder.subtitle; - icon = viewHolder.icon; - checkBox = viewHolder.checkBox; - } - - checkBox.setTag(item); - - checkBox.setChecked(item.isSelected()); - - int iconID = item.getIcon(); - viewHolder.icon.setImageResource(iconID); - viewHolder.title.setText(item.getTitle()); - viewHolder.subtitle.setText(item.getSubtitle()); - viewHolder.checkBox.setVisibility(item.isFile() ? View.VISIBLE : View.GONE); - - return view; - } - - - public class Viewholder { - TextView title, subtitle; - ImageView icon; - CheckBox checkBox; - - public Viewholder(TextView title, TextView subtitle, ImageView icon, CheckBox checkBox) { - super(); - this.icon = icon; - this.checkBox = checkBox; - this.title = title; - this.subtitle = subtitle; - - } - - } - - -} diff --git a/app/src/main/java/com/seafile/seadroid2/fileschooser/FileListFragment.java b/app/src/main/java/com/seafile/seadroid2/fileschooser/FileListFragment.java deleted file mode 100644 index 685295cdc..000000000 --- a/app/src/main/java/com/seafile/seadroid2/fileschooser/FileListFragment.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.seafile.seadroid2.fileschooser; - -import java.util.List; - -import com.seafile.seadroid2.R; - -import android.os.Bundle; -import android.os.Environment; -import androidx.fragment.app.ListFragment; -import androidx.loader.app.LoaderManager; -import androidx.loader.content.Loader; -import android.util.Log; -import android.view.View; -import android.widget.ListView; - -public class FileListFragment extends ListFragment implements LoaderManager.LoaderCallbacks> { - - private static final String LOG_TAG = "FileListFragment"; - private static final int LOADER_ID = 0; - - private FileListAdapter mFileListAdapter; - private String mPath; - - public static FileListFragment newInstance(String path) { - FileListFragment fragment = new FileListFragment(); - Bundle args = new Bundle(); - args.putString(MultiFileChooserActivity.PATH, path); - fragment.setArguments(args); - - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Log.d(LOG_TAG, "onCreate"); - mFileListAdapter = new FileListAdapter(getActivity()); - mPath = getArguments() != null ? getArguments().getString( - MultiFileChooserActivity.PATH) : Environment - .getExternalStorageDirectory().getAbsolutePath(); - } - - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - setEmptyText(getString(R.string.empty_folder)); - setListAdapter(mFileListAdapter); - setListShown(false); - getLoaderManager().initLoader(LOADER_ID, null, this); - super.onActivityCreated(savedInstanceState); - } - - @Override - public void onListItemClick(ListView l, View v, int position, long id) { - FileListAdapter adapter = (FileListAdapter) l.getAdapter(); - if (adapter != null) { - SelectableFile file = adapter.getItem(position); - mPath = file.getAbsolutePath(); - file.toggleSelected(); - if (file.isFile()) { - FileListAdapter.Viewholder viewHolder = (FileListAdapter.Viewholder) v.getTag(); - viewHolder.checkBox.setChecked(file.isSelected()); - } - ((MultiFileChooserActivity) getActivity()).onFileChecked(file); - } - } - - @Override - public void onPause() { - Log.d(LOG_TAG, "onPause"); - super.onPause(); - } - - @Override - public Loader> onCreateLoader(int id, Bundle args) { - return new FileLoader(getActivity(), mPath, ((MultiFileChooserActivity) getActivity()).getSelectedFiles()); - } - - @Override - public void onLoadFinished(Loader> loader, List data) { - mFileListAdapter.setListItems(data); - - if (isResumed()) - setListShown(true); - else - setListShownNoAnimation(true); - - - } - - @Override - public void onLoaderReset(Loader> loader) { - mFileListAdapter.clear(); - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/fileschooser/FileLoader.java b/app/src/main/java/com/seafile/seadroid2/fileschooser/FileLoader.java deleted file mode 100644 index 2c2d3a61f..000000000 --- a/app/src/main/java/com/seafile/seadroid2/fileschooser/FileLoader.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.seafile.seadroid2.fileschooser; - -import java.io.File; -import java.util.List; - -import com.google.common.collect.Lists; -import com.seafile.seadroid2.util.Utils; - -import android.content.Context; -import android.os.FileObserver; -import androidx.loader.content.AsyncTaskLoader; - -public class FileLoader extends AsyncTaskLoader> { - - private static final int FILE_OBSERVER_MASK = FileObserver.CREATE - | FileObserver.DELETE | FileObserver.DELETE_SELF - | FileObserver.MOVED_FROM | FileObserver.MOVED_TO - | FileObserver.MODIFY | FileObserver.MOVE_SELF; - - private FileObserver mFileObserver; - - private List mData; - private String mPath; - private List mSelectedFiles; - - public FileLoader(Context context, String path, List selectedFiles) { - super(context); - this.mPath = path; - mSelectedFiles = Lists.newArrayList(selectedFiles); - } - - @Override - public List loadInBackground() { - return Utils.getFileList(mPath, mSelectedFiles); - } - - @Override - public void deliverResult(List data) { - if (isReset()) { - onReleaseResources(data); - return; - } - - List oldData = mData; - mData = data; - - if (isStarted()) - super.deliverResult(data); - - if (oldData != null && oldData != data) - onReleaseResources(oldData); - } - - @Override - protected void onStartLoading() { - if (mData != null) - deliverResult(mData); - - if (mFileObserver == null) { - mFileObserver = new FileObserver(mPath, FILE_OBSERVER_MASK) { - @Override - public void onEvent(int event, String path) { - onContentChanged(); - } - }; - } - mFileObserver.startWatching(); - - if (takeContentChanged() || mData == null) - forceLoad(); - } - - @Override - protected void onStopLoading() { - cancelLoad(); - } - - @Override - protected void onReset() { - onStopLoading(); - - if (mData != null) { - onReleaseResources(mData); - mData = null; - } - } - - @Override - public void onCanceled(List data) { - super.onCanceled(data); - - onReleaseResources(data); - } - - protected void onReleaseResources(List data) { - - if (mFileObserver != null) { - mFileObserver.stopWatching(); - mFileObserver = null; - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/fileschooser/MultiFileChooserActivity.java b/app/src/main/java/com/seafile/seadroid2/fileschooser/MultiFileChooserActivity.java deleted file mode 100644 index 2cad7e497..000000000 --- a/app/src/main/java/com/seafile/seadroid2/fileschooser/MultiFileChooserActivity.java +++ /dev/null @@ -1,234 +0,0 @@ -package com.seafile.seadroid2.fileschooser; - -import java.io.File; -import java.net.URISyntaxException; -import java.util.List; - -import com.google.common.collect.Lists; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.util.Utils; - -import android.net.Uri; -import android.os.Bundle; -import android.os.Environment; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import androidx.fragment.app.FragmentActivity; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; -import androidx.fragment.app.FragmentManager.BackStackEntry; -import androidx.fragment.app.FragmentManager.OnBackStackChangedListener; -import android.widget.Toast; - -public class MultiFileChooserActivity extends FragmentActivity implements OnBackStackChangedListener { - - public static final String EXTERNAL_BASE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath(); - public static final String PATH = "path"; - public static final String MULTI_FILES_PATHS = "com.seafile.seadroid2.fileschooser.paths"; - - private String mPath; - private FragmentManager mFragmentManager; - private FileFooterFragment mFooterFragment; - private List mSelectedFiles; - - private final BroadcastReceiver mStorageListener = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - Toast.makeText(context, R.string.storage_removed, Toast.LENGTH_LONG).show(); - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.multiple_files_chooser); - - mFragmentManager = getSupportFragmentManager(); - mFragmentManager.addOnBackStackChangedListener(this); - - if (savedInstanceState == null) { - mPath = EXTERNAL_BASE_PATH; - addFragment(mPath); - } else { - mPath = savedInstanceState.getString(PATH); - } - - mFooterFragment = new FileFooterFragment(); - mFragmentManager.beginTransaction().add(R.id.footer_fragment, mFooterFragment).commit(); - - setTitle(mPath); - mSelectedFiles = Lists.newArrayList(); - } - - @Override - protected void onPause() { - super.onPause(); - unregisterStorageListener(); - } - - @Override - protected void onResume() { - super.onResume(); - registerStorageListener(); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - outState.putString(PATH, mPath); - } - - private void addFragment(String path) { - FileListFragment explorerFragment = FileListFragment.newInstance(mPath); - mFragmentManager.beginTransaction().add(R.id.explorer_fragment, explorerFragment).commit(); - } - - @Override - public void onBackStackChanged() { - mPath = EXTERNAL_BASE_PATH; - - int count = mFragmentManager.getBackStackEntryCount(); - if (count > 0) { - BackStackEntry fragment = mFragmentManager - .getBackStackEntryAt(count - 1); - mPath = fragment.getName(); - } - - setTitle(mPath); - } - - public void onCancelButtonClicked() { - setResult(RESULT_CANCELED); - finish(); - } - - public void onConfirmButtonClicked() { - File file; - Uri uri; - String path; - String[] paths = new String[mSelectedFiles.size()]; - - for (int i = 0; i < mSelectedFiles.size(); ++i) { - file = mSelectedFiles.get(i); - uri = Uri.fromFile(file); - try { - path = Utils.getPath(this, uri); - paths[i] = path; - } catch (URISyntaxException e) { - e.printStackTrace(); - return; - } - } - - Intent intent = new Intent(); - intent.putExtra(MULTI_FILES_PATHS, paths); - setResult(RESULT_OK, intent); - finish(); - } - - private void updateSelectionStatus() { - int nSelected = mSelectedFiles.size(); - String status; - if (nSelected == 0) { - status = getResources().getString(R.string.select_upload_files); - } else { - status = getResources().getQuantityString(R.plurals.n_upload_files_selected, nSelected, nSelected); - } - mFooterFragment.getStatusView().setText(status); - } - - private void updateUploadButtonStatus() { - int nSelected = mSelectedFiles.size(); - mFooterFragment.getConfirmButton().setEnabled(nSelected != 0); - } - - private void updateSelectedFileList(SelectableFile file) { - File defaultFile = file.getFile(); - if (file.isSelected()) { - mSelectedFiles.add(defaultFile); - } else { - if (mSelectedFiles.contains(defaultFile)) { - int index = mSelectedFiles.indexOf(defaultFile); - mSelectedFiles.remove(index); - } - } - - } - - public List getSelectedFiles() { - return mSelectedFiles; - } - - /** - * "Replace" the existing Fragment with a new one using given path. - * We're really adding a Fragment to the back stack. - * - * @param path The absolute path of the file (directory) to display. - */ - private void replaceFragment(String path) { - FileListFragment explorerFragment = FileListFragment.newInstance(path); - mFragmentManager.beginTransaction() - .replace(R.id.explorer_fragment, explorerFragment) - .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) - .addToBackStack(path).commit(); - - } - - /** - * Finish this Activity with a result code and URI of the selected file. - * - * @param file The file selected. - */ - private void finishWithResult(SelectableFile file) { - if (file != null) { - Uri uri = Uri.fromFile(file.getFile()); - setResult(RESULT_OK, new Intent().setData(uri)); - finish(); - } else { - setResult(RESULT_CANCELED); - finish(); - } - } - - /** - * Called when the user selects a File - * - * @param file The file that was selected - */ - protected void onFileChecked(SelectableFile file) { - if (file != null) { - mPath = file.getAbsolutePath(); - - if (file.isDirectory()) { - replaceFragment(mPath); - } else { - updateSelectedFileList(file); - updateSelectionStatus(); - updateUploadButtonStatus(); - //finishWithResult(file); - } - } else { - Toast.makeText(MultiFileChooserActivity.this, R.string.error_selecting_file, Toast.LENGTH_SHORT).show(); - } - } - - /** - * Register the external storage BroadcastReceiver. - */ - private void registerStorageListener() { - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_MEDIA_REMOVED); - registerReceiver(mStorageListener, filter); - } - - /** - * Unregister the external storage BroadcastReceiver. - */ - private void unregisterStorageListener() { - unregisterReceiver(mStorageListener); - } - -} diff --git a/app/src/main/java/com/seafile/seadroid2/fileschooser/SelectableFile.java b/app/src/main/java/com/seafile/seadroid2/fileschooser/SelectableFile.java deleted file mode 100644 index a1552b4fd..000000000 --- a/app/src/main/java/com/seafile/seadroid2/fileschooser/SelectableFile.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.seafile.seadroid2.fileschooser; - -import java.io.File; -import java.io.FileFilter; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.data.SeafItem; -import com.seafile.seadroid2.util.Utils; - -public class SelectableFile implements SeafItem { - - private boolean selected; - private File file; - - public SelectableFile(String path) { - selected = false; - file = new File(path); - } - - public SelectableFile(File file, boolean isSelected) { - this.file = file; - selected = isSelected; - } - - public void setSelected(boolean isSelected) { - selected = isSelected; - } - - public boolean isSelected() { - return selected; - } - - public boolean isDirectory() { - return file.isDirectory(); - } - - public boolean isFile() { - return file.isFile(); - } - - public String getName() { - return file.getName(); - } - - @Override - public boolean equals(Object o) { - - if (this == o) { - return true; - } - - if (!(o instanceof SelectableFile)) { - return false; - } - - SelectableFile lhs = (SelectableFile) o; - - return file.equals(lhs.getFile()) && selected == lhs.isSelected(); - } - - public SelectableFile[] listFiles(FileFilter fileFilter) { - File[] files = file.listFiles(fileFilter); - SelectableFile[] selectedFiles = new SelectableFile[files.length]; - for (int i = 0; i < files.length; ++i) { - selectedFiles[i] = new SelectableFile(files[i], false); - } - return selectedFiles; - } - - public long length() { - return file.length(); - } - - public String getAbsolutePath() { - return file.getAbsolutePath(); - } - - public File getFile() { - return file; - } - - public void toggleSelected() { - selected = !selected; - } - - @Override - public String getTitle() { - return getName(); - } - - @Override - public String getSubtitle() { - String timestamp = Utils.translateCommitTime(file.lastModified()); - if (isDirectory()) - return timestamp; - return Utils.readableFileSize(file.length()) + ", " + timestamp; - } - - @Override - public int getIcon() { - if (isDirectory()) - return R.drawable.folder; - return Utils.getFileIcon(getTitle()); - } - -} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/CloudLibraryChooserFragment.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/CloudLibraryChooserFragment.java deleted file mode 100644 index 878b8f437..000000000 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/CloudLibraryChooserFragment.java +++ /dev/null @@ -1,737 +0,0 @@ -package com.seafile.seadroid2.folderbackup; - -import android.content.Intent; -import android.database.Cursor; -import android.os.AsyncTask; -import android.os.Bundle; - -import androidx.fragment.app.Fragment; - -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import com.google.common.collect.Lists; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeafConnection; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.AccountManager; -import com.seafile.seadroid2.avatar.Avatar; -import com.seafile.seadroid2.avatar.AvatarManager; -import com.seafile.seadroid2.cameraupload.CloudLibraryAccountAdapter; -import com.seafile.seadroid2.cameraupload.CloudLibraryAdapter; -import com.seafile.seadroid2.data.DataManager; -import com.seafile.seadroid2.data.SeafDirent; -import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.context.NavContext; -import com.seafile.seadroid2.ui.repo.DirentsAdapter; -import com.seafile.seadroid2.ui.dialog.PasswordDialog; -import com.seafile.seadroid2.ui.dialog.TaskDialog; -import com.seafile.seadroid2.util.ConcurrentAsyncTask; -import com.seafile.seadroid2.util.Utils; - -import java.net.HttpURLConnection; -import java.util.ArrayList; -import java.util.List; - -public class CloudLibraryChooserFragment extends Fragment { - - public static final String DEBUG_TAG = CloudLibraryChooserFragment.class.getSimpleName(); - private FolderBackupConfigActivity mActivity; - private Button mDoneBtn; - public static final String PASSWORD_DIALOG_FRAGMENT_TAG = "passwordDialogFragmentTag"; - public static final String ONLY_SHOW_WRITABLE_REPOS = "onlyShowWritableRepos"; - public static final String ENCRYPTED_REPO_ID = "encryptedRepoId"; - private static final int STEP_CHOOSE_ACCOUNT = 1; - private static final int STEP_CHOOSE_REPO = 2; - private static final int STEP_CHOOSE_DIR = 3; - private int mStep = 1; - private CloudLibraryAccountAdapter mAccountAdapter; - private CloudLibraryAdapter mReposAdapter; - private DirentsAdapter mDirentsAdapter; - private AccountManager mAccountManager; - private DataManager mDataManager; - private NavContext mNavContext; - private Account mAccount; - private LoadAccountsTask mLoadAccountsTask; - private LoadReposTask mLoadReposTask; - private LoadFolderTask mLoadFolderTask; - private AvatarManager avatarManager; - private RelativeLayout mUpLayout; - private TextView mCurrentFolderText; - private TextView mEmptyText, mErrorText; - private ImageView mRefreshBtn; - private View mProgressContainer, mListContainer; - private ListView mFoldersListView; - private Cursor mCursor; - private String mCurrentDir; - private boolean canChooseAccount; - private boolean onlyShowWritableRepos; - private String encryptedRepoId; - private boolean isOnlyChooseRepo; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - - mActivity = (FolderBackupConfigActivity) getActivity(); - View rootView = mActivity.getLayoutInflater().inflate(R.layout.folder_backup_remote_library_fragment, null); - Intent intent = mActivity.getIntent(); - avatarManager = new AvatarManager(); - Account account = intent.getParcelableExtra("account"); - if (account == null) { - canChooseAccount = true; - } else { - mAccount = account; - } - onlyShowWritableRepos = intent.getBooleanExtra(ONLY_SHOW_WRITABLE_REPOS, true); - encryptedRepoId = intent.getStringExtra(ENCRYPTED_REPO_ID); - isOnlyChooseRepo = true; - - mFoldersListView = (ListView) rootView.findViewById(R.id.cuc_multi_selection_lv); - mFoldersListView.setFastScrollEnabled(true); - mUpLayout = (RelativeLayout) rootView.findViewById(R.id.cuc_multi_selection_up_layout); - mCurrentFolderText = (TextView) rootView.findViewById(R.id.cuc_multi_selection_current_folder_txt); - mEmptyText = (TextView) rootView.findViewById(R.id.cuc_multi_selection_empty_msg); - mErrorText = (TextView) rootView.findViewById(R.id.cuc_multi_selection_error_msg); - mRefreshBtn = (ImageView) rootView.findViewById(R.id.cuc_multi_selection_refresh_iv); - mProgressContainer = rootView.findViewById(R.id.cuc_multi_selection_progress_container); - mListContainer = rootView.findViewById(R.id.cuc_multi_selection_list_container); - mDoneBtn = (Button) rootView.findViewById(R.id.cuc_remote_library_btn); - - mDoneBtn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mActivity.saveRepoConfig(); - mActivity.finish(); - } - }); - - mRefreshBtn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - refreshList(true); - } - }); - - mUpLayout.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - try { - stepBack(); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - - mFoldersListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - onListItemClick(parent, position, id); - } - }); - - if (canChooseAccount) { - chooseAccount(); - } else { - chooseRepo(); - } - return rootView; - } - - @Override - public void onResume() { - super.onResume(); - loadAvatarUrls(48); - } - - private void refreshList(final boolean forceRefresh) { - switch (mStep) { - case STEP_CHOOSE_ACCOUNT: - if (mLoadAccountsTask != null && mLoadAccountsTask.getStatus() != AsyncTask.Status.FINISHED) { - return; - } else { - chooseAccount(false); - break; - } - case STEP_CHOOSE_REPO: - if (mLoadReposTask != null && mLoadReposTask.getStatus() != AsyncTask.Status.FINISHED) { - return; - } else { - chooseRepo(forceRefresh); - break; - } - case STEP_CHOOSE_DIR: - if (mLoadFolderTask != null && mLoadFolderTask.getStatus() != AsyncTask.Status.FINISHED) { - return; - } else { - SeafRepo repo = getDataManager().getCachedRepoByID(getNavContext().getRepoID()); - if (repo.encrypted && !getDataManager().getRepoPasswordSet(repo.repo_id)) { - String password = getDataManager().getRepoPassword(repo.repo_id); - showPasswordDialog(repo.repo_name, repo.repo_id, - new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - chooseRepo(forceRefresh); - } - }, password); - } - chooseDir(forceRefresh); - break; - } - } - } - - public void onListItemClick(final View v, final int position, final long id) { - NavContext nav = getNavContext(); - SeafRepo repo = null; - if (mStep == STEP_CHOOSE_REPO) { - repo = getReposAdapter().getItem(position); - //mCurrentFolderText.setText(nav.getRepoName()); - } else if (mStep == STEP_CHOOSE_DIR) { - repo = getDataManager().getCachedRepoByID(nav.getRepoID()); - } - - if (repo != null) { - if (repo.encrypted && !getDataManager().getRepoPasswordSet(repo.repo_id)) { - String password = getDataManager().getRepoPassword(repo.repo_id); - showPasswordDialog(repo.repo_name, repo.repo_id, new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - onListItemClick(v, position, id); - } - }, password); - - return; - } - } - - switch (mStep) { - case STEP_CHOOSE_ACCOUNT: - setAccount(getAccountAdapter().getItem(position)); - mCurrentDir = mAccount.getDisplayName(); - setCurrentDirText(mCurrentDir); - chooseRepo(); - break; - case STEP_CHOOSE_REPO: - if (!isOnlyChooseRepo) { - nav.setRepoName(repo.repo_name); - nav.setRepoID(repo.repo_id); - nav.setDirPath("/"); - chooseDir(); - } - mCurrentDir = getString(R.string.settings_cuc_remote_lib_repo, repo.repo_name); - setCurrentDirText(mCurrentDir); - SeafRepo seafRepo = getReposAdapter().getItem(position); - onRepoSelected(mAccount, seafRepo); - break; - case STEP_CHOOSE_DIR: - SeafDirent dirent = getDirentsAdapter().getItem(position); - mCurrentDir += "/" + dirent.name; - setCurrentDirText(mCurrentDir); - - if (dirent.type == SeafDirent.DirentType.FILE) { - return; - } - - String path = Utils.pathJoin(nav.getDirPath(), dirent.name); - nav.setDirPath(path); - refreshFolder(); - break; - } - } - - private void onRepoSelected(Account account, SeafRepo seafRepo) { - mActivity.saveBackupLibrary(account, seafRepo); - getReposAdapter().setSelectedRepo(seafRepo); - getReposAdapter().notifyDataSetChanged(); - } - - private void stepBack() { - stepBack(false); - } - - private void stepBack(boolean cancelIfFirstStep) { - switch (mStep) { - case STEP_CHOOSE_ACCOUNT: - if (cancelIfFirstStep) { - mActivity.finish(); - } - mUpLayout.setVisibility(View.INVISIBLE); - break; - case STEP_CHOOSE_REPO: - if (canChooseAccount) { - mCurrentDir = getString(R.string.settings_cuc_remote_lib_account); - setCurrentDirText(mCurrentDir); - chooseAccount(false); - } else if (cancelIfFirstStep) { - mActivity.finish(); - } - break; - case STEP_CHOOSE_DIR: - if (getNavContext().isRepoRoot()) { - mCurrentDir = getAccountManager().getCurrentAccount().getEmail(); - setCurrentDirText(mCurrentDir); - chooseRepo(); - } else { - String path = getNavContext().getDirPath(); - mCurrentDir = getNavContext().getRepoName() + Utils.getParentPath(path); - setCurrentDirText(mCurrentDir); - getNavContext().setDirPath(Utils.getParentPath(path)); - refreshFolder(); - } - break; - } - } - - private void showPasswordDialog() { - NavContext nav = getNavContext(); - String repoName = nav.getRepoName(); - String repoID = nav.getRepoID(); - - showPasswordDialog(repoName, repoID, new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - refreshFolder(); - } - }, null); - } - - public void showPasswordDialog(String repoName, String repoID, TaskDialog.TaskDialogListener listener, String password) { - PasswordDialog passwordDialog = new PasswordDialog(); - passwordDialog.setRepo(repoName, repoID, mAccount); - if (password != null) { - passwordDialog.setPassword(password); - } - passwordDialog.setTaskDialogLisenter(listener); - passwordDialog.show(mActivity.getSupportFragmentManager(), PASSWORD_DIALOG_FRAGMENT_TAG); - } - - private void chooseDir() { - chooseDir(false); - } - - private void chooseDir(boolean forceRefresh) { - mStep = STEP_CHOOSE_DIR; - mUpLayout.setVisibility(View.VISIBLE); - mEmptyText.setText(R.string.dir_empty); - setListAdapter(getDirentsAdapter()); - refreshFolder(forceRefresh); - } - - private void chooseAccount() { - chooseAccount(true); - } - - /** - * List all accounts - */ - private void chooseAccount(boolean forwardIfOnlyOneAccount) { - mStep = STEP_CHOOSE_ACCOUNT; - mUpLayout.setVisibility(View.INVISIBLE); - mEmptyText.setText(R.string.no_account); - mCurrentDir = getString(R.string.settings_cuc_remote_lib_account); - setCurrentDirText(mCurrentDir); - mLoadAccountsTask = new LoadAccountsTask(getAccountManager(), forwardIfOnlyOneAccount); - ConcurrentAsyncTask.execute(mLoadAccountsTask); - setListAdapter(getAccountAdapter()); - } - - /** - * List all repos - */ - private void chooseRepo() { - chooseRepo(false); - } - - private void chooseRepo(boolean forceRefresh) { - mStep = STEP_CHOOSE_REPO; - mUpLayout.setVisibility(View.VISIBLE); - mCurrentDir = mAccount.getDisplayName(); - setCurrentDirText(mCurrentDir); - setListAdapter(getReposAdapter()); - getNavContext().setRepoID(null); - - if (!Utils.isNetworkOn() || !forceRefresh) { - List repos = getDataManager().getReposFromCache(); - if (repos != null) { - updateAdapterWithRepos(repos); - return; - } - } - mLoadReposTask = new LoadReposTask(getDataManager()); - ConcurrentAsyncTask.execute(mLoadReposTask); - } - - private void refreshFolder() { - refreshFolder(false); - } - - private void refreshFolder(boolean forceRefresh) { - String repoID = getNavContext().getRepoID(); - String dirPath = getNavContext().getDirPath(); - if (!Utils.isNetworkOn() || !forceRefresh) { - List dirents = getDataManager().getCachedDirents( - getNavContext().getRepoID(), getNavContext().getDirPath()); - if (dirents != null) { - updateAdapterWithDirents(dirents); - return; - } - } - mLoadFolderTask = new LoadFolderTask(repoID, dirPath, getDataManager()); - ConcurrentAsyncTask.execute(mLoadFolderTask); - } - - private void updateAdapterWithDirents(List dirents) { - getDirentsAdapter().setDirents(dirents); - showListOrEmptyText(dirents.size()); - } - - private void updateAdapterWithRepos(List repos) { - // remove encrypted repos in order to "hide" them in selection list - List filteredRepos = Lists.newArrayList(); - for (SeafRepo repo : repos) { - if (!repo.encrypted) - filteredRepos.add(repo); - } - getReposAdapter().setRepos(filteredRepos); - showListOrEmptyText(filteredRepos.size()); - } - - private CloudLibraryAccountAdapter getAccountAdapter() { - if (mAccountAdapter == null) { - mAccountAdapter = new CloudLibraryAccountAdapter(mActivity); - } - return mAccountAdapter; - } - - private CloudLibraryAdapter getReposAdapter() { - if (mReposAdapter == null) { - mReposAdapter = new CloudLibraryAdapter(onlyShowWritableRepos, encryptedRepoId); - } - return mReposAdapter; - } - - private DirentsAdapter getDirentsAdapter() { - if (mDirentsAdapter == null) { - mDirentsAdapter = new DirentsAdapter(); - } - return mDirentsAdapter; - } - - private void showListOrEmptyText(int listSize) { - if (listSize == 0) { - mFoldersListView.setVisibility(View.GONE); - mEmptyText.setVisibility(View.VISIBLE); - } else { - mFoldersListView.setVisibility(View.VISIBLE); - mEmptyText.setVisibility(View.GONE); - } - } - - private void setListAdapter(BaseAdapter adapter) { - mFoldersListView.setAdapter(adapter); - } - - private DataManager getDataManager() { - if (mDataManager == null) { - mDataManager = new DataManager(mAccount); - } - return mDataManager; - } - - private void setAccount(Account account) { - mAccount = account; - mDataManager = new DataManager(account); - } - - private AccountManager getAccountManager() { - if (mAccountManager == null) { - mAccountManager = new AccountManager(getActivity()); - } - return mAccountManager; - } - - private NavContext getNavContext() { - if (mNavContext == null) { - mNavContext = new NavContext(); - } - return mNavContext; - } - - /** - * Sets the current directory's text. - */ - private void setCurrentDirText(String text) { - mCurrentFolderText.setText(text); - } - - @Override - public void onDestroy() { - super.onDestroy(); - getActivity().finish(); - } - - @Override - public void onPause() { - super.onPause(); - getActivity().finish(); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - if (isRemoving()) { - mCursor.close(); - mCursor = null; - } - } - - private class LoadAccountsTask extends AsyncTask { - private List accounts; - private Exception err; - private AccountManager accountManager; - private boolean forwardIfOnlyOneAccount; - - public LoadAccountsTask(AccountManager accountManager, boolean forwardIfOnlyOneAccount) { - this.accountManager = accountManager; - this.forwardIfOnlyOneAccount = forwardIfOnlyOneAccount; - } - - @Override - protected void onPreExecute() { - showLoading(true); - } - - @Override - protected Void doInBackground(Void... params) { - try { - accounts = accountManager.getAccountList(); - } catch (Exception e) { - err = e; - } - return null; - } - - @Override - protected void onPostExecute(Void v) { - showLoading(false); - if (err != null || accounts == null) { - setErrorMessage(R.string.load_accounts_fail); - if (err != null) { - Log.d(DEBUG_TAG, "failed to load accounts: " + err.getMessage()); - } - return; - } - - if (accounts.size() == 1 && forwardIfOnlyOneAccount) { - // Only 1 account. Go to next step. - setAccount(accounts.get(0)); - chooseRepo(); - return; - } - - CloudLibraryAccountAdapter adapter = getAccountAdapter(); - adapter.clear(); - for (Account account : accounts) { - adapter.add(account); - } - adapter.notifyDataSetChanged(); - showListOrEmptyText(accounts.size()); - } - } - - private class LoadReposTask extends AsyncTask { - private List repos; - private SeafException err; - private DataManager dataManager; - - public LoadReposTask(DataManager dataManager) { - this.dataManager = dataManager; - } - - @Override - protected Void doInBackground(Void... params) { - try { - repos = dataManager.getReposFromServer(); - } catch (SeafException e) { - err = e; - } - return null; - } - - @Override - protected void onPreExecute() { - showLoading(true); - } - - @Override - protected void onPostExecute(Void v) { - if (mStep != STEP_CHOOSE_REPO) { - return; - } - showLoading(false); - if (err != null || repos == null) { - setErrorMessage(R.string.load_libraries_fail); - Log.d(DEBUG_TAG, "failed to load repos: " + (err != null ? err.getMessage() : " no error present")); - return; - } - updateAdapterWithRepos(repos); - } - } - - private class LoadFolderTask extends AsyncTask { - private String repoID, dirPath; - private SeafException err; - private DataManager dataManager; - private List dirents; - - public LoadFolderTask(String repoID, String dirPath, DataManager dataManager) { - this.repoID = repoID; - this.dirPath = dirPath; - this.dataManager = dataManager; - } - - @Override - protected void onPreExecute() { - showLoading(true); - } - - @Override - protected Void doInBackground(Void... params) { - try { - dirents = dataManager.getDirentsFromServer(repoID, dirPath); - } catch (SeafException e) { - err = e; - } - return null; - } - - @Override - protected void onPostExecute(Void v) { - if (mStep != STEP_CHOOSE_DIR) { - return; - } - getDirentsAdapter().clearDirents(); - showLoading(false); - if (err != null) { - int retCode = err.getCode(); - if (retCode == SeafConnection.HTTP_STATUS_REPO_PASSWORD_REQUIRED) { - showPasswordDialog(); - } else if (retCode == HttpURLConnection.HTTP_NOT_FOUND) { - final String message = String.format(getString(R.string.op_exception_folder_deleted), dirPath); - mActivity.showShortToast(mActivity, message); - } else { - Log.d(DEBUG_TAG, "failed to load dirents: " + err.getMessage()); - err.printStackTrace(); - setErrorMessage(R.string.load_dir_fail); - } - return; - } - - if (dirents == null) { - Log.d(DEBUG_TAG, "failed to load dirents: no error present"); - setErrorMessage(R.string.load_dir_fail); - return; - } - updateAdapterWithDirents(dirents); - } - } - - public void loadAvatarUrls(int avatarSize) { - List avatars; - if (!Utils.isNetworkOn() || !avatarManager.isNeedToLoadNewAvatars()) { - avatars = avatarManager.getAvatarList(); - if (avatars == null) { - return; - } - mAccountAdapter.setAvatars((ArrayList) avatars); - mAccountAdapter.notifyDataSetChanged(); - return; - } - LoadAvatarUrlsTask task = new LoadAvatarUrlsTask(avatarSize); - ConcurrentAsyncTask.execute(task); - } - - private class LoadAvatarUrlsTask extends AsyncTask> { - - private List avatars; - private int avatarSize; - private SeafConnection httpConnection; - - public LoadAvatarUrlsTask(int avatarSize) { - this.avatarSize = avatarSize; - this.avatars = Lists.newArrayList(); - } - - @Override - protected List doInBackground(Void... params) { - avatars = avatarManager.getAvatarList(); - List acts = avatarManager.getAccountsWithoutAvatars(); - List newAvatars = new ArrayList(acts.size()); - for (Account account : acts) { - httpConnection = new SeafConnection(account); - String avatarRawData = null; - try { - avatarRawData = httpConnection.getAvatar(account.getEmail(), avatarSize); - } catch (SeafException e) { - e.printStackTrace(); - return avatars; - } - Avatar avatar = avatarManager.parseAvatar(avatarRawData); - if (avatar == null) - continue; - - avatar.setSignature(account.getSignature()); - avatars.add(avatar); - newAvatars.add(avatar); - } - avatarManager.saveAvatarList(newAvatars); - return avatars; - } - - @Override - protected void onPostExecute(List avatars) { - if (avatars == null) { - return; - } - mAccountAdapter.setAvatars((ArrayList) avatars); - mAccountAdapter.notifyDataSetChanged(); - } - } - - private void setErrorMessage(int resID) { - mErrorText.setVisibility(View.VISIBLE); - mErrorText.setText(getString(resID)); - } - - private void clearError() { - mErrorText.setVisibility(View.GONE); - } - - private void showLoading(boolean loading) { - clearError(); - if (loading) { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_in)); - mListContainer.startAnimation(AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_out)); - mProgressContainer.setVisibility(View.VISIBLE); - mListContainer.setVisibility(View.INVISIBLE); - } else { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_out)); - mListContainer.startAnimation(AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_in)); - mProgressContainer.setVisibility(View.GONE); - mListContainer.setVisibility(View.VISIBLE); - } - } -} - diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupConfigActivity.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupConfigActivity.java deleted file mode 100644 index 3525db047..000000000 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupConfigActivity.java +++ /dev/null @@ -1,266 +0,0 @@ -package com.seafile.seadroid2.folderbackup; - -import android.app.Activity; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Bundle; -import android.os.IBinder; - -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentStatePagerAdapter; -import androidx.viewpager.widget.ViewPager; - -import android.text.TextUtils; -import android.widget.Toast; - -import com.google.gson.Gson; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SettingsManager; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.folderbackup.selectfolder.SelectBackupFolderFragment; -import com.seafile.seadroid2.folderbackup.selectfolder.StringTools; -import com.seafile.seadroid2.ui.BaseActivity; -import com.seafile.seadroid2.ui.activity.SeafilePathChooserActivity; -import com.seafile.seadroid2.ui.settings.SettingsFragment; -import com.seafile.seadroid2.util.SLogs; - -import java.util.ArrayList; -import java.util.List; - -public class FolderBackupConfigActivity extends BaseActivity { - - public String DEBUG_TAG = FolderBackupConfigActivity.class.getSimpleName(); - public static final String BACKUP_SELECT_REPO = "backup_select_repo"; - public static final String BACKUP_SELECT_PATHS = "backup_select_paths"; - public static final String BACKUP_SELECT_PATHS_SWITCH = "backup_select_paths_switch"; - - private SelectBackupFolderFragment mBucketsFragment; - private CloudLibraryChooserFragment mCloudLibFragment; - - private SeafRepo mSeafRepo; - private Account mAccount; - private boolean isChooseFolderPage; - private boolean isChooseLibPage; - private FolderBackupDBHelper databaseHelper; - private FolderBackupService mBackupService; - private List selectFolderPaths; - private Activity mActivity; - private String originalBackupPaths; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - overridePendingTransition(R.anim.fade_in, R.anim.fade_out); - setContentView(R.layout.folder_backup_activity_layout); - if (getSupportActionBar() != null) - getSupportActionBar().hide(); - - isChooseFolderPage = getIntent().getBooleanExtra(SettingsFragment.FOLDER_BACKUP_REMOTE_PATH, false); - isChooseLibPage = getIntent().getBooleanExtra(SettingsFragment.FOLDER_BACKUP_REMOTE_LIBRARY, false); - - ViewPager mViewPager = (ViewPager) findViewById(R.id.cuc_pager); - FragmentManager fm = getSupportFragmentManager(); - mViewPager.setAdapter(new FolderBackupConfigAdapter(fm)); - mViewPager.setOffscreenPageLimit(2); - - databaseHelper = FolderBackupDBHelper.getDatabaseHelper(); - - //bind service - Intent bindIntent = new Intent(this, FolderBackupService.class); - bindService(bindIntent, mFolderBackupConnection, Context.BIND_AUTO_CREATE); - - mActivity = this; - - originalBackupPaths = SettingsManager.instance().getBackupPaths(); - - if (isChooseFolderPage && !TextUtils.isEmpty(originalBackupPaths)) { - selectFolderPaths = StringTools.getJsonToList(originalBackupPaths); - } - } - - public void saveBackupLibrary(Account account, SeafRepo seafRepo) { - mSeafRepo = seafRepo; - mAccount = account; - } - - public FolderBackupDBHelper getDatabaseHelper() { - return databaseHelper; - } - - public void setFolderPathList(List selectFileList) { - this.selectFolderPaths = selectFileList; - } - - public List getSelectFolderPath() { - return selectFolderPaths; - } - - public void saveRepoConfig() { - if (!isChooseLibPage) { - return; - } - - //FIX an issue: When no folder or library is selected, a crash occurs - if (null == mSeafRepo || null == mAccount) { - SLogs.d("----------No repo is selected"); - return; - } - - Intent intent = new Intent(); - - // update cloud library data - if (mSeafRepo != null && mAccount != null) { - intent.putExtra(SeafilePathChooserActivity.DATA_REPO_NAME, mSeafRepo.repo_name); - intent.putExtra(SeafilePathChooserActivity.DATA_REPO_ID, mSeafRepo.repo_id); - intent.putExtra(SeafilePathChooserActivity.DATA_ACCOUNT, mAccount); - intent.putExtra(BACKUP_SELECT_REPO, true); - SettingsManager.instance().saveBackupEmail(mAccount.getEmail()); - try { - RepoConfig repoConfig = databaseHelper.getRepoConfig(mAccount.getEmail()); - if (repoConfig != null) { - databaseHelper.updateRepoConfig(mAccount.getEmail(), mSeafRepo.getRepoId(), mSeafRepo.getRepoName()); - } else { - databaseHelper.saveRepoConfig(mAccount.getEmail(), mSeafRepo.getRepoId(), mSeafRepo.getRepoName()); - } - Toast.makeText(mActivity, mActivity.getString(R.string.folder_backup_select_repo_update), Toast.LENGTH_SHORT).show(); - } catch (Exception e) { - SLogs.d("=saveRepoConfig=======================" + e.toString()); - } - } - - setResult(RESULT_OK, intent); - - boolean automaticBackup = SettingsManager.instance().isFolderAutomaticBackup(); - if (automaticBackup && mBackupService != null) { - mBackupService.backupFolder(mAccount.getEmail()); - } - } - - public void saveFolderConfig() { - if (!isChooseFolderPage) { - return; - } - - //FIX an issue: When no folder or library is selected, a crash occurs - if (selectFolderPaths == null || selectFolderPaths.isEmpty()) { - SLogs.d("----------No folder is selected"); - - //clear local storage - SettingsManager.instance().saveBackupPaths(""); - - Intent intent = new Intent(); - intent.putExtra(BACKUP_SELECT_PATHS_SWITCH, true); - setResult(RESULT_OK, intent); - return; - } - - String backupEmail = SettingsManager.instance().getBackupEmail(); - String strJsonPath = new Gson().toJson(selectFolderPaths); - - if ((TextUtils.isEmpty(originalBackupPaths) && !TextUtils.isEmpty(strJsonPath)) || !originalBackupPaths.equals(strJsonPath)) { - mBackupService.startFolderMonitor(selectFolderPaths); - SLogs.d("----------Restart monitoring FolderMonitor"); - } - - if (!TextUtils.isEmpty(originalBackupPaths) && TextUtils.isEmpty(strJsonPath)) { - mBackupService.stopFolderMonitor(); - } - - SettingsManager.instance().saveBackupPaths(strJsonPath); - Intent intent = new Intent(); - if (selectFolderPaths != null) { - intent.putStringArrayListExtra(BACKUP_SELECT_PATHS, (ArrayList) selectFolderPaths); - intent.putExtra(BACKUP_SELECT_PATHS_SWITCH, true); - } - - setResult(RESULT_OK, intent); - - boolean folderAutomaticBackup = SettingsManager.instance().isFolderAutomaticBackup(); - if (folderAutomaticBackup && mBackupService != null) { - mBackupService.backupFolder(backupEmail); - } - } - - @Override - public void onBackPressed() { - if (mBucketsFragment != null && mBucketsFragment.onBackPressed()) { - return; - } - setResult(RESULT_CANCELED); - super.onBackPressed(); - } - - public boolean isChooseDirPage() { - return isChooseFolderPage; - } - - @Override - protected void onDestroy() { - if (mBackupService != null) { - unbindService(mFolderBackupConnection); - mBackupService = null; - } - super.onDestroy(); - } - - private final ServiceConnection mFolderBackupConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName className, IBinder binder) { - FolderBackupService.FileBackupBinder fileBackupBinder = (FolderBackupService.FileBackupBinder) binder; - mBackupService = fileBackupBinder.getService(); - } - - @Override - public void onServiceDisconnected(ComponentName className) { - mBackupService = null; - } - - }; - - private class FolderBackupConfigAdapter extends FragmentStatePagerAdapter { - - public FolderBackupConfigAdapter(FragmentManager fm) { - super(fm); - } - - @Override - public Fragment getItem(int position) { - - if (isChooseLibPage) { - return position == 0 ? new CloudLibraryChooserFragment() : null; - } - if (isChooseFolderPage) { - switch (position) { - case 0: - mBucketsFragment = new SelectBackupFolderFragment(); - return mBucketsFragment; - default: - return null; - } - - } - switch (position) { - case 0: - mCloudLibFragment = new CloudLibraryChooserFragment(); - return mCloudLibFragment; - case 1: - mBucketsFragment = new SelectBackupFolderFragment(); - return mBucketsFragment; - default: - return null; - } - } - - @Override - public int getCount() { - if (isChooseLibPage || isChooseFolderPage) - return 1; - else - return 2; - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupSelectedPathActivity.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupSelectedPathActivity.java deleted file mode 100644 index faf03b255..000000000 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupSelectedPathActivity.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.seafile.seadroid2.folderbackup; - -import static com.seafile.seadroid2.folderbackup.FolderBackupConfigActivity.BACKUP_SELECT_PATHS; -import static com.seafile.seadroid2.folderbackup.FolderBackupConfigActivity.BACKUP_SELECT_PATHS_SWITCH; -import static com.seafile.seadroid2.ui.settings.SettingsFragment.FOLDER_BACKUP_REMOTE_PATH; - -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.text.TextUtils; -import android.view.MenuItem; -import android.view.View; - -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.blankj.utilcode.util.CollectionUtils; -import com.chad.library.adapter.base.BaseQuickAdapter; -import com.chad.library.adapter.base.QuickAdapterHelper; -import com.cocosw.bottomsheet.BottomSheet; -import com.google.gson.Gson; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SettingsManager; -import com.seafile.seadroid2.folderbackup.selectfolder.StringTools; -import com.seafile.seadroid2.ui.BaseActivity; -import com.seafile.seadroid2.ui.bottomsheet.BottomSheetTextFragment; - -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; - -public class FolderBackupSelectedPathActivity extends BaseActivity { - private RecyclerView mRecyclerView; - private FolderBackSelectedPathRecyclerViewAdapter mAdapter; - private QuickAdapterHelper helper; - - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - overridePendingTransition(R.anim.fade_in, R.anim.fade_out); - setContentView(R.layout.folder_backup_selected_path_activity); - - - findViewById(R.id.add_backup_folder).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(FolderBackupSelectedPathActivity.this, FolderBackupConfigActivity.class); - intent.putExtra(FOLDER_BACKUP_REMOTE_PATH, true); - startActivity(intent); - } - }); - - - setSupportActionBar(getActionBarToolbar()); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle(R.string.settings_folder_backup_select_title); - - mRecyclerView = findViewById(R.id.lv_search); - mRecyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false)); - - initAdapter(); - } - - @Override - protected void onResume() { - super.onResume(); - initData(); - } - - private void initData() { - String backupPaths = SettingsManager.instance().getBackupPaths(); - if (!TextUtils.isEmpty(backupPaths)) { - List backupSelectPaths = StringTools.getJsonToList(backupPaths); - mAdapter.submitList(backupSelectPaths); - } - } - - private void initAdapter() { - mAdapter = new FolderBackSelectedPathRecyclerViewAdapter(); - View t = findViewById(R.id.ll_message_content); - mAdapter.setEmptyView(t); - mAdapter.setEmptyViewEnable(true); - mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() { - @Override - public void onClick(@NotNull BaseQuickAdapter baseQuickAdapter, @NotNull View view, int i) { - showBottomDialog(mAdapter.getItems().get(i)); - } - }); - mAdapter.addOnItemChildClickListener(R.id.more, new BaseQuickAdapter.OnItemChildClickListener() { - @Override - public void onItemClick(@NotNull BaseQuickAdapter baseQuickAdapter, @NotNull View view, int i) { - showRepoBottomSheet(i); - } - }); - - helper = new QuickAdapterHelper.Builder(mAdapter).build(); - mRecyclerView.setAdapter(helper.getAdapter()); - } - - private void showRepoBottomSheet(int position) { - new BottomSheet.Builder(this).sheet(R.menu.folder_backup_bottom_sheet_delete).listener(new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == R.id.delete) { - mAdapter.removeAt(position); - - String strJsonPath = new Gson().toJson(mAdapter.getItems()); - SettingsManager.instance().saveBackupPaths(strJsonPath); - } - } - }).show(); - } - - private void showBottomDialog(String text) { - BottomSheetTextFragment.newInstance(text).show(getSupportFragmentManager(), BottomSheetTextFragment.class.getSimpleName()); - } - - @Override - public void onBackPressed() { - setFinishPage(); - - super.onBackPressed(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - setFinishPage(); - - finish(); - return true; - } - return super.onOptionsItemSelected(item); - } - - public void setFinishPage() { - Intent intent = new Intent(); - if (!CollectionUtils.isEmpty(mAdapter.getItems())) { - intent.putStringArrayListExtra(BACKUP_SELECT_PATHS, (ArrayList) mAdapter.getItems()); - intent.putExtra(BACKUP_SELECT_PATHS_SWITCH, true); - } - setResult(RESULT_OK, intent); - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileListAdapter.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileListAdapter.java deleted file mode 100644 index 112fd995a..000000000 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileListAdapter.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.seafile.seadroid2.folderbackup.selectfolder; - - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Context; -import androidx.recyclerview.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.seafile.seadroid2.R; - -import java.util.List; - -public class FileListAdapter extends RecyclerView.Adapter { - private List mListData; - private Context mContext; - private OnFileItemClickListener onItemClickListener; - - public void setOnItemClickListener(OnFileItemClickListener onItemClickListener) { - this.onItemClickListener = onItemClickListener; - } - - public FileListAdapter(Activity context, List listData) { - mListData = listData; - mContext = context; - } - - @Override - public FileListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View view = LayoutInflater.from(mContext).inflate(R.layout.item_files_list, parent, false); - FileListViewHolder fileListViewHolder = new FileListViewHolder(view); - return fileListViewHolder; - } - - @SuppressLint("StringFormatMatches") - @Override - public void onBindViewHolder(final FileListViewHolder holder, int positon) { - final FileBean fileBean = mListData.get(positon); - holder.checkBoxFile.setChecked(fileBean.isChecked()); - holder.tvFileName.setText(fileBean.getFileName()); - holder.imgvFiletype.setImageResource(fileBean.getFileImgType()); - boolean isFile = fileBean.isFile(); - if (isFile) { - holder.tvFileDetail.setText(String.format(mContext.getString(R.string.folder_file_item_size), fileBean.getSize())); - } else { - holder.tvFileDetail.setText(String.format(mContext.getString(R.string.folder_file_item_describe), - fileBean.getChildrenFileNumber(), fileBean.getChildrenDirNumber())); - } - if (!isFile) { - holder.checkBoxFile.setVisibility(View.VISIBLE); - } else { - holder.checkBoxFile.setVisibility(View.GONE); - } - - holder.checkBoxFile.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (onItemClickListener != null) { - onItemClickListener.onCheckBoxClick(holder.checkBoxFile, holder.getAdapterPosition()); - } - } - }); - - holder.layoutRoot.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (onItemClickListener != null) { - onItemClickListener.onItemClick(holder.getAdapterPosition()); - } - } - }); - } - - @Override - public int getItemCount() { - if (mListData == null) { - return 0; - } else { - return mListData.size(); - } - } - - public void updateListData(List mListData) { - this.mListData = mListData; - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/OnFileItemClickListener.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/OnFileItemClickListener.java deleted file mode 100644 index acfecbe6b..000000000 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/OnFileItemClickListener.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.seafile.seadroid2.folderbackup.selectfolder; - -import android.view.View; - -public interface OnFileItemClickListener { - void onItemClick(int position); - void onCheckBoxClick(View view, int position); -} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/SelectBackupFolderFragment.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/SelectBackupFolderFragment.java deleted file mode 100644 index a1050697b..000000000 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/SelectBackupFolderFragment.java +++ /dev/null @@ -1,198 +0,0 @@ -package com.seafile.seadroid2.folderbackup.selectfolder; - -import android.app.Activity; -import android.os.Bundle; - -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.Toast; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.folderbackup.FolderBackupConfigActivity; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class SelectBackupFolderFragment extends Fragment { - - private RecyclerView mTabBarFileRecyclerView, mFileRecyclerView; - private SelectOptions mSelectOptions; - private List allPathsList; - private List mShowFileTypes; - private int mSortType; - private List mFileList; - private List mTabbarFileList; - private String mCurrentPath; - private FileListAdapter mFileListAdapter; - private TabBarFileListAdapter mTabBarFileListAdapter; - private FolderBackupConfigActivity mActivity; - private boolean chooseDirPage; - private Button mButton; - private List selectPaths; - private String initialPath; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - mActivity = (FolderBackupConfigActivity) getActivity(); - View rootView = getActivity().getLayoutInflater().inflate(R.layout.folder_selection_fragment, null); - mButton = (Button) rootView.findViewById(R.id.bt_dir_click_to_finish); - - mFileRecyclerView = (RecyclerView) rootView.findViewById(R.id.rcv_files_list); - mFileRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false)); - mFileListAdapter = new FileListAdapter(getActivity(), mFileList); - mFileRecyclerView.setAdapter(mFileListAdapter); - - mTabBarFileRecyclerView = (RecyclerView) rootView.findViewById(R.id.rcv_tabbar_files_list); - mTabBarFileRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false)); - mTabBarFileListAdapter = new TabBarFileListAdapter(getActivity(), mTabbarFileList); - mTabBarFileRecyclerView.setAdapter(mTabBarFileListAdapter); - - chooseDirPage = mActivity.isChooseDirPage(); - - init(); - initData(); - return rootView; - } - - private void init() { - if (chooseDirPage) { - mButton.setVisibility(View.VISIBLE); - } else { - mButton.setVisibility(View.GONE); - } - selectPaths = mActivity.getSelectFolderPath(); - if (selectPaths == null) { - selectPaths = new ArrayList<>(); - } - - mButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mActivity.saveFolderConfig(); - mActivity.finish(); - } - }); - - mFileListAdapter.setOnItemClickListener(new OnFileItemClickListener() { - @Override - public void onItemClick(int position) { - FileBean item = mFileList.get(position); - if (item.isFile()) { - Toast.makeText(getActivity(), getActivity().getString(R.string.selection_file_type), Toast.LENGTH_SHORT).show(); - } else { - mCurrentPath = item.getFilePath(); - refreshFileAndTabBar(BeanListManager.TYPE_ADD_TAB_BAR); - } - } - - @Override - public void onCheckBoxClick(View view, int position) { - FileBean item = mFileList.get(position); - for (FileBean fb : mFileList) { - if (item.equals(fb)) { - if (fb.isChecked()) { - for (int i = 0; i < selectPaths.size(); i++) { - if (item.getFilePath().equals(selectPaths.get(i))) { - selectPaths.remove(i); - i--; - } - } - fb.setChecked(false); - - } else { - selectPaths.add(item.getFilePath()); - fb.setChecked(true); - } - mActivity.setFolderPathList(selectPaths); - } - } - view.post(new Runnable() { - @Override - public void run() { - mFileListAdapter.updateListData(mFileList); - mFileListAdapter.notifyDataSetChanged(); - } - }); - } - }); - - mTabBarFileListAdapter.setOnItemClickListener(new OnFileItemClickListener() { - @Override - public void onItemClick(int position) { - TabBarFileBean item = mTabbarFileList.get(position); - mCurrentPath = item.getFilePath(); - - if (mTabbarFileList.size() > 1) { - refreshFileAndTabBar(BeanListManager.TYPE_DEL_TAB_BAR); - } - } - - @Override - public void onCheckBoxClick(View view, int position) { - - } - }); - } - - @Override - public void onDestroy() { - super.onDestroy(); - getActivity().finish(); - } - - @Override - public void onPause() { - super.onPause(); - getActivity().finish(); - } - - private void initData() { - mSelectOptions = SelectOptions.getResetInstance(getActivity()); - allPathsList = initRootPath(getActivity()); - mShowFileTypes = Arrays.asList(mSelectOptions.getShowFileTypes()); - mSortType = mSelectOptions.getSortType(); - mFileList = new ArrayList<>(); - mTabbarFileList = new ArrayList<>(); - refreshFileAndTabBar(BeanListManager.TYPE_INIT_TAB_BAR); - } - - private List initRootPath(Activity activity) { - List allPaths = FileTools.getAllPaths(activity); - mCurrentPath = mSelectOptions.rootPath; - if (mCurrentPath == null) { - if (allPaths.isEmpty()) { - mCurrentPath = Constants.DEFAULT_ROOTPATH; - } else { - mCurrentPath = allPaths.get(0); - } - } - initialPath = mCurrentPath; - return allPaths; - } - - private void refreshFileAndTabBar(int tabbarType) { - BeanListManager.upDataFileBeanListByAsyn(getActivity(), selectPaths, mFileList, mFileListAdapter, - mCurrentPath, mShowFileTypes, mSortType); - BeanListManager.upDataTabbarFileBeanList(mTabbarFileList, mTabBarFileListAdapter, - mCurrentPath, tabbarType, allPathsList); - } - - public boolean onBackPressed() { - if (mCurrentPath.equals(initialPath) || allPathsList.contains(mCurrentPath)) { - return false; - } else { - mCurrentPath = FileTools.getParentPath(mCurrentPath); - refreshFileAndTabBar(BeanListManager.TYPE_DEL_TAB_BAR); - return true; - } - } - -} - diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/SelectOptions.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/SelectOptions.java deleted file mode 100644 index 6d71c59db..000000000 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/SelectOptions.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.seafile.seadroid2.folderbackup.selectfolder; - -import android.app.Fragment; -import android.app.FragmentManager; -import android.content.Context; -import android.graphics.Color; -import androidx.core.content.ContextCompat; -import android.view.View; - -import com.seafile.seadroid2.R; - -import java.util.List; - -public class SelectOptions { - - private static SelectOptions mSelectOptions; - public Integer requestCode; - public Integer frameLayoutID; - private Context mContext; - public String[] mShowFileTypes; - public String[] mSelectFileTypes; - public Integer mSortType; - public Boolean mSingle; - public Integer mMaxCount; - public Fragment mToolbarFragment; - public Fragment mMoreChooseFragment; - public Boolean onlyShowImages; - public Boolean onlyShowVideos; - public Boolean needMoreOptions; - public Boolean needMoreChoose; - public String[] optionsName; - public String[] MoreChooseItemName; - public boolean[] optionsNeedCallBack; - public boolean[] toolbarViewNeedCallBack; - public boolean[] moreChooseItemNeedCallBack; - public onToolbarListener[] toolbarListeners; - public String rootPath; - public String toolbarMainTitle; - public String toolbarSubtitleTitle; - public Integer toolbarBG; - public Integer toolbarMainTitleColor; - public Integer toolbarSubtitleColor; - public Integer toolbarOptionColor; - public Integer toolbarOptionSize; - public FragmentManager fragmentManager; - public Boolean showToolBarFragment; - public Integer typeLoadCustomView; - - public static SelectOptions getInstance() { - if (mSelectOptions == null) { - mSelectOptions = new SelectOptions(); - } - return mSelectOptions; - } - - public static SelectOptions getResetInstance(Context context) { - mSelectOptions = getInstance(); - mSelectOptions.mContext = context; - mSelectOptions.reset(); - return mSelectOptions; - } - - public Context getContext() { - return mContext; - } - - public String[] getShowFileTypes() { - if (mShowFileTypes == null) { - return new String[]{}; - } - return mShowFileTypes; - } - - public int getSortType() { - if (mSortType == null) { - return Constants.SORT_NAME_ASC; - } - return mSortType; - } - - private void reset() { - requestCode = 100; - frameLayoutID = null; - mShowFileTypes = null; - mSelectFileTypes = null; - mSortType = Constants.SORT_NAME_ASC; - mSingle = true; - mMaxCount = 1; - mToolbarFragment = null; - mMoreChooseFragment = null; - onlyShowImages = false; - onlyShowVideos = false; - needMoreOptions = false; - needMoreChoose = false; - optionsName = null; - MoreChooseItemName = null; - optionsNeedCallBack = null; - toolbarViewNeedCallBack = null; - moreChooseItemNeedCallBack = null; - toolbarListeners = null; - rootPath = null; - toolbarMainTitle = ""; - toolbarSubtitleTitle = null; - toolbarBG = ContextCompat.getColor(mContext, R.color.fancy_orange); - toolbarMainTitleColor = Color.WHITE; - toolbarSubtitleColor = Color.WHITE; - toolbarOptionColor = Color.WHITE; - toolbarOptionSize = 18; - fragmentManager = null; - showToolBarFragment = true; - typeLoadCustomView = Constants.TYPE_CUSTOM_VIEW_NULL; - } - - public interface onToolbarListener { - void onClick(View view, String currentPath, List fileBeanList, - List callBackData, TabBarFileListAdapter tabbarAdapter, - FileListAdapter fileAdapter, List callBackFileBeanList); - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/StringTools.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/StringTools.java deleted file mode 100644 index 58bc019f2..000000000 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/StringTools.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.seafile.seadroid2.folderbackup.selectfolder; - -import android.text.TextUtils; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import com.seafile.seadroid2.SettingsManager; -import com.seafile.seadroid2.util.Utils; - -import java.util.ArrayList; -import java.util.List; - -public class StringTools { - - public static boolean isEmpty(String s) { - return s == null || s.trim().isEmpty() ? true : false; - } - - public static Long getOnlyNumber(String s) { - String temp = s.replaceAll("[^0-9]", ""); - if (temp.equals("")) { - return -1L; - } - Long number = Long.valueOf(temp); - if (number == null) { - return -1L; - } - return Long.valueOf(temp); - } - - public static List getJsonToList(String strJson) { - List list = new ArrayList(); - if (TextUtils.isEmpty(strJson)) { - return list; - } - Gson gson = new Gson(); - list = gson.fromJson(strJson, new TypeToken>() { - }.getType()); - return list; - } - - public static boolean checkFolderUploadNetworkAvailable() { - if (!Utils.isNetworkOn()) { - return false; - } - - // user does not allow mobile connections - if (!Utils.isWiFiOn() && !SettingsManager.instance().isFolderBackupDataPlanAllowed()) { - return false; - } - - // Wi-Fi or 2G/3G/4G connections available - return true; - } - -} diff --git a/app/src/main/java/com/seafile/seadroid2/gallery/Image.java b/app/src/main/java/com/seafile/seadroid2/gallery/Image.java index 081bf3c27..d5640d8ac 100644 --- a/app/src/main/java/com/seafile/seadroid2/gallery/Image.java +++ b/app/src/main/java/com/seafile/seadroid2/gallery/Image.java @@ -4,13 +4,14 @@ import android.content.ContentValues; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.media.ExifInterface; import android.net.Uri; import android.provider.BaseColumns; import android.provider.MediaStore.Images; import android.provider.MediaStore.Images.ImageColumns; import android.util.Log; +import androidx.exifinterface.media.ExifInterface; + import java.io.IOException; /** @@ -24,9 +25,9 @@ public class Image extends BaseImage implements IImage { private int mRotation; public Image(BaseImageList container, ContentResolver cr, - long id, int index, Uri uri, String dataPath, - String mimeType, long dateTaken, String title, - int rotation) { + long id, int index, Uri uri, String dataPath, + String mimeType, long dateTaken, String title, + int rotation) { super(container, cr, id, index, uri, dataPath, mimeType, dateTaken, title); mRotation = rotation; @@ -59,6 +60,7 @@ public boolean isDrm() { /** * Replaces the tag if already there. Otherwise, adds to the exif tags. + * * @param tag * @param value */ @@ -115,6 +117,7 @@ private void setExifRotation(int degrees) { /** * Save the rotated image by updating the Exif "Orientation" tag. + * * @param degrees */ public boolean rotateImageBy(int degrees) { @@ -125,8 +128,8 @@ public boolean rotateImageBy(int degrees) { return true; } - private static final String[] THUMB_PROJECTION = new String[] { - BaseColumns._ID, + private static final String[] THUMB_PROJECTION = new String[]{ + BaseColumns._ID, }; public Bitmap thumbBitmap(boolean rotateAsNeeded) { diff --git a/app/src/main/java/com/seafile/seadroid2/gesturelock/DefaultAppLock.java b/app/src/main/java/com/seafile/seadroid2/gesturelock/DefaultAppLock.java index 4cf7b0d91..1fc287118 100644 --- a/app/src/main/java/com/seafile/seadroid2/gesturelock/DefaultAppLock.java +++ b/app/src/main/java/com/seafile/seadroid2/gesturelock/DefaultAppLock.java @@ -12,7 +12,7 @@ import android.util.Log; import com.google.common.collect.MapMaker; -import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.util.sp.SettingsManager; import com.seafile.seadroid2.ui.gesture.UnlockGesturePasswordActivity; import java.util.concurrent.ConcurrentMap; @@ -24,7 +24,6 @@ public class DefaultAppLock extends AbstractAppLock { public static final String DEBUG_TAG = "DefaultAppLock"; private Application currentApp; //Keep a reference to the app that invoked the locker - private SettingsManager settingsMgr; /** * by default, the returned map uses equality comparisons (the equals method) to determine equality for keys or values. * However, if weakKeys() was specified, the map uses identity (==) comparisons instead for keys. @@ -37,7 +36,6 @@ public class DefaultAppLock extends AbstractAppLock { public DefaultAppLock(Application currentApp) { super(); this.currentApp = currentApp; - this.settingsMgr = SettingsManager.instance(); } public void enable() { @@ -63,7 +61,7 @@ public void onActivityPaused(Activity activity) { return; if (!isActivityBeingChecked(activity)) { - settingsMgr.saveGestureLockTimeStamp(); + SettingsManager.getInstance().saveGestureLockTimeStamp(); } } @@ -86,7 +84,7 @@ public void onActivityResumed(Activity activity) { if (activity.getClass() == UnlockGesturePasswordActivity.class) return; - if (settingsMgr.isGestureLockRequired()) { + if (SettingsManager.getInstance().isGestureLockRequired()) { mCheckedActivities.put(activity, System.currentTimeMillis()); Intent i = new Intent(activity, UnlockGesturePasswordActivity.class); activity.startActivity(i); diff --git a/app/src/main/java/com/seafile/seadroid2/httputils/RequestManager.java b/app/src/main/java/com/seafile/seadroid2/httputils/RequestManager.java index 300053b92..6343cc9cb 100644 --- a/app/src/main/java/com/seafile/seadroid2/httputils/RequestManager.java +++ b/app/src/main/java/com/seafile/seadroid2/httputils/RequestManager.java @@ -23,7 +23,6 @@ import okio.Okio; import okio.Source; - public class RequestManager { private OkHttpClient client; @@ -48,7 +47,6 @@ public OkHttpClient getClient() { return client; } - public static RequestManager getInstance(Account account) { if (mRequestManager == null) { synchronized (RequestManager.class) { diff --git a/app/src/main/java/com/seafile/seadroid2/io/http/BaseIO.java b/app/src/main/java/com/seafile/seadroid2/io/http/BaseIO.java new file mode 100644 index 000000000..7b52f0361 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/io/http/BaseIO.java @@ -0,0 +1,140 @@ +package com.seafile.seadroid2.io.http; + +import com.blankj.utilcode.util.NetworkUtils; +import com.seafile.seadroid2.SeadroidApplication; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import okhttp3.Cache; +import okhttp3.CacheControl; +import okhttp3.ConnectionSpec; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import retrofit2.Converter; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; + +public abstract class BaseIO { + + private final int DEFAULT_TIME_OUT = 5000; + private final File cachePath = SeadroidApplication.getAppContext().getCacheDir(); + + //cache path + final File httpCacheDirectory = new File(cachePath, "cache"); + + //20M + private static final long MAX_CACHE_SIZE = 20 * 1024 * 1024; + + private final Cache cache; + + //8h + private static final int sMaxStale = 60 * 60 * 8; + + //10min + private static final int sMaxAge = 600; + + public BaseIO() { + this.cache = new Cache(httpCacheDirectory, MAX_CACHE_SIZE); + } + + public abstract String getServerUrl(); + + public abstract Converter.Factory getConverterFactory(); + + public abstract List getInterceptors(); + + public T execute(Class clazz) { + Retrofit retrofit = getRetrofit(); + return retrofit.create(clazz); + } + + //cache interceptor + private final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = chain -> { + Request request = chain.request(); + + boolean isConnected = NetworkUtils.isConnected(); + if (!isConnected) { + //no network,use cache data + request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build(); + } else { + request = request.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build(); + } + + Response originalResponse = chain.proceed(request); + if (isConnected) { + return originalResponse.newBuilder() + .removeHeader("Pragma") + .removeHeader("Cache-Control") + .header("Cache-Control", "public, max-age=" + sMaxAge) + .build(); + } else { + return originalResponse.newBuilder() + .removeHeader("Pragma") + .removeHeader("Cache-Control") + .header("Cache-Control", "public, only-if-cached, max-stale=" + sMaxStale) + .build(); + } + }; + + private volatile OkHttpClient okHttpClient = null; + private volatile Retrofit retrofit = null; + + private Retrofit getRetrofit() { + if (retrofit == null) { + synchronized (BaseIO.class) { + if (retrofit == null) { + + Retrofit.Builder rBuilder = new Retrofit.Builder(); + rBuilder.baseUrl(getServerUrl()); + rBuilder.addConverterFactory(getConverterFactory()); + rBuilder.addCallAdapterFactory(RxJava2CallAdapterFactory.create()); + + rBuilder.client(getClient()); + retrofit = rBuilder.build(); + } + } + } + return retrofit; + } + + public OkHttpClient getClient() { + if (okHttpClient == null) { + synchronized (BaseIO.class) { + if (okHttpClient == null) { + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + builder.connectionSpecs(Arrays.asList( + ConnectionSpec.MODERN_TLS, + ConnectionSpec.COMPATIBLE_TLS, + ConnectionSpec.CLEARTEXT)).build(); + builder.cache(cache); + + //cache control + builder.interceptors().add(REWRITE_CACHE_CONTROL_INTERCEPTOR); + builder.networkInterceptors().add(REWRITE_CACHE_CONTROL_INTERCEPTOR); + + //add interceptors + List interceptors = getInterceptors(); + if (interceptors != null && !interceptors.isEmpty()) { + for (Interceptor i : interceptors) { + builder.interceptors().add(i); + } + } + + //timeout + builder.writeTimeout(DEFAULT_TIME_OUT, TimeUnit.MILLISECONDS); + builder.readTimeout(DEFAULT_TIME_OUT, TimeUnit.MILLISECONDS); + builder.connectTimeout(DEFAULT_TIME_OUT, TimeUnit.MILLISECONDS); + + okHttpClient = builder.build(); + } + } + } + return okHttpClient; + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/io/http/IO.java b/app/src/main/java/com/seafile/seadroid2/io/http/IO.java new file mode 100644 index 000000000..63f9d392d --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/io/http/IO.java @@ -0,0 +1,115 @@ +package com.seafile.seadroid2.io.http; + +import com.seafile.seadroid2.BuildConfig; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.config.Constants; +import com.seafile.seadroid2.io.http.converter.ConverterFactory; +import com.seafile.seadroid2.io.http.interceptor.HeaderInterceptor; +import com.seafile.seadroid2.util.SLogs; + +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +import okhttp3.Interceptor; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Converter; + + +public class IO extends BaseIO { + private static volatile IO ioInstance; + private String mServerUrl; + private String mToken; + + /** + * Logged in + */ + public static IO getSingleton() { + if (ioInstance == null) { + synchronized (IO.class) { + if (ioInstance == null) { + ioInstance = new IO(); + + Account cur = SupportAccountManager.getInstance().getCurrentAccount(); + if (cur != null) { + SLogs.d(cur.toString()); + ioInstance.setToken(cur.token); + ioInstance.setServerUrl(cur.server); + } + } + } + } + return ioInstance; + } + + /** + * Not logged in/Log in to another server + */ + public static IO getNewInstance(String hostUrl, String token) { + IO io = new IO(); + io.setServerUrl(hostUrl); + io.setToken(token); + return io; + } + + /** + * When you log in again or switch account, you should reset this IO Singleton. + *

because it's a SINGLETON, unless kill the APP!

+ */ + public static void resetSingleton() { + ioInstance = null; + } + + private void setServerUrl(String mHostUrl) { + this.mServerUrl = mHostUrl; + } + + private void setToken(String mToken) { + this.mToken = mToken; + } + + /** + * server url + */ + @Override + public String getServerUrl() { + return mServerUrl; + } + + /** + * @return host.com + */ + public String getHostDomain() { + String host = mServerUrl; + host = StringUtils.toRootLowerCase(host); + host = StringUtils.removeStart(host, Constants.Protocol.HTTPS); + host = StringUtils.removeStart(host, Constants.Protocol.HTTP); + return StringUtils.removeEnd(host, "/"); + } + + + @Override + public List getInterceptors() { + + List interceptors = new ArrayList<>(); + + //print log + HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); + loggingInterceptor.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.BASIC); + interceptors.add(loggingInterceptor); + + interceptors.add(new HeaderInterceptor(mToken)); +// interceptors.add(new AddCookiesInterceptor()); +// interceptors.add(new ReceivedCookiesInterceptor()); + + return interceptors; + } + + @Override + public Converter.Factory getConverterFactory() { + return ConverterFactory.create(); + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/io/http/converter/ConverterFactory.java b/app/src/main/java/com/seafile/seadroid2/io/http/converter/ConverterFactory.java new file mode 100644 index 000000000..71bf10468 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/io/http/converter/ConverterFactory.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2015 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.seafile.seadroid2.io.http.converter; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapter; +import com.google.gson.reflect.TypeToken; +import com.seafile.seadroid2.data.model.BaseModel; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import retrofit2.Converter; +import retrofit2.Retrofit; + +/** + * A {@linkplain Converter.Factory converter} which uses Gson for JSON. + *

+ * Because Gson is so flexible in the types it supports, this converter assumes that it can handle + * all types. If you are mixing JSON serialization with something else (such as protocol buffers), + * you must {@linkplain Retrofit.Builder#addConverterFactory(Converter.Factory) add this instance} + * last to allow the other converters a chance to see their types. + */ +public final class ConverterFactory extends Converter.Factory { + /** + * Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and + * decoding from JSON (when no charset is specified by a header) will use UTF-8. + */ + public static ConverterFactory create() { + Gson gson = new GsonBuilder().create(); + return create(gson); + } + + /** + * Create an instance using {@code gson} for conversion. Encoding to JSON and + * decoding from JSON (when no charset is specified by a header) will use UTF-8. + */ + public static ConverterFactory create(Gson gson) { + return new ConverterFactory(gson); + } + + private final Gson gson; + + private ConverterFactory(Gson gson) { + if (gson == null) { + throw new NullPointerException("gson == null"); + } + + this.gson = gson; + } + + @Override + public Converter requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { + TypeAdapter adapter = gson.getAdapter(TypeToken.get(type)); + return new GsonRequestBodyConverter<>(gson, adapter); + } + + @Override + public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { + TypeAdapter adapter = gson.getAdapter(TypeToken.get(type)); + + return new SupportResponseConverter(gson, adapter); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/io/http/converter/GsonRequestBodyConverter.java b/app/src/main/java/com/seafile/seadroid2/io/http/converter/GsonRequestBodyConverter.java new file mode 100644 index 000000000..fba906329 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/io/http/converter/GsonRequestBodyConverter.java @@ -0,0 +1,38 @@ +package com.seafile.seadroid2.io.http.converter; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.Charset; + +import okhttp3.MediaType; +import okhttp3.RequestBody; +import okio.Buffer; +import retrofit2.Converter; + +public final class GsonRequestBodyConverter implements Converter { + private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8"); + private static final Charset UTF_8 = Charset.forName("UTF-8"); + + private final Gson gson; + private final TypeAdapter adapter; + + public GsonRequestBodyConverter(Gson gson, TypeAdapter adapter) { + this.gson = gson; + this.adapter = adapter; + } + + @Override + public RequestBody convert(T value) throws IOException { + Buffer buffer = new Buffer(); + Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8); + JsonWriter jsonWriter = gson.newJsonWriter(writer); + adapter.write(jsonWriter, value); + jsonWriter.close(); + return RequestBody.create(MEDIA_TYPE, buffer.readByteString()); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/io/http/converter/SupportResponseConverter.java b/app/src/main/java/com/seafile/seadroid2/io/http/converter/SupportResponseConverter.java new file mode 100644 index 000000000..d07cf1a54 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/io/http/converter/SupportResponseConverter.java @@ -0,0 +1,36 @@ +package com.seafile.seadroid2.io.http.converter; + +import androidx.annotation.NonNull; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; + +import java.io.IOException; + +import okhttp3.ResponseBody; +import retrofit2.Converter; + +public class SupportResponseConverter implements Converter { + private final Gson gson; + TypeAdapter adapter; + + public SupportResponseConverter(Gson gson, TypeAdapter adapter) { + this.gson = gson; + this.adapter = adapter; + } + + @Override + public T convert(@NonNull ResponseBody value) throws IOException { + + try { + String body = value.string(); + return adapter.fromJson(body); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e.getMessage()); + } finally { + value.close(); + } + } +} + diff --git a/app/src/main/java/com/seafile/seadroid2/io/http/interceptor/AddCookiesInterceptor.java b/app/src/main/java/com/seafile/seadroid2/io/http/interceptor/AddCookiesInterceptor.java new file mode 100644 index 000000000..542d3fdd5 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/io/http/interceptor/AddCookiesInterceptor.java @@ -0,0 +1,23 @@ +package com.seafile.seadroid2.io.http.interceptor; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class AddCookiesInterceptor implements Interceptor { + @Override + public Response intercept(Chain chain) throws IOException { + Request.Builder builder = chain.request().newBuilder(); + +// HashSet preferences = SPUtils.get(SPContants.KEY_COOKIES); +// if (preferences != null) { +// for (String cookie : preferences) { +// builder.addHeader("Cookie", cookie); +// } +// SLogs.d("AddCookiesInterceptor Cookie: " + Joiner.on(",").join(preferences)); +// } + return chain.proceed(builder.build()); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/io/http/interceptor/HeaderInterceptor.java b/app/src/main/java/com/seafile/seadroid2/io/http/interceptor/HeaderInterceptor.java new file mode 100644 index 000000000..ef43f9022 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/io/http/interceptor/HeaderInterceptor.java @@ -0,0 +1,35 @@ +package com.seafile.seadroid2.io.http.interceptor; + +import android.text.TextUtils; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class HeaderInterceptor implements Interceptor { + private String authToken = null; + + public HeaderInterceptor(String authToken) { + this.authToken = authToken; + } + + @Override + public Response intercept(Chain chain) throws IOException { + return chain.proceed(initBuilder(chain.request().newBuilder()).build()); + } + + private Request.Builder initBuilder(Request.Builder builder) { + builder.addHeader("Content-Type", "application/json"); + builder.addHeader("Accept", "application/json"); + builder.addHeader("charset", "utf-8"); + builder.addHeader("timestamp", System.currentTimeMillis() + ""); + + if (!TextUtils.isEmpty(authToken)) { + builder.addHeader("Authorization", "Token " + authToken); + } + + return builder; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/io/http/interceptor/ParamsInterceptor.java b/app/src/main/java/com/seafile/seadroid2/io/http/interceptor/ParamsInterceptor.java new file mode 100644 index 000000000..bd497ee3f --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/io/http/interceptor/ParamsInterceptor.java @@ -0,0 +1,119 @@ +package com.seafile.seadroid2.io.http.interceptor; + + +import android.os.Build; + +import androidx.annotation.NonNull; + +import com.blankj.utilcode.util.AppUtils; + +import java.io.IOException; + +import okhttp3.FormBody; +import okhttp3.HttpUrl; +import okhttp3.Interceptor; +import okhttp3.MultipartBody; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class ParamsInterceptor implements Interceptor { + + private static final String TAG = "request params"; + + @Override + public Response intercept(@NonNull Chain chain) throws IOException { + + Request orgRequest = chain.request(); + RequestBody body = orgRequest.body(); + //收集请求参数,方便调试 + StringBuilder paramsBuilder = new StringBuilder(); + + if (body != null) { + + RequestBody newBody; + + if (body instanceof FormBody) { + newBody = addParamsToFormBody((FormBody) body, paramsBuilder); + } else if (body instanceof MultipartBody) { + newBody = addParamsToMultipartBody((MultipartBody) body, paramsBuilder); + } else { + Request original = chain.request(); + HttpUrl originalHttpUrl = original.url(); + + newBody = new FormBody.Builder() + .add("client_version", AppUtils.getAppName()) + .add("platform", "android") + .add("device_name", Build.MODEL) + .add("platform_version", Build.VERSION.RELEASE) + .build(); + + Request.Builder requestBuilder = original.newBuilder() + .method(original.method(), newBody) + .url(originalHttpUrl); + + Request request = requestBuilder.build(); + return chain.proceed(request); + } + + //打印参数 + Request newRequest = orgRequest.newBuilder() + .url(orgRequest.url()) + .method(orgRequest.method(), newBody) + .build(); + + return chain.proceed(newRequest); + + } + + return chain.proceed(orgRequest); + } + + /** + * 为MultipartBody类型请求体添加参数 + *

+ * + * @param body 请求主体 + * @param paramsBuilder 参数builder + * @return builder.build(); + */ + private MultipartBody addParamsToMultipartBody(MultipartBody body, StringBuilder paramsBuilder) { + MultipartBody.Builder builder = new MultipartBody.Builder(); + builder.setType(MultipartBody.FORM); + + builder.addFormDataPart("client_version", AppUtils.getAppName()); + builder.addFormDataPart("platform", "android"); + builder.addFormDataPart("device_name", Build.MODEL); + builder.addFormDataPart("platform_version", Build.VERSION.RELEASE); + + //添加原请求体 + for (int i = 0; i < body.size(); i++) { + builder.addPart(body.part(i)); + } + + return builder.build(); + } + + + /** + * 为FormBody类型请求体添加参数 + *

+ * + * @param body 请求主体 + * @param paramsBuilder 参数builder + * @return builder.build(); + */ + private FormBody addParamsToFormBody(FormBody body, StringBuilder paramsBuilder) { + FormBody.Builder builder = new FormBody.Builder(); + + builder.add("client_version", AppUtils.getAppName()); + builder.add("platform", "android"); + builder.add("device_name", Build.MODEL); + builder.add("platform_version", Build.VERSION.RELEASE); + + return builder.build(); + } + + +} + diff --git a/app/src/main/java/com/seafile/seadroid2/io/http/interceptor/ReceivedCookiesInterceptor.java b/app/src/main/java/com/seafile/seadroid2/io/http/interceptor/ReceivedCookiesInterceptor.java new file mode 100644 index 000000000..c898dd10c --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/io/http/interceptor/ReceivedCookiesInterceptor.java @@ -0,0 +1,21 @@ +package com.seafile.seadroid2.io.http.interceptor; + +import java.io.IOException; +import java.util.HashSet; + +import okhttp3.Interceptor; +import okhttp3.Response; + +public class ReceivedCookiesInterceptor implements Interceptor { + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + +// if (!response.headers("Set-Cookie").isEmpty()) { +// HashSet cookies = new HashSet<>(response.headers("Set-Cookie")); +// SPUtils.put(SPContants.KEY_COOKIES, cookies); +// } + return response; + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/listener/OnFileItemChangeListener.java b/app/src/main/java/com/seafile/seadroid2/listener/OnFileItemChangeListener.java new file mode 100644 index 000000000..a5828a1de --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/listener/OnFileItemChangeListener.java @@ -0,0 +1,7 @@ +package com.seafile.seadroid2.listener; + +import com.seafile.seadroid2.ui.selector.folder_selector.FileBean; + +public interface OnFileItemChangeListener { + void onChanged(FileBean fileBean, int position, boolean isChecked); +} diff --git a/app/src/main/java/com/seafile/seadroid2/monitor/AutoUpdateInfo.java b/app/src/main/java/com/seafile/seadroid2/monitor/AutoUpdateInfo.java index a66a30b5e..808540afd 100644 --- a/app/src/main/java/com/seafile/seadroid2/monitor/AutoUpdateInfo.java +++ b/app/src/main/java/com/seafile/seadroid2/monitor/AutoUpdateInfo.java @@ -1,7 +1,7 @@ package com.seafile.seadroid2.monitor; import com.google.common.base.Objects; -import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.util.sp.SettingsManager; import com.seafile.seadroid2.account.Account; class AutoUpdateInfo { @@ -22,7 +22,7 @@ public AutoUpdateInfo(Account account, String repoID, String repoName, String pa } public boolean canLocalDecrypt() { - return SettingsManager.instance().isEncryptEnabled(); + return SettingsManager.getInstance().isEncryptEnabled(); } @Override diff --git a/app/src/main/java/com/seafile/seadroid2/monitor/MonitorDBHelper.java b/app/src/main/java/com/seafile/seadroid2/monitor/MonitorDBHelper.java index f41fd87dc..5ede449a7 100644 --- a/app/src/main/java/com/seafile/seadroid2/monitor/MonitorDBHelper.java +++ b/app/src/main/java/com/seafile/seadroid2/monitor/MonitorDBHelper.java @@ -11,7 +11,7 @@ import com.google.common.collect.Maps; import com.seafile.seadroid2.SeadroidApplication; import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.AccountManager; +import com.seafile.seadroid2.account.SupportAccountManager; import java.io.File; import java.util.List; @@ -55,12 +55,12 @@ public class MonitorDBHelper extends SQLiteOpenHelper { + " TEXT NOT NULL);"; private static final String[] FULL_PROJECTION = { - AUTO_UPDATE_INFO_COLUMN_ACCOUNT, - AUTO_UPDATE_INFO_COLUMN_REPO_ID, - AUTO_UPDATE_INFO_COLUMN_REPO_NAME, - AUTO_UPDATE_INFO_COLUMN_PARENT_DIR, - AUTO_UPDATE_INFO_COLUMN_LOCAL_PATH, - AUTO_UPDATE_INFO_COLUMN_VERSION,}; + AUTO_UPDATE_INFO_COLUMN_ACCOUNT, + AUTO_UPDATE_INFO_COLUMN_REPO_ID, + AUTO_UPDATE_INFO_COLUMN_REPO_NAME, + AUTO_UPDATE_INFO_COLUMN_PARENT_DIR, + AUTO_UPDATE_INFO_COLUMN_LOCAL_PATH, + AUTO_UPDATE_INFO_COLUMN_VERSION,}; // Use only single dbHelper to prevent multi-thread issue and db is closed exception // Reference @@ -165,9 +165,9 @@ public List getAutoUploadInfos() { } private Map getAllAccounts() { - AccountManager accountMgr = new AccountManager(SeadroidApplication.getAppContext()); Map accounts = Maps.newHashMap(); - for (Account account : accountMgr.getAccountList()) { + List list = SupportAccountManager.getInstance().getAccountList(); + for (Account account : list) { accounts.put(account.getSignature(), account); } diff --git a/app/src/main/java/com/seafile/seadroid2/monitor/SeafileMonitor.java b/app/src/main/java/com/seafile/seadroid2/monitor/SeafileMonitor.java index 17bbe7bb0..41ab5dbd3 100644 --- a/app/src/main/java/com/seafile/seadroid2/monitor/SeafileMonitor.java +++ b/app/src/main/java/com/seafile/seadroid2/monitor/SeafileMonitor.java @@ -1,16 +1,15 @@ package com.seafile.seadroid2.monitor; -import java.util.List; -import java.util.Map; - -import org.apache.commons.io.monitor.FileAlterationMonitor; - import android.util.Log; import com.google.common.collect.Maps; -import com.seafile.seadroid2.SeadroidApplication; import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.AccountManager; +import com.seafile.seadroid2.account.SupportAccountManager; + +import org.apache.commons.io.monitor.FileAlterationMonitor; + +import java.util.List; +import java.util.Map; public class SeafileMonitor { private static final String DEBUG_TAG = "SeafileMonitor"; @@ -54,7 +53,7 @@ private void removeObserver(SeafileObserver fileObserver) { } public synchronized void onFileDownloaded(Account account, String repoID, String repoName, - String pathInRepo, String localPath) { + String pathInRepo, String localPath) { SeafileObserver observer = observers.get(account); if (observer == null) return; @@ -76,7 +75,7 @@ public void stop() throws Exception { * Watch cached files for all accounts */ public synchronized void monitorAllAccounts() { - List accounts = new AccountManager(SeadroidApplication.getAppContext()).getAccountList(); + List accounts = SupportAccountManager.getInstance().getAccountList(); for (Account account : accounts) { monitorFilesForAccount(account); } diff --git a/app/src/main/java/com/seafile/seadroid2/play/exoplayer/CustomExoVideoPlayerActivity.java b/app/src/main/java/com/seafile/seadroid2/play/exoplayer/CustomExoVideoPlayerActivity.java index 71f6f06f7..dfc70a276 100644 --- a/app/src/main/java/com/seafile/seadroid2/play/exoplayer/CustomExoVideoPlayerActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/play/exoplayer/CustomExoVideoPlayerActivity.java @@ -1,5 +1,6 @@ package com.seafile.seadroid2.play.exoplayer; +import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Bundle; @@ -68,6 +69,15 @@ public class CustomExoVideoPlayerActivity extends BaseActivity implements VideoL private int startItemIndex; private long startPosition; + public static void startThis(Context context, String fileName, String repoID, String filePath, Account account) { + Intent intent = new Intent(context, CustomExoVideoPlayerActivity.class); + intent.putExtra("fileName", fileName); + intent.putExtra("repoID", repoID); + intent.putExtra("filePath", filePath); + intent.putExtra("account", account); + context.startActivity(intent); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -94,7 +104,7 @@ protected void onCreate(Bundle savedInstanceState) { } } - private void initUI(){ + private void initUI() { backView = findViewById(R.id.back); backContainer = findViewById(R.id.back_container); progressContainer = findViewById(R.id.progress_container); diff --git a/app/src/main/java/com/seafile/seadroid2/provider/DocumentIdParser.java b/app/src/main/java/com/seafile/seadroid2/provider/DocumentIdParser.java index d3577f88e..dfdb629e2 100644 --- a/app/src/main/java/com/seafile/seadroid2/provider/DocumentIdParser.java +++ b/app/src/main/java/com/seafile/seadroid2/provider/DocumentIdParser.java @@ -20,10 +20,11 @@ import android.content.Context; import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.AccountManager; +import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.util.Utils; import java.io.FileNotFoundException; +import java.util.List; /** * Helper class to create and parse DocumentIds for the DocumentProvider @@ -44,11 +45,10 @@ public class DocumentIdParser { private static final String ROOT_REPO_ID = "root-magic-repo"; Context context; - AccountManager manager; + public DocumentIdParser(Context context) { this.context = context; - this.manager = new AccountManager(context); } /** @@ -62,7 +62,10 @@ public Account getAccountFromId(String documentId) throws FileNotFoundException String[] list = documentId.split(DOC_SEPERATOR, 2); if (list.length > 0) { String server = list[0]; - for (Account a: manager.getAccountList()) { + + //TODO test it. + List accounts = SupportAccountManager.getInstance().getAccountList(); + for (Account a: accounts) { if (a.getSignature().equals(server)) { return a; } diff --git a/app/src/main/java/com/seafile/seadroid2/provider/SeafileProvider.java b/app/src/main/java/com/seafile/seadroid2/provider/SeafileProvider.java index d20606c5e..e9654419a 100644 --- a/app/src/main/java/com/seafile/seadroid2/provider/SeafileProvider.java +++ b/app/src/main/java/com/seafile/seadroid2/provider/SeafileProvider.java @@ -44,7 +44,7 @@ import com.seafile.seadroid2.SeadroidApplication; import com.seafile.seadroid2.SeafException; import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.AccountManager; +import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.data.ProgressMonitor; import com.seafile.seadroid2.data.SeafDirent; @@ -78,6 +78,7 @@ */ public class SeafileProvider extends DocumentsProvider { public static final String DEBUG_TAG = "SeafileProvider"; + public static final String FILE_PROVIDER_AUTHORITY = BuildConfig.APPLICATION_ID+".fileprovider"; private static final String[] SUPPORTED_ROOT_PROJECTION = new String[]{ Root.COLUMN_ROOT_ID, @@ -109,9 +110,8 @@ public class SeafileProvider extends DocumentsProvider { private Set reachableAccounts = new ConcurrentSkipListSet(); private android.accounts.AccountManager androidAccountManager; - private AccountManager accountManager; - public static final Uri NOTIFICATION_URI = DocumentsContract.buildRootsUri(BuildConfig.APPLICATION_ID); + public static final Uri NOTIFICATION_URI = DocumentsContract.buildRootsUri(FILE_PROVIDER_AUTHORITY); private final OnAccountsUpdateListener accountListener = new OnAccountsUpdateListener() { @Override @@ -125,7 +125,6 @@ public void onAccountsUpdated(android.accounts.Account[] accounts) { public boolean onCreate() { docIdParser = new DocumentIdParser(getContext()); - accountManager = new AccountManager(getContext()); androidAccountManager = android.accounts.AccountManager.get(getContext()); androidAccountManager.addOnAccountsUpdatedListener(accountListener, null, true); @@ -142,7 +141,8 @@ public Cursor queryRoots(String[] projection) throws FileNotFoundException { Log.d(DEBUG_TAG, "queryRoots()"); // add a Root for every signed in Seafile account we have. - for (Account a : accountManager.getAccountList()) { + List accounts = SupportAccountManager.getInstance().getAccountList(); + for (Account a : accounts) { includeRoot(result, a); } diff --git a/app/src/main/java/com/seafile/seadroid2/task/StarItemsTask.java b/app/src/main/java/com/seafile/seadroid2/task/StarItemsTask.java deleted file mode 100644 index 985a9af11..000000000 --- a/app/src/main/java/com/seafile/seadroid2/task/StarItemsTask.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.seafile.seadroid2.task; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.listener.OnCallback; -import com.seafile.seadroid2.ui.BrowserActivity; -import com.seafile.seadroid2.util.SupportAsyncTask; - -/** - * star repo、file、dir - */ -public class StarItemsTask extends SupportAsyncTask { - private String repoId; - private String path; - private boolean is_starred; - private SeafException err; - - private OnCallback onCallback; - - public StarItemsTask(BrowserActivity browserActivity, String repoId, String path, boolean starred) { - super(browserActivity); - this.repoId = repoId; - this.path = path; - is_starred = starred; - } - - public void setOnCallback(OnCallback onCallback) { - this.onCallback = onCallback; - } - - @Override - protected Void doInBackground(Void... params) { - try { - if (getContextParam() != null) { - if (is_starred) { - getContextParam().getDataManager().unstarItems(repoId, path); - } else { - getContextParam().getDataManager().starItems(repoId, path); - } - } - } catch (SeafException e) { - err = e; - } - - return null; - } - - @Override - protected void onPostExecute(Void v) { - if (err != null) { - if (getContextParam() != null) { - if (is_starred) { - getContextParam().showShortToast(getContextParam(), R.string.unstar_file_failed); - } else { - getContextParam().showShortToast(getContextParam(), R.string.star_file_failed); - } - } - if (onCallback != null) { - onCallback.onFailed(); - } - return; - } - - if (getContextParam() != null) { - if (is_starred) { - getContextParam().showShortToast(getContextParam(), R.string.unstar); - } else { - getContextParam().showShortToast(getContextParam(), R.string.star_file_succeed); - } - } - if (onCallback != null) { - onCallback.onSuccess(); - } - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/transfer/TransferManager.java b/app/src/main/java/com/seafile/seadroid2/transfer/TransferManager.java index f8d08a4f1..f70ccd353 100644 --- a/app/src/main/java/com/seafile/seadroid2/transfer/TransferManager.java +++ b/app/src/main/java/com/seafile/seadroid2/transfer/TransferManager.java @@ -4,7 +4,7 @@ import com.google.common.collect.Lists; import com.seafile.seadroid2.SeadroidApplication; -import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.util.sp.SettingsManager; import com.seafile.seadroid2.data.CameraSyncEvent; import com.seafile.seadroid2.util.CameraSyncStatus; import com.seafile.seadroid2.util.ConcurrentAsyncTask; @@ -114,7 +114,7 @@ public synchronized void doNext() { EventBus.getDefault().post(new CameraSyncEvent("upload")); } - if (SettingsManager.instance().isFolderAutomaticBackup()) { + if (SettingsManager.getInstance().isFolderAutomaticBackup()) { SeadroidApplication.getInstance().setFolderBackupNumber(folderBackupTotalNumber, folderBackupWaitingNumber); } diff --git a/app/src/main/java/com/seafile/seadroid2/transfer/UploadTask.java b/app/src/main/java/com/seafile/seadroid2/transfer/UploadTask.java index 6fefee5e1..16db9e2d8 100644 --- a/app/src/main/java/com/seafile/seadroid2/transfer/UploadTask.java +++ b/app/src/main/java/com/seafile/seadroid2/transfer/UploadTask.java @@ -6,7 +6,7 @@ import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeadroidApplication; import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.util.sp.SettingsManager; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.data.ProgressMonitor; @@ -113,7 +113,7 @@ protected void onPostExecute(File file) { state = err == null ? TaskState.FINISHED : TaskState.FAILED; if (uploadStateListener != null) { if (err == null) { - SettingsManager.instance().saveUploadCompletedTime(Utils.getSyncCompletedTime()); + SettingsManager.getInstance().saveUploadCompletedTime(Utils.getSyncCompletedTime()); uploadStateListener.onFileUploaded(taskID); } else { if (err.getCode() == SeafException.HTTP_ABOVE_QUOTA) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/BaseActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/BaseActivity.java index e86bb44bc..ab007e300 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/BaseActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/BaseActivity.java @@ -1,47 +1,24 @@ package com.seafile.seadroid2.ui; -import android.app.Activity; -import android.content.Intent; -import android.content.pm.PackageManager; import android.os.Bundle; -import androidx.core.app.NavUtils; -import androidx.core.app.TaskStackBuilder; + import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; -import android.view.View; -import android.widget.AbsListView; -import android.widget.ListView; -import android.widget.Toast; import com.seafile.seadroid2.R; import com.seafile.seadroid2.data.CheckUploadServiceEvent; import org.greenrobot.eventbus.EventBus; -import java.util.HashMap; -import java.util.Map; - /** * A base activity that handles common functionality in the app. This includes Action Bar tweaks. */ public class BaseActivity extends AppCompatActivity { - // variables that control the Action Bar auto hide behavior (aka "quick recall") - private boolean mActionBarAutoHideEnabled = false; - // Primary toolbar and drawer toggle private Toolbar mActionBarToolbar; - private int mActionBarAutoHideMinY = 0; - - private int mActionBarAutoHideSensivity = 0; - - private int mActionBarAutoHideSignal = 0; - - private boolean mActionBarShown = true; - - protected int screenWidth; @Override protected void onCreate(Bundle savedInstanceState) { @@ -72,203 +49,6 @@ public void setContentView(int layoutResID) { getActionBarToolbar(); } - /** - * Initializes the Action Bar auto-hide (aka Quick Recall) effect. - */ - private void initActionBarAutoHide() { - mActionBarAutoHideEnabled = true; - mActionBarAutoHideMinY = getResources().getDimensionPixelSize( - R.dimen.action_bar_auto_hide_min_y); - mActionBarAutoHideSensivity = getResources().getDimensionPixelSize( - R.dimen.action_bar_auto_hide_sensivity); - } - - protected void enableActionBarAutoHide(final ListView listView) { - initActionBarAutoHide(); - listView.setOnScrollListener(new AbsListView.OnScrollListener() { - - /** The heights of all items. */ - private Map heights = new HashMap<>(); - private int lastCurrentScrollY = 0; - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - - // Get the first visible item's view. - View firstVisibleItemView = view.getChildAt(0); - if (firstVisibleItemView == null) { - return; - } - - // Save the height of the visible item. - heights.put(firstVisibleItem, firstVisibleItemView.getHeight()); - - // Calculate the height of all previous (hidden) items. - int previousItemsHeight = 0; - for (int i = 0; i < firstVisibleItem; i++) { - previousItemsHeight += heights.get(i) != null ? heights.get(i) : 0; - } - - int currentScrollY = previousItemsHeight - firstVisibleItemView.getTop() - + view.getPaddingTop(); - - onMainContentScrolled(currentScrollY, currentScrollY - lastCurrentScrollY); - - lastCurrentScrollY = currentScrollY; - } - }); - } - - /** - * Indicates that the main content has scrolled (for the purposes of showing/hiding - * the action bar for the "action bar auto hide" effect). currentY and deltaY may be exact - * (if the underlying view supports it) or may be approximate indications: - * deltaY may be INT_MAX to mean "scrolled forward indeterminately" and INT_MIN to mean - * "scrolled backward indeterminately". currentY may be 0 to mean "somewhere close to the - * start of the list" and INT_MAX to mean "we don't know, but not at the start of the list" - */ - private void onMainContentScrolled(int currentY, int deltaY) { - if (deltaY > mActionBarAutoHideSensivity) { - deltaY = mActionBarAutoHideSensivity; - } else if (deltaY < -mActionBarAutoHideSensivity) { - deltaY = -mActionBarAutoHideSensivity; - } - - if (Math.signum(deltaY) * Math.signum(mActionBarAutoHideSignal) < 0) { - // deltaY is a motion opposite to the accumulated signal, so reset signal - mActionBarAutoHideSignal = deltaY; - } else { - // add to accumulated signal - mActionBarAutoHideSignal += deltaY; - } - - boolean shouldShow = currentY < mActionBarAutoHideMinY || - (mActionBarAutoHideSignal <= -mActionBarAutoHideSensivity); - autoShowOrHideActionBar(shouldShow); - } - - protected void autoShowOrHideActionBar(boolean show) { - if (show == mActionBarShown) { - return; - } - - mActionBarShown = show; - //onActionBarAutoShowOrHide(show); - } - - /** - * This utility method handles Up navigation intents by searching for a parent activity and - * navigating there if defined. When using this for an activity make sure to define both the - * native parentActivity as well as the AppCompat one when supporting API levels less than 16. - * when the activity has a single parent activity. If the activity doesn't have a single parent - * activity then don't define one and this method will use back button functionality. If "Up" - * functionality is still desired for activities without parents then use - * {@code syntheticParentActivity} to define one dynamically. - * - * Note: Up navigation intents are represented by a back arrow in the top left of the Toolbar - * in Material Design guidelines. - * - * @param currentActivity Activity in use when navigate Up action occurred. - * @param syntheticParentActivity Parent activity to use when one is not already configured. - */ - public static void navigateUpOrBack(Activity currentActivity, - Class syntheticParentActivity) { - // Retrieve parent activity from AndroidManifest. - Intent intent = NavUtils.getParentActivityIntent(currentActivity); - - // Synthesize the parent activity when a natural one doesn't exist. - if (intent == null && syntheticParentActivity != null) { - try { - intent = NavUtils.getParentActivityIntent(currentActivity, syntheticParentActivity); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } - } - - if (intent == null) { - // No parent defined in manifest. This indicates the activity may be used by - // in multiple flows throughout the app and doesn't have a strict parent. In - // this case the navigation up button should act in the same manner as the - // back button. This will result in users being forwarded back to other - // applications if currentActivity was invoked from another application. - currentActivity.onBackPressed(); - } else { - if (NavUtils.shouldUpRecreateTask(currentActivity, intent)) { - // Need to synthesize a backstack since currentActivity was probably invoked by a - // different app. The preserves the "Up" functionality within the app according to - // the activity hierarchy defined in AndroidManifest.xml via parentActivity - // attributes. - TaskStackBuilder builder = TaskStackBuilder.create(currentActivity); - builder.addNextIntentWithParentStack(intent); - builder.startActivities(); - } else { - // Navigate normally to the manifest defined "Up" activity. - NavUtils.navigateUpTo(currentActivity, intent); - } - } - } - /** - * Show the given message in a {@link Toast} for a short period of time - *

- * This method may be called from any thread - * - * @param activity - * @param message - */ - public void showShortToast(final Activity activity, final String message) { - if (activity == null) - return; - Toast.makeText(activity, message, Toast.LENGTH_SHORT).show(); - } - - /** - * Show the message with the given resource id in a {@link Toast} for a short period of time - *

- * This method may be called from any thread - * - * @param activity - * @param resId - */ - public void showShortToast(final Activity activity, final int resId) { - if (activity == null) - return; - - showShortToast(activity, activity.getString(resId)); - } - - /** - * Show the given message in a {@link Toast} for a long period of time - *

- * This method may be called from any thread - * - * @param activity - * @param message - */ - public void showLongToast(final Activity activity, final String message) { - if (activity == null) - return; - Toast.makeText(activity, message, Toast.LENGTH_LONG).show(); - } - - /** - * Show the message with the given resource id in a {@link Toast} for a long period of time - *

- * This method may be called from any thread - * - * @param activity - * @param resId - */ - public void showLongToast(final Activity activity, final int resId) { - if (activity == null) - return; - - showLongToast(activity, activity.getString(resId)); - } @Override protected void onResume() { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/BrowserActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/BrowserActivity.java deleted file mode 100644 index ee8d36e4d..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/BrowserActivity.java +++ /dev/null @@ -1,2528 +0,0 @@ -package com.seafile.seadroid2.ui; - -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.ServiceConnection; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.res.Configuration; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.os.IBinder; -import android.provider.MediaStore; -import android.text.TextUtils; -import android.util.Log; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.Window; -import android.widget.FrameLayout; - -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.Toolbar; -import androidx.core.content.FileProvider; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; -import androidx.fragment.app.FragmentTransaction; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import androidx.viewpager.widget.ViewPager; - -import com.google.android.material.tabs.TabLayout; -import com.google.common.collect.Lists; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeafConnection; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.SettingsManager; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.AccountManager; -import com.seafile.seadroid2.cameraupload.CameraUploadManager; -import com.seafile.seadroid2.cameraupload.MediaObserverService; -import com.seafile.seadroid2.config.Constants; -import com.seafile.seadroid2.context.CopyMoveContext; -import com.seafile.seadroid2.context.NavContext; -import com.seafile.seadroid2.data.CheckUploadServiceEvent; -import com.seafile.seadroid2.data.DataManager; -import com.seafile.seadroid2.data.DatabaseHelper; -import com.seafile.seadroid2.data.SeafDirent; -import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.data.SeafStarredFile; -import com.seafile.seadroid2.data.ServerInfo; -import com.seafile.seadroid2.data.StorageManager; -import com.seafile.seadroid2.fileschooser.MultiFileChooserActivity; -import com.seafile.seadroid2.folderbackup.FolderBackupService; -import com.seafile.seadroid2.folderbackup.FolderBackupService.FileBackupBinder; -import com.seafile.seadroid2.monitor.FileMonitorService; -import com.seafile.seadroid2.notification.DownloadNotificationProvider; -import com.seafile.seadroid2.notification.UploadNotificationProvider; -import com.seafile.seadroid2.play.exoplayer.CustomExoVideoPlayerActivity; -import com.seafile.seadroid2.transfer.DownloadTaskInfo; -import com.seafile.seadroid2.transfer.DownloadTaskManager; -import com.seafile.seadroid2.transfer.PendingUploadInfo; -import com.seafile.seadroid2.transfer.TransferManager; -import com.seafile.seadroid2.transfer.TransferService; -import com.seafile.seadroid2.transfer.TransferService.TransferBinder; -import com.seafile.seadroid2.transfer.UploadTaskInfo; -import com.seafile.seadroid2.transfer.UploadTaskManager; -import com.seafile.seadroid2.ui.account.AccountsActivity; -import com.seafile.seadroid2.ui.activity.FileActivity; -import com.seafile.seadroid2.ui.activity.SeafilePathChooserActivity; -import com.seafile.seadroid2.ui.settings.SettingsActivity; -import com.seafile.seadroid2.ui.gesture.UnlockGesturePasswordActivity; -import com.seafile.seadroid2.ui.search.Search2Activity; -import com.seafile.seadroid2.ui.transfer.TransferActivity; -import com.seafile.seadroid2.ui.webview.SeaWebViewActivity; -import com.seafile.seadroid2.ui.adapter.SeafItemAdapter; -import com.seafile.seadroid2.ui.dialog.AppChoiceDialog; -import com.seafile.seadroid2.ui.dialog.AppChoiceDialog.CustomAction; -import com.seafile.seadroid2.ui.dialog.CopyMoveDialog; -import com.seafile.seadroid2.ui.dialog.DeleteFileDialog; -import com.seafile.seadroid2.ui.dialog.DeleteRepoDialog; -import com.seafile.seadroid2.ui.dialog.FetchFileDialog; -import com.seafile.seadroid2.ui.dialog.NewDirDialog; -import com.seafile.seadroid2.ui.dialog.NewFileDialog; -import com.seafile.seadroid2.ui.dialog.NewRepoDialog; -import com.seafile.seadroid2.ui.dialog.PasswordDialog; -import com.seafile.seadroid2.ui.dialog.RenameFileDialog; -import com.seafile.seadroid2.ui.dialog.RenameRepoDialog; -import com.seafile.seadroid2.ui.dialog.SortFilesDialogFragment; -import com.seafile.seadroid2.ui.dialog.SslConfirmDialog; -import com.seafile.seadroid2.ui.dialog.TaskDialog; -import com.seafile.seadroid2.ui.activities.ActivitiesFragment; -import com.seafile.seadroid2.ui.repo.ReposFragment; -import com.seafile.seadroid2.ui.star.StarredFragment; -import com.seafile.seadroid2.util.ConcurrentAsyncTask; -import com.seafile.seadroid2.util.PermissionUtil; -import com.seafile.seadroid2.util.SLogs; -import com.seafile.seadroid2.util.Utils; -import com.seafile.seadroid2.util.UtilsJellyBean; - -import org.apache.commons.io.IOUtils; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; -import org.json.JSONException; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Files; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Locale; - -public class BrowserActivity extends BaseActivity implements ReposFragment.OnFileSelectedListener, - StarredFragment.OnStarredFileSelectedListener, - FragmentManager.OnBackStackChangedListener, - Toolbar.OnMenuItemClickListener, - SortFilesDialogFragment.SortItemClickListener { - private static final String DEBUG_TAG = "BrowserActivity"; - public static final String ACTIONBAR_PARENT_PATH = "/"; - - public static final String OPEN_FILE_DIALOG_FRAGMENT_TAG = "openfile_fragment"; - public static final String PASSWORD_DIALOG_FRAGMENT_TAG = "password_fragment"; - public static final String CHOOSE_APP_DIALOG_FRAGMENT_TAG = "choose_app_fragment"; - public static final String CHARE_LINK_PASSWORD_FRAGMENT_TAG = "share_link_password_fragment"; - public static final String PICK_FILE_DIALOG_FRAGMENT_TAG = "pick_file_fragment"; -// public static final int REQUEST_PERMISSIONS_WRITE_EXTERNAL_STORAGE = 1; - - public static final String TAG_NEW_REPO_DIALOG_FRAGMENT = "NewRepoDialogFragment"; - public static final String TAG_DELETE_REPO_DIALOG_FRAGMENT = "DeleteRepoDialogFragment"; - public static final String TAG_DELETE_FILE_DIALOG_FRAGMENT = "DeleteFileDialogFragment"; - public static final String TAG_DELETE_FILES_DIALOG_FRAGMENT = "DeleteFilesDialogFragment"; - public static final String TAG_RENAME_REPO_DIALOG_FRAGMENT = "RenameRepoDialogFragment"; - public static final String TAG_RENAME_FILE_DIALOG_FRAGMENT = "RenameFileDialogFragment"; - public static final String TAG_COPY_MOVE_DIALOG_FRAGMENT = "CopyMoveDialogFragment"; - public static final String TAG_SORT_FILES_DIALOG_FRAGMENT = "SortFilesDialogFragment"; - - public static final int INDEX_LIBRARY_TAB = 0; - public static final int INDEX_STARRED_TAB = 1; - public static final int INDEX_ACTIVITIES_TAB = 2; - - private static final int[] ICONS = new int[]{ - R.drawable.tab_library, R.drawable.tab_starred, - R.drawable.tab_activity - }; - - private FetchFileDialog fetchFileDialog = null; - private SeafileTabsAdapter adapter; - private View mLayout; - private FrameLayout mContainer; - private TabLayout mTabLayout; - private ViewPager mViewPager; - private NavContext navContext = new NavContext(); - private CopyMoveContext copyMoveContext; - private Menu overFlowMenu; - private MenuItem menuSearch; - - private DataManager dataManager = null; - private TransferService txService = null; - private FolderBackupService mFolderBackupService = null; - private TransferReceiver mTransferReceiver; - private AccountManager accountManager; - - private int currentPosition = 0; - private Intent copyMoveIntent; - private Account account; - - private Intent mediaObserver; - private Intent monitorIntent; - - public DataManager getDataManager() { - return dataManager; - } - - public void addUpdateTask(String repoID, String repoName, String targetDir, String localFilePath) { - if (txService != null) { - txService.addTaskToUploadQue(account, repoID, repoName, targetDir, localFilePath, true, false); - } else { - PendingUploadInfo info = new PendingUploadInfo(repoID, repoName, targetDir, localFilePath, true, false); - pendingUploads.add(info); - } - } - - public void addUpdateBlocksTask(String repoID, String repoName, String targetDir, String localFilePath) { - if (txService != null) { - txService.addTaskToUploadQueBlock(account, repoID, repoName, targetDir, localFilePath, true, false); - } else { - PendingUploadInfo info = new PendingUploadInfo(repoID, repoName, targetDir, localFilePath, true, false); - pendingUploads.add(info); - } - } - - private int addUploadTask(String repoID, String repoName, String targetDir, String localFilePath) { - if (txService != null) { - return txService.addTaskToUploadQue(account, repoID, repoName, targetDir, localFilePath, false, false); - } else { - PendingUploadInfo info = new PendingUploadInfo(repoID, repoName, targetDir, localFilePath, false, false); - pendingUploads.add(info); - return 0; - } - } - - private int addUploadBlocksTask(String repoID, String repoName, String targetDir, String localFilePath) { - if (txService != null) { - return txService.addTaskToUploadQueBlock(account, repoID, repoName, targetDir, localFilePath, false, false); - } else { - PendingUploadInfo info = new PendingUploadInfo(repoID, repoName, targetDir, localFilePath, false, false); - pendingUploads.add(info); - return 0; - } - } - - private ArrayList pendingUploads = Lists.newArrayList(); - - public TransferService getTransferService() { - return txService; - } - - public Account getAccount() { - return account; - } - - public NavContext getNavContext() { - return navContext; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - supportRequestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - super.onCreate(savedInstanceState); - - accountManager = new AccountManager(this); - - // restart service should it have been stopped for some reason - Intent mediaObserver = new Intent(this, MediaObserverService.class); - startService(mediaObserver); - - Intent dIntent = new Intent(this, FolderBackupService.class); - startService(dIntent); - Log.d(DEBUG_TAG, "----start FolderBackupService"); - - Intent dirIntent = new Intent(this, FolderBackupService.class); - bindService(dirIntent, folderBackupConnection, Context.BIND_AUTO_CREATE); - Log.d(DEBUG_TAG, "----try bind FolderBackupService"); - - if (!isTaskRoot()) { - final Intent intent = getIntent(); - final String intentAction = getIntent().getAction(); - if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && intentAction != null && intentAction.equals(Intent.ACTION_MAIN)) { - finish(); - return; - } - } - - setContentView(R.layout.tabs_main); - mLayout = findViewById(R.id.main_layout); - mContainer = (FrameLayout) findViewById(R.id.bottom_sheet_container); - setSupportActionBar(getActionBarToolbar()); - // enable ActionBar app icon to behave as action back - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - findViewById(R.id.view_toolbar_bottom_line).setVisibility(View.GONE); - // Get the message from the intent - Intent intent = getIntent(); - - account = accountManager.getCurrentAccount(); - if (account == null || !account.hasValidToken()) { - finishAndStartAccountsActivity(); - return; - } - - // Log.d(DEBUG_TAG, "browser activity onCreate " + account.server + " " + account.email); - dataManager = new DataManager(account); - - getSupportFragmentManager().addOnBackStackChangedListener(this); - - unsetRefreshing(); - disableUpButton(); - - mViewPager = (ViewPager) findViewById(R.id.pager); - adapter = new SeafileTabsAdapter(getSupportFragmentManager()); - mViewPager.setAdapter(adapter); - - mTabLayout = (TabLayout) findViewById(R.id.sliding_tabs); - mTabLayout.setupWithViewPager(mViewPager); - mTabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { - @Override - public void onTabSelected(TabLayout.Tab tab) { - currentPosition = tab.getPosition(); - supportInvalidateOptionsMenu(); - mViewPager.setCurrentItem(tab.getPosition(), true); - if (currentPosition != INDEX_LIBRARY_TAB) { - disableUpButton(); - } else if (navContext.inRepo()) { - enableUpButton(); - } - - setUpButtonTitleOnSlideTabs(tab.getPosition()); - } - - @Override - public void onTabUnselected(TabLayout.Tab tab) { - - } - - @Override - public void onTabReselected(TabLayout.Tab tab) { - - } - }); - - mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - - } - - @Override - public void onPageSelected(int position) { - currentPosition = position; - supportInvalidateOptionsMenu(); - mViewPager.setCurrentItem(position, true); - if (currentPosition != INDEX_LIBRARY_TAB) { - disableUpButton(); - } else if (navContext.inRepo()) { - enableUpButton(); - } - - setUpButtonTitleOnSlideTabs(position); - } - - @Override - public void onPageScrollStateChanged(int state) { - - } - }); - - mViewPager.setOffscreenPageLimit(3); - - if (savedInstanceState != null) { - Log.d(DEBUG_TAG, "savedInstanceState is not null"); - fetchFileDialog = (FetchFileDialog) getSupportFragmentManager().findFragmentByTag(OPEN_FILE_DIALOG_FRAGMENT_TAG); - - AppChoiceDialog appChoiceDialog = (AppChoiceDialog) getSupportFragmentManager().findFragmentByTag(CHOOSE_APP_DIALOG_FRAGMENT_TAG); - - if (appChoiceDialog != null) { - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - ft.detach(appChoiceDialog); - ft.commit(); - } - - SslConfirmDialog sslConfirmDlg = (SslConfirmDialog) getSupportFragmentManager().findFragmentByTag(SslConfirmDialog.FRAGMENT_TAG); - - if (sslConfirmDlg != null) { - Log.d(DEBUG_TAG, "sslConfirmDlg is not null"); - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - ft.detach(sslConfirmDlg); - ft.commit(); - } else { - Log.d(DEBUG_TAG, "sslConfirmDlg is null"); - } - - String repoID = savedInstanceState.getString("repoID"); - String repoName = savedInstanceState.getString("repoName"); - String path = savedInstanceState.getString("path"); -// String dirID = savedInstanceState.getString("dirID"); - String permission = savedInstanceState.getString("permission"); - if (repoID != null) { - navContext.setRepoID(repoID); - navContext.setRepoName(repoName); - navContext.setDirPath(path); - navContext.setDirPermission(permission); - } - } - - String repoID = intent.getStringExtra("repoID"); - String repoName = intent.getStringExtra("repoName"); - String path = intent.getStringExtra("path"); -// String dirID = intent.getStringExtra("dirID"); - String permission = intent.getStringExtra("permission"); - if (repoID != null) { - navContext.setRepoID(repoID); - navContext.setRepoName(repoName); - navContext.setDirPath(path); - navContext.setDirPermission(permission); - } - - - Intent txIntent = new Intent(this, TransferService.class); - startService(txIntent); - Log.d(DEBUG_TAG, "start TransferService"); - - // bind transfer service - Intent bIntent = new Intent(this, TransferService.class); - bindService(bIntent, mConnection, Context.BIND_AUTO_CREATE); - Log.d(DEBUG_TAG, "try bind TransferService"); - - monitorIntent = new Intent(this, FileMonitorService.class); - startService(monitorIntent); - - requestServerInfo(); - -// requestReadExternalStoragePermission(); - - - Utils.startCameraSyncJob(this); - syncCamera(); - } - - @Override - protected void onPostCreate(Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - - // handle notification permission on API level >= 33 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - // request notification permission first and then prompt for storage permissions - // storage permissions handled in onRequestPermissionsResult - PermissionUtil.requestNotificationPermission(this); - } else { - PermissionUtil.requestExternalStoragePermission(this); - } - } - - private void finishAndStartAccountsActivity() { - Intent newIntent = new Intent(this, AccountsActivity.class); - newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - finish(); - startActivity(newIntent); - } - - private void requestServerInfo() { - if (!checkServerProEdition()) { - // hide Activity tab - adapter.hideActivityTab(); - adapter.notifyDataSetChanged(); - mTabLayout.setupWithViewPager(mViewPager); - } - - if (!checkSearchEnabled()) { - // hide search menu - if (menuSearch != null) - menuSearch.setVisible(false); - } - - if (!Utils.isNetworkOn()) - return; - - ConcurrentAsyncTask.execute(new RequestServerInfoTask()); - } - - public void completeRemoteWipe() { - ConcurrentAsyncTask.execute(new AsyncTask() { - @Override - protected Void doInBackground(Void... objects) { - // clear local caches - StorageManager storageManager = StorageManager.getInstance(); - storageManager.clearCache(); - - // clear cached data from database - DatabaseHelper dbHelper = DatabaseHelper.getDatabaseHelper(); - dbHelper.delCaches(); - - try { - // response to server when finished cleaning caches - getDataManager().completeRemoteWipe(); - } catch (SeafException e) { - e.printStackTrace(); - } - return null; - } - - @Override - protected void onPostExecute(Void o) { - // sign out current account - logoutWhenTokenExpired(); - - } - }); - } - - /** - * Token expired, clear current authorized info and redirect user to login page - */ - public void logoutWhenTokenExpired() { - AccountManager accountMgr = new AccountManager(this); - - // sign out current account - Account account = accountMgr.getCurrentAccount(); - accountMgr.signOutAccount(account); - - // then redirect to AccountsActivity - Intent intent = new Intent(this, AccountsActivity.class); - startActivity(intent); - - // finish current Activity - finish(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - if (navContext.inRepo() && currentPosition == INDEX_LIBRARY_TAB) { - onBackPressed(); - } - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { - case R.id.sort: - showSortFilesDialog(); - return true; - case R.id.search: - Intent searchIntent = new Intent(this, Search2Activity.class); - startActivity(searchIntent); - return true; - case R.id.create_repo: - showNewRepoDialog(); - return true; - case R.id.add: - addFile(); - return true; - case R.id.transfer_tasks: - Intent newIntent = new Intent(this, TransferActivity.class); - newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(newIntent); - return true; - case R.id.accounts: - newIntent = new Intent(this, AccountsActivity.class); - newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(newIntent); - return true; - case R.id.edit: - // start action mode for selecting multiple files/folders - - if (!Utils.isNetworkOn()) { - showShortToast(this, R.string.network_down); - return true; - } - if (currentPosition == INDEX_LIBRARY_TAB) { - if (navContext.inRepo()) { - SeafRepo repo = dataManager.getCachedRepoByID(navContext.getRepoID()); - if (repo.encrypted && !dataManager.getRepoPasswordSet(repo.repo_id)) { - String password = dataManager.getRepoPassword(repo.repo_id); - showPasswordDialog(repo.repo_name, repo.repo_id, - new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - getReposFragment().startContextualActionMode(); - } - }, password); - - return true; - } - } - - getReposFragment().startContextualActionMode(); - } - - return true; - case R.id.settings: - Intent settingsIntent = new Intent(BrowserActivity.this, SettingsActivity.class); - settingsIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(settingsIntent); - return true; - } - return super.onOptionsItemSelected(item); - } - - /** - * Callback received when a permissions request has been completed. - */ - @Override - public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { - // Log.i(DEBUG_TAG, "Received response for permission request."); - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - switch (requestCode) { - case PermissionUtil.PERMISSIONS_POST_NOTIFICATIONS: - // handle notification permission on API level >= 33 - // dialogue was dismissed -> prompt for storage permissions - PermissionUtil.requestExternalStoragePermission(this); - break; - case PermissionUtil.PERMISSIONS_EXTERNAL_STORAGE: - // Check if the only required permission has been granted - // If request is cancelled, the result arrays are empty. - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - // permission was granted - SLogs.i("PERMISSIONS_EXTERNAL_STORAGE has been granted"); - } - break; - } - } - - class RequestServerInfoTask extends AsyncTask { - private SeafException err; - - @Override - protected ServerInfo doInBackground(Void... params) { - try { - return dataManager.getServerInfo(); - } catch (SeafException e) { - err = e; - } catch (JSONException e) { - Log.e(DEBUG_TAG, "JSONException " + e.getMessage()); - } - return null; - } - - @Override - protected void onPostExecute(ServerInfo serverInfo) { - // Check to see whether this activity is in the process of finishing - // to avoid IllegalStateException when AsyncTasks continue to run after the activity has been destroyed - // http://stackoverflow.com/a/35729068/3962551 - if (isFinishing()) return; - - if (serverInfo == null) { - if (err != null) - showShortToast(BrowserActivity.this, err.getMessage()); - return; - } - - if (serverInfo.isProEdition()) { - // show Activity tab - adapter.unHideActivityTab(); - adapter.notifyDataSetChanged(); - mTabLayout.setupWithViewPager(mViewPager); - } - - if (serverInfo.isSearchEnabled()) { - // show search menu - if (menuSearch != null) - menuSearch.setVisible(true); - } - - accountManager.setServerInfo(account, serverInfo); - } - } - - /** - * check if server is pro edition - * - * @return true, if server is pro edition - * false, otherwise. - */ - private boolean checkServerProEdition() { - if (account == null) - return false; - - ServerInfo serverInfo = accountManager.getServerInfo(account); - - return serverInfo.isProEdition(); - } - - /** - * check if server supports searching feature - * - * @return true, if search enabled - * false, otherwise. - */ - private boolean checkSearchEnabled() { - if (account == null) - return false; - - ServerInfo serverInfo = accountManager.getServerInfo(account); - - return serverInfo.isSearchEnabled(); - } - - private class SeafileTabsAdapter extends FragmentPagerAdapter { - public SeafileTabsAdapter(FragmentManager fm) { - super(fm); - } - - private ReposFragment reposFragment = null; - private ActivitiesFragment activitieFragment = null; - private StarredFragment starredFragment = null; - private boolean isHideActivityTab; - - public void hideActivityTab() { - this.isHideActivityTab = true; - } - - public void unHideActivityTab() { - this.isHideActivityTab = false; - } - - @Override - public Fragment getItem(int position) { - switch (position) { - case 0: - - if (reposFragment == null) { - reposFragment = new ReposFragment(); - } - return reposFragment; - case 1: - if (starredFragment == null) { - starredFragment = new StarredFragment(); - } - return starredFragment; - case 2: - if (activitieFragment == null) { - activitieFragment = new ActivitiesFragment(); - } - return activitieFragment; - default: - return new Fragment(); - } - } - - @Override - public CharSequence getPageTitle(int position) { - switch (position) { - case 0: - return getString(R.string.tabs_library).toUpperCase(); - case 1: - return getString(R.string.tabs_starred).toUpperCase(); - case 2: - return getString(R.string.tabs_activity).toUpperCase(); - - default: - return null; - } - } - -// @Override -// public int getIconResId(int index) { -// return ICONS[index]; -// } - - @Override - public int getCount() { - if (!isHideActivityTab) - return ICONS.length; - else - return 2; - } - } - - public int getCurrentPosition() { - return currentPosition; - } - - public void setCurrentPosition(int currentPosition) { - this.currentPosition = currentPosition; - mViewPager.setCurrentItem(currentPosition); - mTabLayout.setScrollPosition(currentPosition, 0, true); - setUpButtonTitleOnSlideTabs(currentPosition); - refreshViewOnSlideTabs(currentPosition); - } - - public Fragment getFragment(int index) { - return (Fragment) adapter.instantiateItem(mViewPager, index); - } - - public ReposFragment getReposFragment() { - return (ReposFragment) getFragment(0); - } - - public StarredFragment getStarredFragment() { - return (StarredFragment) getFragment(1); - } - - public ActivitiesFragment getActivitiesFragment() { - return (ActivitiesFragment) getFragment(2); - } - - private final ServiceConnection mConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - TransferBinder binder = (TransferBinder) service; - txService = binder.getService(); - Log.d(DEBUG_TAG, "bind TransferService"); - - for (PendingUploadInfo info : pendingUploads) { - txService.addTaskToUploadQue(account, - info.repoID, - info.repoName, - info.targetDir, - info.localFilePath, - info.isUpdate, - info.isCopyToLocal); - } - pendingUploads.clear(); - } - - @Override - public void onServiceDisconnected(ComponentName arg0) { - txService = null; - } - }; - - private final ServiceConnection folderBackupConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - FileBackupBinder binder = (FileBackupBinder) service; - mFolderBackupService = binder.getService(); - Log.d(DEBUG_TAG, "-----bind FolderBackupService"); - boolean dirAutomaticUpload = SettingsManager.instance().isFolderAutomaticBackup(); - String backupEmail = SettingsManager.instance().getBackupEmail(); - if (dirAutomaticUpload && mFolderBackupService != null && !TextUtils.isEmpty(backupEmail)) { - mFolderBackupService.backupFolder(backupEmail); - } - } - - @Override - public void onServiceDisconnected(ComponentName arg0) { - mFolderBackupService = null; - } - }; - - @Override - public void onStart() { - Log.d(DEBUG_TAG, "onStart"); - super.onStart(); - EventBus.getDefault().register(this); - - if (android.os.Build.VERSION.SDK_INT < 14 && SettingsManager.instance().isGestureLockRequired()) { - Intent intent = new Intent(this, UnlockGesturePasswordActivity.class); - startActivity(intent); - } - - if (mTransferReceiver == null) { - mTransferReceiver = new TransferReceiver(); - } - - IntentFilter filter = new IntentFilter(TransferManager.BROADCAST_ACTION); - LocalBroadcastManager.getInstance(this).registerReceiver(mTransferReceiver, filter); - } - - @Override - protected void onPause() { - Log.d(DEBUG_TAG, "onPause"); - super.onPause(); - } - - @Override - public void onRestart() { - Log.d(DEBUG_TAG, "onRestart"); - super.onRestart(); - - if (accountManager.getCurrentAccount() == null - || !accountManager.getCurrentAccount().equals(this.account) - || !accountManager.getCurrentAccount().getToken().equals(this.account.getToken())) { - finishAndStartAccountsActivity(); - } - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - Log.d(DEBUG_TAG, "onNewIntent"); - - // if the user started the Seadroid app from the Launcher, keep the old Activity - final String intentAction = intent.getAction(); - if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) - && intentAction != null - && intentAction.equals(Intent.ACTION_MAIN)) { - return; - } - - Account selectedAccount = accountManager.getCurrentAccount(); - Log.d(DEBUG_TAG, "Current account: " + selectedAccount); - if (selectedAccount == null - || !account.equals(selectedAccount) - || !account.getToken().equals(selectedAccount.getToken())) { - Log.d(DEBUG_TAG, "Account switched, restarting activity."); - finish(); - startActivity(intent); - } else { - String repoId = intent.getStringExtra("repoID"); - String repoName = intent.getStringExtra("repoName"); - String path = intent.getStringExtra("path"); - - if (TextUtils.isEmpty(repoId) || TextUtils.isEmpty(path)) { - return; - } - - navContext.setRepoID(repoId); - navContext.setRepoName(repoName); - navContext.setDirPath(path); - -// if (navContext.isRepoRoot()) { -// navContext.setRepoID(null); -// getActionBarToolbar().setTitle(R.string.app_name); -// } else { -// String parentPath = Utils.getParentPath(navContext.getDirPath()); -// navContext.setDir(parentPath, null); -// if (TextUtils.equals(ACTIONBAR_PARENT_PATH, parentPath)) { -// getActionBarToolbar().setTitle(navContext.getRepoName()); -// } else { -// getActionBarToolbar().setTitle(parentPath.substring(parentPath.lastIndexOf(ACTIONBAR_PARENT_PATH) + 1)); -// } -// } - - - getReposFragment().clearAdapterData(); - - mViewPager.setCurrentItem(0); - - if (navContext.isRepoRoot()) { - navContext.setRepoID(null); - getActionBarToolbar().setTitle(R.string.app_name); - } else { - String parentPath = Utils.getParentPath(navContext.getDirPath()); - navContext.setDirPath(parentPath); - if (TextUtils.equals(ACTIONBAR_PARENT_PATH, parentPath)) { - getActionBarToolbar().setTitle(navContext.getRepoName()); - } else { - //fixme parentPath is null - getActionBarToolbar().setTitle(parentPath.substring(parentPath.lastIndexOf(ACTIONBAR_PARENT_PATH) + 1)); - } - } - getReposFragment().clearAdapterData(); - getReposFragment().refreshView(true); - -// -// setUpButtonTitleOnSlideTabs(0); -// refreshViewOnSlideTabs(0); - } - } - - @Override - protected void onStop() { - Log.d(DEBUG_TAG, "onStop"); - super.onStop(); - EventBus.getDefault().unregister(this); - - if (mTransferReceiver != null) { - LocalBroadcastManager.getInstance(this).unregisterReceiver(mTransferReceiver); - } - } - - @Override - protected void onDestroy() { - Log.d(DEBUG_TAG, "onDestroy is called"); - - if (txService != null) { - unbindService(mConnection); - txService = null; - } - if (mFolderBackupService != null) { - unbindService(folderBackupConnection); - mFolderBackupService = null; - } - super.onDestroy(); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - Log.d(DEBUG_TAG, "onSaveInstanceState"); - super.onSaveInstanceState(outState); - //outState.putInt("tab", getActionBarToolbar().getSelectedNavigationIndex()); - if (navContext.getRepoID() != null) { - outState.putString("repoID", navContext.getRepoID()); - outState.putString("repoName", navContext.getRepoName()); - outState.putString("path", navContext.getDirPath()); - outState.putString("permission", navContext.getDirPermission()); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - overFlowMenu = menu; - Toolbar toolbar = getActionBarToolbar(); - toolbar.inflateMenu(R.menu.browser_menu); - toolbar.setOnMenuItemClickListener(this); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - menuSearch = menu.findItem(R.id.search); - MenuItem menuSort = menu.findItem(R.id.sort); - MenuItem menuAdd = menu.findItem(R.id.add); - MenuItem menuCreateRepo = menu.findItem(R.id.create_repo); - MenuItem menuEdit = menu.findItem(R.id.edit); - - // Libraries Tab - if (currentPosition == 0) { - if (navContext.inRepo()) { - menuCreateRepo.setVisible(false); - menuAdd.setVisible(true); - menuEdit.setVisible(true); - if (hasRepoWritePermission()) { - menuAdd.setEnabled(true); - menuEdit.setEnabled(true); - } else { - menuAdd.setEnabled(false); - menuEdit.setEnabled(false); - } - - } else { - menuCreateRepo.setVisible(true); - menuAdd.setVisible(false); - menuEdit.setVisible(false); - } - - menuSort.setVisible(true); - } else { - menuSort.setVisible(false); - menuCreateRepo.setVisible(false); - menuAdd.setVisible(false); - menuEdit.setVisible(false); - } - - // Global menus, e.g. Accounts, TransferTasks, Settings, are visible by default. - // So nothing need to be done here. - - // Though search menu is also a global menu, its state was maintained dynamically at runtime. - if (!checkServerProEdition()) - menuSearch.setVisible(false); - - return true; - } - - @Override - protected void onPostResume() { - super.onPostResume(); - // We can't show the CopyMoveDialog in onActivityResult, this is a - // workaround found in - // http://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult/18345899#18345899 - if (copyMoveIntent != null) { - String dstRepoId, dstDir; - dstRepoId = copyMoveIntent.getStringExtra(SeafilePathChooserActivity.DATA_REPO_ID); - dstDir = copyMoveIntent.getStringExtra(SeafilePathChooserActivity.DATA_DIR); - copyMoveContext.setDest(dstRepoId, dstDir); - doCopyMove(); - copyMoveIntent = null; - } - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - } - - private void showSortFilesDialog() { - SortFilesDialogFragment dialog = new SortFilesDialogFragment(); - dialog.show(getSupportFragmentManager(), TAG_SORT_FILES_DIALOG_FRAGMENT); - } - - @Override - public void onSortFileItemClick(DialogFragment dialog, int position) { - switch (position) { - case 0: // sort by name, ascending - sortFiles(SeafItemAdapter.SORT_BY_NAME, SeafItemAdapter.SORT_ORDER_ASCENDING); - break; - case 1: // sort by name, descending - sortFiles(SeafItemAdapter.SORT_BY_NAME, SeafItemAdapter.SORT_ORDER_DESCENDING); - break; - case 2: // sort by last modified time, ascending - sortFiles(SeafItemAdapter.SORT_BY_LAST_MODIFIED_TIME, SeafItemAdapter.SORT_ORDER_ASCENDING); - break; - case 3: // sort by last modified time, descending - sortFiles(SeafItemAdapter.SORT_BY_LAST_MODIFIED_TIME, SeafItemAdapter.SORT_ORDER_DESCENDING); - break; - default: - return; - } - } - - /** - * Sort files by type and order - * - * @param type - */ - private void sortFiles(final int type, final int order) { - if (currentPosition == INDEX_LIBRARY_TAB) { - if (navContext.inRepo()) { - SeafRepo repo = dataManager.getCachedRepoByID(navContext.getRepoID()); - if (repo.encrypted && !dataManager.getRepoPasswordSet(repo.repo_id)) { - String password = dataManager.getRepoPassword(repo.repo_id); - showPasswordDialog(repo.repo_name, repo.repo_id, - new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - getReposFragment().sortFiles(type, order); - } - }, password); - } - } - getReposFragment().sortFiles(type, order); - } - } - - /** - * create a new repo - */ - private void showNewRepoDialog() { - final NewRepoDialog dialog = new NewRepoDialog(); - dialog.init(account); - dialog.setTaskDialogLisenter(new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - showShortToast( - BrowserActivity.this, - String.format(getResources().getString(R.string.create_new_repo_success), dialog.getRepoName()) - ); - ReposFragment reposFragment = getReposFragment(); - if (currentPosition == INDEX_LIBRARY_TAB && reposFragment != null) { - reposFragment.refreshView(true, true); - } - } - }); - dialog.show(getSupportFragmentManager(), TAG_NEW_REPO_DIALOG_FRAGMENT); - } - - /** - * add new file/files - */ - private void addFile() { - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.add_file)); - builder.setItems(R.array.add_file_options_array, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == 0) // create file - showNewFileDialog(); - else if (which == 1) // create folder - showNewDirDialog(); - else if (which == 2) // upload file - pickFile(); - else if (which == 3) // take a photo - cameraTakePhoto(); - } - }).show(); - } - - private void showNewDirDialog() { - if (!hasRepoWritePermission()) { - showShortToast(this, R.string.library_read_only); - return; - } - - final NewDirDialog dialog = new NewDirDialog(); - dialog.init(navContext.getRepoID(), navContext.getDirPath(), account); - dialog.setTaskDialogLisenter(new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - final String message = String.format(getString(R.string.create_new_folder_success), dialog.getNewDirName()); - showShortToast(BrowserActivity.this, message); - ReposFragment reposFragment = getReposFragment(); - if (currentPosition == INDEX_LIBRARY_TAB && reposFragment != null) { - reposFragment.refreshView(); - } - } - }); - dialog.show(getSupportFragmentManager(), "NewDirDialogFragment"); - } - - private void showNewFileDialog() { - if (!hasRepoWritePermission()) { - showShortToast(this, R.string.library_read_only); - return; - } - - final NewFileDialog dialog = new NewFileDialog(); - dialog.init(navContext.getRepoID(), navContext.getDirPath(), account); - dialog.setTaskDialogLisenter(new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - final String message = String.format(getString(R.string.create_new_file_success), dialog.getNewFileName()); - showShortToast(BrowserActivity.this, message); - ReposFragment reposFragment = getReposFragment(); - if (currentPosition == INDEX_LIBRARY_TAB && reposFragment != null) { - reposFragment.refreshView(); - } - } - }); - dialog.show(getSupportFragmentManager(), "NewFileDialogFragment"); - } - - public void setRefreshing() { - setSupportProgressBarIndeterminateVisibility(Boolean.TRUE); - } - - public void unsetRefreshing() { - setSupportProgressBarIndeterminateVisibility(Boolean.FALSE); - } - - private File takeCameraPhotoTempFile; - - private void cameraTakePhoto() { - Intent imageCaptureIntent = new Intent("android.media.action.IMAGE_CAPTURE"); - - try { - File ImgDir = DataManager.createTempDir(); - - String fileName = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(new Date()) + ".jpg"; - takeCameraPhotoTempFile = new File(ImgDir, fileName); - - Uri photo = FileProvider.getUriForFile(this, getApplicationContext().getPackageName(), takeCameraPhotoTempFile); - imageCaptureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photo); - startActivityForResult(imageCaptureIntent, TAKE_PHOTO_REQUEST); - - } catch (IOException e) { - showShortToast(BrowserActivity.this, R.string.unknow_error); - } - } - - public void enableUpButton() { - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setSupportActionBar(getActionBarToolbar()); - //getActionBarToolbar().setLogo(getResources().getDrawable(R.color.transparent)); - } - - public void disableUpButton() { - getSupportActionBar().setDisplayHomeAsUpEnabled(false); - //getActionBarToolbar().setEnabled(false); - //getActionBarToolbar().setLogo(R.drawable.icon); - } - - public void setUpButtonTitle(String title) { - getActionBarToolbar().setTitle(title); - } - - /** - * update up button title when sliding among tabs - * - * @param position - */ - private void setUpButtonTitleOnSlideTabs(int position) { - if (navContext == null) - return; - - if (position == INDEX_LIBRARY_TAB) { - if (navContext.inRepo()) { - if (navContext.getDirPath().equals(BrowserActivity.ACTIONBAR_PARENT_PATH)) { - setUpButtonTitle(navContext.getRepoName()); - } else { - setUpButtonTitle(navContext.getDirPathName()); - } - } else { - setUpButtonTitle(getString(R.string.tabs_library).toUpperCase()); - } - } else { - setUpButtonTitle(currentPosition == 1 ? getString(R.string.tabs_starred).toUpperCase() : getString(R.string.tabs_activity).toUpperCase()); - } - } - - /** - * refresh view when sliding among tabs - * - * @param position - */ - private void refreshViewOnSlideTabs(int position) { - if (navContext == null) - return; - - if (position == INDEX_LIBRARY_TAB) { - if (navContext.inRepo()) { - getReposFragment().refreshView(); - } - } - - } - - /*********** Start other activity ***************/ - - public static final int PICK_FILES_REQUEST = 1; - public static final int PICK_PHOTOS_VIDEOS_REQUEST = 2; - public static final int PICK_FILE_REQUEST = 3; - public static final int TAKE_PHOTO_REQUEST = 4; - public static final int CHOOSE_COPY_MOVE_DEST_REQUEST = 5; - public static final int DOWNLOAD_FILE_REQUEST = 6; - - public boolean hasRepoWritePermission() { - if (navContext == null) { - return false; - } - if (navContext.getDirPermission() == null || navContext.getDirPermission().indexOf('w') == -1) { - return false; - } - return true; - } - - void pickFile() { - if (!hasRepoWritePermission()) { - showShortToast(this, R.string.library_read_only); - return; - } - - Intent target = Utils.createGetContentIntent(); - Intent intent = Intent.createChooser(target, getString(R.string.choose_file)); - startActivityForResult(intent, BrowserActivity.PICK_FILE_REQUEST); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (resultCode != RESULT_OK || data == null) { - return; - } - switch (requestCode) { - case PICK_FILES_REQUEST: { - String[] paths = data.getStringArrayExtra(MultiFileChooserActivity.MULTI_FILES_PATHS); - if (paths == null) - return; - showShortToast(this, getString(R.string.added_to_upload_tasks)); - - List list = dataManager.getCachedDirents(navContext.getRepoID(), navContext.getDirPath()); - if (list == null) return; - - for (String path : paths) { - boolean duplicate = false; - for (SeafDirent dirent : list) { - if (dirent.name.equals(Utils.fileNameFromPath(path))) { - duplicate = true; - break; - } - } - if (!duplicate) { - showShortToast(BrowserActivity.this, getString(R.string.added_to_upload_tasks)); - final SeafRepo repo = dataManager.getCachedRepoByID(navContext.getRepoID()); - if (repo != null && repo.canLocalDecrypt()) { - addUploadBlocksTask(navContext.getRepoID(), navContext.getRepoName(), navContext.getDirPath(), path); - } else { - addUploadTask(navContext.getRepoID(), navContext.getRepoName(), navContext.getDirPath(), path); - } - } else { - showFileExistDialog(path); - } - } - } - break; - case PICK_PHOTOS_VIDEOS_REQUEST: { - ArrayList paths = data.getStringArrayListExtra("photos"); - if (paths == null) - return; - showShortToast(this, getString(R.string.added_to_upload_tasks)); - - List list = dataManager.getCachedDirents(navContext.getRepoID(), navContext.getDirPath()); - if (list == null) return; - - for (String path : paths) { - boolean duplicate = false; - for (SeafDirent dirent : list) { - if (dirent.name.equals(Utils.fileNameFromPath(path))) { - duplicate = true; - break; - } - } - if (!duplicate) { - showShortToast(BrowserActivity.this, getString(R.string.added_to_upload_tasks)); - final SeafRepo repo = dataManager.getCachedRepoByID(navContext.getRepoID()); - if (repo != null && repo.canLocalDecrypt()) { - addUploadBlocksTask(navContext.getRepoID(), navContext.getRepoName(), navContext.getDirPath(), path); - } else { - addUploadTask(navContext.getRepoID(), navContext.getRepoName(), navContext.getDirPath(), path); - } - } else { - showFileExistDialog(path); - } - } - } - break; - case PICK_FILE_REQUEST: { - if (!Utils.isNetworkOn()) { - showShortToast(this, R.string.network_down); - return; - } - - List uriList = UtilsJellyBean.extractUriListFromIntent(data); - if (uriList.size() > 0) { - ConcurrentAsyncTask.execute(new SAFLoadRemoteFileTask(), uriList.toArray(new Uri[]{})); - } else { - showShortToast(BrowserActivity.this, R.string.saf_upload_path_not_available); - } - } - break; - case CHOOSE_COPY_MOVE_DEST_REQUEST: { - if (!Utils.isNetworkOn()) { - showShortToast(this, R.string.network_down); - return; - } - - copyMoveIntent = data; - } - break; - case TAKE_PHOTO_REQUEST: { - showShortToast(this, getString(R.string.take_photo_successfully)); - if (!Utils.isNetworkOn()) { - showShortToast(this, R.string.network_down); - return; - } - - if (takeCameraPhotoTempFile == null) { - showShortToast(this, getString(R.string.saf_upload_path_not_available)); - Log.i(DEBUG_TAG, "Pick file request did not return a path"); - return; - } - showShortToast(this, getString(R.string.added_to_upload_tasks)); - final SeafRepo repo = dataManager.getCachedRepoByID(navContext.getRepoID()); - if (repo != null && repo.canLocalDecrypt()) { - addUploadBlocksTask(navContext.getRepoID(), navContext.getRepoName(), navContext.getDirPath(), takeCameraPhotoTempFile.getAbsolutePath()); - } else { - addUploadTask(navContext.getRepoID(), navContext.getRepoName(), navContext.getDirPath(), takeCameraPhotoTempFile.getAbsolutePath()); - } - } - break; - case DOWNLOAD_FILE_REQUEST: { - File file = new File(data.getStringExtra("path")); - boolean isOpenWith = data.getBooleanExtra("is_open_with", false); - WidgetUtils.showFile(BrowserActivity.this, file, isOpenWith); - } - default: - break; - } - } - - private class SAFLoadRemoteFileTask extends AsyncTask { - - @Override - protected File[] doInBackground(Uri... uriList) { - if (uriList == null) - return null; - - List fileList = new ArrayList(); - for (Uri uri : uriList) { - // Log.d(DEBUG_TAG, "Uploading file from uri: " + uri); - InputStream in = null; - OutputStream out = null; - - try { - File tempDir = DataManager.createTempDir(); - File tempFile = new File(tempDir, Utils.getFilenamefromUri(BrowserActivity.this, uri)); - - if (!tempFile.createNewFile()) { - throw new RuntimeException("could not create temporary file"); - } - - in = getContentResolver().openInputStream(uri); - out = Files.newOutputStream(tempFile.toPath()); - IOUtils.copy(in, out); - - fileList.add(tempFile); - } catch (IOException | RuntimeException e) { - Log.d(DEBUG_TAG, "Could not open requested document", e); - } finally { - IOUtils.closeQuietly(in); - IOUtils.closeQuietly(out); - } - } - return fileList.toArray(new File[]{}); - } - - @Override - protected void onPostExecute(File... fileList) { - if (fileList == null) return; - - List list = dataManager.getCachedDirents(navContext.getRepoID(), navContext.getDirPath()); - - for (final File file : fileList) { - if (file == null) { - showShortToast(BrowserActivity.this, R.string.saf_upload_path_not_available); - } else { - if (list == null) { - Log.e(DEBUG_TAG, "Seadroid dirent cache is empty in uploadFile. Should not happen, aborting."); - return; - } - - boolean duplicate = false; - for (SeafDirent dirent : list) { - if (dirent.name.equals(file.getName())) { - duplicate = true; - break; - } - } - - if (!duplicate) { - final SeafRepo repo = dataManager.getCachedRepoByID(navContext.getRepoID()); - showShortToast(BrowserActivity.this, getString(R.string.added_to_upload_tasks)); - if (repo != null && repo.canLocalDecrypt()) { - addUploadBlocksTask(repo.repo_id, repo.repo_name, navContext.getDirPath(), file.getAbsolutePath()); - } else { - addUploadTask(navContext.getRepoID(), navContext.getRepoName(), navContext.getDirPath(), file.getAbsolutePath()); - } - } else { - showFileExistDialog(file); - } - } - } - - if (txService == null) - return; - - if (!txService.hasUploadNotifProvider()) { - UploadNotificationProvider provider = new UploadNotificationProvider( - txService.getUploadTaskManager(), - txService); - txService.saveUploadNotifProvider(provider); - } - } - } - - private void showFileExistDialog(final String filePath) { - showFileExistDialog(new File(filePath)); - } - - private void showFileExistDialog(final File file) { - final SeafRepo repo = dataManager.getCachedRepoByID(navContext.getRepoID()); - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.upload_file_exist)); - builder.setMessage(String.format(getString(R.string.upload_duplicate_found), file.getName())); - builder.setPositiveButton(getString(R.string.upload_replace), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - showShortToast(BrowserActivity.this, getString(R.string.added_to_upload_tasks)); - if (repo != null && repo.canLocalDecrypt()) { - addUpdateBlocksTask(repo.repo_id, repo.repo_name, navContext.getDirPath(), file.getAbsolutePath()); - } else { - addUpdateTask(navContext.getRepoID(), navContext.getRepoName(), navContext.getDirPath(), file.getAbsolutePath()); - } - } - }); - builder.setNeutralButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - } - }); - builder.setNegativeButton(getString(R.string.upload_keep_both), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (repo != null && repo.canLocalDecrypt()) { - addUploadBlocksTask(repo.repo_id, repo.repo_name, navContext.getDirPath(), file.getAbsolutePath()); - } else { - addUploadTask(navContext.getRepoID(), navContext.getRepoName(), navContext.getDirPath(), file.getAbsolutePath()); - } - } - }); - builder.show(); - } - - public void onItemSelected() { - // update contextual action bar (CAB) title - getReposFragment().updateContextualActionBar(); - } - - /*************** Navigation *************/ - - public void onFileSelected(SeafDirent dirent, boolean isOpenWith) { - final String fileName = dirent.name; - final long fileSize = dirent.size; - final String repoName = navContext.getRepoName(); - final String repoID = navContext.getRepoID(); - final String dirPath = navContext.getDirPath(); - final String filePath = Utils.pathJoin(navContext.getDirPath(), fileName); - final SeafRepo repo = dataManager.getCachedRepoByID(repoID); - - // Encrypted repo doesn\`t support gallery, - // because pic thumbnail under encrypted repo was not supported at the server side - if (Utils.isViewableImage(fileName) - && repo != null && !repo.encrypted) { - WidgetUtils.startGalleryActivity(this, repoName, repoID, dirPath, fileName, account); - return; - } - - final File localFile = dataManager.getLocalCachedFile(repoName, repoID, filePath, dirent.id); - if (localFile != null) { - if (fileName.endsWith(Constants.Format.DOT_SDOC)) { -// SeaWebViewActivity.openThis(this,localFile.getAbsolutePath()); - } else { - WidgetUtils.showFile(this, localFile, isOpenWith); - return; - } - } - - boolean videoFile = Utils.isVideoFile(fileName); - if (videoFile) { // is video file - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setItems(R.array.video_download_array, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == 0) // create file - startPlayActivity(fileName, repoID, filePath); - else if (which == 1) // create folder - startFileActivityForDirent(repoName, repoID, dirent, isOpenWith); - } - }).show(); - return; - } - startFileActivityForDirent(repoName, repoID, dirent, isOpenWith); - } - - @Override - public void onFileSelected(SeafDirent dirent) { - onFileSelected(dirent, false); - } - - /** - * Download a file - * - * @param dir - * @param fileName - */ - public void downloadFile(String dir, String fileName) { - // txService maybe null if layout orientation has changed - if (txService == null) { - return; - } - String filePath = Utils.pathJoin(dir, fileName); - txService.addDownloadTask(account, - navContext.getRepoName(), - navContext.getRepoID(), - filePath); - - if (!txService.hasDownloadNotifProvider()) { - DownloadNotificationProvider provider = new DownloadNotificationProvider(txService.getDownloadTaskManager(), txService); - txService.saveDownloadNotifProvider(provider); - } - - SeafItemAdapter adapter = getReposFragment().getAdapter(); - List infos = txService.getDownloadTaskInfosByPath(navContext.getRepoID(), dir); - // update downloading progress - adapter.setDownloadTaskList(infos); - } - - /** - * Download all files (folders) under a given folder - * - * @param dirPath - * @param fileName name of the download folder - * @param recurse - */ - public void downloadDir(String dirPath, String fileName, boolean recurse) { - if (!Utils.isNetworkOn()) { - showShortToast(this, R.string.network_down); - return; - } - - ConcurrentAsyncTask.execute(new DownloadDirTask(), - navContext.getRepoName(), - navContext.getRepoID(), - dirPath, - String.valueOf(recurse), - fileName); - } - - private class DownloadDirTask extends AsyncTask> { - - private String repoName; - private String repoID; - private String fileName; - private String dirPath; - private int fileCount; - private boolean recurse; - private ArrayList dirPaths = Lists.newArrayList(); - private SeafException err = null; - - @Override - protected List doInBackground(String... params) { - if (params.length != 5) { - Log.d(DEBUG_TAG, "Wrong params to LoadDirTask"); - return null; - } - - repoName = params[0]; - repoID = params[1]; - dirPath = params[2]; - recurse = Boolean.valueOf(params[3]); - fileName = params[4]; - - - ArrayList dirents = Lists.newArrayList(); - - dirPaths.add(Utils.pathJoin(dirPath, fileName)); - - // don`t use for each loop here - for (int i = 0; i < dirPaths.size(); i++) { - - List currentDirents; - try { - currentDirents = dataManager.getDirentsFromServer(repoID, dirPaths.get(i)); - } catch (SeafException e) { - err = e; - e.printStackTrace(); - return null; - } - - if (currentDirents == null) - continue; - - for (SeafDirent seafDirent : currentDirents) { - if (seafDirent.isDir()) { - if (recurse) { - dirPaths.add(Utils.pathJoin(dirPaths.get(i), seafDirent.name)); - } - } else { - File localCachedFile = dataManager.getLocalCachedFile(repoName, - repoID, - Utils.pathJoin(dirPaths.get(i), - seafDirent.name), - seafDirent.id); - if (localCachedFile != null) { - continue; - } - - // txService maybe null if layout orientation has changed - // e.g. landscape and portrait switch - if (txService == null) - return null; - - txService.addTaskToDownloadQue(account, - repoName, - repoID, - Utils.pathJoin(dirPaths.get(i), - seafDirent.name)); - - fileCount++; - } - } - } - - return dirents; - } - - @Override - protected void onPostExecute(List dirents) { - if (dirents == null) { - if (err != null) { - showShortToast(BrowserActivity.this, R.string.transfer_list_network_error); - } - return; - } - - if (fileCount == 0) - showShortToast(BrowserActivity.this, R.string.transfer_download_no_task); - else { - showShortToast(BrowserActivity.this, getResources().getQuantityString(R.plurals.transfer_download_started, fileCount, fileCount)); - if (!txService.hasDownloadNotifProvider()) { - DownloadNotificationProvider provider = new DownloadNotificationProvider(txService.getDownloadTaskManager(), - txService); - txService.saveDownloadNotifProvider(provider); - } - } - - // set download tasks info to adapter in order to update download progress in UI thread - getReposFragment().getAdapter().setDownloadTaskList(txService.getDownloadTaskInfosByPath(repoID, dirPath)); - } - } - - private void startFileActivityForDirent(String repoName, String repoID, SeafDirent dirent, boolean isOpenWith) { - if (dirent.name.endsWith(Constants.Format.DOT_SDOC)) { - SeaWebViewActivity.openSdoc(this, repoName, repoID, dirent.parent_dir + dirent.name); - return; - } - - final String filePath = Utils.pathJoin(navContext.getDirPath(), dirent.name); - startFileActivity(repoName, repoID, filePath, dirent.size, isOpenWith); - } - - private void startFileActivityForStarItems(String repoName, String repoID, SeafStarredFile starredFile, boolean isOpenWith) { - if (starredFile.getObjName().endsWith(Constants.Format.DOT_SDOC)) { - SeaWebViewActivity.openSdoc(this, repoName, repoID, starredFile.getPath()); - return; - } - - startFileActivity(repoName, repoID, starredFile.getPath(), starredFile.getSize(), isOpenWith); - } - - private void startFileActivity(String repoName, String repoID, String path, long size, boolean isOpenWith) { - // txService maybe null if layout orientation has changed - if (txService == null) { - return; - } - - int taskID = txService.addDownloadTask(account, repoName, repoID, path, size); - Intent intent = new Intent(this, FileActivity.class); - intent.putExtra("repoName", repoName); - intent.putExtra("repoID", repoID); - intent.putExtra("filePath", path); - intent.putExtra("account", account); - intent.putExtra("taskID", taskID); - intent.putExtra("is_open_with", isOpenWith); - startActivityForResult(intent, DOWNLOAD_FILE_REQUEST); - } - - private void startPlayActivity(String fileName, String repoID, String filePath) { - Intent intent = new Intent(this, CustomExoVideoPlayerActivity.class); - intent.putExtra("fileName", fileName); - intent.putExtra("repoID", repoID); - intent.putExtra("filePath", filePath); - intent.putExtra("account", account); - startActivity(intent); - } - - - public void onStarredFileSelected(final SeafStarredFile starredFile, boolean isOpenWith) { - final long fileSize = starredFile.getSize(); - final String repoID = starredFile.getRepoID(); - final SeafRepo repo = dataManager.getCachedRepoByID(repoID); - if (repo == null) - return; - - if (repo.encrypted && !dataManager.getRepoPasswordSet(repo.repo_id)) { - String password = dataManager.getRepoPassword(repo.repo_id); - showPasswordDialog(repo.repo_name, repo.repo_id, - new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - onStarredFileSelected(starredFile); - } - }, password); - - return; - } - - final String repoName = repo.getRepoName(); - final String filePath = starredFile.getPath(); - final String dirPath = Utils.getParentPath(filePath); - - // Encrypted repo doesn\`t support gallery, - // because pic thumbnail under encrypted repo was not supported at the server side - if (Utils.isViewableImage(starredFile.getTitle()) && !repo.encrypted) { - WidgetUtils.startGalleryActivity(this, repoName, repoID, dirPath, starredFile.getTitle(), account); - return; - } - - final File localFile = dataManager.getLocalCachedFile(repoName, repoID, filePath, null); - if (localFile != null) { - WidgetUtils.showFile(this, localFile); - return; - } - -// startFileActivity(repoName, repoID, filePath, fileSize, isOpenWith); - startFileActivityForStarItems(repoName, repoID, starredFile, isOpenWith); - } - - @Override - public void onStarredFileSelected(SeafStarredFile starredFile) { - onStarredFileSelected(starredFile, false); - } - - @Override - public void onBackPressed() { - if (getSupportFragmentManager().getBackStackEntryCount() != 0) { - getSupportFragmentManager().popBackStack(); - return; - } - - if (currentPosition == INDEX_LIBRARY_TAB && navContext.inRepo()) { - if (navContext.isRepoRoot()) { - navContext.setRepoID(null); - getActionBarToolbar().setTitle(R.string.app_name); - } else { - String parentPath = Utils.getParentPath(navContext.getDirPath()); - navContext.setDirPath(parentPath); - if (TextUtils.equals(ACTIONBAR_PARENT_PATH, parentPath)) { - getActionBarToolbar().setTitle(navContext.getRepoName()); - } else { - getActionBarToolbar().setTitle(parentPath.substring(parentPath.lastIndexOf(ACTIONBAR_PARENT_PATH) + 1)); - } - } - getReposFragment().clearAdapterData(); - getReposFragment().refreshView(true); - - } else { - super.onBackPressed(); - } - } - - @Override - public void onBackStackChanged() { - } - - - /************ Files ************/ - - /** - * Export a file. - * 1. first ask the user to choose an app - * 2. then download the latest version of the file - * 3. start the choosen app - * - * @param fileName The name of the file to share in the current navcontext - */ - public void exportFile(String fileName, long fileSize) { - String repoName = navContext.getRepoName(); - String repoID = navContext.getRepoID(); - String dirPath = navContext.getDirPath(); - String fullPath = Utils.pathJoin(dirPath, fileName); - chooseExportApp(repoName, repoID, fullPath, fileSize); - } - - private void chooseExportApp(final String repoName, final String repoID, final String path, final long fileSize) { - final File file = dataManager.getLocalRepoFile(repoName, repoID, path); - Uri uri = null; - if (android.os.Build.VERSION.SDK_INT > 23) { - uri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName(), file); - } else { - uri = Uri.fromFile(file); - } - final Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.setType(Utils.getFileMimeType(file)); - sendIntent.putExtra(Intent.EXTRA_STREAM, uri); - - // Get a list of apps - List infos = Utils.getAppsByIntent(sendIntent); - - if (infos.isEmpty()) { - showShortToast(this, R.string.no_app_available); - return; - } - - AppChoiceDialog dialog = new AppChoiceDialog(); - dialog.init(getString(R.string.export_file), infos, new AppChoiceDialog.OnItemSelectedListener() { - @Override - public void onCustomActionSelected(CustomAction action) { - } - - @Override - public void onAppSelected(ResolveInfo appInfo) { - String className = appInfo.activityInfo.name; - String packageName = appInfo.activityInfo.packageName; - sendIntent.setClassName(packageName, className); - - if (!Utils.isNetworkOn() && file.exists()) { - startActivity(sendIntent); - return; - } - fetchFileAndExport(appInfo, sendIntent, repoName, repoID, path, fileSize); - } - - }); - dialog.show(getSupportFragmentManager(), CHOOSE_APP_DIALOG_FRAGMENT_TAG); - } - - public void fetchFileAndExport(final ResolveInfo appInfo, final Intent intent, - final String repoName, final String repoID, final String path, final long fileSize) { - - fetchFileDialog = new FetchFileDialog(); - fetchFileDialog.init(repoName, repoID, path, fileSize, new FetchFileDialog.FetchFileListener() { - @Override - public void onSuccess() { - startActivity(intent); - } - - @Override - public void onDismiss() { - fetchFileDialog = null; - } - - @Override - public void onFailure(SeafException err) { - } - }); - fetchFileDialog.show(getSupportFragmentManager(), OPEN_FILE_DIALOG_FRAGMENT_TAG); - } - - public void starRepo(String repoID, String repoName) { - final RenameRepoDialog dialog = new RenameRepoDialog(); - dialog.init(repoID, repoName, account); - dialog.setTaskDialogLisenter(new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - showShortToast(BrowserActivity.this, R.string.rename_successful); - ReposFragment reposFragment = getReposFragment(); - if (currentPosition == INDEX_LIBRARY_TAB && reposFragment != null) { - reposFragment.refreshView(true, true); - } - } - }); - dialog.show(getSupportFragmentManager(), TAG_RENAME_REPO_DIALOG_FRAGMENT); - } - - public void renameRepo(String repoID, String repoName) { - final RenameRepoDialog dialog = new RenameRepoDialog(); - dialog.init(repoID, repoName, account); - dialog.setTaskDialogLisenter(new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - showShortToast(BrowserActivity.this, R.string.rename_successful); - ReposFragment reposFragment = getReposFragment(); - if (currentPosition == INDEX_LIBRARY_TAB && reposFragment != null) { - reposFragment.refreshView(true, true); - } - } - }); - dialog.show(getSupportFragmentManager(), TAG_RENAME_REPO_DIALOG_FRAGMENT); - } - - public void deleteRepo(String repoID) { - final DeleteRepoDialog dialog = new DeleteRepoDialog(); - dialog.init(repoID, account); - dialog.setTaskDialogLisenter(new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - showShortToast(BrowserActivity.this, R.string.delete_successful); - ReposFragment reposFragment = getReposFragment(); - if (currentPosition == INDEX_LIBRARY_TAB && reposFragment != null) { - reposFragment.refreshView(true, true); - } - } - }); - dialog.show(getSupportFragmentManager(), TAG_DELETE_REPO_DIALOG_FRAGMENT); - } - - /** - * Share a file. Generating a file share link and send the link or file to someone - * through some app. - * - * @param repoID - * @param path - */ - public void showShareDialog(String repoID, String path, boolean isDir, long fileSize, String fileName) { - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - boolean inChina = Utils.isInChina(); - String[] strings; - //if user in China ,system add WeChat share - if (inChina) { - strings = getResources().getStringArray(R.array.file_action_share_array_zh); - } else { - strings = getResources().getStringArray(R.array.file_action_share_array); - } - builder.setItems(strings, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (!inChina) { - which++; - } - switch (which) { - case 0: - WidgetUtils.ShareWeChat(BrowserActivity.this, account, repoID, path, fileName, fileSize, isDir); - break; - case 1: - // need input password - WidgetUtils.chooseShareApp(BrowserActivity.this, repoID, path, isDir, account, null, null); - break; - case 2: - WidgetUtils.inputSharePassword(BrowserActivity.this, repoID, path, isDir, account); - break; - } - } - }).show(); - } - - public void renameFile(String repoID, String repoName, String path) { - doRename(repoID, repoName, path, false); - } - - public void renameDir(String repoID, String repoName, String path) { - doRename(repoID, repoName, path, true); - } - - private void doRename(String repoID, String repoName, String path, boolean isdir) { - final RenameFileDialog dialog = new RenameFileDialog(); - dialog.init(repoID, path, isdir, account); - dialog.setTaskDialogLisenter(new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - showShortToast(BrowserActivity.this, R.string.rename_successful); - ReposFragment reposFragment = getReposFragment(); - if (currentPosition == INDEX_LIBRARY_TAB && reposFragment != null) { - reposFragment.refreshView(); - } - } - }); - dialog.show(getSupportFragmentManager(), TAG_RENAME_FILE_DIALOG_FRAGMENT); - } - - public void deleteFile(String repoID, String repoName, String path) { - doDelete(repoID, repoName, path, false); - } - - public void deleteDir(String repoID, String repoName, String path) { - doDelete(repoID, repoName, path, true); - } - - private void doDelete(final String repoID, String repoName, String path, boolean isdir) { - final DeleteFileDialog dialog = new DeleteFileDialog(); - dialog.init(repoID, path, isdir, account); - dialog.setTaskDialogLisenter(new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - showShortToast(BrowserActivity.this, R.string.delete_successful); - List cachedDirents = getDataManager() - .getCachedDirents(repoID, getNavContext().getDirPath()); - getReposFragment().getAdapter().setItems(cachedDirents); - getReposFragment().getAdapter().notifyDataSetChanged(); - } - }); - dialog.show(getSupportFragmentManager(), TAG_DELETE_FILE_DIALOG_FRAGMENT); - } - - public void copyFile(String srcRepoId, String srcRepoName, String srcDir, String srcFn, boolean isdir) { - chooseCopyMoveDest(srcRepoId, srcRepoName, srcDir, srcFn, isdir, CopyMoveContext.OP.COPY); - } - - public void moveFile(String srcRepoId, String srcRepoName, String srcDir, String srcFn, boolean isdir) { - chooseCopyMoveDest(srcRepoId, srcRepoName, srcDir, srcFn, isdir, CopyMoveContext.OP.MOVE); - } - - public void starFile(String srcRepoId, String srcDir, String srcFn) { - getStarredFragment().doStarFile(srcRepoId, srcDir, srcFn); - } - - private void chooseCopyMoveDest(String repoID, String repoName, String path, String filename, boolean isdir, CopyMoveContext.OP op) { - copyMoveContext = new CopyMoveContext(repoID, repoName, path, filename, - isdir, op); - Intent intent = new Intent(this, SeafilePathChooserActivity.class); - intent.putExtra(SeafilePathChooserActivity.DATA_ACCOUNT, account); - SeafRepo repo = dataManager.getCachedRepoByID(repoID); - boolean isShowEncryptDir = false; - if (repo.encrypted) { - isShowEncryptDir = true; - intent.putExtra(SeafilePathChooserActivity.ENCRYPTED_REPO_ID, repoID); - } - intent.putExtra(SeafilePathChooserActivity.SHOW_ENCRYPTED_REPOS, isShowEncryptDir); - startActivityForResult(intent, CHOOSE_COPY_MOVE_DEST_REQUEST); - return; - } - - private void doCopyMove() { - if (!copyMoveContext.checkCopyMoveToSubfolder()) { - showShortToast(this, copyMoveContext.isCopy() - ? R.string.cannot_copy_folder_to_subfolder - : R.string.cannot_move_folder_to_subfolder); - return; - } - final CopyMoveDialog dialog = new CopyMoveDialog(); - dialog.init(account, copyMoveContext); - dialog.setCancelable(false); - dialog.setTaskDialogLisenter(new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - showShortToast(BrowserActivity.this, copyMoveContext.isCopy() - ? R.string.copied_successfully - : R.string.moved_successfully); - if (copyMoveContext.batch) { - List cachedDirents = getDataManager().getCachedDirents(getNavContext().getRepoID(), - getNavContext().getDirPath()); - - // refresh view - if (getReposFragment().getAdapter() != null) { - getReposFragment().getAdapter().setItems(cachedDirents); - getReposFragment().getAdapter().notifyDataSetChanged(); - } - - if (cachedDirents.size() == 0) - getReposFragment().getEmptyView().setVisibility(View.VISIBLE); - return; - } - - if (copyMoveContext.isMove()) { - ReposFragment reposFragment = getReposFragment(); - if (currentPosition == INDEX_LIBRARY_TAB && reposFragment != null) { - reposFragment.refreshView(); - } - } - } - }); - dialog.show(getSupportFragmentManager(), TAG_COPY_MOVE_DIALOG_FRAGMENT); - } - - private void onFileDownloadFailed(int taskID) { - if (txService == null) { - return; - } - - DownloadTaskInfo info = txService.getDownloadTaskInfo(taskID); - if (info == null) - return; - - final SeafException err = info.err; - final String repoName = info.repoName; - final String repoID = info.repoID; - final String path = info.pathInRepo; - - if (err != null && err.getCode() == SeafConnection.HTTP_STATUS_REPO_PASSWORD_REQUIRED) { - if (currentPosition == INDEX_LIBRARY_TAB - && repoID.equals(navContext.getRepoID()) - && Utils.getParentPath(path).equals(navContext.getDirPath())) { - showPasswordDialog(repoName, repoID, - new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - txService.addDownloadTask(account, - repoName, - repoID, - path); - } - }); - return; - } - } - - showShortToast(this, getString(R.string.download_failed)); - } - - private void onFileUploaded(int taskID) { - if (txService == null) { - return; - } - - UploadTaskInfo info = txService.getUploadTaskInfo(taskID); - - if (info == null) { - return; - } - - String repoID = info.repoID; - String dir = info.parentDir; - if (currentPosition == INDEX_LIBRARY_TAB && repoID.equals(navContext.getRepoID()) && dir.equals(navContext.getDirPath())) { - getReposFragment().refreshView(true, true); - String verb = getString(info.isUpdate ? R.string.updated : R.string.uploaded); - showShortToast(this, verb + " " + Utils.fileNameFromPath(info.localFilePath)); - } - } - - private int intShowErrorTime; - - private void onFileUploadFailed(int taskID) { - if (++intShowErrorTime <= 1) - showShortToast(this, getString(R.string.upload_failed)); - } - - public PasswordDialog showPasswordDialog(String repoName, String repoID, TaskDialog.TaskDialogListener listener) { - return showPasswordDialog(repoName, repoID, listener, null); - } - - public PasswordDialog showPasswordDialog(String repoName, String repoID, TaskDialog.TaskDialogListener listener, String password) { - PasswordDialog passwordDialog = new PasswordDialog(); - passwordDialog.setRepo(repoName, repoID, account); - if (password != null) { - passwordDialog.setPassword(password); - } - passwordDialog.setTaskDialogLisenter(listener); - passwordDialog.show(getSupportFragmentManager(), PASSWORD_DIALOG_FRAGMENT_TAG); - return passwordDialog; - } - - /************ Multiple Files ************/ - - /** - * Delete multiple fiels - * - * @param repoID - * @param path - * @param dirents - */ - public void deleteFiles(final String repoID, String path, List dirents) { - final DeleteFileDialog dialog = new DeleteFileDialog(); - dialog.init(repoID, path, dirents, account); - dialog.setCancelable(false); - dialog.setTaskDialogLisenter(new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - showShortToast(BrowserActivity.this, R.string.delete_successful); - if (getDataManager() != null) { - List cachedDirents = getDataManager().getCachedDirents(repoID, - getNavContext().getDirPath()); - getReposFragment().getAdapter().setItems(cachedDirents); - getReposFragment().getAdapter().notifyDataSetChanged(); - // update contextual action bar (CAB) title - getReposFragment().updateContextualActionBar(); - if (cachedDirents.size() == 0) - getReposFragment().getEmptyView().setVisibility(View.VISIBLE); - } - } - }); - dialog.show(getSupportFragmentManager(), TAG_DELETE_FILES_DIALOG_FRAGMENT); - } - - /** - * Copy multiple files - * - * @param srcRepoId - * @param srcRepoName - * @param srcDir - * @param dirents - */ - public void copyFiles(String srcRepoId, String srcRepoName, String srcDir, List dirents) { - chooseCopyMoveDestForMultiFiles(srcRepoId, srcRepoName, srcDir, dirents, CopyMoveContext.OP.COPY); - } - - /** - * Move multiple files - * - * @param srcRepoId - * @param srcRepoName - * @param srcDir - * @param dirents - */ - public void moveFiles(String srcRepoId, String srcRepoName, String srcDir, List dirents) { - chooseCopyMoveDestForMultiFiles(srcRepoId, srcRepoName, srcDir, dirents, CopyMoveContext.OP.MOVE); - } - - /** - * Choose copy/move destination for multiple files - * - * @param repoID - * @param repoName - * @param dirPath - * @param dirents - * @param op - */ - private void chooseCopyMoveDestForMultiFiles(String repoID, String repoName, String dirPath, List dirents, CopyMoveContext.OP op) { - copyMoveContext = new CopyMoveContext(repoID, repoName, dirPath, dirents, op); - Intent intent = new Intent(this, SeafilePathChooserActivity.class); - intent.putExtra(SeafilePathChooserActivity.DATA_ACCOUNT, account); - SeafRepo repo = getDataManager().getCachedRepoByID(repoID); - boolean isShowEncryptDir = true; - if (repo.encrypted) { - intent.putExtra(SeafilePathChooserActivity.ENCRYPTED_REPO_ID, repoID); - } - intent.putExtra(SeafilePathChooserActivity.REPO_ENCRYPTED, repo.encrypted); - intent.putExtra(SeafilePathChooserActivity.SHOW_ENCRYPTED_REPOS, isShowEncryptDir); - startActivityForResult(intent, BrowserActivity.CHOOSE_COPY_MOVE_DEST_REQUEST); - } - - /** - * Add selected files (folders) to downloading queue, - * folders with subfolder will be downloaded recursively. - * - * @param repoID - * @param repoName - * @param dirPath - * @param dirents - */ - public void downloadFiles(String repoID, String repoName, String dirPath, List dirents) { - if (!Utils.isNetworkOn()) { - showShortToast(this, R.string.network_down); - return; - } - - DownloadFilesTask task = new DownloadFilesTask(repoID, repoName, dirPath, dirents); - ConcurrentAsyncTask.execute(task); - } - - /** - * Task for asynchronously downloading selected files (folders), - * files wont be added to downloading queue if they have already been cached locally. - */ - private class DownloadFilesTask extends AsyncTask { - private String repoID, repoName, dirPath; - private List dirents; - private SeafException err; - private int fileCount; - - public DownloadFilesTask(String repoID, String repoName, String dirPath, List dirents) { - this.repoID = repoID; - this.repoName = repoName; - this.dirPath = dirPath; - this.dirents = dirents; - } - - @Override - protected void onPreExecute() { - getReposFragment().showLoading(true); - } - - @Override - protected Void doInBackground(Void... params) { - ArrayList dirPaths = Lists.newArrayList(dirPath); - for (int i = 0; i < dirPaths.size(); i++) { - if (i > 0) { - try { - dirents = getDataManager().getDirentsFromServer(repoID, dirPaths.get(i)); - } catch (SeafException e) { - err = e; - Log.e(DEBUG_TAG, e.getMessage() + e.getCode()); - } - } - - if (dirents == null) - continue; - - for (SeafDirent seafDirent : dirents) { - if (seafDirent.isDir()) { - // download files recursively - dirPaths.add(Utils.pathJoin(dirPaths.get(i), seafDirent.name)); - } else { - File localCachedFile = getDataManager().getLocalCachedFile(repoName, - repoID, - Utils.pathJoin(dirPaths.get(i), - seafDirent.name), - seafDirent.id); - if (localCachedFile != null) { - continue; - } - - // txService maybe null if layout orientation has changed - // e.g. landscape and portrait switch - if (txService == null) - return null; - - txService.addTaskToDownloadQue(account, - repoName, - repoID, - Utils.pathJoin(dirPaths.get(i), - seafDirent.name)); - - fileCount++; - } - - } - } - - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - // update ui - getReposFragment().showLoading(false); - - if (err != null) { - showShortToast(BrowserActivity.this, R.string.transfer_list_network_error); - return; - } - - if (fileCount == 0) - showShortToast(BrowserActivity.this, R.string.transfer_download_no_task); - else { - showShortToast(BrowserActivity.this, - getResources().getQuantityString(R.plurals.transfer_download_started, - fileCount, - fileCount)); - - if (!txService.hasDownloadNotifProvider()) { - DownloadNotificationProvider provider = - new DownloadNotificationProvider(txService.getDownloadTaskManager(), - txService); - txService.saveDownloadNotifProvider(provider); - } - - } - - } - } - - @Override - public boolean onKeyUp(int keycode, KeyEvent e) { - switch (keycode) { - case KeyEvent.KEYCODE_MENU: - if (overFlowMenu != null) { - overFlowMenu.performIdentifierAction(R.id.menu_overflow, 0); - } - } - - return super.onKeyUp(keycode, e); - } - - // for receive broadcast from TransferService - private class TransferReceiver extends BroadcastReceiver { - - private TransferReceiver() { - } - - public void onReceive(Context context, Intent intent) { - String type = intent.getStringExtra("type"); - if (type.equals(DownloadTaskManager.BROADCAST_FILE_DOWNLOAD_FAILED)) { - int taskID = intent.getIntExtra("taskID", 0); - onFileDownloadFailed(taskID); - } else if (type.equals(UploadTaskManager.BROADCAST_FILE_UPLOAD_SUCCESS)) { - int taskID = intent.getIntExtra("taskID", 0); - onFileUploaded(taskID); - } else if (type.equals(UploadTaskManager.BROADCAST_FILE_UPLOAD_FAILED)) { - int taskID = intent.getIntExtra("taskID", 0); - onFileUploadFailed(taskID); - } - } - - } // TransferReceiver - - - public void showRepoBottomSheet(SeafRepo repo) { - getReposFragment().showRepoBottomSheet(repo); - } - - public void showFileBottomSheet(String title, final SeafDirent dirent) { - getReposFragment().showFileBottomSheet(title, dirent); - } - - public void showDirBottomSheet(String title, final SeafDirent dirent) { - getReposFragment().showDirBottomSheet(title, dirent); - } - - private void syncCamera() { - SettingsManager settingsManager = SettingsManager.instance(); - CameraUploadManager cameraManager = new CameraUploadManager(getApplicationContext()); - if (cameraManager.isCameraUploadEnabled() && settingsManager.isVideosUploadAllowed()) { - cameraManager.performFullSync(); - } - } - - @Subscribe(threadMode = ThreadMode.MAIN) - public void onEvent(CheckUploadServiceEvent result) { - if (!Utils.isServiceRunning(BrowserActivity.this, "com.seafile.seadroid2.cameraupload.MediaObserverService")) { - mediaObserver = new Intent(this, MediaObserverService.class); - startService(mediaObserver); - syncCamera(); - Log.d(DEBUG_TAG, "onEvent============false "); - } else { - Log.d(DEBUG_TAG, "onEvent============true "); - } - - if (!Utils.isServiceRunning(BrowserActivity.this, "com.seafile.seadroid2.monitor.FileMonitorService")) { - monitorIntent = new Intent(this, FileMonitorService.class); - startService(monitorIntent); - Log.d(DEBUG_TAG, "FileMonitorService============false "); - } - - if (!Utils.isServiceRunning(BrowserActivity.this, "com.seafile.seadroid2.folderbackup.FolderBackupService")) { - monitorIntent = new Intent(this, FolderBackupService.class); - startService(monitorIntent); - Log.d(DEBUG_TAG, "FolderBackupService============false "); - } - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/CustomNotificationBuilder.java b/app/src/main/java/com/seafile/seadroid2/ui/CustomNotificationBuilder.java index fcc402506..883808987 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/CustomNotificationBuilder.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/CustomNotificationBuilder.java @@ -3,6 +3,8 @@ import android.app.Notification; import android.content.Context; import android.os.Build; + +import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; import android.view.View; import android.widget.RemoteViews; @@ -35,13 +37,7 @@ public class CustomNotificationBuilder extends NotificationCompat.Builder { * {@link NotificationCompat.Builder}, when it is good enough. */ public static NotificationCompat.Builder getNotificationBuilder(Context context, String channelId) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - return new CustomNotificationBuilder(context); - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - return new NotificationCompat.Builder(context, channelId); - } else { - return new NotificationCompat.Builder(context); - } + return new NotificationCompat.Builder(context, channelId); } /** @@ -58,6 +54,7 @@ private CustomNotificationBuilder(Context context) { /** * {@inheritDoc} */ + @NonNull @Override public NotificationCompat.Builder setProgress(int max, int progress, boolean indeterminate) { mContentView.setProgressBar(R.id.progress, max, progress, indeterminate); @@ -72,6 +69,7 @@ public NotificationCompat.Builder setProgress(int max, int progress, boolean ind /** * {@inheritDoc} */ + @NonNull @Override public NotificationCompat.Builder setSmallIcon(int icon) { super.setSmallIcon(icon); // necessary @@ -82,6 +80,7 @@ public NotificationCompat.Builder setSmallIcon(int icon) { /** * {@inheritDoc} */ + @NonNull @Override public NotificationCompat.Builder setContentTitle(CharSequence title) { super.setContentTitle(title); @@ -92,6 +91,7 @@ public NotificationCompat.Builder setContentTitle(CharSequence title) { /** * {@inheritDoc} */ + @NonNull @Override public NotificationCompat.Builder setContentText(CharSequence text) { super.setContentText(text); @@ -104,15 +104,4 @@ public NotificationCompat.Builder setContentText(CharSequence text) { return this; } - @Override - public Notification build() { - Notification result = super.build(); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { - // super.build() in Android 2.x totally ruins whatever was made #setContent - result.contentView = mContentView; - } - return result; - } - - } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/EmailAutoCompleteTextView.java b/app/src/main/java/com/seafile/seadroid2/ui/EmailAutoCompleteTextView.java deleted file mode 100644 index 9100c2600..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/EmailAutoCompleteTextView.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.seafile.seadroid2.ui; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.content.Context; -import androidx.appcompat.widget.AppCompatAutoCompleteTextView; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.Patterns; -import android.widget.ArrayAdapter; - -import com.seafile.seadroid2.R; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -/** - * Automatically fills in email accounts - */ -public class EmailAutoCompleteTextView extends AppCompatAutoCompleteTextView { - - public EmailAutoCompleteTextView(Context context) { - super(context); - init(); - } - - public EmailAutoCompleteTextView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - public EmailAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(); - } - - private void init() { - if (isInEditMode()) { return; } - retrieveAccounts(); - } - - /** - * Manually retrieve the accounts, typically used for API 23+ after getting the permission. Called automatically - * on creation, but needs to be recalled if the permission is granted later - */ - public void retrieveAccounts() { - Collection accounts = getEmailAccounts(); - if (accounts != null && !accounts.isEmpty()) { - ArrayAdapter adapter = new ArrayAdapter<>(getContext(), - R.layout.support_simple_spinner_dropdown_item, - new ArrayList<>(accounts)); - setAdapter(adapter); - } - } - - /** - * Get all the accounts that appear to be email accounts. HashSet so that we do not get duplicates - * @return list of email accounts - */ - private Set getEmailAccounts() { - HashSet emailAccounts = new HashSet<>(); - AccountManager manager = (AccountManager) getContext().getSystemService(Context.ACCOUNT_SERVICE); - final Account[] accounts = manager.getAccounts(); - for (Account account : accounts) { - if (!TextUtils.isEmpty(account.name) && Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) { - emailAccounts.add(account.name); - } - } - - ArrayList accountlist = new com.seafile.seadroid2.account.AccountManager(getContext()).getAccountAutoCompleteTexts(); - if (accountlist != null) { - for (String account : accountlist) { - emailAccounts.add(account); - } - } - - return emailAccounts; - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/SplashActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/SplashActivity.java new file mode 100644 index 000000000..71911a683 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/SplashActivity.java @@ -0,0 +1,60 @@ +package com.seafile.seadroid2.ui; + +import android.content.Intent; +import android.os.Bundle; + +import com.blankj.utilcode.util.ActivityUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.ui.account.AccountsActivity; +import com.seafile.seadroid2.ui.data_migrate.DataMigrationActivity; +import com.seafile.seadroid2.ui.main.MainActivity; +import com.seafile.seadroid2.util.sp.AppSPs; + +import java.util.concurrent.TimeUnit; + +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; + +public class SplashActivity extends BaseActivity { + private Disposable disposable; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_splash); + + long duration = 500; + disposable = Observable.timer(duration, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(aLong -> { + Account curAccount = SupportAccountManager.getInstance().getCurrentAccount(); + if (curAccount == null || !curAccount.hasValidToken()) { + Intent newIntent = new Intent(this, AccountsActivity.class); + newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + ActivityUtils.startActivity(newIntent); + } +// else if (!AppSPs.isMigratedWhenV300()) { +// ActivityUtils.startActivity(DataMigrationActivity.class); +// } + else { + ActivityUtils.startActivity(MainActivity.class); + } + finish(); + }); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + if (disposable != null) { + disposable.dispose(); + disposable = null; + } + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/WidgetUtils.java b/app/src/main/java/com/seafile/seadroid2/ui/WidgetUtils.java index 06d518556..a07558197 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/WidgetUtils.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/WidgetUtils.java @@ -16,24 +16,27 @@ import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Build; +import android.text.ClipboardManager; +import android.webkit.MimeTypeMap; +import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.NotificationCompat; import androidx.core.content.FileProvider; -import android.text.ClipboardManager; -import android.webkit.MimeTypeMap; - import com.blankj.utilcode.util.ToastUtils; +import com.seafile.seadroid2.BuildConfig; import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeadroidApplication; import com.seafile.seadroid2.SeafException; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.ui.activity.GalleryActivity; -import com.seafile.seadroid2.ui.markdown.MarkdownActivity; import com.seafile.seadroid2.ui.dialog.AppChoiceDialog; import com.seafile.seadroid2.ui.dialog.GetShareLinkDialog; -import com.seafile.seadroid2.ui.dialog.GetShareLinkEncryptDialog; import com.seafile.seadroid2.ui.dialog.TaskDialog; +import com.seafile.seadroid2.ui.dialog_fragment.GetShareLinkPasswordDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.listener.OnRefreshDataListener; +import com.seafile.seadroid2.ui.main.MainActivity; +import com.seafile.seadroid2.ui.markdown.MarkdownActivity; import com.seafile.seadroid2.util.FileExports; import com.seafile.seadroid2.util.Utils; @@ -49,7 +52,7 @@ public class WidgetUtils { public static final String MIME_ANDROID = "application/vnd.android.package-archive"; - public static void chooseShareApp(final BaseActivity activity, + public static void chooseShareApp(final AppCompatActivity activity, final String repoID, final String path, final boolean isdir, @@ -118,25 +121,25 @@ public void onTaskSuccess() { } }); - dialog.show(activity.getSupportFragmentManager(), BrowserActivity.CHOOSE_APP_DIALOG_FRAGMENT_TAG); + dialog.show(activity.getSupportFragmentManager(), AppChoiceDialog.class.getSimpleName()); } - public static void inputSharePassword(final BaseActivity activity, + public static void inputSharePassword(final AppCompatActivity activity, final String repoID, final String path, final boolean isdir, final Account account) { - final GetShareLinkEncryptDialog dialog = new GetShareLinkEncryptDialog(); - dialog.setTaskDialogLisenter(new TaskDialog.TaskDialogListener() { + + GetShareLinkPasswordDialogFragment dialogFragment = new GetShareLinkPasswordDialogFragment(); + dialogFragment.setRefreshListener(new OnRefreshDataListener() { @Override - public void onTaskSuccess() { - String password = dialog.getPassword(); - String days = dialog.getDays(); + public void onActionStatus(boolean isDone) { + String password = dialogFragment.getPassword(); + String days = dialogFragment.getDays(); chooseShareApp(activity, repoID, path, isdir, account, password, days); } }); - dialog.show(activity.getSupportFragmentManager(), BrowserActivity.CHARE_LINK_PASSWORD_FRAGMENT_TAG); - + dialogFragment.show(activity.getSupportFragmentManager(), GetShareLinkPasswordDialogFragment.class.getSimpleName()); } /** @@ -163,7 +166,7 @@ public static void ShareWeChat(final BaseActivity activity, Account account, Str shareIntent.setType("text/plain"); ResolveInfo weChatInfo = Utils.getWeChatIntent(shareIntent); if (weChatInfo == null) { - activity.showShortToast(activity, R.string.no_app_available); + ToastUtils.showLong(R.string.no_app_available); return; } String className = weChatInfo.activityInfo.name; @@ -180,35 +183,36 @@ public void onTaskSuccess() { }); gdialog.show(activity.getSupportFragmentManager(), "DialogFragment"); } else {//share files - BrowserActivity browserActivity = ((BrowserActivity) activity); - String repoName = ((BrowserActivity) activity).getNavContext().getRepoName(); - String dirPath = ((BrowserActivity) activity).getNavContext().getDirPath(); - - String fullPath = Utils.pathJoin(dirPath, fileName); - final File file = browserActivity.getDataManager().getLocalRepoFile(repoName, repoID, fullPath); - Uri uri = null; - if (android.os.Build.VERSION.SDK_INT > 23) { - uri = FileProvider.getUriForFile(activity, activity.getApplicationContext().getPackageName(), file); - } else { - uri = Uri.fromFile(file); - } - final Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.setType(Utils.getFileMimeType(file)); - sendIntent.putExtra(Intent.EXTRA_STREAM, uri); - ResolveInfo weChatInfo = Utils.getWeChatIntent(sendIntent); - if (weChatInfo == null) { - activity.showShortToast(activity, R.string.no_app_available); - return; - } - String className = weChatInfo.activityInfo.name; - String packageName = weChatInfo.activityInfo.packageName; - sendIntent.setClassName(packageName, className); - if (!Utils.isNetworkOn() && file.exists()) { - activity.startActivity(sendIntent); - return; - } - browserActivity.fetchFileAndExport(weChatInfo, sendIntent, repoName, repoID, path, fileSize); + //TODO +// BrowserActivity browserActivity = ((BrowserActivity) activity); +// String repoName = ((BrowserActivity) activity).getNavContext().getRepoName(); +// String dirPath = ((BrowserActivity) activity).getNavContext().getDirPath(); +// +// String fullPath = Utils.pathJoin(dirPath, fileName); +// final File file = browserActivity.getDataManager().getLocalRepoFile(repoName, repoID, fullPath); +// Uri uri = null; +// if (android.os.Build.VERSION.SDK_INT > 23) { +// uri = FileProvider.getUriForFile(activity, BuildConfig.FILE_PROVIDER_AUTHORITIES, file); +// } else { +// uri = Uri.fromFile(file); +// } +// final Intent sendIntent = new Intent(); +// sendIntent.setAction(Intent.ACTION_SEND); +// sendIntent.setType(Utils.getFileMimeType(file)); +// sendIntent.putExtra(Intent.EXTRA_STREAM, uri); +// ResolveInfo weChatInfo = Utils.getWeChatIntent(sendIntent); +// if (weChatInfo == null) { +// activity.showShortToast(activity, R.string.no_app_available); +// return; +// } +// String className = weChatInfo.activityInfo.name; +// String packageName = weChatInfo.activityInfo.packageName; +// sendIntent.setClassName(packageName, className); +// if (!Utils.isNetworkOn() && file.exists()) { +// activity.startActivity(sendIntent); +// return; +// } +// browserActivity.fetchFileAndExport(weChatInfo, sendIntent, repoName, repoID, path, fileSize); } } @@ -222,7 +226,7 @@ public static void showFile(final BaseActivity activity, File file) { * * @param file */ - public static void showFile(final BaseActivity activity, File file, boolean isOpenWith) { + public static void showFile(final Activity activity, File file, boolean isOpenWith) { String name = file.getName(); String suffix = name.substring(name.lastIndexOf('.') + 1).toLowerCase(); @@ -251,7 +255,7 @@ public static void showFile(final BaseActivity activity, File file, boolean isOp open.addFlags(FLAG_ACTIVITY_NEW_TASK); if (Build.VERSION.SDK_INT > 23) { - Uri photoURI = FileProvider.getUriForFile(activity, activity.getApplicationContext().getPackageName(), file); + Uri photoURI = FileProvider.getUriForFile(activity, BuildConfig.FILE_PROVIDER_AUTHORITIES, file); open.setDataAndType(photoURI, mime); open.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } else { @@ -260,7 +264,7 @@ public static void showFile(final BaseActivity activity, File file, boolean isOp if (Build.VERSION.SDK_INT < 30) { if (activity.getPackageManager().resolveActivity(open, 0) == null) { String message = String.format(activity.getString(R.string.op_exception_suitable_app_not_found), mime); - activity.showShortToast(activity, message); + ToastUtils.showLong(message); mime = "*/*"; open.setType(mime); } @@ -307,7 +311,7 @@ private static void showFileForAndroid(final BaseActivity activity, File file, S } public static void showRepo(Context context, String repoID, String repoName, String path, String dirID) { - Intent intent = new Intent(context, BrowserActivity.class); + Intent intent = new Intent(context, MainActivity.class); intent.putExtra("repoID", repoID); intent.putExtra("repoName", repoName); intent.putExtra("path", path); @@ -315,13 +319,6 @@ public static void showRepo(Context context, String repoID, String repoName, Str context.startActivity(intent); } - public static void showStarredRepo(Activity activity, String repoID, String repoName, String path) { - Intent intent = new Intent(activity, BrowserActivity.class); - intent.putExtra("repoID", repoID); - intent.putExtra("repoName", repoName); - intent.putExtra("path", path); - activity.startActivityForResult(intent, 0); - } public static void startMarkdownActivity(Context context, String path) { Intent intent = new Intent(context, MarkdownActivity.class); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/account/AccountDetailActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/account/AccountDetailActivity.java index d16f1cb63..d8c6f3b03 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/account/AccountDetailActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/account/AccountDetailActivity.java @@ -7,10 +7,6 @@ import android.net.NetworkInfo; import android.os.AsyncTask; import android.os.Bundle; -import com.google.android.material.textfield.TextInputLayout; -import androidx.core.app.NavUtils; -import androidx.core.app.TaskStackBuilder; -import androidx.appcompat.widget.Toolbar; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; @@ -27,6 +23,13 @@ import android.widget.RelativeLayout; import android.widget.TextView; +import androidx.appcompat.widget.Toolbar; +import androidx.core.app.NavUtils; +import androidx.core.app.TaskStackBuilder; + +import com.blankj.utilcode.util.NetworkUtils; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeafConnection; import com.seafile.seadroid2.SeafException; @@ -35,7 +38,6 @@ import com.seafile.seadroid2.account.Authenticator; import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.ssl.CertsManager; -import com.seafile.seadroid2.ui.EmailAutoCompleteTextView; import com.seafile.seadroid2.ui.BaseActivity; import com.seafile.seadroid2.ui.dialog.SslConfirmDialog; import com.seafile.seadroid2.util.ConcurrentAsyncTask; @@ -57,18 +59,16 @@ public class AccountDetailActivity extends BaseActivity implements Toolbar.OnMen private Button mLoginBtn; private EditText mServerEt; private ProgressDialog mProgressDialog; - private EmailAutoCompleteTextView mEmailEt; + private TextInputEditText mEmailEt; private EditText mPasswdEt; private CheckBox mHttpsCheckBox; private TextView mSeaHubUrlHintTv; - private ImageView mClearEmailIv, mClearPasswordIv, mEyeClickIv; - private RelativeLayout mEyeContainer; + private ImageView mClearEmailIv; private TextInputLayout mAuthTokenInputLayout; private EditText mAuthTokenEt; private android.accounts.AccountManager mAccountManager; private boolean serverTextHasFocus; - private boolean isPasswordVisible; private CheckBox mRemDeviceCheckBox; private String mSessionKey; @@ -86,14 +86,11 @@ public void onCreate(Bundle savedInstanceState) { mLoginBtn = (Button) findViewById(R.id.login_button); mHttpsCheckBox = (CheckBox) findViewById(R.id.https_checkbox); mServerEt = (EditText) findViewById(R.id.server_url); - mEmailEt = (EmailAutoCompleteTextView) findViewById(R.id.email_address); + mEmailEt = findViewById(R.id.email_address); mPasswdEt = (EditText) findViewById(R.id.password); mSeaHubUrlHintTv = (TextView) findViewById(R.id.seahub_url_hint); mClearEmailIv = (ImageView) findViewById(R.id.iv_delete_email); - mClearPasswordIv = (ImageView) findViewById(R.id.iv_delete_pwd); - mEyeContainer = (RelativeLayout) findViewById(R.id.rl_layout_eye); - mEyeClickIv = (ImageView) findViewById(R.id.iv_eye_click); mAuthTokenInputLayout = (TextInputLayout) findViewById(R.id.auth_token_hint); mAuthTokenEt = (EditText) findViewById(R.id.auth_token); @@ -125,8 +122,6 @@ public void onCreate(Bundle savedInstanceState) { mEmailEt.setText(email); mEmailEt.requestFocus(); mSeaHubUrlHintTv.setVisibility(View.GONE); - - } else if (defaultServerUri != null) { if (defaultServerUri.startsWith(HTTPS_PREFIX)) mHttpsCheckBox.setChecked(true); @@ -158,17 +153,6 @@ public void onFocusChange(View v, boolean hasFocus) { } }); - mPasswdEt.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus && mPasswdEt.getText().toString().trim().length() > 0) { - mClearPasswordIv.setVisibility(View.VISIBLE); - } else { - mClearPasswordIv.setVisibility(View.INVISIBLE); - } - } - }); - mEmailEt.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { @@ -189,25 +173,6 @@ public void afterTextChanged(Editable s) { }); - mPasswdEt.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - if (mPasswdEt.getText().toString().trim().length() > 0) { - mClearPasswordIv.setVisibility(View.VISIBLE); - } else { - mClearPasswordIv.setVisibility(View.INVISIBLE); - } - } - - @Override - public void afterTextChanged(Editable s) { - } - }); - mClearEmailIv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -215,25 +180,18 @@ public void onClick(View v) { } }); - mClearPasswordIv.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mPasswdEt.setText(""); - } - }); - - mEyeContainer.setOnClickListener(new View.OnClickListener() { + TextInputLayout passwordInputLayout = findViewById(R.id.password_hint); + passwordInputLayout.setEndIconOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (!isPasswordVisible) { - mEyeClickIv.setImageResource(R.drawable.icon_eye_open); + if (mPasswdEt.getTransformationMethod() instanceof PasswordTransformationMethod) { mPasswdEt.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); + passwordInputLayout.setEndIconDrawable(R.drawable.icon_eye_open); } else { - mEyeClickIv.setImageResource(R.drawable.icon_eye_close); + passwordInputLayout.setEndIconDrawable(R.drawable.icon_eye_close); mPasswdEt.setTransformationMethod(PasswordTransformationMethod.getInstance()); } - isPasswordVisible = !isPasswordVisible; - mPasswdEt.postInvalidate(); + String input = mPasswdEt.getText().toString().trim(); if (!TextUtils.isEmpty(input)) { mPasswdEt.setSelection(input.length()); @@ -371,11 +329,8 @@ public void login(View view) { String serverURL = mServerEt.getText().toString().trim(); String email = mEmailEt.getText().toString().trim(); String passwd = mPasswdEt.getText().toString(); - ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); - if (networkInfo == null || !networkInfo.isConnected()) { -// if (!NetworkUtils.isConnected()) { + if (!NetworkUtils.isConnected()) { mStatusTv.setText(R.string.network_down); return; } @@ -423,7 +378,7 @@ public void login(View view) { } mLoginBtn.setEnabled(false); - Account tmpAccount = new Account(null, serverURL, email, null, false, mSessionKey); + Account tmpAccount = new Account(null, serverURL, email, null, null, false, mSessionKey); mProgressDialog = new ProgressDialog(this); mProgressDialog.setMessage(getString(R.string.settings_cuc_loading)); mProgressDialog.setCancelable(false); @@ -508,6 +463,9 @@ public void onRejected() { retData.putExtra(android.accounts.AccountManager.KEY_ACCOUNT_NAME, loginAccount.getSignature()); retData.putExtra(android.accounts.AccountManager.KEY_AUTHTOKEN, loginAccount.getToken()); retData.putExtra(android.accounts.AccountManager.KEY_ACCOUNT_TYPE, getIntent().getStringExtra(SeafileAuthenticatorActivity.ARG_ACCOUNT_TYPE)); + + //extra params + retData.putExtra(SeafileAuthenticatorActivity.ARG_AVATAR_URL, loginAccount.getAvatarUrl()); retData.putExtra(SeafileAuthenticatorActivity.ARG_EMAIL, loginAccount.getEmail()); retData.putExtra(SeafileAuthenticatorActivity.ARG_NAME, loginAccount.getName()); retData.putExtra(SeafileAuthenticatorActivity.ARG_AUTH_SESSION_KEY, loginAccount.getSessionKey()); @@ -537,8 +495,7 @@ private String doLogin() { return "Unknown error"; // replace email address/username given by the user with the address known by the server. -// loginAccount = new Account(loginAccount.server, accountInfo.getEmail(), loginAccount.token, false, loginAccount.sessionKey); - loginAccount = new Account(accountInfo.getName(), loginAccount.server, accountInfo.getEmail(), loginAccount.token, false, loginAccount.sessionKey); + loginAccount = new Account(accountInfo.getName(), loginAccount.server, accountInfo.getEmail(), accountInfo.getAvatarUrl(), loginAccount.token, false, loginAccount.sessionKey); return "Success"; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/account/AccountsActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/account/AccountsActivity.java index 9a4d3df02..2a17b95df 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/account/AccountsActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/account/AccountsActivity.java @@ -7,12 +7,8 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.os.AsyncTask; import android.os.Bundle; import android.os.IBinder; - -import androidx.appcompat.widget.Toolbar; - import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; @@ -27,26 +23,22 @@ import android.widget.Button; import android.widget.ListView; -import com.google.common.collect.Lists; +import androidx.appcompat.widget.Toolbar; + +import com.blankj.utilcode.util.AppUtils; import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeafConnection; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.listener.OnCallback; +import com.seafile.seadroid2.ui.dialog_fragment.PolicyDialogFragment; +import com.seafile.seadroid2.util.sp.SettingsManager; import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.AccountManager; import com.seafile.seadroid2.account.Authenticator; -import com.seafile.seadroid2.avatar.Avatar; -import com.seafile.seadroid2.avatar.AvatarManager; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.config.Constants; import com.seafile.seadroid2.monitor.FileMonitorService; import com.seafile.seadroid2.ui.BaseActivity; -import com.seafile.seadroid2.ui.BrowserActivity; import com.seafile.seadroid2.ui.account.adapter.AccountAdapter; -import com.seafile.seadroid2.ui.account.adapter.SeafAccountAdapter; -import com.seafile.seadroid2.ui.dialog.PolicyDialog; -import com.seafile.seadroid2.util.ConcurrentAsyncTask; -import com.seafile.seadroid2.util.Utils; +import com.seafile.seadroid2.ui.main.MainActivity; -import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -59,8 +51,6 @@ public class AccountsActivity extends BaseActivity implements Toolbar.OnMenuItem private ListView accountsView; private android.accounts.AccountManager mAccountManager; - private AccountManager accountManager; - private AvatarManager avatarManager; private AccountAdapter adapter; private List accounts; private FileMonitorService mMonitorService; @@ -88,6 +78,28 @@ public void onServiceDisconnected(ComponentName className) { }; + private final AccountManagerCallback accountCallback = new AccountManagerCallback() { + + @Override + public void run(AccountManagerFuture future) { + if (future.isCancelled()) + return; + + try { + Bundle b = future.getResult(); + + if (b.getBoolean(android.accounts.AccountManager.KEY_BOOLEAN_RESULT)) { + String accountName = b.getString(android.accounts.AccountManager.KEY_ACCOUNT_NAME); + Log.d(DEBUG_TAG, "switching to account " + accountName); + SupportAccountManager.getInstance().saveCurrentAccount(accountName); + startFilesActivity(); + } + } catch (Exception e) { + Log.e(DEBUG_TAG, "unexpected error: " + e); + } + } + }; + @Override public void onCreate(Bundle savedInstanceState) { Log.d(DEBUG_TAG, "AccountsActivity.onCreate is called"); @@ -95,25 +107,26 @@ public void onCreate(Bundle savedInstanceState) { setContentView(R.layout.start); mAccountManager = android.accounts.AccountManager.get(this); accountsView = (ListView) findViewById(R.id.account_list_view); - accountManager = new AccountManager(this); - avatarManager = new AvatarManager(); - currentDefaultAccount = accountManager.getCurrentAccount(); + + currentDefaultAccount = SupportAccountManager.getInstance().getCurrentAccount(); View footerView = ((LayoutInflater) this .getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate( R.layout.account_list_footer, null, false); + Button addAccount = (Button) footerView.findViewById(R.id.account_footer_btn); addAccount.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View btn) { - mAccountManager.addAccount(Account.ACCOUNT_TYPE, + mAccountManager.addAccount(Constants.Account.ACCOUNT_TYPE, Authenticator.AUTHTOKEN_TYPE, null, null, AccountsActivity.this, accountCallback, null); } }); + accountsView.addFooterView(footerView, null, true); accountsView.setFooterDividersEnabled(false); - adapter = new SeafAccountAdapter(this); + adapter = new AccountAdapter(this); accountsView.setAdapter(adapter); accountsView.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView parent, View view, int position, long id) { @@ -124,7 +137,7 @@ public void onItemClick(AdapterView parent, View view, int position, long id) startEditAccountActivity(account); } else { // update current Account info from SharedPreference - accountManager.saveCurrentAccount(account.getSignature()); + SupportAccountManager.getInstance().saveCurrentAccount(account.getSignature()); startFilesActivity(); } } @@ -138,7 +151,7 @@ public void onItemClick(AdapterView parent, View view, int position, long id) toolbar.setOnMenuItemClickListener(this); setSupportActionBar(toolbar); - accounts = accountManager.getAccountList(); + accounts = SupportAccountManager.getInstance().getAccountList(); // updates toolbar back button if (currentDefaultAccount == null || !currentDefaultAccount.hasValidToken()) { getSupportActionBar().setDisplayHomeAsUpEnabled(false); @@ -150,7 +163,7 @@ public void onItemClick(AdapterView parent, View view, int position, long id) String country = Locale.getDefault().getCountry(); String language = Locale.getDefault().getLanguage(); - int privacyPolicyConfirmed = SettingsManager.instance().getPrivacyPolicyConfirmed(); + int privacyPolicyConfirmed = SettingsManager.getInstance().getPrivacyPolicyConfirmed(); if (country.equals("CN") && language.equals("zh") && (privacyPolicyConfirmed == 0)) { showDialog(); } @@ -202,8 +215,8 @@ public boolean onOptionsItemSelected(MenuItem item) { case android.R.id.home: // if the current account sign out and no account was to logged in, // then always goes to AccountsActivity - if (accountManager.getCurrentAccount() == null) { - Intent intent = new Intent(this, BrowserActivity.class); + if (SupportAccountManager.getInstance().getCurrentAccount() == null) { + Intent intent = new Intent(this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } @@ -216,13 +229,13 @@ public boolean onOptionsItemSelected(MenuItem item) { private void refreshView() { Log.d(DEBUG_TAG, "refreshView"); - accounts = accountManager.getAccountList(); + accounts = SupportAccountManager.getInstance().getAccountList(); adapter.clear(); adapter.setItems(accounts); // if the user switched default account while we were in background, // switch to BrowserActivity - Account newCurrentAccount = accountManager.getCurrentAccount(); + Account newCurrentAccount = SupportAccountManager.getInstance().getCurrentAccount(); if (newCurrentAccount != null && !newCurrentAccount.equals(currentDefaultAccount)) { startFilesActivity(); } @@ -232,8 +245,6 @@ private void refreshView() { getSupportActionBar().setDisplayHomeAsUpEnabled(false); } - loadAvatarUrls(160); - adapter.notifyChanged(); } @@ -241,7 +252,7 @@ private void startFilesActivity() { removeAllCookie(); - Intent intent = new Intent(this, BrowserActivity.class); + Intent intent = new Intent(this, MainActivity.class); // first finish this activity, so the BrowserActivity is again "on top" intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); @@ -259,27 +270,6 @@ public void onReceiveValue(Boolean value) { }); } - private final AccountManagerCallback accountCallback = new AccountManagerCallback() { - - @Override - public void run(AccountManagerFuture future) { - if (future.isCancelled()) - return; - - try { - Bundle b = future.getResult(); - - if (b.getBoolean(android.accounts.AccountManager.KEY_BOOLEAN_RESULT)) { - String accountName = b.getString(android.accounts.AccountManager.KEY_ACCOUNT_NAME); - Log.d(DEBUG_TAG, "switching to account " + accountName); - accountManager.saveCurrentAccount(accountName); - startFilesActivity(); - } - } catch (Exception e) { - Log.e(DEBUG_TAG, "unexpected error: " + e); - } - } - }; private void startEditAccountActivity(Account account) { mAccountManager.updateCredentials(account.getAndroidAccount(), Authenticator.AUTHTOKEN_TYPE, null, this, accountCallback, null); @@ -332,10 +322,10 @@ public boolean onContextItemSelected(android.view.MenuItem item) { @Override public void onBackPressed() { - Account account = accountManager.getCurrentAccount(); + Account account = SupportAccountManager.getInstance().getCurrentAccount(); if (account != null) { // force exit when current account was deleted - Intent i = new Intent(this, BrowserActivity.class); + Intent i = new Intent(this, MainActivity.class); i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(i); finish(); @@ -343,120 +333,20 @@ public void onBackPressed() { super.onBackPressed(); } - /** - * asynchronously load avatars - * - * @param avatarSize set a avatar size in one of 24*24, 32*32, 48*48, 64*64, 72*72, 96*96 - */ - public void loadAvatarUrls(int avatarSize) { - List avatars; - - if (!Utils.isNetworkOn() || !avatarManager.isNeedToLoadNewAvatars()) { - // Toast.makeText(AccountsActivity.this, getString(R.string.network_down), Toast.LENGTH_SHORT).show(); - - // use cached avatars - avatars = avatarManager.getAvatarList(); - - if (avatars == null) { - return; - } - - // set avatars url to adapter - adapter.setAvatars((ArrayList) avatars); - - // notify adapter data changed - adapter.notifyDataSetChanged(); - - return; - } - - LoadAvatarUrlsTask task = new LoadAvatarUrlsTask(avatarSize); - - ConcurrentAsyncTask.execute(task); - - } - - private class LoadAvatarUrlsTask extends AsyncTask> { - - private List avatars; - private int avatarSize; - private SeafConnection httpConnection; - - public LoadAvatarUrlsTask(int avatarSize) { - this.avatarSize = avatarSize; - this.avatars = Lists.newArrayList(); - } - - @Override - protected List doInBackground(Void... params) { - // reuse cached avatars - avatars = avatarManager.getAvatarList(); - - // contains accounts who don`t have avatars yet - List acts = avatarManager.getAccountsWithoutAvatars(); - - // contains new avatars in order to persist them to database - List newAvatars = new ArrayList(acts.size()); - - // load avatars from server - for (Account account : acts) { - httpConnection = new SeafConnection(account); - - String avatarRawData = null; - try { - avatarRawData = httpConnection.getAvatar(account.getEmail(), avatarSize); - } catch (SeafException e) { - e.printStackTrace(); - return avatars; - } - - Avatar avatar = avatarManager.parseAvatar(avatarRawData); - if (avatar == null) - continue; - - avatar.setSignature(account.getSignature()); - - avatars.add(avatar); - newAvatars.add(avatar); + private void showDialog() { + PolicyDialogFragment dialogFragment = new PolicyDialogFragment(); + dialogFragment.setOnCallback(new OnCallback() { + @Override + public void onFailed() { + AppUtils.exitApp(); } - // save new added avatars to database - avatarManager.saveAvatarList(newAvatars); - - return avatars; - } - - @Override - protected void onPostExecute(List avatars) { - if (avatars == null) { - return; + @Override + public void onSuccess() { + SettingsManager.getInstance().savePrivacyPolicyConfirmed(1); } - - // set avatars url to adapter - adapter.setAvatars((ArrayList) avatars); - - // notify adapter data changed - adapter.notifyDataSetChanged(); - } - } - - private void showDialog() { - PolicyDialog mDialog = new PolicyDialog(AccountsActivity.this, R.style.PolicyDialog, - new PolicyDialog.OnCloseListener() { - @Override - public void onClick(boolean confirm) { - if (confirm) { - // TODO: - SettingsManager.instance().savePrivacyPolicyConfirmed(1); - } else { - // TODO: - System.exit(0); - } - } - }); - mDialog.show(); - mDialog.setCancelable(false); - + }); + dialogFragment.show(getSupportFragmentManager(),PolicyDialogFragment.class.getSimpleName()); } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/account/SeafileAuthenticatorActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/account/SeafileAuthenticatorActivity.java index db5386122..0e168f7e2 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/account/SeafileAuthenticatorActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/account/SeafileAuthenticatorActivity.java @@ -2,25 +2,33 @@ import android.accounts.Account; import android.accounts.AccountManager; +import android.app.Activity; import android.content.ContentResolver; import android.content.Intent; +import android.content.pm.PackageManager; import android.os.Bundle; -import androidx.appcompat.widget.Toolbar; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; +import androidx.core.app.NavUtils; +import androidx.core.app.TaskStackBuilder; + import com.seafile.seadroid2.R; import com.seafile.seadroid2.account.Authenticator; -import com.seafile.seadroid2.cameraupload.CameraUploadManager; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.ui.camera_upload.CameraUploadManager; +import com.seafile.seadroid2.config.Constants; /** * The Authenticator activity. - * + *

* Called by the Authenticator and in charge of identifing the user. - * + *

* It sends back to the Authenticator the result. */ public class SeafileAuthenticatorActivity extends BaseAuthenticatorActivity { @@ -34,6 +42,7 @@ public class SeafileAuthenticatorActivity extends BaseAuthenticatorActivity { public final static String ARG_SERVER_URI = "SERVER_URI"; public final static String ARG_EDIT_OLD_ACCOUNT_NAME = "EDIT_OLD_ACCOUNT"; public final static String ARG_EMAIL = "EMAIL"; + public final static String ARG_AVATAR_URL = "AVATAR_URL"; public final static String ARG_NAME = "NAME"; public final static String ARG_SHIB = "SHIB"; public final static String ARG_AUTH_SESSION_KEY = "TWO_FACTOR_AUTH"; @@ -43,8 +52,6 @@ public class SeafileAuthenticatorActivity extends BaseAuthenticatorActivity { private final String DEBUG_TAG = this.getClass().getSimpleName(); - private AccountManager mAccountManager; - /** * Called when the activity is first created. */ @@ -61,7 +68,7 @@ public void onCreate(Bundle savedInstanceState) { strArray[i + 1] = array[i]; } ArrayAdapter listAdapter = new ArrayAdapter<>(this, R.layout.list_item_authenticator, strArray); - ListView listView = (ListView)findViewById(R.id.account_create_list); + ListView listView = (ListView) findViewById(R.id.account_create_list); listView.setAdapter(listAdapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override @@ -90,15 +97,18 @@ public void onItemClick(AdapterView parent, View view, int position, long id) } }); - mAccountManager = AccountManager.get(getBaseContext()); - if (getIntent().getBooleanExtra(ARG_SHIB, false)) { + Intent intent = new Intent(this, SingleSignOnAuthorizeActivity.class); - android.accounts.Account account = new android.accounts.Account(getIntent().getStringExtra(SeafileAuthenticatorActivity.ARG_ACCOUNT_NAME), com.seafile.seadroid2.account.Account.ACCOUNT_TYPE); - intent.putExtra(SingleSignOnActivity.SINGLE_SIGN_ON_SERVER_URL, mAccountManager.getUserData(account, Authenticator.KEY_SERVER_URI)); + Account account = new Account(getIntent().getStringExtra(SeafileAuthenticatorActivity.ARG_ACCOUNT_NAME), Constants.Account.ACCOUNT_TYPE); + + String serverUrl = SupportAccountManager.getInstance().getUserData(account, Authenticator.KEY_SERVER_URI); + intent.putExtra(SingleSignOnActivity.SINGLE_SIGN_ON_SERVER_URL, serverUrl); intent.putExtras(getIntent().getExtras()); startActivityForResult(intent, SeafileAuthenticatorActivity.REQ_SIGNUP); + } else if (getIntent().getBooleanExtra(ARG_IS_EDITING, false)) { + Intent intent = new Intent(this, AccountDetailActivity.class); intent.putExtras(getIntent().getExtras()); startActivityForResult(intent, SeafileAuthenticatorActivity.REQ_SIGNUP); @@ -116,9 +126,61 @@ public void onClick(View view) { }); } + /** + * This utility method handles Up navigation intents by searching for a parent activity and + * navigating there if defined. When using this for an activity make sure to define both the + * native parentActivity as well as the AppCompat one when supporting API levels less than 16. + * when the activity has a single parent activity. If the activity doesn't have a single parent + * activity then don't define one and this method will use back button functionality. If "Up" + * functionality is still desired for activities without parents then use + * {@code syntheticParentActivity} to define one dynamically. + *

+ * Note: Up navigation intents are represented by a back arrow in the top left of the Toolbar + * in Material Design guidelines. + * + * @param currentActivity Activity in use when navigate Up action occurred. + * @param syntheticParentActivity Parent activity to use when one is not already configured. + */ + public void navigateUpOrBack(Activity currentActivity, Class syntheticParentActivity) { + // Retrieve parent activity from AndroidManifest. + Intent intent = NavUtils.getParentActivityIntent(currentActivity); + + // Synthesize the parent activity when a natural one doesn't exist. + if (intent == null && syntheticParentActivity != null) { + try { + intent = NavUtils.getParentActivityIntent(currentActivity, syntheticParentActivity); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + } + + if (intent == null) { + // No parent defined in manifest. This indicates the activity may be used by + // in multiple flows throughout the app and doesn't have a strict parent. In + // this case the navigation up button should act in the same manner as the + // back button. This will result in users being forwarded back to other + // applications if currentActivity was invoked from another application. + currentActivity.onBackPressed(); + } else { + if (NavUtils.shouldUpRecreateTask(currentActivity, intent)) { + // Need to synthesize a backstack since currentActivity was probably invoked by a + // different app. The preserves the "Up" functionality within the app according to + // the activity hierarchy defined in AndroidManifest.xml via parentActivity + // attributes. + TaskStackBuilder builder = TaskStackBuilder.create(currentActivity); + builder.addNextIntentWithParentStack(intent); + builder.startActivities(); + } else { + // Navigate normally to the manifest defined "Up" activity. + NavUtils.navigateUpTo(currentActivity, intent); + } + } + } + @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); + Log.d(DEBUG_TAG, "onActivityResult"); // The sign up activity returned that the user has successfully created an account @@ -133,15 +195,19 @@ private void finishLogin(Intent intent) { Log.d(DEBUG_TAG, "finishLogin"); String newAccountName = intent.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); - final Account newAccount = new Account(newAccountName, intent.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE)); + String accountType = intent.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE); + String authToken = intent.getStringExtra(AccountManager.KEY_AUTHTOKEN); - String authtoken = intent.getStringExtra(AccountManager.KEY_AUTHTOKEN); - String serveruri = intent.getStringExtra(ARG_SERVER_URI); + String avatarUrl = intent.getStringExtra(ARG_AVATAR_URL); String email = intent.getStringExtra(ARG_EMAIL); String name = intent.getStringExtra(ARG_NAME); String sessionKey = intent.getStringExtra(ARG_AUTH_SESSION_KEY); + String serverUri = intent.getStringExtra(ARG_SERVER_URI); boolean shib = intent.getBooleanExtra(ARG_SHIB, false); + //new account + final Account newAccount = new Account(newAccountName, accountType); + int cameraIsSyncable = 0; boolean cameraSyncAutomatically = true; @@ -153,9 +219,9 @@ private void finishLogin(Intent intent) { // serverUri and mail stay the same. so just update the token and exit if (oldAccount.equals(newAccount)) { - mAccountManager.setAuthToken(newAccount, Authenticator.AUTHTOKEN_TYPE, authtoken); - mAccountManager.setUserData(newAccount, Authenticator.SESSION_KEY, sessionKey); - mAccountManager.setUserData(newAccount, Authenticator.KEY_NAME, name); + SupportAccountManager.getInstance().setAuthToken(newAccount, Authenticator.AUTHTOKEN_TYPE, authToken); + SupportAccountManager.getInstance().setUserData(newAccount, Authenticator.SESSION_KEY, sessionKey); + SupportAccountManager.getInstance().setUserData(newAccount, Authenticator.KEY_NAME, name); Bundle result = new Bundle(); result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); @@ -171,21 +237,28 @@ private void finishLogin(Intent intent) { cameraIsSyncable = ContentResolver.getIsSyncable(oldAccount, CameraUploadManager.AUTHORITY); cameraSyncAutomatically = ContentResolver.getSyncAutomatically(oldAccount, CameraUploadManager.AUTHORITY); - mAccountManager.removeAccount(oldAccount, null, null); + SupportAccountManager.getInstance().removeAccount(oldAccount, null, null); } - Log.d(DEBUG_TAG, "adding new account "+newAccountName); - mAccountManager.addAccountExplicitly(newAccount, null, null); + Log.d(DEBUG_TAG, "adding new account " + newAccountName); + + Bundle bundle = new Bundle(); + bundle.putString(Authenticator.KEY_SERVER_URI, serverUri); + bundle.putString(Authenticator.KEY_EMAIL, email); + bundle.putString(Authenticator.KEY_NAME, name); + bundle.putString(Authenticator.SESSION_KEY, sessionKey); + bundle.putString(Authenticator.KEY_AVATAR_URL, avatarUrl); + + //add account + SupportAccountManager.getInstance().addAccountExplicitly(newAccount, null, bundle); + SupportAccountManager.getInstance().setAuthToken(newAccount, Authenticator.AUTHTOKEN_TYPE, authToken); - mAccountManager.setAuthToken(newAccount, Authenticator.AUTHTOKEN_TYPE, authtoken); - mAccountManager.setUserData(newAccount, Authenticator.KEY_SERVER_URI, serveruri); - mAccountManager.setUserData(newAccount, Authenticator.KEY_EMAIL, email); - mAccountManager.setUserData(newAccount, Authenticator.KEY_NAME, name); - mAccountManager.setUserData(newAccount, Authenticator.SESSION_KEY, sessionKey); if (shib) { - mAccountManager.setUserData(newAccount, Authenticator.KEY_SHIB, "shib"); + SupportAccountManager.getInstance().setUserData(newAccount, Authenticator.KEY_SHIB, "shib"); } + SupportAccountManager.getInstance().saveCurrentAccount(newAccountName); + // set sync settings ContentResolver.setIsSyncable(newAccount, CameraUploadManager.AUTHORITY, cameraIsSyncable); ContentResolver.setSyncAutomatically(newAccount, CameraUploadManager.AUTHORITY, cameraSyncAutomatically); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnActivity.java index fb64efd56..d1c8d255f 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnActivity.java @@ -9,6 +9,7 @@ import android.widget.Button; import android.widget.EditText; +import com.blankj.utilcode.util.ToastUtils; import com.seafile.seadroid2.R; import com.seafile.seadroid2.ui.BaseActivity; @@ -72,12 +73,12 @@ public boolean onOptionsItemSelected(MenuItem item) { private boolean isServerUrlValid(String serverUrl) { if (serverUrl == null || serverUrl.isEmpty()) { - showShortToast(this, getString(R.string.shib_server_url_empty)); + ToastUtils.showLong(R.string.shib_server_url_empty); return false; } if (!serverUrl.startsWith(SINGLE_SIGN_ON_HTTPS_PREFIX)) { - showShortToast(this, getString(R.string.shib_server_incorrect_prefix)); + ToastUtils.showLong(getString(R.string.shib_server_incorrect_prefix)); return false; } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnAuthorizeActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnAuthorizeActivity.java index 950280299..0b621c87d 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnAuthorizeActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/account/SingleSignOnAuthorizeActivity.java @@ -1,5 +1,6 @@ package com.seafile.seadroid2.ui.account; +import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; @@ -11,6 +12,7 @@ import android.os.Bundle; import androidx.appcompat.widget.Toolbar; + import android.util.Log; import android.view.MenuItem; import android.view.View; @@ -21,6 +23,7 @@ import android.webkit.WebViewClient; import android.widget.LinearLayout; +import com.blankj.utilcode.util.ToastUtils; import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeadroidApplication; import com.seafile.seadroid2.SeafException; @@ -54,6 +57,7 @@ public class SingleSignOnAuthorizeActivity extends BaseActivity implements Toolb private LinearLayout mloadingAnimation; public String serverUrl; + @SuppressLint("SetJavaScriptEnabled") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -85,7 +89,7 @@ private void openAuthorizePage(String url) { serverUrl = url; if (!Utils.isNetworkOn()) { - showShortToast(this, getString(R.string.network_down)); + ToastUtils.showLong(R.string.network_down); return; } @@ -161,15 +165,14 @@ public boolean onMenuItemClick(MenuItem item) { private void displaySSLError() { showPageLoading(false); - showShortToast(this, R.string.ssl_error); + ToastUtils.showLong(R.string.ssl_error); } class CustomWebviewClient extends WebViewClient { @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { // Display error messages - showShortToast(SingleSignOnAuthorizeActivity.this, - String.format((R.string.shib_load_page_error) + description)); + ToastUtils.showLong(String.format((R.string.shib_load_page_error) + description)); showPageLoading(false); } @@ -178,7 +181,7 @@ public void onReceivedError(WebView view, int errorCode, String description, Str @Override public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) { Log.d(DEBUG_TAG, "onReceivedSslError " + error.getCertificate().toString()); - final Account account = new Account(serverUrl, null, null, null, false); + final Account account = new Account(serverUrl, null, null, null, null, false); SslCertificate sslCert = error.getCertificate(); X509Certificate savedCert = CertsManager.instance().getCertificate(account); @@ -251,7 +254,7 @@ private Account parseAccount(String url, String cookie) { Log.d(DEBUG_TAG, "email: " + email); Log.d(DEBUG_TAG, "token: " + token); - return new Account(url, email, "", token, true); + return new Account(url, email, "", null, token, true); } private class AccountInfoTask extends AsyncTask { @@ -317,7 +320,7 @@ private String getAccountInfo() { AccountInfo accountInfo = manager.getAccountInfo(); if (accountInfo == null) return "Unknown error"; - loginAccount = new Account(accountInfo.getName(), loginAccount.server, accountInfo.getEmail(), loginAccount.token, loginAccount.is_shib, loginAccount.sessionKey); + loginAccount = new Account(accountInfo.getName(), loginAccount.server, accountInfo.getEmail(), accountInfo.getAvatarUrl(), loginAccount.token, loginAccount.is_shib, loginAccount.sessionKey); return "Success"; } catch (SeafException e) { @@ -325,11 +328,11 @@ private String getAccountInfo() { if (e == SeafException.sslException) { return getString(R.string.ssl_error); } else { - showShortToast(SingleSignOnAuthorizeActivity.this, e.getMessage()); + ToastUtils.showLong(e.getMessage()); return e.getMessage(); } } catch (JSONException e) { - showShortToast(SingleSignOnAuthorizeActivity.this, e.getMessage()); + ToastUtils.showLong(e.getMessage()); return e.getMessage(); } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/account/adapter/AccountAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/account/adapter/AccountAdapter.java index 63c68c6fc..4d94b8261 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/account/adapter/AccountAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/account/adapter/AccountAdapter.java @@ -1,6 +1,7 @@ package com.seafile.seadroid2.ui.account.adapter; import android.content.Context; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -9,8 +10,8 @@ import android.widget.TextView; import com.google.common.collect.Lists; +import com.seafile.seadroid2.R; import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.avatar.Avatar; import com.seafile.seadroid2.config.GlideLoadConfig; import com.seafile.seadroid2.util.GlideApp; @@ -20,20 +21,14 @@ /** * Base account adapter */ -public abstract class AccountAdapter extends BaseAdapter { - private static final String DEBUG_TAG = "AccountAdapter"; +public class AccountAdapter extends BaseAdapter { - // private ImageLoadingListener animateFirstListener = new AnimateFirstDisplayListener(); - -// private DisplayImageOptions options; private ArrayList items; - private ArrayList avatars; private Context context; public AccountAdapter(Context context) { this.context = context; items = Lists.newArrayList(); - avatars = Lists.newArrayList(); } @Override @@ -65,10 +60,6 @@ public void setItems(List items) { } - public void setAvatars(ArrayList avatars) { - this.avatars = avatars; - } - @Override public long getItemId(int position) { return position; @@ -80,34 +71,33 @@ public void clear() { private ViewHolder viewHolder; - protected abstract int getChildLayout(); - - protected abstract int getChildTitleId(); - - protected abstract int getChildSubTitleId(); - - protected abstract int getChildIconId(); - @Override public View getView(int position, View convertView, ViewGroup parent) { View view = convertView; if (convertView == null) { - view = LayoutInflater.from(context).inflate(getChildLayout(), null); - TextView title = (TextView) view.findViewById(getChildTitleId()); - TextView subtitle = (TextView) view.findViewById(getChildSubTitleId()); - ImageView icon = (ImageView) view.findViewById(getChildIconId()); - viewHolder = new ViewHolder(title, subtitle, icon); + view = LayoutInflater.from(context).inflate(R.layout.list_item_account_entry, null); + TextView title = view.findViewById(R.id.list_item_account_title); + TextView subtitle = view.findViewById(R.id.list_item_account_subtitle); + ImageView icon = view.findViewById(R.id.list_item_account_icon); + ImageView selectView = view.findViewById(R.id.item_select_view); + viewHolder = new ViewHolder(title, subtitle, icon, selectView); view.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } + Account account = items.get(position); + viewHolder.selectView.setVisibility(account.is_selected ? View.VISIBLE : View.INVISIBLE); + viewHolder.title.setText(account.getServerHost()); // viewHolder.subtitle.setText(account.getEmail()); viewHolder.subtitle.setText(account.getName()); - if (getAvatarUrl(account) != null) { + + if (TextUtils.isEmpty(account.avatar_url)) { + viewHolder.icon.setImageResource(com.seafile.seadroid2.R.drawable.default_avatar); + } else { GlideApp.with(viewHolder.icon) - .load(GlideLoadConfig.getGlideUrl(getAvatarUrl(account))) + .load(GlideLoadConfig.getGlideUrl(account.avatar_url)) .apply(GlideLoadConfig.getOptions()) .into(viewHolder.icon); } @@ -115,45 +105,17 @@ public View getView(int position, View convertView, ViewGroup parent) { return view; } - private String getAvatarUrl(Account account) { - if (avatars == null) { - return null; - } - for (Avatar avatar : avatars) { - if (avatar.getSignature().equals(account.getSignature())) { - return avatar.getUrl(); - } - } - - return null; - } - -// private static class AnimateFirstDisplayListener extends SimpleImageLoadingListener { -// -// static final List displayedImages = Collections.synchronizedList(new LinkedList()); -// -// @Override -// public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { -// if (loadedImage != null) { -// ImageView imageView = (ImageView) view; -// boolean firstDisplay = !displayedImages.contains(imageUri); -// if (firstDisplay) { -// FadeInBitmapDisplayer.animate(imageView, 500); -// displayedImages.add(imageUri); -// } -// } -// } -// } private static class ViewHolder { TextView title, subtitle; - ImageView icon; + ImageView icon, selectView; - public ViewHolder(TextView title, TextView subtitle, ImageView icon) { + public ViewHolder(TextView title, TextView subtitle, ImageView icon, ImageView selectView) { super(); this.icon = icon; this.title = title; this.subtitle = subtitle; + this.selectView = selectView; } } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/account/adapter/SeafAccountAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/account/adapter/SeafAccountAdapter.java deleted file mode 100644 index 816c25dfd..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/account/adapter/SeafAccountAdapter.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.seafile.seadroid2.ui.account.adapter; - -import android.content.Context; -import com.seafile.seadroid2.R; - -/** - * Adapter for showing account in a list view. - */ -public class SeafAccountAdapter extends AccountAdapter { - - public SeafAccountAdapter(Context context) { - super(context); - } - - @Override - protected int getChildLayout() { - return R.layout.list_item_account_entry; - } - - @Override - protected int getChildTitleId() { - return R.id.list_item_account_title; - } - - @Override - protected int getChildSubTitleId() { - return R.id.list_item_account_subtitle; - } - - @Override - protected int getChildIconId() { - return R.id.list_item_account_icon; - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivitiesFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivitiesFragment.java deleted file mode 100644 index 04c63396f..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivitiesFragment.java +++ /dev/null @@ -1,495 +0,0 @@ -package com.seafile.seadroid2.ui.activities; - -import android.app.Activity; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; - -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; - -import android.text.TextUtils; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; -import android.widget.AbsListView; -import android.widget.AdapterView; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.TextView; - -import com.google.common.collect.Lists; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.AccountManager; -import com.seafile.seadroid2.data.CommitDetails; -import com.seafile.seadroid2.data.EventDetailsFileItem; -import com.seafile.seadroid2.data.EventDetailsTree; -import com.seafile.seadroid2.data.SeafActivities; -import com.seafile.seadroid2.data.SeafDirent; -import com.seafile.seadroid2.data.SeafEvent; -import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.data.ServerInfo; -import com.seafile.seadroid2.context.NavContext; -import com.seafile.seadroid2.ui.BrowserActivity; -import com.seafile.seadroid2.ui.activity.FileActivity; -import com.seafile.seadroid2.ui.adapter.BottomSheetAdapter; -import com.seafile.seadroid2.ui.bottomsheet.BottomSheetListFragment; -import com.seafile.seadroid2.ui.dialog.TaskDialog; -import com.seafile.seadroid2.util.ConcurrentAsyncTask; -import com.seafile.seadroid2.util.Utils; - -import org.json.JSONException; - -import java.util.List; -import java.util.Locale; - -public class ActivitiesFragment extends Fragment { - private static final String DEBUG_TAG = "ActivitiesFragment"; - public static final int REFRESH_ON_NONE = 0; - public static final int REFRESH_ON_PULL_DOWN_SWIPE = 1; - public static final int REFRESH_ON_PULL_DOWN_RESUME = 3; - public static final int REFRESH_ON_PULL_UP = 2; - public static final int VERSIONS_NUMBER = 6; - private static int mRefreshType = REFRESH_ON_NONE; - private boolean useNewActivity = false; - - private BrowserActivity mActivity; - private SwipeRefreshLayout refreshLayout; - private ListView listView; - private ActivitiesItemAdapter adapter; - private ImageView mEmptyView; - private View mProgressContainer; - private View mListContainer; - private TextView mErrorText; - private List events; - private int offset; - private Account account; - private AccountManager accountManager; - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - // Log.d(DEBUG_TAG, "ActivitiesFragment Attached"); - mActivity = (BrowserActivity) getActivity(); - } - - @Override - public void onDetach() { - super.onDetach(); - mActivity = null; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.activities_fragment, container, false); - } - - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - refreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swiperefresh); - listView = (ListView) view.findViewById(R.id.activities_listview); - mEmptyView = (ImageView) view.findViewById(R.id.empty); - mListContainer = view.findViewById(R.id.fl_activities_list_container); - mErrorText = (TextView) view.findViewById(R.id.error_message); - mProgressContainer = view.findViewById(R.id.progressContainer); - - events = Lists.newArrayList(); - } - - @Override - public void onActivityCreated(final Bundle savedInstanceState) { - // Log.d(DEBUG_TAG, "onActivityCreated"); - - refreshLayout.setColorSchemeResources(R.color.fancy_orange); - refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - mRefreshType = REFRESH_ON_PULL_DOWN_SWIPE; - offset = 0; - refreshView(); - } - }); - - adapter = new ActivitiesItemAdapter(mActivity); - listView.setAdapter(adapter); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView adapterView, View view, int position, long l) { - final SeafEvent seafEvent = (SeafEvent) adapterView.getItemAtPosition(position); - if (mActivity == null) return; - - if (TextUtils.isEmpty(seafEvent.getCommit_id()) || TextUtils.equals("null", seafEvent.getCommit_id().toLowerCase(Locale.getDefault()))) { - return; - } - - final String repoId = seafEvent.getRepo_id(); - final String repoName = seafEvent.getRepo_name(); - - if (seafEvent.isRepo_encrypted()) { - final SeafRepo repo = mActivity.getDataManager().getCachedRepoByID(repoId); - - if (repo == null) { - mActivity.showShortToast(mActivity, getString(R.string.repo_not_found)); - return; - } - - if (!mActivity.getDataManager().getRepoPasswordSet(repo.repo_id)) { - String password = mActivity.getDataManager().getRepoPassword(repoId); - mActivity.showPasswordDialog(repoName, repoId, - new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - LoadHistoryChangesTask task = new LoadHistoryChangesTask(seafEvent); - ConcurrentAsyncTask.execute(task); - } - }, password); - return; - } - } - LoadHistoryChangesTask task = new LoadHistoryChangesTask(seafEvent); - ConcurrentAsyncTask.execute(task); - } - }); - - listView.setOnScrollListener(new AbsListView.OnScrollListener() { - @Override - public void onScrollStateChanged(AbsListView view, int i) { - if (adapter == null || adapter.getCount() == 0) { - return; - } - boolean scrollEnd = false; - try { - if (view.getPositionForView(adapter.getFooterView()) == view.getLastVisiblePosition()) - scrollEnd = true; - } catch (Exception e) { - scrollEnd = false; - } - - if (mRefreshType == REFRESH_ON_NONE && scrollEnd && offset > 0) { - refreshView(); - mRefreshType = REFRESH_ON_PULL_UP; - adapter.setFooterViewLoading(true); - } else { - adapter.setFooterViewLoading(false); - } - - adapter.setState(mRefreshType); - } - - @Override - public void onScroll(AbsListView absListView, int i, int i1, int i2) { - } - }); - - mRefreshType = REFRESH_ON_PULL_DOWN_RESUME; - offset = 0; - accountManager = new AccountManager(getActivity()); - refreshView(); - - mActivity.supportInvalidateOptionsMenu(); - - super.onActivityCreated(savedInstanceState); - } - - public void refreshView() { - account = accountManager.getCurrentAccount(); - ServerInfo serverInfo = accountManager.getServerInfo(account); - String server_version = serverInfo.getVersion(); - if (!TextUtils.isEmpty(server_version)) { - String[] arr1 = server_version.split("\\."); - if (arr1 != null && Integer.parseInt(arr1[0]) > VERSIONS_NUMBER) { - useNewActivity = true; - } else { - useNewActivity = false; - } - } - new LoadEventsTask().execute(); - } - - private void showError(int strID) { - showError(mActivity.getResources().getString(strID)); - } - - private void showError(String msg) { - mProgressContainer.setVisibility(View.GONE); - mListContainer.setVisibility(View.GONE); - - adapter.clear(); - adapter.notifyChanged(); - - mErrorText.setText(msg); - mErrorText.setVisibility(View.VISIBLE); - mErrorText.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - refreshView(); - } - }); - } - - public void showLoading(boolean show) { - mErrorText.setVisibility(View.GONE); - if (show) { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_in)); - mListContainer.startAnimation(AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_out)); - - mProgressContainer.setVisibility(View.VISIBLE); - mListContainer.setVisibility(View.INVISIBLE); - } else { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_out)); - mListContainer.startAnimation(AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_in)); - - mProgressContainer.setVisibility(View.GONE); - mListContainer.setVisibility(View.VISIBLE); - } - } - - private void onItemClicked(EventDetailsFileItem fileItem) { - if (fileItem == null) { - return; - } - - if (fileItem.isFileOpenable()) { - openLocalFile(fileItem); - } - } - - private void openLocalFile(EventDetailsFileItem fileItem) { - if (fileItem.isDir()) { - viewRepo(fileItem.getEvent().getRepo_id(), fileItem.getPath()); - } else { - viewFile(fileItem.getEvent().getRepo_id(), fileItem.getPath()); - } - } - - private class LoadEventsTask extends AsyncTask { - SeafException err; - - @Override - protected void onPreExecute() { - if (mRefreshType == REFRESH_ON_PULL_DOWN_RESUME) - showLoading(true); - } - - @Override - protected SeafActivities doInBackground(Void... voids) { - if (mActivity == null) return null; - - try { - // Log.d(DEBUG_TAG, "offset " + offset); - return mActivity.getDataManager().getEvents(offset, useNewActivity); - } catch (SeafException e) { - err = e; - e.printStackTrace(); - return null; - } - } - - @Override - protected void onPostExecute(SeafActivities result) { - if (mActivity == null) - // this occurs if user navigation to another activity - return; - - if (mRefreshType == REFRESH_ON_PULL_DOWN_RESUME) { - showLoading(false); - } else if (mRefreshType == REFRESH_ON_PULL_DOWN_SWIPE) { - refreshLayout.setRefreshing(false); - } - - if (result == null) { - if (err != null) { - if (err == SeafException.remoteWipedException) { - mActivity.completeRemoteWipe(); - } else { -// mActivity.showShortToast(mActivity, err.getMessage()); - showError(R.string.error_when_load_activities); - } - } - return; - } - - if (mRefreshType == REFRESH_ON_PULL_DOWN_SWIPE) { - events = result.getEvents(); - if (events.isEmpty()) { - listView.setVisibility(View.GONE); - mEmptyView.setVisibility(View.VISIBLE); - } else { - listView.setVisibility(View.VISIBLE); - mEmptyView.setVisibility(View.GONE); - } - } else { - if (offset == result.getOffset()) { - // duplicate data - // Log.d(DEBUG_TAG, "duplicate data " + offset); - return; - } - - // Log.d(DEBUG_TAG, "return offset " + offset); - if (result.getEvents() != null) { - events.addAll(result.getEvents()); - } - } - - mRefreshType = REFRESH_ON_NONE; - - offset = result.getOffset(); - if (!result.isMore()) { - mActivity.showShortToast(mActivity, getString(R.string.no_more_activities)); - return; - } - - adapter.setState(mRefreshType); - adapter.setItems(events, useNewActivity); - adapter.notifyDataSetChanged(); - } - } - - private class LoadHistoryChangesTask extends AsyncTask { - private SeafException err; - private SeafEvent event; - - public LoadHistoryChangesTask(SeafEvent event) { - this.event = event; - } - - @Override - protected CommitDetails doInBackground(String... params) { - try { - - final String ret = mActivity.getDataManager().getHistoryChanges(event.getRepo_id(), event.getCommit_id()); - return CommitDetails.fromJson(ret); - } catch (SeafException e) { - err = e; - e.printStackTrace(); - return null; - } catch (JSONException e) { - e.printStackTrace(); - return null; - } - } - - @Override - protected void onPostExecute(CommitDetails ret) { - super.onPostExecute(ret); - - if (ret == null) { - if (err != null) { - Log.e(DEBUG_TAG, err.getCode() + err.getMessage()); - mActivity.showShortToast(mActivity, err.getMessage()); - } - return; - } - - final EventDetailsTree tree = new EventDetailsTree(event); - final List items = tree.setCommitDetails(ret); - - showChangesDialog2(items); - } - } - - private void showChangesDialog2(final List items) { - BottomSheetListFragment sheetFragment = new BottomSheetListFragment(); - sheetFragment.setOnItemClickListener((parent, view, position, id) -> { - final EventDetailsFileItem fileItem = items.get(position); - onItemClicked(fileItem); - }); - - BottomSheetAdapter adapter = new BottomSheetAdapter(mActivity, items); - sheetFragment.setAdapter(adapter); - sheetFragment.show(getChildFragmentManager(), BottomSheetListFragment.class.getSimpleName()); - } - - private void viewRepo(final String repoID, final String path) { - final SeafRepo repo = mActivity.getDataManager().getCachedRepoByID(repoID); - - if (repo == null) { - mActivity.showShortToast(mActivity, getString(R.string.repo_not_found)); - return; - } - - if (repo.encrypted && !mActivity.getDataManager().getRepoPasswordSet(repo.repo_id)) { - String password = mActivity.getDataManager().getRepoPassword(repo.repo_id); - mActivity.showPasswordDialog(repo.repo_name, repo.repo_id, - new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - switchTab(repoID, repo.getRepoName(), path); - } - }, password); - - switchTab(repoID, repo.getRepoName(), path); - } - } - - private void viewFile(final String repoID, final String path) { - final SeafRepo repo = mActivity.getDataManager().getCachedRepoByID(repoID); - - if (repo == null) { - mActivity.showShortToast(mActivity, R.string.library_not_found); - return; - } - - if (repo.encrypted && !mActivity.getDataManager().getRepoPasswordSet(repo.repo_id)) { - String password = mActivity.getDataManager().getRepoPassword(repo.repo_id); - mActivity.showPasswordDialog(repo.repo_name, repo.repo_id, - new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - openFile(repoID, repo.getRepoName(), path); - } - }, password); - - } else { - openFile(repoID, repo.getRepoName(), path); - } - } - - private void switchTab(String repoID, String repoName, String path) { - NavContext nav = mActivity.getNavContext(); - nav.setRepoID(repoID); - nav.setRepoName(repoName); - if (!path.startsWith("/")) - path = "/" + path; - - if (!path.endsWith("/")) - path = path + "/"; - - path = Utils.getParentPath(path); - - nav.setDirPath(path); - - // switch to LIBRARY TAB - mActivity.setCurrentPosition(BrowserActivity.INDEX_LIBRARY_TAB); - } - - private void openFile(String repoID, String repoName, String filePath) { - // Log.d(DEBUG_TAG, "open file " + repoName + filePath); - final String parentPath = Utils.getParentPath(filePath); - final List cachedDirents = mActivity.getDataManager().getCachedDirents(repoID, parentPath); - long fileSize = -1L; - if (cachedDirents != null) { - for (SeafDirent seafDirent : cachedDirents) { - if (seafDirent.name.equals(filePath)) { - fileSize = seafDirent.size; - } - } - } - - // Log.d(DEBUG_TAG, "open file " + repoName + filePath); - int taskID = mActivity.getTransferService().addDownloadTask(mActivity.getAccount(), repoName, repoID, filePath, fileSize); - Intent intent = new Intent(getActivity(), FileActivity.class); - intent.putExtra("repoName", repoName); - intent.putExtra("repoID", repoID); - intent.putExtra("filePath", filePath); - intent.putExtra("account", mActivity.getAccount()); - intent.putExtra("taskID", taskID); - mActivity.startActivityForResult(intent, BrowserActivity.DOWNLOAD_FILE_REQUEST); - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivitiesItemAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivitiesItemAdapter.java deleted file mode 100644 index 0d281fe67..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivitiesItemAdapter.java +++ /dev/null @@ -1,242 +0,0 @@ -package com.seafile.seadroid2.ui.activities; - -import androidx.annotation.NonNull; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import com.google.common.collect.Lists; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.config.GlideLoadConfig; -import com.seafile.seadroid2.data.SeafEvent; -import com.seafile.seadroid2.data.SeafItem; -import com.seafile.seadroid2.ui.BrowserActivity; -import com.seafile.seadroid2.view.CircleImageView; -import com.seafile.seadroid2.util.GlideApp; -import com.seafile.seadroid2.util.SystemSwitchUtils; -import com.seafile.seadroid2.util.Utils; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Adapter for Activities tab - */ -public class ActivitiesItemAdapter extends BaseAdapter { - public static final String DEBUG_TAG = ActivitiesItemAdapter.class.getSimpleName(); - - public static final int REFRESH_ON_NONE = 0; - public static final int REFRESH_ON_PULL_DOWN = 1; - public static final int REFRESH_ON_PULL_UP = 2; - private int state = REFRESH_ON_NONE; - - private ArrayList items; - private BrowserActivity mActivity; - - private boolean useNewActivity; - - public ActivitiesItemAdapter(BrowserActivity activity) { - this.mActivity = activity; - items = Lists.newArrayList(); - } - - @Override - public int getCount() { - return items.size() + 1; - } - - public void clear() { - items.clear(); - } - - public void add(SeafEvent entry) { - items.add(entry); - } - - public void notifyChanged() { - notifyDataSetChanged(); - } - - @Override - public SeafItem getItem(int position) { - return items.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - public void setItems(List events, boolean useNewActivity) { - this.useNewActivity = useNewActivity; - items.clear(); - items.addAll(events); - } - - private LinearLayout mFooterView; - - public void setFooterViewLoading(boolean more) { - ProgressBar progress = (ProgressBar) mFooterView.findViewById(R.id.progressbar); - TextView text = (TextView) mFooterView.findViewById(R.id.text); - if (more) { - mFooterView.setVisibility(View.VISIBLE); - progress.setVisibility(View.VISIBLE); - text.setVisibility(View.VISIBLE); - } else { - progress.setVisibility(View.GONE); - mFooterView.setVisibility(View.GONE); - text.setVisibility(View.GONE); - } - } - - public void setState(int state) { - this.state = state; - } - - public View getFooterView() { - return this.mFooterView; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - if (position == getCount() - 1) { - this.mFooterView = (LinearLayout) LayoutInflater.from(parent.getContext()).inflate(R.layout.footer_load_more, null); - switch (state) { - case REFRESH_ON_NONE: - case REFRESH_ON_PULL_DOWN: - setFooterViewLoading(false); - break; - case REFRESH_ON_PULL_UP: - setFooterViewLoading(true); - break; - } - return mFooterView; - } - if (position < 0) { - position = 0; - } - - final SeafEvent item = items.get(position); - View view = convertView; - // TODO optimize by setting tags - final ViewHolder viewHolder; - - view = LayoutInflater.from(mActivity).inflate(R.layout.list_item_activities, null); - RelativeLayout rl_old = view.findViewById(R.id.rl_activities_old); - - TextView title = view.findViewById(R.id.tv_activities_mod_desc); - TextView nick = view.findViewById(R.id.tv_activities_nick); - TextView date = view.findViewById(R.id.tv_activities_date); - TextView repoName = view.findViewById(R.id.tv_activities_repo_name); - CircleImageView icon = view.findViewById(R.id.iv_activities_avatar); - - View rl_new = view.findViewById(R.id.rl_activities_new); - CircleImageView icon_url = view.findViewById(R.id.iv_activities_avatar_url); - TextView tv_name = view.findViewById(R.id.tv_activities_name); - TextView tv_state = view.findViewById(R.id.tv_activities_state); - TextView tv_desc = view.findViewById(R.id.tv_activities_desc); - TextView tv_time = view.findViewById(R.id.tv_activities_time); - TextView tv_mod = view.findViewById(R.id.tv_activities_mod); - viewHolder = new ViewHolder(title, nick, date, repoName, icon, tv_name, tv_state, tv_desc, tv_time, tv_mod, icon_url, rl_old, rl_new); - view.setTag(viewHolder); - if (useNewActivity) { - rl_old.setVisibility(View.GONE); - rl_new.setVisibility(View.VISIBLE); - item.setAvatar(item.getAvatar_url()); - viewHolder.tv_name.setText(item.getAuthor_name()); - viewHolder.tv_time.setText(SystemSwitchUtils.parseDateTime(item.getV_time())); - viewHolder.tv_mod.setText(item.getRepo_name()); - viewHolder.tv_desc.setText(item.getPath()); - viewHolder.tv_state.setText(SystemSwitchUtils.obj_type(mActivity, item.getObj_type(), item.getOp_type())); - // - GlideApp.with(viewHolder.icon_url) - .load(GlideLoadConfig.getGlideUrl(item.getAvatar_url())) - .apply(GlideLoadConfig.getDefaultAvatarOptions()) - .into(viewHolder.icon_url); - - } else { - rl_old.setVisibility(View.VISIBLE); - rl_new.setVisibility(View.GONE); - } - - if (!TextUtils.isEmpty(item.getAvatar())) { - final String avatar = parseAvatar(item.getAvatar()); - - GlideApp.with(viewHolder.icon) - .load(GlideLoadConfig.getGlideUrl(avatar)) - .apply(GlideLoadConfig.getDefaultAvatarOptions()) - .into(viewHolder.icon); - } - - viewHolder.title.setText(item.getDesc()); - viewHolder.nick.setText(item.getNick()); - - if (!TextUtils.isEmpty(item.getTime_relative())) { - final String relative = parseRelativeTime(item.getTime_relative()); - viewHolder.date.setText(relative); - viewHolder.date.setVisibility(View.VISIBLE); - } else { - viewHolder.date.setVisibility(View.GONE); - } - viewHolder.repoName.setText(item.getRepo_name()); - return view; - } - - private String parseAvatar(@NonNull String avatar) { - // - String re1 = ".*?"; // Non-greedy match on filler - String re2 = "(src)"; // Variable Name 1 - String re3 = ".*?"; // Non-greedy match on filler - String re4 = "((?:\\/[\\w\\.\\-]+)+)"; // Unix Path 1 - - Pattern p = Pattern.compile(re1 + re2 + re3 + re4, Pattern.CASE_INSENSITIVE | Pattern.DOTALL); - Matcher m = p.matcher(avatar); - if (m.find()) { - String avatarPath = m.group(2); - return Utils.pathJoin(mActivity.getAccount().getServer(), avatarPath); - } else return avatar; - } - - private String parseRelativeTime(@NonNull String relativeTime) { - String regex = "(<[^>]+>)"; - final String[] split = relativeTime.split(regex); - if (split.length > 1) { - return split[1]; - } else return relativeTime; - } - - private static class ViewHolder { - TextView title, nick, date, repoName; - ImageView icon, icon_url; - TextView tv_name, tv_state, tv_desc, tv_mod, tv_time; - View rl_old, rl_new; - - public ViewHolder(TextView title, TextView nick, TextView date, TextView repoName, ImageView icon, TextView tv_name, - TextView tv_state, TextView tv_desc, TextView tv_time, TextView tv_mod, ImageView icon_url, RelativeLayout rl_old, View rl_new) { - super(); - this.icon = icon; - this.title = title; - this.nick = nick; - this.date = date; - this.repoName = repoName; - this.icon_url = icon_url; - this.tv_name = tv_name; - this.tv_state = tv_state; - this.tv_desc = tv_desc; - this.tv_time = tv_time; - this.tv_mod = tv_mod; - this.rl_old = rl_old; - this.rl_new = rl_new; - } - } - -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivityAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivityAdapter.java new file mode 100644 index 000000000..c5d2c1d54 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivityAdapter.java @@ -0,0 +1,103 @@ +package com.seafile.seadroid2.ui.activities; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.RecyclerView; + +import com.blankj.utilcode.util.SpanUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.adapter.BaseMultiAdapter; +import com.seafile.seadroid2.ui.viewholder.GroupItemViewHolder; +import com.seafile.seadroid2.config.AbsLayoutItemType; +import com.seafile.seadroid2.config.GlideLoadConfig; +import com.seafile.seadroid2.data.model.BaseModel; +import com.seafile.seadroid2.data.model.GroupItemModel; +import com.seafile.seadroid2.data.model.activities.ActivityModel; +import com.seafile.seadroid2.databinding.ItemActivityBinding; +import com.seafile.seadroid2.databinding.ItemGroupItemBinding; +import com.seafile.seadroid2.util.GlideApp; +import com.seafile.seadroid2.util.SystemSwitchUtils; + +import java.util.List; + +public class ActivityAdapter extends BaseMultiAdapter { + + public ActivityAdapter() { + addItemType(AbsLayoutItemType.GROUP_ITEM, new OnMultiItem() { + @NonNull + @Override + public GroupItemViewHolder onCreate(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) { + ItemGroupItemBinding binding = ItemGroupItemBinding.inflate(LayoutInflater.from(context), viewGroup, false); + return new GroupItemViewHolder(binding); + } + + @Override + public void onBind(@NonNull RecyclerView.ViewHolder viewHolder, int i, @Nullable BaseModel baseModel) { + + } + }).addItemType(AbsLayoutItemType.ACTIVITY, new OnMultiItem() { + @NonNull + @Override + public ActivityViewHolder onCreate(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) { + ItemActivityBinding binding = ItemActivityBinding.inflate(LayoutInflater.from(context), viewGroup, false); + return new ActivityViewHolder(binding); + } + + @Override + public void onBind(@NonNull ActivityViewHolder holder, int i, @Nullable BaseModel activityModel) { + onBindActivity(holder, (ActivityModel) activityModel); + } + }).onItemViewType(new OnItemViewTypeListener() { + @Override + public int onItemViewType(int i, @NonNull List list) { + if (list.get(i) instanceof ActivityModel) { + return AbsLayoutItemType.ACTIVITY; + } else if (list.get(i) instanceof GroupItemModel) { + return AbsLayoutItemType.GROUP_ITEM; + } + return AbsLayoutItemType.UNSUPPORTED; + } + }); + } + + private void onBindActivity(ActivityViewHolder holder, ActivityModel model) { + holder.binding.itemNickName.setText(model.author_name); + holder.binding.itemTime.setText(model.getTime()); + + String desc = SystemSwitchUtils.obj_type(getContext(), model.obj_type, model.op_type); + holder.binding.itemDesc.setText(desc); + + if (model.obj_type.equals("repo")) { + holder.binding.itemRepoName.setText(""); + holder.binding.itemDetail.setText(model.repo_name); + } else { + holder.binding.itemRepoName.setText(model.repo_name); + + if (model.op_type.equals("rename")) { + SpanUtils.with(holder.binding.itemDetail) + .append(model.old_name) + .append(" => ") + .append(model.name) + .setForegroundColor(ContextCompat.getColor(getContext(), R.color.fancy_orange)) + .create(); + + } else if (model.op_type.equals("delete")) { + holder.binding.itemDetail.setText(model.name); + holder.binding.itemDetail.setTextColor(ContextCompat.getColor(getContext(), R.color.dark_gray)); + } else { + holder.binding.itemDetail.setText(model.name); + holder.binding.itemDetail.setTextColor(ContextCompat.getColor(getContext(), R.color.fancy_orange)); + } + } + + GlideApp.with(getContext()) + .load(GlideLoadConfig.getGlideUrl(model.avatar_url)) + .apply(GlideLoadConfig.getOptions()) + .into(holder.binding.itemAvatar); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivityContainerFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivityContainerFragment.java new file mode 100644 index 000000000..c0a8c440c --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivityContainerFragment.java @@ -0,0 +1,69 @@ +package com.seafile.seadroid2.ui.activities; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.fragment.BaseFragment; +import com.seafile.seadroid2.databinding.FragmentActivityBinding; +import com.seafile.seadroid2.ui.adapter.ViewPagerAdapter; + +public class ActivityContainerFragment extends BaseFragment { + + private FragmentActivityBinding binding; + + public static ActivityContainerFragment newInstance() { + + Bundle args = new Bundle(); + + ActivityContainerFragment fragment = new ActivityContainerFragment(); + fragment.setArguments(args); + return fragment; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = FragmentActivityBinding.inflate(getLayoutInflater()); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + initTabLayout(); + + initViewPager(); + } + + private void initTabLayout() { + binding.tabs.setTabIndicatorAnimationMode(TabLayout.INDICATOR_ANIMATION_MODE_ELASTIC); + binding.tabs.setSelectedTabIndicator(R.drawable.cat_tabs_rounded_line_indicator); + binding.tabs.setTabIndicatorFullWidth(false); + binding.tabs.setTabGravity(TabLayout.GRAVITY_START); + } + + private void initViewPager() { + ViewPagerAdapter adapter = new ViewPagerAdapter(getChildFragmentManager(), getLifecycle()); + adapter.addFragment(AllActivitiesFragment.newInstance()); + adapter.addFragment(MineActivitiesFragment.newInstance()); + binding.viewPager.setAdapter(adapter); + + String[] tabArray = getResources().getStringArray(R.array.activity_fragment_titles); + for (String s : tabArray) { + new TabLayoutMediator(binding.tabs, binding.viewPager, new TabLayoutMediator.TabConfigurationStrategy() { + @Override + public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) { + tab.setText(s); + } + }).attach(); + } + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivityViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivityViewHolder.java new file mode 100644 index 000000000..8c0b4af49 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivityViewHolder.java @@ -0,0 +1,15 @@ +package com.seafile.seadroid2.ui.activities; + +import androidx.annotation.NonNull; + +import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; +import com.seafile.seadroid2.databinding.ItemActivityBinding; + +public class ActivityViewHolder extends BaseViewHolder { + public ItemActivityBinding binding; + + public ActivityViewHolder(@NonNull ItemActivityBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivityViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivityViewModel.java new file mode 100644 index 000000000..9708f9306 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/activities/ActivityViewModel.java @@ -0,0 +1,103 @@ +package com.seafile.seadroid2.ui.activities; + +import androidx.lifecycle.MutableLiveData; + +import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.ToastUtils; +import com.seafile.seadroid2.SeafException; +import com.seafile.seadroid2.data.db.AppDatabase; +import com.seafile.seadroid2.data.db.entities.RepoModel; +import com.seafile.seadroid2.data.remote.api.ActivityService; +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.enums.OpType; +import com.seafile.seadroid2.io.http.IO; +import com.seafile.seadroid2.data.model.activities.ActivityModel; +import com.seafile.seadroid2.data.model.activities.ActivityWrapperModel; +import com.seafile.seadroid2.util.SLogs; + +import java.util.List; + +import io.reactivex.Single; +import io.reactivex.functions.Consumer; +import kotlin.Pair; + +public class ActivityViewModel extends BaseViewModel { + private final MutableLiveData> listLiveData = new MutableLiveData<>(); + + public MutableLiveData> getListLiveData() { + return listLiveData; + } + + public void getRepoModelFromLocal(String repoId, Consumer consumer) { + Single> singleDb = AppDatabase.getInstance().repoDao().getRepoById(repoId); + addSingleDisposable(singleDb, new Consumer>() { + @Override + public void accept(List repoModels) throws Exception { + if (consumer != null) { + if (CollectionUtils.isEmpty(repoModels)) { + //no data in sqlite + } else { + consumer.accept(repoModels.get(0)); + } + } + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + SLogs.e(throwable); + } + }); + } + + public void loadAllData(int page) { + getRefreshLiveData().setValue(true); + Single flowable = IO.getSingleton().execute(ActivityService.class).getActivities(page); + addSingleDisposable(flowable, new Consumer() { + @Override + public void accept(ActivityWrapperModel wrapperModel) throws Exception { + getRefreshLiveData().setValue(false); + + if (wrapperModel == null) { + return; + } + for (ActivityModel event : wrapperModel.events) { + switch (event.op_type) { + case "create": + event.opType = OpType.CREATE; + break; + case "edit": + event.opType = OpType.EDIT; + break; + case "rename": + event.opType = OpType.RENAME; + break; + case "delete": + event.opType = OpType.DELETE; + break; + case "restore": + event.opType = OpType.RESTORE; + break; + case "move": + event.opType = OpType.MOVE; + break; + case "update": + event.opType = OpType.UPDATE; + break; + case "public": + event.opType = OpType.PUBLISH; + break; + } + } + getListLiveData().setValue(wrapperModel.events); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getRefreshLiveData().setValue(false); + getExceptionLiveData().setValue(new Pair<>(400, SeafException.networkException)); + String msg = getErrorMsgByThrowable(throwable); + ToastUtils.showLong(msg); + } + }); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activities/AllActivitiesFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/activities/AllActivitiesFragment.java new file mode 100644 index 000000000..2344ec5ba --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/activities/AllActivitiesFragment.java @@ -0,0 +1,214 @@ +package com.seafile.seadroid2.ui.activities; + +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.blankj.utilcode.util.ToastUtils; +import com.chad.library.adapter4.QuickAdapterHelper; +import com.chad.library.adapter4.loadState.LoadState; +import com.chad.library.adapter4.loadState.trailing.TrailingLoadStateAdapter; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.ui.base.adapter.CustomLoadMoreAdapter; +import com.seafile.seadroid2.ui.base.fragment.BaseFragmentWithVM; +import com.seafile.seadroid2.data.model.activities.ActivityModel; +import com.seafile.seadroid2.data.db.entities.RepoModel; +import com.seafile.seadroid2.databinding.LayoutFrameSwipeRvBinding; +import com.seafile.seadroid2.io.http.IO; +import com.seafile.seadroid2.play.exoplayer.CustomExoVideoPlayerActivity; +import com.seafile.seadroid2.ui.dialog_fragment.PasswordDialogFragment; +import com.seafile.seadroid2.ui.webview.SeaWebViewActivity; +import com.seafile.seadroid2.util.Utils; +import com.seafile.seadroid2.view.TipsViews; + +import io.reactivex.functions.Consumer; + +public class AllActivitiesFragment extends BaseFragmentWithVM { + private LayoutFrameSwipeRvBinding binding; + private ActivityAdapter adapter; + private QuickAdapterHelper helper; + private int page = 0; + + public static AllActivitiesFragment newInstance() { + Bundle args = new Bundle(); + AllActivitiesFragment fragment = new AllActivitiesFragment(); + fragment.setArguments(args); + return fragment; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = LayoutFrameSwipeRvBinding.inflate(inflater, container, false); + binding.swipeRefreshLayout.setOnRefreshListener(this::reload); + return binding.getRoot(); + } + + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + initAdapter(); + + initViewModel(); + } + + private boolean isFirstLoadData = true; + + @Override + public void onResume() { + super.onResume(); + d("load data:onResume"); + if (isFirstLoadData) { + isFirstLoadData = false; + d("load data:isFirstLoadData"); + loadNext(); + } + } + + private void initAdapter() { + adapter = new ActivityAdapter(); + TextView tipView = TipsViews.getTipTextView(requireContext()); + tipView.setText(R.string.no_starred_file); + tipView.setOnClickListener(v -> reload()); + adapter.setStateView(tipView); + adapter.setStateViewEnable(false); + + adapter.setOnItemClickListener((baseQuickAdapter, view, i) -> { + ActivityModel activityModel = (ActivityModel) adapter.getItems().get(i); + checkOpen(activityModel); + }); + + CustomLoadMoreAdapter loadMoreAdapter = new CustomLoadMoreAdapter(); + loadMoreAdapter.setOnLoadMoreListener(new TrailingLoadStateAdapter.OnTrailingListener() { + @Override + public void onLoad() { + loadNext(); + } + + @Override + public void onFailRetry() { + page--; + loadNext(); + } + + @Override + public boolean isAllowLoading() { + return !binding.swipeRefreshLayout.isRefreshing(); + } + }); + + helper = new QuickAdapterHelper.Builder(adapter) + .setTrailingLoadStateAdapter(loadMoreAdapter) + .build(); + binding.rv.setAdapter(helper.getAdapter()); + } + + private void showErrorTip() { + adapter.submitList(null); + TextView tipView = TipsViews.getTipTextView(requireContext()); + tipView.setText(R.string.error_when_load_activities); + tipView.setOnClickListener(v -> reload()); + adapter.setStateView(tipView); + adapter.setStateViewEnable(true); + } + + private void initViewModel() { + getViewModel().getRefreshLiveData().observe(getViewLifecycleOwner(), binding.swipeRefreshLayout::setRefreshing); + + getViewModel().getExceptionLiveData().observe(getViewLifecycleOwner(), exceptionPair -> showErrorTip()); + + getViewModel().getListLiveData().observe(getViewLifecycleOwner(), activityModels -> { + adapter.setStateViewEnable(true); + + if (page == 1) { + adapter.submitList(activityModels); + } else { + adapter.addAll(activityModels); + } + + if (activityModels.isEmpty()) { + helper.setTrailingLoadState(new LoadState.NotLoading(true)); + } else { + helper.setTrailingLoadState(new LoadState.NotLoading(false)); + } + }); + } + + private void loadNext() { + page++; + + getViewModel().loadAllData(page); + } + + private void reload() { + adapter.setStateViewEnable(false); + page = 0; + + loadNext(); + } + + //https://dev.seafile.com/seahub/mobile-login/?next=https://dev.seafile.com/seahub/lib/92d156fd-22ca-485a-a867-390e3e9ce43c/file/在线文档开发计划.sdoc + private void checkOpen(ActivityModel activityModel) { + if (activityModel.isDir()) { + return; + } + + //TODO repo is encrypted + getViewModel().getRepoModelFromLocal(activityModel.repo_id, new Consumer() { + @Override + public void accept(RepoModel repoModel) throws Exception { + if (repoModel == null) { + ToastUtils.showLong(R.string.repo_not_found); + return; + } + if (repoModel.encrypted) { + showPasswordDialog(repoModel, activityModel); + } else { + open(activityModel); + } + } + }); + } + + private void showPasswordDialog(RepoModel repoModel, ActivityModel activityModel) { + PasswordDialogFragment dialogFragment = PasswordDialogFragment.newInstance(); + dialogFragment.initData(repoModel.repo_id, repoModel.repo_name); + dialogFragment.setRefreshListener(isDone -> { + if (isDone) { + open(activityModel); + } + }); + dialogFragment.show(getChildFragmentManager(), PasswordDialogFragment.class.getSimpleName()); + } + + private void open(ActivityModel activityModel) { + if (Utils.isViewableImage(activityModel.name)) { + ToastUtils.showLong("TODO: 图片预览"); +// WidgetUtils.startGalleryActivity(requireActivity(), activityModel.repo_name, activityModel.repo_id, activityModel.path, activityModel.name, +// SupportAccountManager.getInstance().getCurrentAccount()); + } else if (Utils.isVideoFile(activityModel.name)) { + startPlayActivity(activityModel.name, activityModel.repo_id, activityModel.path); + } else if (activityModel.isFileOpenable()) { + String host = IO.getSingleton().getServerUrl(); + String url = String.format("%slib/%s/file/%s", host, activityModel.repo_id, activityModel.path); + SeaWebViewActivity.openUrl(requireContext(), url); + } + } + + private void startPlayActivity(String fileName, String repoID, String filePath) { + Intent intent = new Intent(requireContext(), CustomExoVideoPlayerActivity.class); + intent.putExtra("fileName", fileName); + intent.putExtra("repoID", repoID); + intent.putExtra("filePath", filePath); + intent.putExtra("account", SupportAccountManager.getInstance().getCurrentAccount()); + startActivity(intent); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activities/MineActivitiesFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/activities/MineActivitiesFragment.java new file mode 100644 index 000000000..89eb7f756 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/activities/MineActivitiesFragment.java @@ -0,0 +1,104 @@ +package com.seafile.seadroid2.ui.activities; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.Observer; + +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.SeafException; +import com.seafile.seadroid2.ui.base.fragment.BaseFragmentWithVM; +import com.seafile.seadroid2.databinding.LayoutFrameSwipeRvBinding; +import com.seafile.seadroid2.data.model.activities.ActivityModel; +import com.seafile.seadroid2.view.TipsViews; + +import java.util.List; + +import kotlin.Pair; + +@Deprecated +public class MineActivitiesFragment extends BaseFragmentWithVM { + + private LayoutFrameSwipeRvBinding binding; + private ActivityAdapter adapter; + + public static MineActivitiesFragment newInstance() { + Bundle args = new Bundle(); + MineActivitiesFragment fragment = new MineActivitiesFragment(); + fragment.setArguments(args); + return fragment; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = LayoutFrameSwipeRvBinding.inflate(inflater, container, false); + binding.swipeRefreshLayout.setOnRefreshListener(this::reload); + return binding.getRoot(); + } + + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + initAdapter(); + + initViewModel(); + + reload(); + } + + private void initAdapter() { + adapter = new ActivityAdapter(); + TextView tipView = TipsViews.getTipTextView(requireContext()); + tipView.setText(R.string.no_starred_file); + tipView.setOnClickListener(v -> reload()); + adapter.setStateView(tipView); + adapter.setStateViewEnable(false); + + binding.rv.setAdapter(createMuiltAdapterHelper(adapter).getAdapter()); + } + + private void showErrorTip() { + TextView tipView = TipsViews.getTipTextView(requireContext()); + tipView.setText(R.string.error_when_load_starred); + tipView.setOnClickListener(v -> reload()); + adapter.setStateView(tipView); + adapter.setStateViewEnable(false); + } + + private void initViewModel() { + getViewModel().getRefreshLiveData().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + binding.swipeRefreshLayout.setRefreshing(aBoolean); + } + }); + + getViewModel().getExceptionLiveData().observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(Pair exceptionPair) { + showErrorTip(); + } + }); + + getViewModel().getListLiveData().observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(List activityModels) { + adapter.setStateViewEnable(true); + + adapter.submitList(activityModels); + } + }); + } + + private void reload() { + adapter.setStateViewEnable(false); + getViewModel().loadAllData(1); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activities/bottomsheet/ActivityBottomSheetAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/activities/bottomsheet/ActivityBottomSheetAdapter.java new file mode 100644 index 000000000..aa67a6f19 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/activities/bottomsheet/ActivityBottomSheetAdapter.java @@ -0,0 +1,59 @@ +package com.seafile.seadroid2.ui.activities.bottomsheet; + +import android.content.Context; +import android.graphics.Color; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.joanzapata.iconify.fonts.MaterialCommunityIcons; +import com.seafile.seadroid2.ui.base.adapter.BaseAdapter; +import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; +import com.seafile.seadroid2.data.EventDetailsFileItem; +import com.seafile.seadroid2.databinding.ListItemDiffBinding; + +public class ActivityBottomSheetAdapter extends BaseAdapter { + + @Override + protected void onBindViewHolder(@NonNull ActivityBottomSheetViewHolder holder, int i, @Nullable EventDetailsFileItem eventDetailsFileItem) { + holder.binding.tvDiffFileName.setText(eventDetailsFileItem.getPath()); + switch (eventDetailsFileItem.geteType()) { + case FILE_ADDED: + case DIR_ADDED: + holder.binding.tvDiffIcon.setTextColor(Color.parseColor("#6CC644")); + holder.binding.tvDiffIcon.setText("{" + MaterialCommunityIcons.mdi_plus.key() + " #6CC644}"); + break; + case FILE_MODIFIED: + holder.binding.tvDiffIcon.setTextColor(Color.parseColor("#D0B44C")); + holder.binding.tvDiffIcon.setText("{" + MaterialCommunityIcons.mdi_pencil.key() + " #D0B44C}"); + break; + case FILE_RENAMED: + holder.binding.tvDiffIcon.setTextColor(Color.parseColor("#677A85")); + holder.binding.tvDiffIcon.setText("{" + MaterialCommunityIcons.mdi_arrow_right.key() + " #677A85}"); + break; + case FILE_DELETED: + case DIR_DELETED: + holder.binding.tvDiffIcon.setTextColor(Color.parseColor("#BD2C00")); + holder.binding.tvDiffIcon.setText("{" + MaterialCommunityIcons.mdi_minus.key() + " #BD2C00}"); + break; + } + } + + @NonNull + @Override + protected ActivityBottomSheetViewHolder onCreateViewHolder(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) { + ListItemDiffBinding binding = ListItemDiffBinding.inflate(LayoutInflater.from(context), viewGroup, false); + return new ActivityBottomSheetViewHolder(binding); + } + + static class ActivityBottomSheetViewHolder extends BaseViewHolder { + public ListItemDiffBinding binding; + + public ActivityBottomSheetViewHolder(@NonNull ListItemDiffBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activities/bottomsheet/ActivityBottomSheetDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/activities/bottomsheet/ActivityBottomSheetDialogFragment.java new file mode 100644 index 000000000..742ba97d1 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/activities/bottomsheet/ActivityBottomSheetDialogFragment.java @@ -0,0 +1,64 @@ +package com.seafile.seadroid2.ui.activities.bottomsheet; + +import android.app.Dialog; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +import com.chad.library.adapter4.BaseQuickAdapter; +import com.chad.library.adapter4.QuickAdapterHelper; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.data.EventDetailsFileItem; + +import java.util.List; + +public class ActivityBottomSheetDialogFragment extends BottomSheetDialogFragment { + + private com.google.android.material.bottomsheet.BottomSheetDialog bottomSheetDialog; + private List items; + + public void setItems(List items) { + this.items = items; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + bottomSheetDialog = new com.google.android.material.bottomsheet.BottomSheetDialog(requireContext(), R.style.ThemeOverlay_Catalog_BottomSheetDialog_Scrollable); + bottomSheetDialog.setContentView(R.layout.dialog_activity_bottom_sheet); + bottomSheetDialog.setDismissWithAnimation(true); + + rv = bottomSheetDialog.findViewById(R.id.rv); + +// View bottomSheetInternal = bottomSheetDialog.findViewById(R.id.design_bottom_sheet); +// BottomSheetBehavior.from(bottomSheetInternal).setPeekHeight(400); + + setAdapter(); + + return bottomSheetDialog; + } + + private RecyclerView rv; + + public void setAdapter() { + if (rv != null) { + ActivityBottomSheetAdapter adapter = new ActivityBottomSheetAdapter(); + QuickAdapterHelper helper = new QuickAdapterHelper.Builder(adapter).build(); + rv.setAdapter(helper.getAdapter()); + + adapter.submitList(items); + if (onItemClickListener != null) { + adapter.setOnItemClickListener((BaseQuickAdapter.OnItemClickListener) onItemClickListener); + } + } + } + + private BaseQuickAdapter.OnItemClickListener onItemClickListener; + + public void setOnItemClickListener(BaseQuickAdapter.OnItemClickListener onItemClickListener) { + this.onItemClickListener = onItemClickListener; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/FileActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/activity/FileActivity.java index d89772269..1a64f198f 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/FileActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/activity/FileActivity.java @@ -7,7 +7,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; -import androidx.appcompat.widget.Toolbar; import android.util.Log; import android.view.MenuItem; import android.view.View; @@ -17,6 +16,8 @@ import android.widget.TextView; import android.widget.Toast; +import androidx.appcompat.widget.Toolbar; + import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeafConnection; import com.seafile.seadroid2.SeafException; @@ -28,8 +29,9 @@ import com.seafile.seadroid2.transfer.TransferService; import com.seafile.seadroid2.transfer.TransferService.TransferBinder; import com.seafile.seadroid2.ui.BaseActivity; -import com.seafile.seadroid2.ui.dialog.PasswordDialog; import com.seafile.seadroid2.ui.dialog.TaskDialog; +import com.seafile.seadroid2.ui.dialog_fragment.PasswordDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.listener.OnRefreshDataListener; import com.seafile.seadroid2.util.Utils; import java.io.File; @@ -239,23 +241,23 @@ private void onFileDownloadFailed(DownloadTaskInfo info) { } private void handlePassword() { - PasswordDialog passwordDialog = new PasswordDialog(); - passwordDialog.setRepo(mRepoName, mRepoID, mAccount); - passwordDialog.setTaskDialogLisenter(new TaskDialog.TaskDialogListener() { + PasswordDialogFragment dialogFragment = PasswordDialogFragment.newInstance(); + dialogFragment.initData(mRepoID, mRepoName); + dialogFragment.setRefreshListener(new OnRefreshDataListener() { @Override - public void onTaskSuccess() { - mTaskID = mTransferService.addDownloadTask(mAccount, - mRepoName, - mRepoID, - mFilePath); - } - - @Override - public void onTaskCancelled() { - finish(); + public void onActionStatus(boolean isDone) { + if (isDone) { + mTaskID = mTransferService.addDownloadTask(mAccount, + mRepoName, + mRepoID, + mFilePath); + } else { + finish(); + } } }); - passwordDialog.show(getSupportFragmentManager(), "DialogFragment"); + + dialogFragment.show(getSupportFragmentManager(), PasswordDialogFragment.class.getSimpleName()); } public void showToast(CharSequence msg) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/GalleryActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/activity/GalleryActivity.java index 88e12f88f..a914b700d 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/GalleryActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/activity/GalleryActivity.java @@ -10,27 +10,27 @@ import android.widget.LinearLayout; import android.widget.TextView; +import com.blankj.utilcode.util.ToastUtils; import com.google.common.collect.Lists; import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.SettingsManager; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.data.SeafDirent; import com.seafile.seadroid2.data.SeafPhoto; +import com.seafile.seadroid2.data.db.entities.DirentModel; import com.seafile.seadroid2.transfer.DownloadStateListener; import com.seafile.seadroid2.transfer.DownloadTask; import com.seafile.seadroid2.ui.BaseActivity; -import com.seafile.seadroid2.view.HackyViewPager; import com.seafile.seadroid2.ui.WidgetUtils; -import com.seafile.seadroid2.view.ZoomOutPageTransformer; import com.seafile.seadroid2.ui.adapter.GalleryAdapter; -import com.seafile.seadroid2.ui.adapter.SeafItemAdapter; -import com.seafile.seadroid2.ui.dialog.DeleteFileDialog; -import com.seafile.seadroid2.ui.dialog.TaskDialog; -import com.seafile.seadroid2.ui.repo.ReposFragment; +import com.seafile.seadroid2.ui.dialog_fragment.DeleteFileDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.listener.OnRefreshDataListener; import com.seafile.seadroid2.util.ConcurrentAsyncTask; +import com.seafile.seadroid2.util.sp.Sorts; import com.seafile.seadroid2.util.Utils; +import com.seafile.seadroid2.view.HackyViewPager; +import com.seafile.seadroid2.view.ZoomOutPageTransformer; import java.util.Collections; import java.util.List; @@ -65,10 +65,12 @@ public class GalleryActivity extends BaseActivity { private List mPhotos = Lists.newArrayList(); public static int taskID; private int count; - private static int TALLY=3; + private static int TALLY = 3; private ProgressDialog progressDialog; - /** flag to mark if the tool bar was shown */ + /** + * flag to mark if the tool bar was shown + */ private boolean showToolBar = true; private View.OnClickListener onClickListener = new View.OnClickListener() { @Override @@ -128,10 +130,12 @@ public void onPageScrolled(int position, float positionOffset, int positionOffse } @Override - public void onPageSelected(int position) {} + public void onPageSelected(int position) { + } @Override - public void onPageScrollStateChanged(int state) {} + public void onPageScrollStateChanged(int state) { + } }); mPageIndexContainer = (LinearLayout) findViewById(R.id.page_index_container); @@ -175,11 +179,12 @@ protected void onRestoreInstanceState(Bundle savedInstanceState) { * Load thumbnail urls in order to display them in the gallery. * Prior to use caches to calculate those urls. * If caches are not available, load them asynchronously. - * + *

* NOTE: When user browsing files in "LIBRARY" tab, he has to navigate into a repo in order to open gallery. * Method which get called is {@link ReposFragment#navToReposView(boolean)} or {@link ReposFragment#navToDirectory(boolean)}, * so seafDirents were already cached and it will always use them to calculate thumbnail urls for displaying photos in gallery. * But for browsing "STARRED" tab, caches of starred files may or may not cached, that is where the asynchronous loading code segment comes into use. + * * @param repoID * @param dirPath */ @@ -188,12 +193,9 @@ private void displayPhotosInGallery(String repoName, String repoID, String dirPa List seafDirents = dataMgr.getCachedDirents(repoID, dirPath); if (seafDirents != null) { // sort files by type and order - seafDirents = sortFiles(seafDirents, - SettingsManager.instance().getSortFilesTypePref(), - SettingsManager.instance().getSortFilesOrderPref()); + seafDirents = sortFiles(seafDirents); for (SeafDirent seafDirent : seafDirents) { - if (!seafDirent.isDir() - && Utils.isViewableImage(seafDirent.name)) { + if (!seafDirent.isDir() && Utils.isViewableImage(seafDirent.name)) { mPhotos.add(new SeafPhoto(repoName, repoID, dirPath, seafDirent)); } } @@ -207,7 +209,7 @@ private void displayPhotosInGallery(String repoName, String repoID, String dirPa navToSelectedPage(); } else { if (!Utils.isNetworkOn()) { - showShortToast(this, R.string.network_down); + ToastUtils.showLong(R.string.network_down); // data is not available finish(); } @@ -247,12 +249,9 @@ protected List doInBackground(String... params) { return null; // sort photos according to global sort settings - seafDirents = sortFiles(seafDirents, - SettingsManager.instance().getSortFilesTypePref(), - SettingsManager.instance().getSortFilesOrderPref()); + seafDirents = sortFiles(seafDirents); for (SeafDirent seafDirent : seafDirents) { - if (!seafDirent.isDir() - && Utils.isViewableImage(seafDirent.name)) { + if (!seafDirent.isDir() && Utils.isViewableImage(seafDirent.name)) { photos.add(new SeafPhoto(repoName, repoID, dirPath, seafDirent)); } } @@ -264,7 +263,7 @@ protected void onPostExecute(List photos) { if (photos.isEmpty() || fileName == null) { if (err != null) { - showShortToast(GalleryActivity.this, R.string.gallery_load_photos_error); + ToastUtils.showLong(R.string.gallery_load_photos_error); Log.e(DEBUG_TAG, "error message " + err.getMessage() + " error code " + err.getCode()); } @@ -281,26 +280,24 @@ protected void onPostExecute(List photos) { /** * Sorts the given list by type and order. - * Sorting type is one of {@link SeafItemAdapter#SORT_BY_NAME} or {@link SeafItemAdapter#SORT_BY_LAST_MODIFIED_TIME}. - * Sorting order is one of {@link SeafItemAdapter#SORT_ORDER_ASCENDING} or {@link SeafItemAdapter#SORT_ORDER_DESCENDING}. * * @param dirents - * @param type - * @param order * @return sorted file list */ - public List sortFiles(List dirents, int type, int order) { + public List sortFiles(List dirents) { // sort SeafDirents - if (type == SeafItemAdapter.SORT_BY_NAME) { + int sortType = Sorts.getSortType(); + + if (sortType <= Sorts.SORT_BY_NAME_DESC) { // sort by name, in ascending order Collections.sort(dirents, new SeafDirent.DirentNameComparator()); - if (order == SeafItemAdapter.SORT_ORDER_DESCENDING) { + if (sortType == Sorts.SORT_BY_NAME_DESC) { Collections.reverse(dirents); } - } else if (type == SeafItemAdapter.SORT_BY_LAST_MODIFIED_TIME) { + } else { // sort by last modified time, in ascending order - Collections.sort(dirents, new SeafDirent.DirentLastMTimeComparator()); - if (order == SeafItemAdapter.SORT_ORDER_DESCENDING) { + Collections.sort(dirents, new SeafDirent.DirentLastMTimeComparator()); + if (sortType == Sorts.SORT_BY_MODIFIED_TIME_DESC) { Collections.reverse(dirents); } } @@ -310,7 +307,6 @@ public List sortFiles(List dirents, int type, int order) /** * Dynamically navigate to the starting page index selected by user * by default the starting page index is 0 - * */ private void navToSelectedPage() { int size = mPhotos.size(); @@ -356,21 +352,28 @@ private void downloadFile(String repoID, String dirPath, String fileName) { } private void deleteFile(String repoID, String path) { - final DeleteFileDialog dialog = new DeleteFileDialog(); - dialog.init(repoID, path, false, mAccount); - dialog.setTaskDialogLisenter(new TaskDialog.TaskDialogListener() { + //TODO + DirentModel direntModel = new DirentModel(); + direntModel.repo_id = repoID; + direntModel.full_path = path; + + DeleteFileDialogFragment dialogFragment = DeleteFileDialogFragment.newInstance(); + dialogFragment.initData(direntModel); + dialogFragment.setRefreshListener(new OnRefreshDataListener() { @Override - public void onTaskSuccess() { - showShortToast(GalleryActivity.this, R.string.delete_successful); - removePageAndRefreshView(); + public void onActionStatus(boolean isDone) { + if (isDone) { + ToastUtils.showLong(R.string.delete_successful); + removePageAndRefreshView(); + } } }); - dialog.show(getSupportFragmentManager(), "DialogFragment"); + } private void starFile(String repoId, String dir, String fileName) { if (!Utils.isNetworkOn()) { - showShortToast(this, R.string.network_down); + ToastUtils.showLong(R.string.network_down); return; } @@ -414,11 +417,11 @@ protected Void doInBackground(Void... params) { @Override protected void onPostExecute(Void v) { if (err != null) { - showShortToast(GalleryActivity.this, R.string.star_file_failed); + ToastUtils.showLong(R.string.star_file_failed); return; } - showShortToast(GalleryActivity.this, R.string.star_file_succeed); + ToastUtils.showLong(R.string.star_file_succeed); } } @@ -439,7 +442,7 @@ private void removePageAndRefreshView() { return; } - mPageIndex = mPageIndex > size - 1 ? size -1 : mPageIndex; + mPageIndex = mPageIndex > size - 1 ? size - 1 : mPageIndex; // page index starting from 1 instead of 0 in user interface, so plus one here mPageIndexTextView.setText(String.valueOf(mPageIndex + 1)); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/SeafilePathChooserActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/activity/SeafilePathChooserActivity.java deleted file mode 100644 index 40bd3ead2..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/SeafilePathChooserActivity.java +++ /dev/null @@ -1,843 +0,0 @@ -package com.seafile.seadroid2.ui.activity; - -import android.annotation.SuppressLint; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.widget.Toolbar; -import android.text.TextUtils; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.animation.AnimationUtils; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.Button; -import android.widget.ListView; -import android.widget.TextView; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeafConnection; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.SettingsManager; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.AccountManager; -import com.seafile.seadroid2.data.DataManager; -import com.seafile.seadroid2.data.SeafDirent; -import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.ui.BaseActivity; -import com.seafile.seadroid2.context.NavContext; -import com.seafile.seadroid2.ui.account.adapter.AccountAdapter; -import com.seafile.seadroid2.ui.repo.DirentsAdapter; -import com.seafile.seadroid2.ui.account.adapter.SeafAccountAdapter; -import com.seafile.seadroid2.ui.repo.SeafReposAdapter; -import com.seafile.seadroid2.ui.dialog.NewDirDialog; -import com.seafile.seadroid2.ui.dialog.PasswordDialog; -import com.seafile.seadroid2.ui.dialog.TaskDialog; -import com.seafile.seadroid2.ui.gesture.UnlockGesturePasswordActivity; -import com.seafile.seadroid2.ui.settings.SettingsFragment; -import com.seafile.seadroid2.util.ConcurrentAsyncTask; -import com.seafile.seadroid2.util.Utils; - -import java.net.HttpURLConnection; -import java.util.List; - -/** - * Path chooser - Let the user choose a target path (account, repo, dir) - */ -public class SeafilePathChooserActivity extends BaseActivity implements Toolbar.OnMenuItemClickListener { - private static final String DEBUG_TAG = "SeafilePathChooserActivity"; - - public static final String PASSWORD_DIALOG_FRAGMENT_TAG = "password_dialog_fragment_tag"; - - private NavContext mNavContext; - - private Account mAccount; - - private AccountManager mAccountManager; - private DataManager mDataManager; - - private AccountAdapter mAccountAdapter; - private SeafReposAdapter mReposAdapter; - private DirentsAdapter mDirentsAdapter; - - private LoadDirTask mLoadDirTask; - private LoadReposTask mLoadReposTask; - private LoadAccountsTask mLoadAccountsTask; - - private boolean canChooseAccount; - private boolean onlyShowWritableRepos; - private String encryptedRepoId; - - private boolean isOnlyChooseRepo; - - private View mProgressContainer, mListContainer, mContentArea; - private Button mOkButton, mCancelButton, mNewFolder; - private TextView mEmptyText, mErrorText; - private ListView mListView; - - private static final int STEP_CHOOSE_ACCOUNT = 1; - private static final int STEP_CHOOSE_REPO = 2; - private static final int STEP_CHOOSE_DIR = 3; - private int mStep = 1; - - public static final String DATA_REPO_PERMISSION = "permission"; - public static final String DATA_REPO_ID = "repoID"; - public static final String DATA_REPO_NAME = "repoNAME"; - public static final String DATA_DIRECTORY_PATH = "dirPath"; - public static final String DATA_DIR = "dir"; - public static final String DATA_ACCOUNT = "account"; - - public static final String ONLY_SHOW_WRITABLE_REPOS = "onlyShowWritableRepos"; - public static final String SHOW_ENCRYPTED_REPOS = "showEncryptedRepos"; - public static final String ENCRYPTED_REPO_ID = "encryptedRepoId"; - public static final String REPO_ENCRYPTED = "repo_encrypted"; - private boolean showEncryptedRepos; - private boolean isShowEncryptedRepos; - - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.seafile_path_chooser); - Intent intent = getIntent(); - Account account = (Account) intent.getParcelableExtra("account"); - if (account == null) { - canChooseAccount = true; - } else { - mAccount = account; - } - onlyShowWritableRepos = intent.getBooleanExtra(ONLY_SHOW_WRITABLE_REPOS, true); - showEncryptedRepos = intent.getBooleanExtra(SHOW_ENCRYPTED_REPOS, true); - encryptedRepoId = intent.getStringExtra(ENCRYPTED_REPO_ID); - isShowEncryptedRepos = intent.getBooleanExtra(REPO_ENCRYPTED, true); - - mOkButton = (Button) findViewById(R.id.ok); - mNewFolder = (Button) findViewById(R.id.new_folder); - mCancelButton = (Button) findViewById(R.id.cancel); - mListView = (ListView) findViewById(android.R.id.list); - mEmptyText = (TextView) findViewById(android.R.id.empty); - mErrorText = (TextView) findViewById(R.id.error_message); - mListContainer = findViewById(R.id.listContainer); - mProgressContainer = findViewById(R.id.progressContainer); - mContentArea = findViewById(R.id.content); - isOnlyChooseRepo = intent.getBooleanExtra(SettingsFragment.CAMERA_UPLOAD_BOTH_PAGES, false); - if (isOnlyChooseRepo) { - mOkButton.setVisibility(View.GONE); - mNewFolder.setVisibility(View.GONE); - } - mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); - mListView.setOnItemClickListener(new ListView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView l, View view, int position, long id) { - onListItemClick(view, position, id); - } - }); - - mOkButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - String repoName = mNavContext.getRepoName(); - String repoID = mNavContext.getRepoID(); - String dir = mNavContext.getDirPath(); - Intent intent = new Intent(); - intent.putExtra(DATA_REPO_NAME, repoName); - intent.putExtra(DATA_REPO_ID, repoID); - intent.putExtra(DATA_DIR, dir); - intent.putExtra(DATA_ACCOUNT, mAccount); - setResult(RESULT_OK, intent); - finish(); - } - }); - - mNewFolder.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - createNewFolder(); - } - }); - - mCancelButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - setResult(RESULT_CANCELED); - finish(); - } - }); - - if (canChooseAccount) { - chooseAccount(); - } else { - chooseRepo(); - } - - Toolbar toolbar = getActionBarToolbar(); - toolbar.setOnMenuItemClickListener(this); - setSupportActionBar(toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle(R.string.app_name); - } - - @Override - protected void onStart() { - super.onStart(); - if (SettingsManager.instance().isGestureLockRequired()) { - Intent newIntent = new Intent(this, UnlockGesturePasswordActivity.class); - startActivity(newIntent); - } - } - - @SuppressLint("LongLogTag") - @Override - protected void onDestroy() { - Log.d(DEBUG_TAG, "onDestroy is called"); - - if (mLoadReposTask != null && mLoadReposTask.getStatus() != AsyncTask.Status.FINISHED) { - mLoadReposTask.cancel(true); - } - - if (mLoadDirTask != null && mLoadDirTask.getStatus() != AsyncTask.Status.FINISHED) { - mLoadDirTask.cancel(true); - } - - if (mLoadAccountsTask != null && mLoadAccountsTask.getStatus() != AsyncTask.Status.FINISHED) { - mLoadAccountsTask.cancel(true); - } - - super.onDestroy(); - } - - public void onListItemClick(final View v, final int position, final long id) { - NavContext nav = getNavContext(); - SeafRepo repo = null; - - if (mStep == STEP_CHOOSE_REPO) { - repo = getReposAdapter().getItem(position); - } else if (mStep == STEP_CHOOSE_DIR) { - repo = getDataManager().getCachedRepoByID(nav.getRepoID()); - } - - if (repo != null) { - if (repo.encrypted && !mDataManager.getRepoPasswordSet(repo.repo_id)) { - String password = mDataManager.getRepoPassword(repo.repo_id); - showPasswordDialog(repo.repo_name, repo.repo_id, - new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - onListItemClick(v, position, id); - } - }, password); - - return; - } - } - - switch (mStep) { - case STEP_CHOOSE_ACCOUNT: - setAccount(getAccountAdapter().getItem(position)); - chooseRepo(); - break; - case STEP_CHOOSE_REPO: - if (!isOnlyChooseRepo) { - nav.setRepoName(repo.repo_name); - nav.setRepoID(repo.repo_id); - nav.setDirPermission(repo.permission); - nav.setDirPath("/"); - chooseDir(); - } else { - Intent intent = new Intent(); - intent.putExtra(DATA_REPO_NAME, repo.repo_name); - intent.putExtra(DATA_REPO_ID, repo.repo_id); - intent.putExtra(DATA_REPO_PERMISSION, repo.permission); - //TODO test it. - intent.putExtra(DATA_DIR,"/"); - intent.putExtra(DATA_ACCOUNT, mAccount); - setResult(RESULT_OK, intent); - finish(); - } - break; - case STEP_CHOOSE_DIR: - SeafDirent dirent = getDirentsAdapter().getItem(position); - if (dirent.type == SeafDirent.DirentType.FILE) { - return; - } - - String path = Utils.pathJoin(nav.getDirPath(), dirent.name); - nav.setDirPath(path); - refreshDir(); - break; - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - Toolbar toolbar = getActionBarToolbar(); - toolbar.inflateMenu(R.menu.seafile_path_chooser_menu); - toolbar.setOnMenuItemClickListener(this); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - MenuItem menuRefresh = menu.findItem(R.id.refresh); - menuRefresh.setVisible(true); - menuRefresh.setEnabled(true); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - stepBack(); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { - case R.id.refresh: - refreshList(true); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onBackPressed() { - stepBack(true); - } - - private void refreshList(final boolean forceRefresh) { - switch (mStep) { - case STEP_CHOOSE_ACCOUNT: - if (mLoadAccountsTask != null && mLoadAccountsTask.getStatus() != AsyncTask.Status.FINISHED) { - return; - } else { - chooseAccount(false); - break; - } - case STEP_CHOOSE_REPO: - if (mLoadReposTask != null && mLoadReposTask.getStatus() != AsyncTask.Status.FINISHED) { - return; - } else { - chooseRepo(forceRefresh); - break; - } - case STEP_CHOOSE_DIR: - if (mLoadDirTask != null && mLoadDirTask.getStatus() != AsyncTask.Status.FINISHED) { - return; - } else { - SeafRepo repo = getDataManager().getCachedRepoByID(getNavContext().getRepoID()); - if (repo.encrypted && !mDataManager.getRepoPasswordSet(repo.repo_id)) { - String password = mDataManager.getRepoPassword(repo.repo_id); - showPasswordDialog(repo.repo_name, repo.repo_id, - new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - chooseRepo(forceRefresh); - } - }, password); - } - chooseDir(forceRefresh); - break; - } - } - } - - private void stepBack() { - stepBack(false); - } - - private void stepBack(boolean cancelIfFirstStep) { - switch (mStep) { - case STEP_CHOOSE_ACCOUNT: - if (cancelIfFirstStep) { - finish(); - } - break; - case STEP_CHOOSE_REPO: - if (canChooseAccount) { - chooseAccount(false); - } else if (cancelIfFirstStep) { - finish(); - } - break; - case STEP_CHOOSE_DIR: - if (getNavContext().isRepoRoot()) { - chooseRepo(); - } else { - String path = getNavContext().getDirPath(); - getNavContext().setDirPath(Utils.getParentPath(path)); - refreshDir(); - } - break; - } - } - - private void setListAdapter(BaseAdapter adapter) { - mListView.setAdapter(adapter); - } - - /** - * List all accounts - */ - private void chooseAccount(boolean forwardIfOnlyOneAccount) { - mStep = STEP_CHOOSE_ACCOUNT; - mEmptyText.setText(R.string.no_account); - - mLoadAccountsTask = new LoadAccountsTask(getAccountManager(), forwardIfOnlyOneAccount); - - ConcurrentAsyncTask.execute(mLoadAccountsTask); - setListAdapter(getAccountAdapter()); - mOkButton.setVisibility(View.GONE); - mNewFolder.setVisibility(View.GONE); - - // update action bar - ActionBar bar = getSupportActionBar(); - bar.setDisplayHomeAsUpEnabled(false); - bar.setTitle(R.string.choose_an_account); - } - - private void chooseAccount() { - chooseAccount(true); - } - - /** - * List all repos - */ - private void chooseRepo() { - chooseRepo(false); - } - - private void chooseRepo(boolean forceRefresh) { - mStep = STEP_CHOOSE_REPO; - mEmptyText.setText(R.string.no_repo); - - setListAdapter(getReposAdapter()); - mOkButton.setVisibility(View.GONE); - mNewFolder.setVisibility(View.GONE); - - getNavContext().setRepoID(null); - - if (!Utils.isNetworkOn() || !forceRefresh) { - List repos = getDataManager().getReposFromCache(); - if (repos != null) { - updateAdapterWithRepos(repos); - // update action bar - ActionBar bar = getSupportActionBar(); - bar.setDisplayHomeAsUpEnabled(true); - bar.setTitle(R.string.choose_a_library); - return; - } - } - - showLoading(true); - mLoadReposTask = new LoadReposTask(getDataManager()); - ConcurrentAsyncTask.execute(mLoadReposTask); - - // update action bar - ActionBar bar = getSupportActionBar(); - bar.setDisplayHomeAsUpEnabled(true); - bar.setTitle(R.string.choose_a_library); - } - - private void chooseDir() { - chooseDir(false); - } - - private void chooseDir(boolean forceRefresh) { - mStep = STEP_CHOOSE_DIR; - mEmptyText.setText(R.string.dir_empty); - - // update action bar - setListAdapter(getDirentsAdapter()); - mOkButton.setVisibility(View.VISIBLE); -// mNewFolder.setVisibility(View.VISIBLE); - refreshDir(forceRefresh); - } - - private void refreshDir() { - refreshDir(false); - } - - private void updateAdapterWithDirents(List dirents) { - DirentsAdapter adapter = getDirentsAdapter(); - if (dirents.size() > 0) { - adapter.clearDirents(); - for (SeafDirent dirent : dirents) { - adapter.add(dirent); - } - int sort_type = SettingsManager.instance().getSortFilesTypePref(); - int sort_order = SettingsManager.instance().getSortFilesOrderPref(); - adapter.sortFiles(sort_type, sort_order); - adapter.notifyDataSetChanged(); - } - showListOrEmptyText(dirents.size()); - } - - private void updateAdapterWithRepos(List repos) { - SeafReposAdapter adapter = getReposAdapter(); - if (repos.size() > 0) { - adapter.clearRepos(); - for (SeafRepo item : repos) { - boolean isContains = false; - for (SeafRepo data : adapter.getData()) { - if (TextUtils.equals(data.getRepoId(), item.getRepoId())) { - isContains = true; - break; - } - } - if (onlyShowWritableRepos && !item.hasWritePermission()) { - // Read only dir need not show in list - continue; - } - -// if (item.encrypted && !showEncryptedRepos) { - if (item.encrypted && !isShowEncryptedRepos) { - // encrypted dir need not show in list - continue; - } - - if (item.encrypted && TextUtils.equals(item.repo_id, encryptedRepoId)) { - NavContext nav = getNavContext(); - nav.setRepoName(item.repo_name); - nav.setRepoID(item.repo_id); - nav.setDirPermission(item.permission); - nav.setDirPath("/"); - chooseDir(); -// mStep = STEP_CHOOSE_REPO; - break; - } - if (!isContains) { - adapter.add(item); - } - } - int sort_type = SettingsManager.instance().getSortFilesTypePref(); - int sort_order = SettingsManager.instance().getSortFilesOrderPref(); - adapter.sortFiles(sort_type, sort_order); - adapter.notifyDataSetChanged(); - } - showListOrEmptyText(repos.size()); - } - - private void refreshDir(boolean forceRefresh) { - String repoID = getNavContext().getRepoID(); - String dirPath = getNavContext().getDirPath(); - - if (!Utils.isNetworkOn() || !forceRefresh) { - List dirents = getDataManager().getCachedDirents( - getNavContext().getRepoID(), getNavContext().getDirPath()); - if (dirents != null) { - updateAdapterWithDirents(dirents); - // update action bar - ActionBar bar = getSupportActionBar(); - bar.setDisplayHomeAsUpEnabled(true); - bar.setTitle(R.string.choose_a_folder); - return; - } - } - - showLoading(true); - mLoadDirTask = new LoadDirTask(repoID, dirPath, getDataManager()); - ConcurrentAsyncTask.execute(mLoadDirTask); - - // update action bar - ActionBar bar = getSupportActionBar(); - bar.setDisplayHomeAsUpEnabled(true); - bar.setTitle(R.string.choose_a_folder); - } - - private void showPasswordDialog() { - NavContext nav = getNavContext(); - String repoName = nav.getRepoName(); - String repoID = nav.getRepoID(); - - showPasswordDialog(repoName, repoID, new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - refreshDir(); - } - }, null); - } - - public void showPasswordDialog(String repoName, String repoID, - TaskDialog.TaskDialogListener listener, String password) { - PasswordDialog passwordDialog = new PasswordDialog(); - passwordDialog.setRepo(repoName, repoID, mAccount); - if (password != null) { - passwordDialog.setPassword(password); - } - passwordDialog.setTaskDialogLisenter(listener); - passwordDialog.show(getSupportFragmentManager(), PASSWORD_DIALOG_FRAGMENT_TAG); - } - - private void showLoading(boolean loading) { - clearError(); - if (loading) { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation( - this, android.R.anim.fade_in)); - mListContainer.startAnimation(AnimationUtils.loadAnimation( - this, android.R.anim.fade_out)); - - mProgressContainer.setVisibility(View.VISIBLE); - mListContainer.setVisibility(View.INVISIBLE); - } else { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation( - this, android.R.anim.fade_out)); - mListContainer.startAnimation(AnimationUtils.loadAnimation( - this, android.R.anim.fade_in)); - - mProgressContainer.setVisibility(View.GONE); - mListContainer.setVisibility(View.VISIBLE); - } - } - - private void setErrorMessage(int resID) { - mContentArea.setVisibility(View.GONE); - mErrorText.setVisibility(View.VISIBLE); - mErrorText.setText(getString(resID)); - } - - private void clearError() { - mErrorText.setVisibility(View.GONE); - mContentArea.setVisibility(View.VISIBLE); - } - - private void showListOrEmptyText(int listSize) { - if (listSize == 0) { - mListView.setVisibility(View.GONE); - mEmptyText.setVisibility(View.VISIBLE); - } else { - mListView.setVisibility(View.VISIBLE); - mEmptyText.setVisibility(View.GONE); - } - } - - private DataManager getDataManager() { - if (mDataManager == null) { - mDataManager = new DataManager(mAccount); - } - - return mDataManager; - } - - private AccountManager getAccountManager() { - if (mAccountManager == null) { - mAccountManager = new AccountManager(this); - } - - return mAccountManager; - } - - private NavContext getNavContext() { - if (mNavContext == null) { - mNavContext = new NavContext(); - } - - return mNavContext; - } - - private AccountAdapter getAccountAdapter() { - if (mAccountAdapter == null) { - mAccountAdapter = new SeafAccountAdapter(this); - } - - return mAccountAdapter; - } - - private SeafReposAdapter getReposAdapter() { - if (mReposAdapter == null) { - mReposAdapter = new SeafReposAdapter(onlyShowWritableRepos, encryptedRepoId); - } - - return mReposAdapter; - } - - private DirentsAdapter getDirentsAdapter() { - if (mDirentsAdapter == null) { - mDirentsAdapter = new DirentsAdapter(); - } - - return mDirentsAdapter; - } - - private void setAccount(Account account) { - mAccount = account; - mDataManager = new DataManager(account); - } - - private class LoadAccountsTask extends AsyncTask { - private List accounts; - private Exception err; - private AccountManager accountManager; - private boolean forwardIfOnlyOneAccount; - - public LoadAccountsTask(AccountManager accountManager, boolean forwardIfOnlyOneAccount) { - this.accountManager = accountManager; - this.forwardIfOnlyOneAccount = forwardIfOnlyOneAccount; - } - - @Override - protected Void doInBackground(Void... params) { - try { - accounts = accountManager.getSignedInAccountList(); - } catch (Exception e) { - err = e; - } - - return null; - } - - @SuppressLint("LongLogTag") - @Override - protected void onPostExecute(Void v) { - showLoading(false); - if (err != null || accounts == null) { - setErrorMessage(R.string.load_accounts_fail); - if (err != null) { - Log.d(DEBUG_TAG, "failed to load accounts: " + err.getMessage()); - } - return; - } - - if (accounts.size() == 1 && forwardIfOnlyOneAccount) { - // Only 1 account. Go to the next next step. - setAccount(accounts.get(0)); - chooseRepo(); - return; - } - - AccountAdapter adapter = getAccountAdapter(); - adapter.clear(); - for (Account account : accounts) { - adapter.add(account); - } - adapter.notifyDataSetChanged(); - showListOrEmptyText(accounts.size()); - } - } - - private class LoadReposTask extends AsyncTask { - private List repos; - private SeafException err; - private DataManager dataManager; - - public LoadReposTask(DataManager dataManager) { - this.dataManager = dataManager; - } - - @Override - protected Void doInBackground(Void... params) { - try { - repos = dataManager.getReposFromServer(); - } catch (SeafException e) { - err = e; - } - - return null; - } - - @SuppressLint("LongLogTag") - @Override - protected void onPostExecute(Void v) { - if (mStep != STEP_CHOOSE_REPO) { - return; - } - - showLoading(false); - if (err != null || repos == null) { - setErrorMessage(R.string.load_libraries_fail); - Log.d(DEBUG_TAG, "failed to load repos: " + (err != null ? err.getMessage() : " no error present")); - return; - } - - updateAdapterWithRepos(repos); - } - } - - private class LoadDirTask extends AsyncTask { - private String repoID, dirPath; - private SeafException err; - private DataManager dataManager; - private List dirents; - - public LoadDirTask(String repoID, String dirPath, DataManager dataManager) { - this.repoID = repoID; - this.dirPath = dirPath; - this.dataManager = dataManager; - } - - @Override - protected Void doInBackground(Void... params) { - try { - dirents = dataManager.getDirentsFromServer(repoID, dirPath); - } catch (SeafException e) { - err = e; - } - - return null; - } - - @SuppressLint("LongLogTag") - @Override - protected void onPostExecute(Void v) { - if (mStep != STEP_CHOOSE_DIR) { - return; - } - - getDirentsAdapter().clearDirents(); - showLoading(false); - if (err != null) { - int retCode = err.getCode(); - if (retCode == SeafConnection.HTTP_STATUS_REPO_PASSWORD_REQUIRED) { - showPasswordDialog(); - } else if (retCode == HttpURLConnection.HTTP_NOT_FOUND) { - final String message = String.format(getString(R.string.op_exception_folder_deleted), dirPath); - showShortToast(SeafilePathChooserActivity.this, message); - } else { - Log.d(DEBUG_TAG, "failed to load dirents: " + err.getMessage()); - err.printStackTrace(); - setErrorMessage(R.string.load_dir_fail); - } - return; - } - - if (dirents == null) { - Log.d(DEBUG_TAG, "failed to load dirents: no error present"); - setErrorMessage(R.string.load_dir_fail); - return; - } - - updateAdapterWithDirents(dirents); - } - } - - private void createNewFolder() { - if (!hasRepoWritePermission()) { - showShortToast(this, R.string.library_read_only); - return; - } - - final NewDirDialog dialog = new NewDirDialog(); - dialog.init(mNavContext.getRepoID(), mNavContext.getDirPath(), mAccount); - dialog.setTaskDialogLisenter(new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - refreshDir(); - } - }); - dialog.show(getSupportFragmentManager(), "PathChooserNewDirDialogFragment"); - } - - public boolean hasRepoWritePermission() { - if (mNavContext == null) { - return false; - } - if (mNavContext.getDirPermission() == null || mNavContext.getDirPermission().indexOf('w') == -1) { - return false; - } - return true; - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/ShareToSeafileActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/activity/ShareToSeafileActivity.java index 224512fcc..d214355b4 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/ShareToSeafileActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/activity/ShareToSeafileActivity.java @@ -1,5 +1,6 @@ package com.seafile.seadroid2.ui.activity; +import android.app.Activity; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -12,9 +13,17 @@ import android.os.Bundle; import android.os.IBinder; import android.provider.MediaStore.Images; + +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.app.AlertDialog; +import androidx.preference.SwitchPreferenceCompat; + import android.util.Log; +import com.blankj.utilcode.util.ToastUtils; import com.google.common.collect.Lists; import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeafException; @@ -26,8 +35,10 @@ import com.seafile.seadroid2.transfer.TransferService; import com.seafile.seadroid2.transfer.TransferService.TransferBinder; import com.seafile.seadroid2.ui.BaseActivity; +import com.seafile.seadroid2.ui.selector.ObjSelectorActivity; import com.seafile.seadroid2.util.ConcurrentAsyncTask; import com.seafile.seadroid2.util.Utils; +import com.seafile.seadroid2.util.sp.SettingsManager; import org.apache.commons.io.IOUtils; @@ -43,13 +54,13 @@ public class ShareToSeafileActivity extends BaseActivity { private static final String DEBUG_TAG = "ShareToSeafileActivity"; public static final String PASSWORD_DIALOG_FRAGMENT_TAG = "password_dialog_fragment_tag"; - private static final int CHOOSE_COPY_MOVE_DEST_REQUEST = 1; private TransferService mTxService; private ServiceConnection mConnection; private ArrayList localPathList; private Intent dstData; private Boolean isFinishActivity = false; + protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); @@ -57,10 +68,10 @@ protected void onCreate(Bundle savedInstanceState) { Bundle extras = intent.getExtras(); if (extras != null) { Object extraStream = extras.get(Intent.EXTRA_STREAM); - if(localPathList == null) localPathList = Lists.newArrayList(); + if (localPathList == null) localPathList = Lists.newArrayList(); loadSharedFiles(extraStream); } - + } private void loadSharedFiles(Object extraStream) { @@ -87,7 +98,7 @@ private String getSharedFilePath(Uri uri) { if (cursor == null || !cursor.moveToFirst()) { return null; } - String filePath = cursor.getString(cursor.getColumnIndex(Images.Media.DATA)); + String filePath = cursor.getString(cursor.getColumnIndexOrThrow(Images.Media.DATA)); return filePath; } } @@ -100,7 +111,7 @@ protected File[] doInBackground(Uri... uriList) { return null; List fileList = new ArrayList(); - for (Uri uri: uriList) { + for (Uri uri : uriList) { InputStream in = null; OutputStream out = null; @@ -132,23 +143,23 @@ protected File[] doInBackground(Uri... uriList) { @Override protected void onPostExecute(File... fileList) { - for (File file: fileList) { + for (File file : fileList) { if (file == null) { - showShortToast(ShareToSeafileActivity.this, R.string.saf_upload_path_not_available); + ToastUtils.showLong(R.string.saf_upload_path_not_available); } else { localPathList.add(file.getAbsolutePath()); } } if (localPathList == null || localPathList.size() == 0) { - showShortToast(ShareToSeafileActivity.this, R.string.not_supported_share); + ToastUtils.showLong(R.string.not_supported_share); finish(); return; } // Log.d(DEBUG_TAG, "share " + localPathList); - Intent chooserIntent = new Intent(ShareToSeafileActivity.this, SeafilePathChooserActivity.class); - startActivityForResult(chooserIntent, CHOOSE_COPY_MOVE_DEST_REQUEST); + Intent chooserIntent = new Intent(ShareToSeafileActivity.this, ObjSelectorActivity.class); + objSelectorLauncher.launch(chooserIntent); } } @@ -197,13 +208,11 @@ private void addUploadTask(Account account, String repoName, String repoID, Stri * @param repoID * @param targetDir * @param localPaths - * @param update - * update the file to avoid duplicates if true, - * upload directly, otherwise. - * + * @param update update the file to avoid duplicates if true, + * upload directly, otherwise. */ private void bindTransferService(final Account account, final String repoName, final String repoID, - final String targetDir, final ArrayList localPaths, final boolean update) { + final String targetDir, final ArrayList localPaths, final boolean update) { // start transfer service Intent txIntent = new Intent(this, TransferService.class); startService(txIntent); @@ -229,7 +238,7 @@ public void onServiceConnected(ComponentName className, IBinder service) { } Log.d(DEBUG_TAG, path + (update ? " updated" : " uploaded")); } - showShortToast(ShareToSeafileActivity.this, R.string.upload_started); + ToastUtils.showLong(R.string.upload_started); if (mTxService == null) return; @@ -251,33 +260,31 @@ public void onServiceDisconnected(ComponentName arg0) { Log.d(DEBUG_TAG, "try bind TransferService"); } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode != CHOOSE_COPY_MOVE_DEST_REQUEST) { - return; - } - - isFinishActivity = true; + private final ActivityResultLauncher objSelectorLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback() { + @Override + public void onActivityResult(ActivityResult o) { + if (o.getResultCode() != Activity.RESULT_OK) { + finish(); + return; + } - if (resultCode == RESULT_OK) { + isFinishActivity = true; if (!Utils.isNetworkOn()) { - showShortToast(this, R.string.network_down); + + ToastUtils.showLong(R.string.network_down); return; } - dstData = data; + + dstData = o.getData(); String dstRepoId, dstRepoName, dstDir; Account account; - dstRepoName = dstData.getStringExtra(SeafilePathChooserActivity.DATA_REPO_NAME); - dstRepoId = dstData.getStringExtra(SeafilePathChooserActivity.DATA_REPO_ID); - dstDir = dstData.getStringExtra(SeafilePathChooserActivity.DATA_DIR); - account = dstData.getParcelableExtra(SeafilePathChooserActivity.DATA_ACCOUNT); + dstRepoName = dstData.getStringExtra(ObjSelectorActivity.DATA_REPO_NAME); + dstRepoId = dstData.getStringExtra(ObjSelectorActivity.DATA_REPO_ID); + dstDir = dstData.getStringExtra(ObjSelectorActivity.DATA_DIR); + account = dstData.getParcelableExtra(ObjSelectorActivity.DATA_ACCOUNT); notifyFileOverwriting(account, dstRepoName, dstRepoId, dstDir); - Log.i(DEBUG_TAG, "CHOOSE_COPY_MOVE_DEST_REQUEST returns"); - } else { - finish(); } - } + }); @Override protected void onPostResume() { @@ -339,7 +346,7 @@ private void notifyFileOverwriting(final Account account, @Override public void onClick(DialogInterface dialog, int which) { addUpdateTask(account, repoName, repoID, targetDir, localPathList); - if(isFinishActivity) { + if (isFinishActivity) { Log.d(DEBUG_TAG, "finish!"); finish(); } @@ -348,7 +355,7 @@ public void onClick(DialogInterface dialog, int which) { .setNeutralButton(R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - if(isFinishActivity) { + if (isFinishActivity) { Log.d(DEBUG_TAG, "finish!"); finish(); } @@ -368,7 +375,7 @@ public void onClick(DialogInterface dialog, int which) { builder.show(); } else { if (!Utils.isNetworkOn()) { - showShortToast(this, R.string.network_down); + ToastUtils.showLong(R.string.network_down); return; } @@ -438,7 +445,7 @@ protected Void doInBackground(Void... params) { @Override protected void onPostExecute(Void aVoid) { if (fileExistent) - showShortToast(ShareToSeafileActivity.this, R.string.overwrite_existing_file_exist); + ToastUtils.showLong(R.string.overwrite_existing_file_exist); finish(); } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/adapter/BottomSheetAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/adapter/BottomSheetAdapter.java deleted file mode 100644 index 4547b5a81..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/adapter/BottomSheetAdapter.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.seafile.seadroid2.ui.adapter; - -import android.content.Context; -import android.graphics.Color; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import com.joanzapata.iconify.fonts.MaterialCommunityIcons; -import com.joanzapata.iconify.widget.IconTextView; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.data.EventDetailsFileItem; -import com.seafile.seadroid2.data.EventDetailsTree; - -import java.util.List; - -public class BottomSheetAdapter extends BaseAdapter { - private List items; - private Context context; - - public BottomSheetAdapter(Context context, List items) { - this.items = items; - this.context = context; - } - - @Override - public int getCount() { - return items.size(); - } - - @Override - public Object getItem(int i) { - return items.get(i); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View contentView, ViewGroup viewGroup) { - ViewHolder holder; - if (contentView == null) { - holder = new ViewHolder(); - contentView = View.inflate(context, R.layout.list_item_diff, null); - holder.file = (TextView) contentView.findViewById(R.id.tv_diff_file_name); - holder.icon = (IconTextView) contentView.findViewById(R.id.tv_diff_icon); - contentView.setTag(holder); - } else { - holder = (ViewHolder) contentView.getTag(); - } - - final EventDetailsFileItem eventDetailsFileItem = items.get(position); - - holder.file.setText(eventDetailsFileItem.getPath()); - switch (eventDetailsFileItem.geteType()) { - case FILE_ADDED: - case DIR_ADDED: - holder.file.setTextColor(Color.parseColor("#6CC644")); - holder.icon.setText("{" + MaterialCommunityIcons.mdi_plus.key() + " #6CC644}"); - break; - case FILE_MODIFIED: - holder.file.setTextColor(Color.parseColor("#D0B44C")); - holder.icon.setText("{" + MaterialCommunityIcons.mdi_pencil.key() + " #D0B44C}"); - break; - case FILE_RENAMED: - holder.file.setTextColor(Color.parseColor("#677A85")); - holder.icon.setText("{" + MaterialCommunityIcons.mdi_arrow_right.key() + " #677A85}"); - break; - case FILE_DELETED: - case DIR_DELETED: - holder.file.setTextColor(Color.parseColor("#BD2C00")); - holder.icon.setText("{" + MaterialCommunityIcons.mdi_minus.key() + " #BD2C00}"); - break; - } - - return contentView; - } - - static class ViewHolder { - public TextView file; - public IconTextView icon; - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafItemAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafItemAdapter.java deleted file mode 100644 index 740203672..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafItemAdapter.java +++ /dev/null @@ -1,606 +0,0 @@ -package com.seafile.seadroid2.ui.adapter; - -import android.util.SparseBooleanArray; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.google.common.collect.Lists; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeadroidApplication; -import com.seafile.seadroid2.config.GlideLoadConfig; -import com.seafile.seadroid2.data.DataManager; -import com.seafile.seadroid2.data.SeafCachedFile; -import com.seafile.seadroid2.data.SeafDirent; -import com.seafile.seadroid2.data.SeafGroup; -import com.seafile.seadroid2.data.SeafItem; -import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.transfer.DownloadTaskInfo; -import com.seafile.seadroid2.context.NavContext; -import com.seafile.seadroid2.ui.BrowserActivity; -import com.seafile.seadroid2.ui.repo.ReposFragment; -import com.seafile.seadroid2.util.GlideApp; -import com.seafile.seadroid2.util.Utils; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class SeafItemAdapter extends BaseAdapter { - - private static final String DEBUG_TAG = "SeafItemAdapter"; - private ArrayList items; - private BrowserActivity mActivity; - private boolean repoIsEncrypted; - private boolean actionModeOn; - private NavContext nav; - private DataManager dataManager; - - private SparseBooleanArray mSelectedItemsIds; - private List mSelectedItemsPositions = Lists.newArrayList(); - private List mSelectedItemsValues = Lists.newArrayList(); - - /** - * DownloadTask instance container - **/ - private List mDownloadTaskInfos; - - public SeafItemAdapter(BrowserActivity activity) { - mActivity = activity; - items = Lists.newArrayList(); - mSelectedItemsIds = new SparseBooleanArray(); - nav = mActivity.getNavContext(); - dataManager = mActivity.getDataManager(); - } - - /** - * sort files type - */ - public static final int SORT_BY_NAME = 9; - /** - * sort files type - */ - public static final int SORT_BY_LAST_MODIFIED_TIME = 10; - /** - * sort files order - */ - public static final int SORT_ORDER_ASCENDING = 11; - /** - * sort files order - */ - public static final int SORT_ORDER_DESCENDING = 12; - - @Override - public int getCount() { - return items.size(); - } - - @Override - public boolean isEmpty() { - return items.isEmpty(); - } - - /** - * To refresh downloading status of {@link ReposFragment#mListView}, - * use this method to update data set. - *

- * This method should be called after the "Download folder" menu was clicked. - * - * @param newList - */ - public void setDownloadTaskList(List newList) { - if (!equalLists(newList, mDownloadTaskInfos)) { - this.mDownloadTaskInfos = newList; - // redraw the list - notifyDataSetChanged(); - } - } - - /** - * Compare two lists - * - * @param newList - * @param oldList - * @return true if the two lists are equal, - * false, otherwise. - */ - private boolean equalLists(List newList, List oldList) { - if (newList == null && oldList == null) - return true; - - if ((newList == null && oldList != null) - || newList != null && oldList == null - || newList.size() != oldList.size()) - return false; - - return newList.equals(oldList); - } - - public void addEntry(SeafItem entry) { - items.add(entry); - // Collections.sort(items); - notifyDataSetChanged(); - } - - public void add(SeafItem entry) { - items.add(entry); - } - - public void notifyChanged() { - notifyDataSetChanged(); - } - - @Override - public SeafItem getItem(int position) { - return items.get(position); - } - - public void setItems(List dirents) { - items.clear(); - items.addAll(dirents); - this.mSelectedItemsIds.clear(); - this.mSelectedItemsPositions.clear(); - this.mSelectedItemsValues.clear(); - } - - public void deselectAllItems() { - mSelectedItemsIds.clear(); - mSelectedItemsPositions.clear(); - mSelectedItemsValues.clear(); - notifyDataSetChanged(); - } - - public void selectAllItems() { - mSelectedItemsIds.clear(); - mSelectedItemsPositions.clear(); - mSelectedItemsValues.clear(); - for (int i = 0; i < items.size(); i++) { - mSelectedItemsIds.put(i, true); - mSelectedItemsPositions.add(i); - mSelectedItemsValues.add((SeafDirent) items.get(i)); - } - notifyDataSetChanged(); - } - - @Override - public long getItemId(int position) { - return position; - } - - public void clear() { - items.clear(); - } - - public boolean areAllItemsSelectable() { - return false; - } - - public boolean isEnable(int position) { - SeafItem item = items.get(position); - return !(item instanceof SeafGroup); - } - - public boolean isClickable(int position) { - SeafItem item = items.get(position); - return !(item instanceof SeafGroup); - } - - public int getViewTypeCount() { - return 2; - } - - public int getItemViewType(int position) { - SeafItem item = items.get(position); - if (item instanceof SeafGroup) - return 0; - else - return 1; - } - - private View getRepoView(final SeafRepo repo, View convertView, ViewGroup parent) { - View view = convertView; - ViewHolder viewHolder; - - if (convertView == null) { - view = LayoutInflater.from(mActivity).inflate(R.layout.list_item_entry, null); - TextView title = (TextView) view.findViewById(R.id.list_item_title); - TextView subtitle = (TextView) view.findViewById(R.id.list_item_subtitle); - ImageView multiSelect = (ImageView) view.findViewById(R.id.list_item_multi_select_btn); - ImageView icon = (ImageView) view.findViewById(R.id.list_item_icon); - View action = view.findViewById(R.id.expandable_toggle_button); - ImageView downloadStatusIcon = (ImageView) view.findViewById(R.id.list_item_download_status_icon); - ProgressBar progressBar = (ProgressBar) view.findViewById(R.id.list_item_download_status_progressbar); - viewHolder = new ViewHolder(title, subtitle, multiSelect, icon, action, downloadStatusIcon, progressBar); - view.setTag(viewHolder); - } else { - viewHolder = (ViewHolder) convertView.getTag(); - } - - viewHolder.action.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mActivity.showRepoBottomSheet(repo); - } - }); - - viewHolder.multiSelect.setVisibility(View.GONE); - viewHolder.downloadStatusIcon.setVisibility(View.GONE); - viewHolder.progressBar.setVisibility(View.GONE); - viewHolder.title.setText(repo.getTitle()); - viewHolder.subtitle.setText(repo.getSubtitle()); - viewHolder.icon.setImageResource(repo.getIcon()); - if (repo.hasWritePermission()) { - viewHolder.action.setVisibility(View.VISIBLE); - } else { - viewHolder.action.setVisibility(View.INVISIBLE); - } - return view; - } - - private View getGroupView(SeafGroup group) { - View view = LayoutInflater.from(mActivity).inflate(R.layout.group_item, null); - TextView tv = (TextView) view.findViewById(R.id.textview_groupname); - String groupTitle = group.getTitle(); - if ("Organization".equals(groupTitle)) { - groupTitle = mActivity.getString(R.string.shared_with_all); - } - tv.setText(groupTitle); - return view; - } - - private View getDirentView(final SeafDirent dirent, View convertView, ViewGroup parent, final int position) { - View view = convertView; - final ViewHolder viewHolder; - - if (convertView == null) { - view = LayoutInflater.from(mActivity).inflate(R.layout.list_item_entry, null); - TextView title = (TextView) view.findViewById(R.id.list_item_title); - TextView subtitle = (TextView) view.findViewById(R.id.list_item_subtitle); - ImageView icon = (ImageView) view.findViewById(R.id.list_item_icon); - ImageView multiSelect = (ImageView) view.findViewById(R.id.list_item_multi_select_btn); - View action = view.findViewById(R.id.expandable_toggle_button); - ImageView downloadStatusIcon = (ImageView) view.findViewById(R.id.list_item_download_status_icon); - ProgressBar progressBar = (ProgressBar) view.findViewById(R.id.list_item_download_status_progressbar); - viewHolder = new ViewHolder(title, subtitle, multiSelect, icon, action, downloadStatusIcon, progressBar); - view.setTag(viewHolder); - } else { - viewHolder = (ViewHolder) convertView.getTag(); - } - viewHolder.action.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (dirent.isDir()) - mActivity.showDirBottomSheet(dirent.getTitle(), (SeafDirent) getItem(position)); - else - mActivity.showFileBottomSheet(dirent.getTitle(), (SeafDirent) getItem(position)); - } - }); - - if (actionModeOn) { - viewHolder.multiSelect.setVisibility(View.VISIBLE); - if (mSelectedItemsIds.get(position)) { - viewHolder.multiSelect.setImageResource(R.drawable.multi_select_item_checked); - } else - viewHolder.multiSelect.setImageResource(R.drawable.multi_select_item_unchecked); - - viewHolder.multiSelect.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (!mSelectedItemsIds.get(position)) { - viewHolder.multiSelect.setImageResource(R.drawable.multi_select_item_checked); - mSelectedItemsIds.put(position, true); - mSelectedItemsPositions.add(position); - mSelectedItemsValues.add(dirent); - } else { - viewHolder.multiSelect.setImageResource(R.drawable.multi_select_item_unchecked); - mSelectedItemsIds.delete(position); - mSelectedItemsPositions.remove(Integer.valueOf(position)); - mSelectedItemsValues.remove(dirent); - } - - mActivity.onItemSelected(); - } - }); - } else - viewHolder.multiSelect.setVisibility(View.GONE); - - viewHolder.title.setText(dirent.getTitle()); - viewHolder.icon.setTag(R.id.imageloader_uri, dirent.getTitle()); - if (dirent.isDir()) { - viewHolder.downloadStatusIcon.setVisibility(View.GONE); - viewHolder.progressBar.setVisibility(View.GONE); - - viewHolder.subtitle.setText(dirent.getSubtitle()); - - if (repoIsEncrypted) { - viewHolder.action.setVisibility(View.GONE); - } else { - viewHolder.action.setVisibility(View.VISIBLE); - } - - viewHolder.icon.setImageResource(dirent.getIcon()); - } else { - viewHolder.downloadStatusIcon.setVisibility(View.GONE); - viewHolder.progressBar.setVisibility(View.GONE); - viewHolder.action.setVisibility(View.VISIBLE); - setFileView(dirent, viewHolder, position); - } - - return view; - } - - /** - * use to refresh view of {@link ReposFragment #mPullRefreshListView} - *

- *

when to show download status icons
- * if the dirent is a file and already cached, show cached icon.
- * if the dirent is a file and waiting to download, show downloading icon.
- * if the dirent is a file and is downloading, show indeterminate progressbar.
- * ignore directories and repos.
- * - * @param dirent - * @param viewHolder - * @param position - */ - private void setFileView(SeafDirent dirent, ViewHolder viewHolder, int position) { - String repoName = nav.getRepoName(); - String repoID = nav.getRepoID(); - String filePath = Utils.pathJoin(nav.getDirPath(), dirent.name); - if (repoName == null || repoID == null) - return; - - File file = null; - try { - file = dataManager.getLocalRepoFile(repoName, repoID, filePath); - } catch (RuntimeException e) { - mActivity.showShortToast(mActivity, mActivity.getResources().getString(R.string.storage_space_insufficient)); - e.printStackTrace(); - return; - } - boolean cacheExists = false; - - if (file.exists() && file.length() == dirent.getFileSize()) { - SeafCachedFile cf = dataManager.getCachedFile(repoName, repoID, filePath); - String subtitle = null; - subtitle = dirent.getSubtitle(); - if (cf != null) { - cacheExists = true; - } - // show file download finished - viewHolder.downloadStatusIcon.setVisibility(View.VISIBLE); - viewHolder.downloadStatusIcon.setImageResource(R.drawable.list_item_download_finished); - viewHolder.subtitle.setText(subtitle); - viewHolder.progressBar.setVisibility(View.GONE); - - } else { - int downloadStatusIcon = R.drawable.list_item_download_waiting; - if (mDownloadTaskInfos != null) { - for (DownloadTaskInfo downloadTaskInfo : mDownloadTaskInfos) { - // use repoID and path to identify the task - if (downloadTaskInfo.repoID.equals(repoID) - && downloadTaskInfo.pathInRepo.equals(filePath)) { - switch (downloadTaskInfo.state) { - case INIT: - case FAILED: - downloadStatusIcon = R.drawable.list_item_download_waiting; - viewHolder.downloadStatusIcon.setVisibility(View.VISIBLE); - viewHolder.progressBar.setVisibility(View.GONE); - break; - case CANCELLED: - viewHolder.downloadStatusIcon.setVisibility(View.GONE); - viewHolder.progressBar.setVisibility(View.GONE); - break; - case TRANSFERRING: - viewHolder.downloadStatusIcon.setVisibility(View.GONE); - viewHolder.progressBar.setVisibility(View.VISIBLE); - break; - case FINISHED: - downloadStatusIcon = R.drawable.list_item_download_finished; - viewHolder.downloadStatusIcon.setVisibility(View.VISIBLE); - viewHolder.progressBar.setVisibility(View.GONE); - break; - default: - downloadStatusIcon = R.drawable.list_item_download_waiting; - break; - } - } - } - } else { - viewHolder.downloadStatusIcon.setVisibility(View.GONE); - viewHolder.progressBar.setVisibility(View.GONE); - } - - viewHolder.downloadStatusIcon.setImageResource(downloadStatusIcon); - viewHolder.subtitle.setText(dirent.getSubtitle()); - } - if (Utils.isViewableImage(file.getName())) { - String url = dataManager.getImageThumbnailLink(repoName, repoID, filePath, getThumbnailWidth()); - if (url == null) { - viewHolder.icon.setImageResource(dirent.getIcon()); - } else { - GlideApp.with(viewHolder.action) - .load(GlideLoadConfig.getGlideUrl(url)) - .apply(GlideLoadConfig.getOptions(dirent.size + "")) - .thumbnail(0.1f) - .into(viewHolder.icon); - - } - } else { - viewHolder.icon.setImageResource(dirent.getIcon()); - } - - } - - private View getCacheView(SeafCachedFile item, View convertView, ViewGroup parent) { - View view = convertView; - ViewHolder viewHolder; - - if (convertView == null) { - view = LayoutInflater.from(mActivity).inflate(R.layout.list_item_entry, null); - TextView title = (TextView) view.findViewById(R.id.list_item_title); - TextView subtitle = (TextView) view.findViewById(R.id.list_item_subtitle); - ImageView multiSelect = (ImageView) view.findViewById(R.id.list_item_multi_select_btn); - ImageView icon = (ImageView) view.findViewById(R.id.list_item_icon); - View action = view.findViewById(R.id.expandable_toggle_button); - ImageView downloadStatusIcon = (ImageView) view.findViewById(R.id.list_item_download_status_icon); - ProgressBar progressBar = (ProgressBar) view.findViewById(R.id.list_item_download_status_progressbar); - viewHolder = new ViewHolder(title, subtitle, multiSelect, icon, action, downloadStatusIcon, progressBar); - view.setTag(viewHolder); - } else { - viewHolder = (ViewHolder) convertView.getTag(); - } - - viewHolder.downloadStatusIcon.setVisibility(View.VISIBLE); - viewHolder.downloadStatusIcon.setImageResource(R.drawable.list_item_download_finished); - viewHolder.progressBar.setVisibility(View.GONE); - viewHolder.title.setText(item.getTitle()); - viewHolder.subtitle.setText(item.getSubtitle()); - viewHolder.icon.setImageResource(item.getIcon()); - viewHolder.action.setVisibility(View.INVISIBLE); - return view; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - SeafItem item = items.get(position); - if (item instanceof SeafRepo) { - return getRepoView((SeafRepo) item, convertView, parent); - } else if (item instanceof SeafGroup) { - return getGroupView((SeafGroup) item); - } else if (item instanceof SeafCachedFile) { - return getCacheView((SeafCachedFile) item, convertView, parent); - } else { - return getDirentView((SeafDirent) item, convertView, parent, position); - } - } - - public void setActionModeOn(boolean actionModeOn) { - this.actionModeOn = actionModeOn; - } - - public void toggleSelection(int position) { - if (mSelectedItemsIds.get(position)) { - // unselected - mSelectedItemsIds.delete(position); - mSelectedItemsPositions.remove(Integer.valueOf(position)); - mSelectedItemsValues.remove(items.get(position)); - } else { - mSelectedItemsIds.put(position, true); - mSelectedItemsPositions.add(position); - mSelectedItemsValues.add((SeafDirent) items.get(position)); - } - - mActivity.onItemSelected(); - notifyDataSetChanged(); - } - - public int getCheckedItemCount() { - return mSelectedItemsIds.size(); - } - - public List getSelectedItemsValues() { - return mSelectedItemsValues; - } - - private static class ViewHolder { - TextView title, subtitle; - ImageView icon, multiSelect, downloadStatusIcon; // downloadStatusIcon used to show file downloading status, it is invisible by - // default - ProgressBar progressBar; - View action; - - public ViewHolder(TextView title, - TextView subtitle, - ImageView multiSelect, - ImageView icon, - View action, - ImageView downloadStatusIcon, - ProgressBar progressBar - ) { - super(); - this.icon = icon; - this.multiSelect = multiSelect; - this.action = action; - this.title = title; - this.subtitle = subtitle; - this.downloadStatusIcon = downloadStatusIcon; - this.progressBar = progressBar; - } - } - - private int getThumbnailWidth() { - return (int) SeadroidApplication.getAppContext().getResources().getDimension(R.dimen.lv_icon_width); - } - - public void setEncryptedRepo(boolean encrypted) { - repoIsEncrypted = encrypted; - } - - /** - * Sorts the given list by type of {@link #SORT_BY_NAME} or {@link #SORT_BY_LAST_MODIFIED_TIME}, - * and by order of {@link #SORT_ORDER_ASCENDING} or {@link #SORT_ORDER_DESCENDING} - */ - public void sortFiles(int type, int order) { - List groups = Lists.newArrayList(); - List cachedFiles = Lists.newArrayList(); - List folders = Lists.newArrayList(); - List files = Lists.newArrayList(); - SeafGroup group = null; - - for (SeafItem item : items) { - if (item instanceof SeafGroup) { - group = (SeafGroup) item; - groups.add(group); - } else if (item instanceof SeafRepo) { - if (group == null) - continue; - group.addIfAbsent((SeafRepo) item); - } else if (item instanceof SeafCachedFile) { - cachedFiles.add(((SeafCachedFile) item)); - } else { - if (((SeafDirent) item).isDir()) - folders.add(((SeafDirent) item)); - else - files.add(((SeafDirent) item)); - } - } - - items.clear(); - - // sort SeafGroups and SeafRepos - for (SeafGroup sg : groups) { - sg.sortByType(type, order); - items.add(sg); - items.addAll(sg.getRepos()); - } - - // sort SeafDirents - if (type == SORT_BY_NAME) { - // sort by name, in ascending order - Collections.sort(folders, new SeafDirent.DirentNameComparator()); - Collections.sort(files, new SeafDirent.DirentNameComparator()); - if (order == SORT_ORDER_DESCENDING) { - Collections.reverse(folders); - Collections.reverse(files); - } - } else if (type == SORT_BY_LAST_MODIFIED_TIME) { - // sort by last modified time, in ascending order - Collections.sort(folders, new SeafDirent.DirentLastMTimeComparator()); - Collections.sort(files, new SeafDirent.DirentLastMTimeComparator()); - if (order == SORT_ORDER_DESCENDING) { - Collections.reverse(folders); - Collections.reverse(files); - } - } - // Adds the objects in the specified collection to this ArrayList - items.addAll(cachedFiles); - items.addAll(folders); - items.addAll(files); - } -} - diff --git a/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafItemCheckableAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafItemCheckableAdapter.java deleted file mode 100644 index 90cdcf0c3..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/adapter/SeafItemCheckableAdapter.java +++ /dev/null @@ -1,238 +0,0 @@ -package com.seafile.seadroid2.ui.adapter; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.ImageView; -import android.widget.TextView; - -import com.google.common.collect.Lists; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.data.DataManager; -import com.seafile.seadroid2.data.SeafCachedFile; -import com.seafile.seadroid2.data.SeafDirent; -import com.seafile.seadroid2.data.SeafItem; -import com.seafile.seadroid2.context.NavContext; -import com.seafile.seadroid2.ui.BrowserActivity; -import com.seafile.seadroid2.util.Utils; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -public class SeafItemCheckableAdapter extends BaseAdapter { - - public interface OnCheckedChangeListener { - void onCheckedChanged(SeafItem item, boolean isChecked); - } - - private ArrayList items; - private BrowserActivity mActivity; - private OnCheckedChangeListener listener = null; - - public SeafItemCheckableAdapter(BrowserActivity mActivity) { - this.mActivity = mActivity; - items = Lists.newArrayList(); - } - - public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { - this.listener = listener; - } - - @Override - public int getCount() { - return items.size(); - } - - @Override - public boolean isEmpty() { - return items.isEmpty(); - } - - public void addEntry(SeafItem entry) { - SeafItemWrap w = new SeafItemWrap(entry); - items.add(w); - // Collections.sort(items); - notifyDataSetChanged(); - } - - public void add(SeafItem entry) { - SeafItemWrap w = new SeafItemWrap(entry); - items.add(w); - } - - public void notifyChanged() { - notifyDataSetChanged(); - } - - @Override - public SeafItem getItem(int position) { - return items.get(position).item; - } - - public void setItem(SeafItem item, int listviewPosition) { - SeafItemWrap w = new SeafItemWrap(item); - items.set(listviewPosition, w); - notifyDataSetChanged(); - } - - @Override - public long getItemId(int position) { - return position; - } - - public int getNumSelected() { - int i = 0; - for (SeafItemWrap w : items) { - if (w.seleted) - i++; - } - return i; - } - - - public List getSelectedItems() { - List r = Lists.newArrayList(); - for (SeafItemWrap w : items) { - if (w.seleted) - r.add(w.item); - } - return r; - } - - public void removeSelectedItems() { - List tmp = Lists.newArrayList(); - for (SeafItemWrap w : items) { - if (w.seleted) - tmp.add(w); - } - items.removeAll(tmp); - notifyDataSetChanged(); - } - - public void clear() { - items.clear(); - } - - private View getDirentView(SeafItemWrap w, View convertView, - ViewGroup parent, int position) { - View view = convertView; - Viewholder viewHolder; - - if (convertView == null) { - view = LayoutInflater.from(mActivity).inflate(R.layout.list_item_entry_check, null); - TextView title = (TextView) view.findViewById(R.id.list_item_title); - TextView subtitle = (TextView) view.findViewById(R.id.list_item_subtitle); - ImageView icon = (ImageView) view.findViewById(R.id.list_item_icon); - CheckBox ck = (CheckBox) view.findViewById(R.id.list_item_checkbox); - viewHolder = new Viewholder(title, subtitle, icon, ck); - view.setTag(viewHolder); - } else { - viewHolder = (Viewholder) convertView.getTag(); - } - - viewHolder.title.setText(w.item.getTitle()); - SeafDirent dirent = (SeafDirent) w.item; - if (dirent.isDir()) { - viewHolder.subtitle.setText(""); - } else { - NavContext nav = mActivity.getNavContext(); - DataManager dataManager = mActivity.getDataManager(); - String repoName = nav.getRepoName(); - String repoID = nav.getRepoID(); - String filePath = Utils.pathJoin(nav.getDirPath(), dirent.name); - File file = dataManager.getLocalRepoFile(repoName, repoID, filePath); - if (file.exists()) - viewHolder.subtitle.setText(dirent.getSubtitle() + " cached"); - else - viewHolder.subtitle.setText(dirent.getSubtitle()); - } - viewHolder.icon.setImageResource(dirent.getIcon()); - viewHolder.checkbox.setChecked(w.isSelected()); - return view; - } - - private View getCacheView(SeafItemWrap w, View convertView, - ViewGroup parent, int position) { - View view = convertView; - final Viewholder viewHolder; - - if (convertView == null) { - view = LayoutInflater.from(mActivity).inflate(R.layout.list_item_entry_check, null); - TextView title = (TextView) view.findViewById(R.id.list_item_title); - TextView subtitle = (TextView) view.findViewById(R.id.list_item_subtitle); - ImageView icon = (ImageView) view.findViewById(R.id.list_item_icon); - CheckBox ck = (CheckBox) view.findViewById(R.id.list_item_checkbox); - viewHolder = new Viewholder(title, subtitle, icon, ck); - view.setTag(viewHolder); - ck.setTag(items.get(position)); - - ck.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - SeafItemWrap w = (SeafItemWrap)viewHolder.checkbox.getTag(); - w.setSeleted(isChecked); - if (listener != null) { - listener.onCheckedChanged(w.item, isChecked); - } - } - }); - } else { - viewHolder = (Viewholder) convertView.getTag(); - viewHolder.checkbox.setTag(w); - } - - viewHolder.title.setText(w.item.getTitle()); - viewHolder.subtitle.setText(w.item.getSubtitle()); - viewHolder.icon.setImageResource(w.item.getIcon()); - viewHolder.checkbox.setChecked(w.isSelected()); - return view; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - SeafItemWrap w = items.get(position); - if (w.item instanceof SeafCachedFile) { - return getCacheView(w, convertView, parent, position); - } else { - return getDirentView(w, convertView, parent, position); - } - } - - - private class Viewholder { - TextView title, subtitle; - ImageView icon; - CheckBox checkbox; - - public Viewholder(TextView title, TextView subtitle, ImageView icon, CheckBox checkbox) { - this.icon = icon; - this.title = title; - this.subtitle = subtitle; - this.checkbox = checkbox; - } - } - - public class SeafItemWrap { - SeafItem item; - boolean seleted; - - public SeafItemWrap(SeafItem item) { - this.item = item; - seleted = false; - } - - public void setSeleted(boolean selected) { - this.seleted = selected; - } - - public boolean isSelected() { - return seleted; - } - } - -} - diff --git a/app/src/main/java/com/seafile/seadroid2/ui/adapter/ViewPager2Adapter.java b/app/src/main/java/com/seafile/seadroid2/ui/adapter/ViewPager2Adapter.java new file mode 100644 index 000000000..622e291b8 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/adapter/ViewPager2Adapter.java @@ -0,0 +1,68 @@ +package com.seafile.seadroid2.ui.adapter; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +public class ViewPager2Adapter extends FragmentStateAdapter { + private final List fragments = new ArrayList<>(); + private final List fragmentIds = new ArrayList<>(); + private final HashSet createIds = new HashSet<>(); + + public ViewPager2Adapter(FragmentActivity fa) { + super(fa); + } + + public void addFragments(List fragments) { + this.fragments.clear(); + this.fragments.addAll(fragments); + + this.fragmentIds.clear(); + for (Fragment fragment : fragments) { + fragmentIds.add((long) fragment.hashCode()); + } + } + + public List getFragments() { + return fragments; + } + + public void replaceFragment(int position, Fragment fragment) { + fragments.set(position, fragment); + fragmentIds.set(position, (long) fragment.hashCode()); + createIds.add(fragmentIds.get(position)); + } + + public void removeFragment(int position) { + fragments.remove(position); + fragmentIds.remove(position); +// createIds.add(fragmentIds.get(position)); + } + + @NonNull + @Override + public Fragment createFragment(int position) { + createIds.add(fragmentIds.get(position)); + return fragments.get(position); + } + + @Override + public int getItemCount() { + return fragments.size(); + } + + @Override + public long getItemId(int position) { + return fragmentIds.get(position); + } + + @Override + public boolean containsItem(long itemId) { + return createIds.contains(itemId); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/ui/adapter/ViewPagerAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/adapter/ViewPagerAdapter.java new file mode 100644 index 000000000..de06618eb --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/adapter/ViewPagerAdapter.java @@ -0,0 +1,31 @@ +package com.seafile.seadroid2.ui.adapter; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.lifecycle.Lifecycle; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import java.util.ArrayList; + +public class ViewPagerAdapter extends FragmentStateAdapter { + private final ArrayList arrayList = new ArrayList<>(); + + public ViewPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) { + super(fragmentManager, lifecycle); + } + + public void addFragment(Fragment fragment) { + arrayList.add(fragment); + } + + @Override + public int getItemCount() { + return arrayList.size(); + } + + @Override + public Fragment createFragment(int position) { + return arrayList.get(position); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/base/BaseQuickActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/base/BaseQuickActivity.java new file mode 100644 index 000000000..af8fbcaac --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/base/BaseQuickActivity.java @@ -0,0 +1,44 @@ +package com.seafile.seadroid2.ui.base; + +import android.os.Bundle; + +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; + +import com.seafile.seadroid2.R; + +public class BaseQuickActivity extends AppCompatActivity { + // Primary toolbar and drawer toggle + private Toolbar mActionBarToolbar; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ActionBar ab = getSupportActionBar(); + if (ab != null) { + ab.setDisplayHomeAsUpEnabled(true); + } + } + + protected Toolbar getActionBarToolbar() { + if (mActionBarToolbar == null) { + mActionBarToolbar = (Toolbar) findViewById(R.id.toolbar_actionbar); + if (mActionBarToolbar != null) { + // Depending on which version of Android you are on the Toolbar or the ActionBar may be + // active so the a11y description is set here. + mActionBarToolbar.setNavigationContentDescription(getResources().getString(R.string + .navdrawer_description_a11y)); + setSupportActionBar(mActionBarToolbar); + } + } + return mActionBarToolbar; + } + + @Override + public void setContentView(int layoutResID) { + super.setContentView(layoutResID); + getActionBarToolbar(); + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/BaseAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/BaseAdapter.java new file mode 100644 index 000000000..f34393bef --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/BaseAdapter.java @@ -0,0 +1,13 @@ +package com.seafile.seadroid2.ui.base.adapter; + +import com.chad.library.adapter4.BaseQuickAdapter; +import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; +import com.seafile.seadroid2.util.SLogs; + +public abstract class BaseAdapter extends BaseQuickAdapter { + public void d(String d) { + SLogs.d(this.getClass().getSimpleName() + " => " + d); + } + + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/BaseMultiAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/BaseMultiAdapter.java new file mode 100644 index 000000000..4e2b42c07 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/BaseMultiAdapter.java @@ -0,0 +1,10 @@ +package com.seafile.seadroid2.ui.base.adapter; + +import com.chad.library.adapter4.BaseMultiItemAdapter; +import com.seafile.seadroid2.util.SLogs; + +public abstract class BaseMultiAdapter extends BaseMultiItemAdapter { + public void d(String d) { + SLogs.d(this.getClass().getSimpleName() + " => " + d); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/CustomLoadMoreAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/CustomLoadMoreAdapter.java index a6244433d..fffa47ab3 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/CustomLoadMoreAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/CustomLoadMoreAdapter.java @@ -7,8 +7,8 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; -import com.chad.library.adapter.base.loadState.LoadState; -import com.chad.library.adapter.base.loadState.trailing.TrailingLoadStateAdapter; +import com.chad.library.adapter4.loadState.LoadState; +import com.chad.library.adapter4.loadState.trailing.TrailingLoadStateAdapter; import com.seafile.seadroid2.R; public class CustomLoadMoreAdapter extends TrailingLoadStateAdapter { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/ParentAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/ParentAdapter.java deleted file mode 100644 index 15ffb9d63..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/ParentAdapter.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.seafile.seadroid2.ui.base.adapter; - -import com.blankj.utilcode.util.SizeUtils; -import com.chad.library.adapter.base.BaseQuickAdapter; - -public abstract class ParentAdapter extends BaseQuickAdapter { - public int DP_2 = SizeUtils.dp2px(2); - public int DP_4 = SizeUtils.dp2px(4); - public int DP_8 = SizeUtils.dp2px(8); - public int DP_16 = SizeUtils.dp2px(16); -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/ParentMultiAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/ParentMultiAdapter.java deleted file mode 100644 index 558686709..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/ParentMultiAdapter.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.seafile.seadroid2.ui.base.adapter; - -import com.blankj.utilcode.util.SizeUtils; -import com.chad.library.adapter.base.BaseMultiItemAdapter; - -public abstract class ParentMultiAdapter extends BaseMultiItemAdapter { - public int DP_2 = SizeUtils.dp2px(2); - public int DP_4 = SizeUtils.dp2px(4); - public int DP_8 = SizeUtils.dp2px(8); - public int DP_16 = SizeUtils.dp2px(16); -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/base/fragment/BaseDialogFragmentWithVM.java b/app/src/main/java/com/seafile/seadroid2/ui/base/fragment/BaseDialogFragmentWithVM.java new file mode 100644 index 000000000..36fc38491 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/base/fragment/BaseDialogFragmentWithVM.java @@ -0,0 +1,46 @@ +package com.seafile.seadroid2.ui.base.fragment; + +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; + +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.util.TUtil; + +public class BaseDialogFragmentWithVM extends DialogFragment { + private VM tvm; + + public VM getViewModel() { + return tvm; + } + + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + initViewModelClass(); + } + + protected void initViewModelClass() { + VM t = TUtil.getT(this, 0); + if (t == null) { + throw new IllegalStateException("VM generic parameters that inherit BaseViewModel cannot be instantiated"); + } + + ViewModel viewModel = new ViewModelProvider(this).get(t.getClass()); + tvm = (VM) viewModel; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (tvm != null) { + tvm.disposeAll(); + tvm = null; + } + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/base/fragment/BaseFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/base/fragment/BaseFragment.java new file mode 100644 index 000000000..22eeaab16 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/base/fragment/BaseFragment.java @@ -0,0 +1,11 @@ +package com.seafile.seadroid2.ui.base.fragment; + +import androidx.fragment.app.Fragment; + +import com.seafile.seadroid2.util.SLogs; + +public class BaseFragment extends Fragment { + public void d(String e) { + SLogs.d(this.getClass().getSimpleName() + " => " + e); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/base/fragment/BaseFragmentWithVM.java b/app/src/main/java/com/seafile/seadroid2/ui/base/fragment/BaseFragmentWithVM.java new file mode 100644 index 000000000..23188098a --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/base/fragment/BaseFragmentWithVM.java @@ -0,0 +1,54 @@ +package com.seafile.seadroid2.ui.base.fragment; + +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; + +import com.chad.library.adapter4.QuickAdapterHelper; +import com.seafile.seadroid2.ui.base.adapter.BaseAdapter; +import com.seafile.seadroid2.ui.base.adapter.BaseMultiAdapter; +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.util.TUtil; + +public class BaseFragmentWithVM extends BaseFragment { + private VM tvm; + + public VM getViewModel() { + return tvm; + } + + private QuickAdapterHelper helper; + + public QuickAdapterHelper createAdapterHelper(BaseAdapter adapter) { + if (null == helper) { + helper = new QuickAdapterHelper.Builder(adapter).build(); + } + return helper; + } + + public QuickAdapterHelper createMuiltAdapterHelper(BaseMultiAdapter adapter) { + if (null == helper) { + helper = new QuickAdapterHelper.Builder(adapter).build(); + } + return helper; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + initViewModelClass(); + } + + protected void initViewModelClass() { + VM t = TUtil.getT(this, 0); + if (t == null) { + throw new IllegalStateException("VM generic parameters that inherit BaseViewModel cannot be instantiated"); + } + + ViewModel viewModel = new ViewModelProvider(this).get(t.getClass()); + tvm = (VM) viewModel; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/base/fragment/CustomDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/base/fragment/CustomDialogFragment.java new file mode 100644 index 000000000..67c0fbe71 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/base/fragment/CustomDialogFragment.java @@ -0,0 +1,157 @@ +package com.seafile.seadroid2.ui.base.fragment; + +import android.app.Dialog; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.textfield.TextInputLayout; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.dialog_fragment.listener.OnRefreshDataListener; +import com.seafile.seadroid2.util.SLogs; + +import java.util.concurrent.TimeUnit; + +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Function; + +public abstract class CustomDialogFragment extends DialogFragment { + // Use this instance of the interface to deliver action events + private OnRefreshDataListener mListener; + + public OnRefreshDataListener getRefreshListener() { + return mListener; + } + + public void setRefreshListener(OnRefreshDataListener mListener) { + this.mListener = mListener; + } + + public void refreshData() { + if (mListener != null) { + mListener.onActionStatus(true); + } + } + + protected abstract int getLayoutId(); + + protected abstract void onPositiveClick(); + + private View rootView; + + public View getDialogView() { + return rootView; + } + + private TextView positiveView; + private TextView negativeView; + private ProgressBar loadingBar; + + public TextView getPositiveView() { + return positiveView; + } + + public TextView getNegativeView() { + return negativeView; + } + + public ProgressBar getLoadingBar() { + return loadingBar; + } + + public void showLoading(boolean isShow) { + if (positiveView != null) { + positiveView.setVisibility(isShow ? View.GONE : View.VISIBLE); + } + if (negativeView != null) { + negativeView.setVisibility(isShow ? View.GONE : View.VISIBLE); + } + if (loadingBar != null) { + loadingBar.setVisibility(isShow ? View.VISIBLE : View.GONE); + } + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + rootView = LayoutInflater.from(requireContext()).inflate(R.layout.layout_dialog_container, null); + LinearLayout containerView = rootView.findViewById(R.id.container); + + // + LayoutInflater.from(requireContext()).inflate(getLayoutId(), containerView); + + //action bar + positiveView = rootView.findViewById(R.id.text_view_positive); + negativeView = rootView.findViewById(R.id.text_view_negative); + loadingBar = rootView.findViewById(R.id.progress_bar); + + if (positiveView != null) { + positiveView.setOnClickListener(v -> onPositiveClick()); + } + + if (negativeView != null) { + negativeView.setOnClickListener(v -> dismiss()); + } + + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity()); + + //title + String dialogTitleString = getDialogTitleString(); + int dialogTitleRes = getDialogTitleRes(); + + if (dialogTitleRes != 0) { + builder.setTitle(dialogTitleRes); + } else if (!TextUtils.isEmpty(dialogTitleString)) { + builder.setTitle(dialogTitleString); + } + + // + initView(containerView); + + // + builder.setView(getDialogView()); + + // + initViewModel(); + + final AlertDialog dialog = builder.create(); + + onDialogCreated(dialog); + + return dialog; + } + + protected void initView(LinearLayout containerView) { + } + + protected void initViewModel() { + } + + /** + * This hook method is called right after the dialog is built. + */ + protected void onDialogCreated(Dialog dialog) { + } + + public int getDialogTitleRes() { + return 0; + } + + public String getDialogTitleString() { + return ""; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/base/fragment/RequestCustomDialogFragmentWithVM.java b/app/src/main/java/com/seafile/seadroid2/ui/base/fragment/RequestCustomDialogFragmentWithVM.java new file mode 100644 index 000000000..207831e8f --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/base/fragment/RequestCustomDialogFragmentWithVM.java @@ -0,0 +1,237 @@ +package com.seafile.seadroid2.ui.base.fragment; + +import android.app.Dialog; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.textfield.TextInputLayout; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.ui.dialog_fragment.listener.OnRefreshDataListener; +import com.seafile.seadroid2.util.SLogs; + +import java.util.concurrent.TimeUnit; + +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Function; + +public abstract class RequestCustomDialogFragmentWithVM extends BaseDialogFragmentWithVM { + // Use this instance of the interface to deliver action events + private OnRefreshDataListener mListener; + + public OnRefreshDataListener getRefreshListener() { + return mListener; + } + + public void setRefreshListener(OnRefreshDataListener mListener) { + this.mListener = mListener; + } + + public void refreshData() { + if (mListener != null) { + mListener.onActionStatus(true); + } + } + + public void refreshData(boolean status) { + if (mListener != null) { + mListener.onActionStatus(status); + } + } + + protected abstract int getLayoutId(); + + protected abstract void onPositiveClick(); + + private View rootView; + + public View getDialogView() { + return rootView; + } + + private TextView positiveView; + private TextView negativeView; + private ProgressBar loadingBar; + + public TextView getPositiveView() { + return positiveView; + } + + public TextView getNegativeView() { + return negativeView; + } + + public ProgressBar getLoadingBar() { + return loadingBar; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + if (getDialog() != null) { + getDialog().setCanceledOnTouchOutside(false); + } + + return super.onCreateView(inflater, container, savedInstanceState); + } + + public void showLoading(boolean isShow) { + if (positiveView != null) { + positiveView.setVisibility(isShow ? View.GONE : View.VISIBLE); + } + if (negativeView != null) { + negativeView.setVisibility(isShow ? View.GONE : View.VISIBLE); + } + if (loadingBar != null) { + loadingBar.setVisibility(isShow ? View.VISIBLE : View.GONE); + } + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + rootView = LayoutInflater.from(requireContext()).inflate(R.layout.layout_dialog_container, null); + LinearLayout containerView = rootView.findViewById(R.id.container); + + // + LayoutInflater.from(requireContext()).inflate(getLayoutId(), containerView); + + //action bar + positiveView = rootView.findViewById(R.id.text_view_positive); + negativeView = rootView.findViewById(R.id.text_view_negative); + loadingBar = rootView.findViewById(R.id.progress_bar); + + if (positiveView != null) { + positiveView.setOnClickListener(v -> onPositiveClick()); + } + + if (negativeView != null) { + negativeView.setOnClickListener(v -> dismiss()); + } + + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity()); + + //title + String dialogTitleString = getDialogTitleString(); + int dialogTitleRes = getDialogTitleRes(); + + if (dialogTitleRes != 0) { + builder.setTitle(dialogTitleRes); + } else if (!TextUtils.isEmpty(dialogTitleString)) { + builder.setTitle(dialogTitleString); + } + + // + initView(containerView); + + // + builder.setView(getDialogView()); + + // + initViewModel(); + + final AlertDialog dialog = builder.create(); + + onDialogCreated(dialog); + + return dialog; + } + + protected void initView(LinearLayout containerView) { + } + + protected void initViewModel() { + } + + /** + * This hook method is called right after the dialog is built. + */ + protected void onDialogCreated(Dialog dialog) { + } + + public int getDialogTitleRes() { + return 0; + } + + public String getDialogTitleString() { + return ""; + } + + + /////////////////////////////////////////TextInput Error/////////////////////////////////////////// + private int textInputLayoutId; + + private final int countDownDuration = 2; + private Disposable disposable; + + public void setInputError(@IdRes int textInputLayoutId, String error) { + this.textInputLayoutId = textInputLayoutId; + + TextInputLayout passwordInputLayout = getDialogView().findViewById(textInputLayoutId); + passwordInputLayout.setError(error); + + if (!TextUtils.isEmpty(error)) { + restoreTextInputError(); + } + } + + private void restoreTextInputError() { + if (disposable != null && !disposable.isDisposed()) { + disposable.dispose(); + } + + Observable.interval(1, TimeUnit.SECONDS) + .take(countDownDuration + 1) + .map(new Function() { + @Override + public Long apply(Long aLong) throws Exception { + return countDownDuration - aLong; + } + }) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(observer); + + getViewModel().addDisposable(disposable); + } + + + private final io.reactivex.Observer observer = new io.reactivex.Observer() { + @Override + public void onSubscribe(Disposable d) { + disposable = d; + } + + @Override + public void onNext(Long aLong) { + SLogs.e("倒计时:" + aLong); + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onComplete() { + SLogs.e("倒计时:完成"); + + if (textInputLayoutId != 0) { + setInputError(textInputLayoutId, null); + } + } + }; +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/BaseViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/base/viewholder/BaseViewHolder.java similarity index 62% rename from app/src/main/java/com/seafile/seadroid2/ui/base/adapter/BaseViewHolder.java rename to app/src/main/java/com/seafile/seadroid2/ui/base/viewholder/BaseViewHolder.java index c23a1ce3d..1055100a8 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/base/adapter/BaseViewHolder.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/base/viewholder/BaseViewHolder.java @@ -1,11 +1,11 @@ -package com.seafile.seadroid2.ui.base.adapter; +package com.seafile.seadroid2.ui.base.viewholder; import android.view.View; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; -public abstract class BaseViewHolder extends RecyclerView.ViewHolder { +public class BaseViewHolder extends RecyclerView.ViewHolder { public BaseViewHolder(@NonNull View itemView) { super(itemView); } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/base/viewmodel/BaseViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/base/viewmodel/BaseViewModel.java new file mode 100644 index 000000000..88431bb93 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/base/viewmodel/BaseViewModel.java @@ -0,0 +1,207 @@ +package com.seafile.seadroid2.ui.base.viewmodel; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import com.blankj.utilcode.util.GsonUtils; +import com.blankj.utilcode.util.ToastUtils; +import com.seafile.seadroid2.BuildConfig; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.SeadroidApplication; +import com.seafile.seadroid2.SeafException; +import com.seafile.seadroid2.data.model.ResultModel; +import com.seafile.seadroid2.util.SLogs; + +import java.io.Closeable; +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.util.HashMap; +import java.util.Map; + +import javax.net.ssl.SSLHandshakeException; + +import io.reactivex.Completable; +import io.reactivex.Flowable; +import io.reactivex.Single; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; +import io.reactivex.schedulers.Schedulers; +import kotlin.Pair; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import retrofit2.HttpException; + +public class BaseViewModel extends ViewModel { + private final MutableLiveData RefreshLiveData = new MutableLiveData<>(false); + private final MutableLiveData> ExceptionLiveData = new MutableLiveData<>(); + + public MutableLiveData getRefreshLiveData() { + return RefreshLiveData; + } + + public MutableLiveData> getExceptionLiveData() { + return ExceptionLiveData; + } + + public void showRefresh() { + getRefreshLiveData().setValue(true); + } + + public void closeRefresh() { + getRefreshLiveData().setValue(false); + } + + private final CompositeDisposable compositeDisposable = new CompositeDisposable(); + + public void addDisposable(@NonNull Disposable closeable) { + compositeDisposable.add(closeable); + } + + private final Consumer throwable = throwable -> { + SLogs.e(throwable); + closeRefresh(); + + if (BuildConfig.DEBUG) { + ToastUtils.showLong(throwable.getMessage()); + } + + //check and callback + checkException(throwable); + }; + + public void disposeAll() { + compositeDisposable.clear(); + SLogs.d("CompositeDisposable clear all"); + } + + @Override + protected void onCleared() { + super.onCleared(); + SLogs.d("onCleared"); + if (!compositeDisposable.isDisposed()) { + compositeDisposable.dispose(); + SLogs.d("CompositeDisposable dispose"); + } + } + + public Map generateRequestBody(Map requestDataMap) { + Map requestBodyMap = new HashMap<>(); + if (requestDataMap == null || requestDataMap.isEmpty()) { + requestBodyMap.put("x-test", RequestBody.create(MediaType.parse("multipart/form-data"), "test")); + return requestBodyMap; + } + + for (String key : requestDataMap.keySet()) { + String s = requestDataMap.get(key); + if (!TextUtils.isEmpty(s)) { + RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), s); + requestBodyMap.put(key, requestBody); + } + } + return requestBodyMap; + } + + public void addSingleDisposable(Single single, Consumer consumer) { + compositeDisposable.add(single + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(consumer, throwable)); + } + + public void addSingleDisposable(Single single, Consumer consumer, Consumer throwable) { + compositeDisposable.add(single + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(consumer, throwable)); + } + + public void addFlowableDisposable(Flowable flowable, Consumer consumer) { + compositeDisposable.add(flowable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread(), true) + .subscribe(consumer, throwable)); + } + + public void addCompletableDisposable(Completable completable, Action action) { + compositeDisposable.add(completable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(action, throwable)); + } + + public void addCompletableDisposable(Completable completable, Action action, Consumer throwable1) { + compositeDisposable.add(completable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(action, throwable1)); + } + + + private void checkException(Throwable throwable) { + if (throwable instanceof HttpException) { + HttpException httpException = (HttpException) throwable; + + if (httpException.getCause() instanceof SSLHandshakeException) { + getExceptionLiveData().setValue(new kotlin.Pair<>(httpException.code(), SeafException.sslException)); + } else { + getExceptionLiveData().setValue(new kotlin.Pair<>(httpException.code(), SeafException.networkException)); + } + } + } + + //TODO 优化异常返回 + public String getErrorMsgByThrowable(Throwable throwable) throws IOException { + if (throwable instanceof HttpException) { + HttpException httpException = (HttpException) throwable; + if (httpException.response() != null && httpException.response().errorBody() != null) { + + if (504 == httpException.code()){ + return SeadroidApplication.getAppContext().getString(R.string.network_unavailable); + } + + String json = httpException.response().errorBody().string(); + if (TextUtils.isEmpty(json)) { + return SeadroidApplication.getAppContext().getString(R.string.unknow_error); + } + + if (json.contains("{\"error_msg\":")) { + ResultModel resultModel = GsonUtils.fromJson(json, ResultModel.class); + if (TextUtils.equals("Wrong password", resultModel.error_msg)) { + return SeadroidApplication.getAppContext().getString(R.string.wrong_password); + } + + return resultModel.error_msg; + } + + return json; + } + +// if (httpException.code() == 404) { +// if (httpException.response() != null && httpException.response().errorBody() != null) { +// +// } +// } else if (httpException.code() == 400) { +// +//// {"error_msg":"Wrong password"} +// +// return SeadroidApplication.getAppContext().getString(R.string.no_permission); +// } + } else if (throwable instanceof SSLHandshakeException) { + SSLHandshakeException sslHandshakeException = (SSLHandshakeException) throwable; + SLogs.e(sslHandshakeException.getMessage()); + return SeadroidApplication.getAppContext().getString(R.string.network_unavailable); + } else if (throwable instanceof SocketTimeoutException) { + SocketTimeoutException socketTimeoutException = (SocketTimeoutException) throwable; + SLogs.e(socketTimeoutException.getMessage()); + return SeadroidApplication.getAppContext().getString(R.string.network_unavailable); + } + + return SeadroidApplication.getAppContext().getString(R.string.unknow_error); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/bottomsheet/BaseBottomSheetDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/bottomsheet/BaseBottomSheetDialogFragment.java deleted file mode 100644 index df85e6c6c..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/bottomsheet/BaseBottomSheetDialogFragment.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.seafile.seadroid2.ui.bottomsheet; - -import android.app.Dialog; -import android.os.Bundle; -import androidx.annotation.NonNull; -import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.google.android.material.bottomsheet.BottomSheetDialog; -import com.google.android.material.bottomsheet.BottomSheetDialogFragment; -import androidx.coordinatorlayout.widget.CoordinatorLayout; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; - -import com.blankj.utilcode.util.BarUtils; -import com.blankj.utilcode.util.ScreenUtils; -import com.seafile.seadroid2.R; - -public abstract class BaseBottomSheetDialogFragment extends BottomSheetDialogFragment { - - private View mRootView; - - public BottomSheetBehavior behavior; - - public View getRootView() { - return mRootView; - } - - public void e(String e) { - Log.e(this.getClass().getSimpleName(), " => " + e); - } - - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - return new BottomSheetDialog(requireContext(), R.style.BottomSheetStyle); - } - - @NonNull - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - mRootView = inflater.inflate(getLayoutId(), container, false); - initView(); - if (getCancelId() != View.NO_ID) { - View cancelView = mRootView.findViewById(getCancelId()); - if (cancelView != null) { - cancelView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - dismiss(); - } - }); - } - } - return mRootView; - } - - @Override - public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - init(); - } - - @Override - public void onStart() { - super.onStart(); - ((View) getRootView().getParent()).setBackgroundResource(R.color.transparent); - - BottomSheetDialog dialog = (BottomSheetDialog) getDialog(); - FrameLayout bottomSheet = dialog.getDelegate().findViewById(com.google.android.material.R.id.design_bottom_sheet); - - if (bottomSheet != null) { - CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) bottomSheet.getLayoutParams(); - layoutParams.height = getHeight(); - behavior = BottomSheetBehavior.from(bottomSheet); - behavior.setState(BottomSheetBehavior.STATE_COLLAPSED); - } - } - - private int getHeight() { - int height = ScreenUtils.getScreenHeight(); - if (BarUtils.isNavBarVisible(getActivity().getWindow())) { - height -= BarUtils.getNavBarHeight(); - } - return height; - } - - public BottomSheetBehavior getBehavior() { - return behavior; - } - - protected abstract int getLayoutId(); - - protected abstract int getCancelId(); - - protected abstract void initView(); - - protected abstract void init(); -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/bottomsheet/BottomSheetListFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/bottomsheet/BottomSheetListFragment.java deleted file mode 100644 index 41d43e2c3..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/bottomsheet/BottomSheetListFragment.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.seafile.seadroid2.ui.bottomsheet; - -import android.content.DialogInterface; -import androidx.annotation.Nullable; -import androidx.coordinatorlayout.widget.CoordinatorLayout; - -import android.view.View; -import android.widget.AdapterView; -import android.widget.ListAdapter; -import android.widget.ListView; - -import com.seafile.seadroid2.R; - -public class BottomSheetListFragment extends BaseBottomSheetDialogFragment { - - private CoordinatorLayout container; - private ListAdapter adapter; - private ListView listView; - private AdapterView.OnItemClickListener onItemClickListener; - private DialogInterface.OnDismissListener onDismissListener; - - @Override - protected int getLayoutId() { - return R.layout.dialog_bottom_sheet_list; - } - - @Override - protected int getCancelId() { - return R.id.button_cancel; - } - - @Override - protected void initView() { - listView = getRootView().findViewById(R.id.list_view); - container = getRootView().findViewById(R.id.bottom_sheet_container); - } - - @Nullable - public ListView getListView() { - return listView; - } - - public void setOnItemClickListener(AdapterView.OnItemClickListener onItemClickListener) { - this.onItemClickListener = onItemClickListener; - } - - public void setOnDismissListener(DialogInterface.OnDismissListener onDismissListener) { - this.onDismissListener = onDismissListener; - } - - public void setAdapter(ListAdapter adapter) { - this.adapter = adapter; - } - - - private float downY = 0f, moveY; - - @Override - protected void init() { - if (adapter != null) { - listView.setAdapter(adapter); - } - - if (onItemClickListener != null) { - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - onItemClickListener.onItemClick(parent, view, position, id); - } - }); - } - - if (getDialog() != null && onDismissListener != null) { - getDialog().setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - onDismissListener.onDismiss(dialog); - } - }); - } - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/bottomsheet/BottomSheetTextFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/bottomsheet/BottomSheetTextFragment.java deleted file mode 100644 index b461173bd..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/bottomsheet/BottomSheetTextFragment.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.seafile.seadroid2.ui.bottomsheet; - -import android.os.Bundle; -import androidx.annotation.Nullable; -import android.view.View; -import android.widget.TextView; - -import com.seafile.seadroid2.R; - -public class BottomSheetTextFragment extends BaseBottomSheetDialogFragment { - - private TextView textView; - private String text; - - public static BottomSheetTextFragment newInstance(String text) { - BottomSheetTextFragment fragment = new BottomSheetTextFragment(); - Bundle args = new Bundle(); - args.putString("text",text); - fragment.setArguments(args); - return fragment; - } - - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (getArguments() != null) { - text = getArguments().getString("text"); - } - } - - @Override - protected int getLayoutId() { - return R.layout.dialog_bottom_sheet_text; - } - - @Override - protected int getCancelId() { - return View.NO_ID; - } - - @Override - protected void initView() { - textView = getRootView().findViewById(R.id.text); - } - - public TextView getTextView() { - return textView; - } - - @Override - protected void init() { - this.textView.setText(text); - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraSyncAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraSyncAdapter.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/cameraupload/CameraSyncAdapter.java rename to app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraSyncAdapter.java index b90f393de..7e14570f9 100644 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraSyncAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraSyncAdapter.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.cameraupload; +package com.seafile.seadroid2.ui.camera_upload; import static android.app.PendingIntent.FLAG_IMMUTABLE; @@ -18,18 +18,17 @@ import android.os.Bundle; import android.os.IBinder; import android.provider.MediaStore; +import android.util.Log; import androidx.core.app.NotificationCompat; -import android.util.Log; - import com.google.common.base.Joiner; import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeadroidApplication; import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.util.sp.SettingsManager; import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.AccountManager; +import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.data.CameraSyncEvent; import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.data.SeafDirent; @@ -65,8 +64,7 @@ public class CameraSyncAdapter extends AbstractThreadedSyncAdapter { private ContentResolver contentResolver; - private SettingsManager settingsMgr = SettingsManager.instance(); - private com.seafile.seadroid2.account.AccountManager manager; + private SettingsManager settingsMgr = SettingsManager.getInstance(); private CameraUploadDBHelper dbHelper; private String targetRepoId; @@ -124,7 +122,6 @@ public CameraSyncAdapter(Context context) { // Log.d(DEBUG_TAG, "CameraSyncAdapter created."); contentResolver = context.getContentResolver(); - manager = new AccountManager(context); dbHelper = CameraUploadDBHelper.getInstance(); } @@ -192,7 +189,8 @@ public void onPerformSync(android.accounts.Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { - Log.e(DEBUG_TAG, "onPerformSync"); + + SLogs.d("onPerformSync"); synchronized (this) { cancelled = false; @@ -225,7 +223,7 @@ public void onPerformSync(android.accounts.Account account, return; } - Account seafileAccount = manager.getSeafileAccount(account); + Account seafileAccount = SupportAccountManager.getInstance().getSeafileAccount(account); DataManager dataManager = new DataManager(seafileAccount); /** @@ -332,7 +330,7 @@ public void onPerformSync(android.accounts.Account account, } } SeadroidApplication.getInstance().setScanUploadStatus(CameraSyncStatus.SCAN_END); - SettingsManager.instance().saveUploadCompletedTime(Utils.getSyncCompletedTime()); + SettingsManager.getInstance().saveUploadCompletedTime(Utils.getSyncCompletedTime()); EventBus.getDefault().post(new CameraSyncEvent("end")); } diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraSyncService.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraSyncService.java similarity index 86% rename from app/src/main/java/com/seafile/seadroid2/cameraupload/CameraSyncService.java rename to app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraSyncService.java index 592d3200e..55e05b2ac 100644 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraSyncService.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraSyncService.java @@ -1,14 +1,8 @@ -package com.seafile.seadroid2.cameraupload; +package com.seafile.seadroid2.ui.camera_upload; import android.app.Service; import android.content.Intent; import android.os.IBinder; -import android.util.Log; - -import com.blankj.utilcode.util.TimeUtils; - -import java.util.Timer; -import java.util.TimerTask; /** * Camera Sync Service. diff --git a/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadConfigActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadConfigActivity.java new file mode 100644 index 000000000..ff2af5b5b --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadConfigActivity.java @@ -0,0 +1,188 @@ +package com.seafile.seadroid2.ui.camera_upload; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Pair; +import android.view.View; + +import androidx.fragment.app.Fragment; +import androidx.viewpager2.widget.ViewPager2; + +import com.google.android.material.tabs.TabLayoutMediator; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.data.db.entities.RepoModel; +import com.seafile.seadroid2.databinding.CucActivityLayoutBinding; +import com.seafile.seadroid2.ui.BaseActivity; +import com.seafile.seadroid2.ui.adapter.ViewPager2Adapter; +import com.seafile.seadroid2.ui.camera_upload.config_fragment.BucketsFragment; +import com.seafile.seadroid2.ui.camera_upload.config_fragment.ConfigWelcomeFragment; +import com.seafile.seadroid2.ui.camera_upload.config_fragment.HowToUploadFragment; +import com.seafile.seadroid2.ui.camera_upload.config_fragment.ReadyToScanFragment; +import com.seafile.seadroid2.ui.camera_upload.config_fragment.WhatToUploadFragment; +import com.seafile.seadroid2.ui.selector.ObjSelectorActivity; +import com.seafile.seadroid2.ui.selector.ObjSelectorFragment; +import com.seafile.seadroid2.ui.settings.SettingsFragment; +import com.seafile.seadroid2.util.SystemSwitchUtils; +import com.seafile.seadroid2.util.sp.SettingsManager; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Camera upload configuration helper + */ +public class CameraUploadConfigActivity extends BaseActivity { + + private RepoModel repoModel; + private Account mAccount; + + /** + * handling data from cloud library page + */ + private boolean isChooseRepoPage; + /** + * handling data from local directory page + */ + private boolean isChooseDirPage; + private int mCurrentPosition; + + private CucActivityLayoutBinding binding; + private final List fragmentList = new ArrayList<>(); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = CucActivityLayoutBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + if (getSupportActionBar() != null) + getSupportActionBar().hide(); + + isChooseRepoPage = getIntent().getBooleanExtra(SettingsFragment.CAMERA_UPLOAD_REMOTE_LIBRARY, false); + isChooseDirPage = getIntent().getBooleanExtra(SettingsFragment.CAMERA_UPLOAD_LOCAL_DIRECTORIES, false); + + initView(); + } + + private void initView() { + fragmentList.clear(); + + if (isChooseRepoPage) { + fragmentList.add(new ObjSelectorFragment()); + binding.tabs.setVisibility(View.GONE); + } else if (isChooseDirPage) { + fragmentList.add(new BucketsFragment()); + binding.tabs.setVisibility(View.GONE); + } else { + fragmentList.add(new ConfigWelcomeFragment()); + fragmentList.add(new HowToUploadFragment()); + fragmentList.add(new WhatToUploadFragment()); + fragmentList.add(new BucketsFragment()); + fragmentList.add(new ObjSelectorFragment()); + fragmentList.add(new ReadyToScanFragment()); + } + + ViewPager2Adapter viewPager2Adapter = new ViewPager2Adapter(this); + viewPager2Adapter.addFragments(fragmentList); + binding.pager.setAdapter(viewPager2Adapter); + binding.pager.setOffscreenPageLimit(6); + binding.pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + mCurrentPosition = position; + + checkLastPosition(position); + } + }); + + new TabLayoutMediator(binding.tabs, binding.pager, (tab, position) -> { + }).attach(); + + binding.confirmButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + saveSettings(); + } + }); + + checkLastPosition(0); + } + + private void checkLastPosition(int position) { + int size = fragmentList.size(); + if (size == 1) { + binding.confirmButton.setVisibility(View.VISIBLE); + } else if (position == size - 1) { + binding.confirmButton.setVisibility(View.VISIBLE); + } else { + binding.confirmButton.setVisibility(View.GONE); + } + } + + public void saveCameraUploadInfo(Account account, RepoModel seafRepo) { + repoModel = seafRepo; + mAccount = account; + } + + private void saveSettings() { + SystemSwitchUtils.getInstance(this).syncSwitchUtils(); + for (Fragment fragment : fragmentList) { + if (fragment instanceof HowToUploadFragment) { + HowToUploadFragment howToUploadFragment = (HowToUploadFragment) fragment; + SettingsManager.getInstance().saveDataPlanAllowed(howToUploadFragment.getHowToUpload()); + } else if (fragment instanceof WhatToUploadFragment) { + WhatToUploadFragment whatToUploadFragment = (WhatToUploadFragment) fragment; + SettingsManager.getInstance().saveVideosAllowed(whatToUploadFragment.getWhatToUpload()); + } else if (fragment instanceof BucketsFragment) { + BucketsFragment bucketsFragment = (BucketsFragment) fragment; + + List selectedBuckets = bucketsFragment.getSelectedBuckets(); + if (bucketsFragment.isAutoScanSelected()) { + selectedBuckets.clear(); + } + SettingsManager.getInstance().setCameraUploadBucketList(selectedBuckets); + + } else if (fragment instanceof ObjSelectorFragment) { + ObjSelectorFragment cloudLibrarySelectorFragment = (ObjSelectorFragment) fragment; + Pair pair = cloudLibrarySelectorFragment.getCameraUploadInfo(); + mAccount = pair.first; + repoModel = pair.second; + } + } + + Intent intent = new Intent(); + // update cloud library data + if (repoModel != null && mAccount != null) { + intent.putExtra(ObjSelectorActivity.DATA_REPO_NAME, repoModel.repo_name); + intent.putExtra(ObjSelectorActivity.DATA_REPO_ID, repoModel.repo_id); + intent.putExtra(ObjSelectorActivity.DATA_ACCOUNT, mAccount); + } + + setResult(RESULT_OK, intent); + finish(); + } + + + @Override + public void onBackPressed() { + if (mCurrentPosition == 0) { + setResult(RESULT_CANCELED); + super.onBackPressed(); + } else { + // navigate to previous page when press back button + mCurrentPosition -= 1; + binding.pager.setCurrentItem(mCurrentPosition); + } + } + + public boolean isChooseRepoPage() { + return isChooseRepoPage; + } + + public boolean isChooseDirPage() { + return isChooseDirPage; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadDBHelper.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadDBHelper.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadDBHelper.java rename to app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadDBHelper.java index 3b573942f..087c57765 100644 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadDBHelper.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadDBHelper.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.cameraupload; +package com.seafile.seadroid2.ui.camera_upload; import android.content.ContentValues; import android.content.Context; diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadManager.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadManager.java similarity index 83% rename from app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadManager.java rename to app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadManager.java index 3436a05b6..e238b8719 100644 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadManager.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadManager.java @@ -1,13 +1,14 @@ -package com.seafile.seadroid2.cameraupload; +package com.seafile.seadroid2.ui.camera_upload; import android.content.ContentResolver; -import android.content.Context; import android.os.Bundle; import android.util.Log; import com.seafile.seadroid2.BuildConfig; import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.AccountManager; +import com.seafile.seadroid2.account.SupportAccountManager; + +import java.util.List; /** @@ -23,12 +24,6 @@ public class CameraUploadManager { */ public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".cameraupload.provider"; - AccountManager accountManager; - - public CameraUploadManager(Context context) { - accountManager = new AccountManager(context); - } - /** * Is camera upload enabled? * @@ -45,7 +40,8 @@ public boolean isCameraUploadEnabled() { * @return the account if camera is enabled, null otherwise. */ public Account getCameraAccount() { - for (Account account : accountManager.getAccountList()) { + List list = SupportAccountManager.getInstance().getAccountList(); + for (Account account : list) { int isSyncable = ContentResolver.getIsSyncable(account.getAndroidAccount(), AUTHORITY); if (isSyncable > 0) return account; @@ -68,7 +64,7 @@ public void performSync() { public void performFullSync() { Bundle b = new Bundle(); b.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true); - Log.d(CameraUploadManager.class.getName(),"performFullSync()~"); + Log.d(CameraUploadManager.class.getName(), "performFullSync()~"); Account cameraAccount = getCameraAccount(); if (cameraAccount != null) @@ -76,7 +72,7 @@ public void performFullSync() { } public void performFullSyncIfEnable() { - if (!isCameraUploadEnabled()){ + if (!isCameraUploadEnabled()) { return; } performFullSync(); @@ -88,7 +84,8 @@ public void performFullSyncIfEnable() { * @param account An account. must not be null. */ public void setCameraAccount(Account account) { - for (Account a : accountManager.getAccountList()) { + List list = SupportAccountManager.getInstance().getAccountList(); + for (Account a : list) { if (a.equals(account)) { // enable camera upload on this account ContentResolver.setIsSyncable(a.getAndroidAccount(), AUTHORITY, 1); @@ -97,7 +94,6 @@ public void setCameraAccount(Account account) { // disable on all the others ContentResolver.cancelSync(a.getAndroidAccount(), AUTHORITY); ContentResolver.setIsSyncable(a.getAndroidAccount(), AUTHORITY, 0); - } } } @@ -106,7 +102,8 @@ public void setCameraAccount(Account account) { * Disable camera upload. */ public void disableCameraUpload() { - for (Account account : accountManager.getAccountList()) { + List list = SupportAccountManager.getInstance().getAccountList(); + for (Account account : list) { ContentResolver.cancelSync(account.getAndroidAccount(), AUTHORITY); ContentResolver.setIsSyncable(account.getAndroidAccount(), AUTHORITY, 0); } diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/GalleryBucketUtils.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/GalleryBucketUtils.java similarity index 77% rename from app/src/main/java/com/seafile/seadroid2/cameraupload/GalleryBucketUtils.java rename to app/src/main/java/com/seafile/seadroid2/ui/camera_upload/GalleryBucketUtils.java index 097989ed7..bc82fc4d5 100644 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/GalleryBucketUtils.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/GalleryBucketUtils.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.cameraupload; +package com.seafile.seadroid2.ui.camera_upload; import android.content.Context; import android.database.Cursor; @@ -6,11 +6,10 @@ import android.os.Build; import android.provider.MediaStore; +import com.blankj.utilcode.util.CollectionUtils; import com.seafile.seadroid2.data.StorageManager; import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -20,8 +19,6 @@ * media content providers. */ public class GalleryBucketUtils { - private static final String DEBUG_TAG = "GalleryBucketUtils"; - /** * Per default we will upload images/videos from these buckets * @@ -30,18 +27,17 @@ public class GalleryBucketUtils { *
* - https://stackoverflow.com/questions/6248887/android-device-specific-camera-path-issue */ - public static final String[] CAMERA_BUCKET_NAMES = {"Camera", "100ANDRO", "100MEDIA"}; - public static final String IMAGES = "IMAGES"; + public static final List CAMERA_BUCKET_NAMES_LIST = CollectionUtils.newArrayList("Camera", "100ANDRO", "100MEDIA"); public static class Bucket { public String id; public String name; public String imageId; public String videoId; + + public Uri uri; public String isImages; - public String videoPath; - public String imagePath; - public int image_id = -1; + public boolean isCameraBucket; @Override @@ -76,6 +72,7 @@ public static List getMediaBuckets(Context context) { List video; List image; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + //TODO 测试 android 10以下版本 video = getVideoBucketsBelowAndroid10Api29(context); image = getImageBucketsBelowAndroid10Api29(context); } else { @@ -118,16 +115,15 @@ private static List getVideoBucketsBelowAndroid10Api29(Context context) b.id = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.BUCKET_ID)); b.name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.BUCKET_DISPLAY_NAME)); - b.isCameraBucket = false; + if (b.name == null) { continue; } - for (String name : CAMERA_BUCKET_NAMES) { - if (b.name.equalsIgnoreCase(name)) { - b.isCameraBucket = true; - break; - } - } + + b.isCameraBucket = CAMERA_BUCKET_NAMES_LIST.contains(b.name.toLowerCase()); + + Uri baseUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + b.uri = Uri.withAppendedPath(baseUri, b.videoId); // ignore buckets created by Seadroid String file = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)); @@ -172,16 +168,16 @@ private static List getImageBucketsBelowAndroid10Api29(Context context) Bucket b = new Bucket(); b.id = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_ID)); b.name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_DISPLAY_NAME)); - b.image_id = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)); - b.isCameraBucket = false; + b.imageId = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)); + if (b.name == null) { continue; } - for (String name : CAMERA_BUCKET_NAMES) { - if (b.name.equalsIgnoreCase(name)) { - b.isCameraBucket = true; - } - } + + Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + b.uri = Uri.withAppendedPath(baseUri, b.imageId); + + b.isCameraBucket = CAMERA_BUCKET_NAMES_LIST.contains(b.name.toLowerCase()); // ignore buckets created by Seadroid String file = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)); @@ -204,11 +200,12 @@ private static List getVideoBuckets(Context context) { MediaStore.Video.Media.BUCKET_ID, MediaStore.Video.Media.BUCKET_DISPLAY_NAME, }; + String sortOrder = MediaStore.Video.Media.DATE_ADDED + " DESC"; Cursor cursor = context.getContentResolver().query(images, projection, // Which columns to return null, // Which rows to return (all rows) null, // Selection arguments (none) - null // Ordering + sortOrder ); List buckets = new ArrayList(); @@ -220,21 +217,22 @@ private static List getVideoBuckets(Context context) { try { while (cursor.moveToNext()) { Bucket b = new Bucket(); - b.id = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.BUCKET_ID)); - b.name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.BUCKET_DISPLAY_NAME)); - b.videoId = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)); + int mediaIdIndex = cursor.getColumnIndexOrThrow(projection[0]); + int idIndex = cursor.getColumnIndexOrThrow(projection[1]); + int nameIndex = cursor.getColumnIndexOrThrow(projection[2]); + + b.videoId = cursor.getString(mediaIdIndex); + b.id = cursor.getString(idIndex); + b.name = cursor.getString(nameIndex); + + Uri baseUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + b.uri = Uri.withAppendedPath(baseUri, b.videoId); if (b.name == null) { continue; } + b.isCameraBucket = CAMERA_BUCKET_NAMES_LIST.contains(b.name.toLowerCase()); - b.isCameraBucket = false; - for (String name : CAMERA_BUCKET_NAMES) { - if (b.name.equalsIgnoreCase(name)) { - b.isCameraBucket = true; - break; - } - } buckets.add(b); } } catch (IllegalArgumentException e) { @@ -249,16 +247,17 @@ private static List getVideoBuckets(Context context) { private static List getImageBuckets(Context context) { Uri images = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; String[] projection = new String[]{ + MediaStore.Images.Media._ID, MediaStore.Images.Media.BUCKET_ID, - MediaStore.Images.Media.BUCKET_DISPLAY_NAME, - MediaStore.Images.Media._ID + MediaStore.Images.Media.BUCKET_DISPLAY_NAME }; + String sortOrder = MediaStore.Images.Media.DATE_ADDED + " DESC"; Cursor cursor = context.getContentResolver().query(images, projection, // Which columns to return null, // Which rows to return (all rows) null, // Selection arguments (none) - null // Ordering + sortOrder ); List buckets = new ArrayList(); @@ -269,22 +268,23 @@ private static List getImageBuckets(Context context) { try { while (cursor.moveToNext()) { Bucket b = new Bucket(); - b.id = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_ID)); - b.name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_DISPLAY_NAME)); - b.imageId = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)); + int mediaIdIndex = cursor.getColumnIndexOrThrow(projection[0]); + int idIndex = cursor.getColumnIndexOrThrow(projection[1]); + int nameIndex = cursor.getColumnIndexOrThrow(projection[2]); + + b.imageId = cursor.getString(mediaIdIndex); + b.id = cursor.getString(idIndex); + b.name = cursor.getString(nameIndex); + + Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + b.uri = Uri.withAppendedPath(baseUri, b.imageId); if (b.name == null) { continue; } - b.isCameraBucket = false; - b.isImages = GalleryBucketUtils.IMAGES; - for (String name : CAMERA_BUCKET_NAMES) { - if (b.name.equalsIgnoreCase(name)) { - b.isCameraBucket = true; - break; - } - } + b.isCameraBucket = CAMERA_BUCKET_NAMES_LIST.contains(b.name.toLowerCase()); + buckets.add(b); } } catch (IllegalArgumentException e) { @@ -292,7 +292,9 @@ private static List getImageBuckets(Context context) { } finally { cursor.close(); } - return buckets.stream().distinct().collect(Collectors.toList()); + return buckets.stream() + .distinct() + .collect(Collectors.toList()); } } \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/MediaObserverService.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/MediaObserverService.java similarity index 71% rename from app/src/main/java/com/seafile/seadroid2/cameraupload/MediaObserverService.java rename to app/src/main/java/com/seafile/seadroid2/ui/camera_upload/MediaObserverService.java index 20fd32270..f69dd7b91 100644 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/MediaObserverService.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/MediaObserverService.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.cameraupload; +package com.seafile.seadroid2.ui.camera_upload; import android.app.Service; import android.content.Intent; @@ -7,31 +7,27 @@ import android.net.Uri; import android.os.IBinder; import android.provider.MediaStore; -import android.util.Log; -import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.util.SLogs; +import com.seafile.seadroid2.util.sp.SettingsManager; /** * This service monitors the media provider content provider for new images/videos. - * + *

* If new content appears, this service will get notified and send a syncRequest to the MediaSyncProvider. - * + *

* This service is always running, even if camera upload is not active. * However, it will only register it's ContentObservers if Camera Upload is enabled in Seadroid. */ public class MediaObserverService extends Service { - private static final String DEBUG_TAG = "MediaObserverService"; - private MediaObserver mediaObserver = null; - private SettingsManager settingsManager; private CameraUploadManager cameraManager; /** * If camera upload settings have changed, we might have to trigger a full resync. * This listener takes care of that. */ - private SharedPreferences.OnSharedPreferenceChangeListener settingsListener = - new SharedPreferences.OnSharedPreferenceChangeListener() { + private SharedPreferences.OnSharedPreferenceChangeListener settingsListener = new SharedPreferences.OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { @@ -42,27 +38,28 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin // necessary. switch (key) { - // if video upload has been switched on, do a full sync, to upload // any older videos already recorded. - case SettingsManager.CAMERA_UPLOAD_ALLOW_VIDEOS_SWITCH_KEY: - if (settingsManager.isVideosUploadAllowed()) + case SettingsManager.CAMERA_UPLOAD_ALLOW_VIDEOS_SWITCH_KEY: { + if (SettingsManager.getInstance().isVideosUploadAllowed()) { doFullResync = true; - break; + } + } + break; // same goes for if the list of selected buckets has been changed case SettingsManager.SHARED_PREF_CAMERA_UPLOAD_BUCKETS: + case SettingsManager.SHARED_PREF_CAMERA_UPLOAD_REPO_ID: { doFullResync = true; - break; - - // the repo changed, also do a full resync - case SettingsManager.SHARED_PREF_CAMERA_UPLOAD_REPO_ID: - doFullResync = true; + } + break; + default: + SLogs.d(key + ": was changed"); break; } if (cameraManager.isCameraUploadEnabled() && doFullResync) { - // Log.i(DEBUG_TAG, "Doing a full resync of all media content."); + SLogs.d("Doing a full resync of all media content."); cameraManager.performFullSync(); } } @@ -70,10 +67,8 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin @Override public void onCreate() { - // Log.d(DEBUG_TAG, "onCreate"); - settingsManager = SettingsManager.instance(); - settingsManager.registerSharedPreferencesListener(settingsListener); - cameraManager = new CameraUploadManager(getApplicationContext()); + SettingsManager.getInstance().registerSharedPreferencesListener(settingsListener); + cameraManager = new CameraUploadManager(); registerContentObservers(); if (cameraManager.isCameraUploadEnabled()) { @@ -84,8 +79,7 @@ public void onCreate() { @Override public void onDestroy() { - // Log.d(DEBUG_TAG, "onDestroy"); - settingsManager.unregisterSharedPreferencesListener(settingsListener); + SettingsManager.getInstance().unregisterSharedPreferencesListener(settingsListener); unregisterContentObservers(); } @@ -108,14 +102,13 @@ private void registerContentObservers() { getApplicationContext().getContentResolver().registerContentObserver (MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true, mediaObserver); - Log.i(DEBUG_TAG, "Started watchting for new media content."); + SLogs.i("Started watchting for new media content."); } private void unregisterContentObservers() { - this.getApplicationContext().getContentResolver() - .unregisterContentObserver(mediaObserver); + this.getApplicationContext().getContentResolver().unregisterContentObserver(mediaObserver); - Log.i(DEBUG_TAG, "Stopped watchting for new media content."); + SLogs.i("Stopped watchting for new media content."); } private class MediaObserver extends ContentObserver { @@ -130,9 +123,8 @@ public void onChange(boolean selfChange) { @Override public void onChange(boolean selfChange, Uri changeUri) { - if (cameraManager.isCameraUploadEnabled()) { - Log.d(DEBUG_TAG, "Noticed a change in the media provider, scheduling sync."); + SLogs.d("Noticed a change in the media provider, scheduling sync."); cameraManager.performSync(); } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/MediaSchedulerService.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/MediaSchedulerService.java new file mode 100644 index 000000000..a7b492ea4 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/MediaSchedulerService.java @@ -0,0 +1,32 @@ +package com.seafile.seadroid2.ui.camera_upload; + + +import android.app.job.JobParameters; +import android.app.job.JobService; + +import com.seafile.seadroid2.util.SLogs; +import com.seafile.seadroid2.util.sp.SettingsManager; + +/** + * This service monitors the media provider content provider for new images/videos. + */ +public class MediaSchedulerService extends JobService { + @Override + public boolean onStartJob(JobParameters jobParameters) { + SLogs.d("MediaSchedulerService exec job"); + + CameraUploadManager mCameraManager = new CameraUploadManager(); + if (mCameraManager.isCameraUploadEnabled() && SettingsManager.getInstance().isVideosUploadAllowed()) { + mCameraManager.performFullSync(); + } + + jobFinished(jobParameters, true); + return true; + } + + @Override + public boolean onStopJob(JobParameters jobParameters) { + SLogs.d("MediaSchedulerService job stop"); + return false; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/StubContentProvider.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/StubContentProvider.java similarity index 96% rename from app/src/main/java/com/seafile/seadroid2/cameraupload/StubContentProvider.java rename to app/src/main/java/com/seafile/seadroid2/ui/camera_upload/StubContentProvider.java index 165223ba9..73bc144ec 100644 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/StubContentProvider.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/StubContentProvider.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.cameraupload; +package com.seafile.seadroid2.ui.camera_upload; import android.content.ContentProvider; import android.content.ContentValues; diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/BucketsFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/BucketsFragment.java similarity index 62% rename from app/src/main/java/com/seafile/seadroid2/cameraupload/BucketsFragment.java rename to app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/BucketsFragment.java index 1b90e1800..575865d7b 100644 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/BucketsFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/BucketsFragment.java @@ -1,12 +1,10 @@ -package com.seafile.seadroid2.cameraupload; +package com.seafile.seadroid2.ui.camera_upload.config_fragment; import android.content.Context; -import android.graphics.Bitmap; -import android.net.Uri; -import android.os.Build; import android.os.Bundle; -import android.provider.MediaStore; + import androidx.fragment.app.Fragment; + import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -15,7 +13,6 @@ import android.view.animation.Animation.AnimationListener; import android.view.animation.TranslateAnimation; import android.widget.BaseAdapter; -import android.widget.Button; import android.widget.GridView; import android.widget.ImageView; import android.widget.RadioGroup; @@ -23,12 +20,11 @@ import android.widget.TextView; import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeadroidApplication; -import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.ui.camera_upload.CameraUploadConfigActivity; +import com.seafile.seadroid2.ui.camera_upload.GalleryBucketUtils; +import com.seafile.seadroid2.util.sp.SettingsManager; import com.seafile.seadroid2.util.GlideApp; -import com.seafile.seadroid2.util.Utils; -import java.io.File; import java.util.ArrayList; import java.util.List; @@ -39,14 +35,13 @@ public class BucketsFragment extends Fragment { private CameraUploadConfigActivity mActivity; private RadioGroup mRadioGroup; - private Button mDoneBtn; + private TranslateAnimation mSlideInAnimation; private TranslateAnimation mSlideOutAnimation; private GridView mGridView; private List buckets; private boolean[] selectedBuckets; private ImageAdapter imageAdapter; - private Bitmap[] thumbnails; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -58,7 +53,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa Animation.RELATIVE_TO_SELF, 2.0f, Animation.RELATIVE_TO_SELF, 0.0f); - mSlideInAnimation.setDuration(600); + mSlideInAnimation.setDuration(200); mSlideInAnimation.setInterpolator(new AccelerateDecelerateInterpolator()); mSlideInAnimation.setAnimationListener(slideInListener); @@ -66,17 +61,13 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 2.0f); - mSlideOutAnimation.setDuration(600); + mSlideOutAnimation.setDuration(200); mSlideOutAnimation.setInterpolator(new AccelerateDecelerateInterpolator()); mSlideOutAnimation.setAnimationListener(slideOutListener); - mGridView = (GridView) rootView.findViewById(R.id.cuc_bucket_selection_grid); - mRadioGroup = (RadioGroup) rootView.findViewById(R.id.cuc_local_directory_radio_group); - mDoneBtn = (Button) rootView.findViewById(R.id.cuc_local_directory_btn); - mDoneBtn.setOnClickListener(onClickListener); - if (mActivity.isChooseDirPage()) - mDoneBtn.setVisibility(View.VISIBLE); - - SettingsManager settingsManager = SettingsManager.instance(); + mGridView = rootView.findViewById(R.id.cuc_bucket_selection_grid); + mRadioGroup = rootView.findViewById(R.id.cuc_local_directory_radio_group); + + SettingsManager settingsManager = SettingsManager.getInstance(); if (settingsManager.getCameraUploadBucketList().isEmpty()) { // auto scan mGridView.setVisibility(View.INVISIBLE); @@ -92,41 +83,14 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa mRadioGroup.setOnCheckedChangeListener(onCheckedChangeListener); List currentBucketList = settingsManager.getCameraUploadBucketList(); buckets = GalleryBucketUtils.getMediaBuckets(getActivity().getApplicationContext()); - selectedBuckets = new boolean[buckets.size()]; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - thumbnails = new Bitmap[buckets.size()]; - for (int i = 0; i < buckets.size(); i++) { - GalleryBucketUtils.Bucket b = buckets.get(i); - if (b.image_id > 0) { - thumbnails[i] = MediaStore.Images.Thumbnails.getThumbnail( - requireContext().getContentResolver(), b.image_id, - MediaStore.Images.Thumbnails.MINI_KIND, null); - } - if (currentBucketList.size() > 0) - selectedBuckets[i] = currentBucketList.contains(b.id); - else - selectedBuckets[i] = b.isCameraBucket; - } - } else { - for (int i = 0; i < this.buckets.size(); i++) { - GalleryBucketUtils.Bucket b = this.buckets.get(i); - if (b.isImages != null && b.isImages.equals(GalleryBucketUtils.IMAGES)) { - Uri image_uri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, b.imageId); - String image_path = Utils.getRealPathFromURI(SeadroidApplication.getAppContext(), image_uri, "images"); - b.imagePath = image_path; - } else { - Uri video_uri = Uri.withAppendedPath(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, b.videoId); - String videoPath = Utils.getRealPathFromURI(SeadroidApplication.getAppContext(), video_uri, "video"); - b.videoPath = videoPath; - } - // if the user has previously selected buckets, mark these. - // otherwise, select the ones that will be auto-guessed. - if (currentBucketList.size() > 0) - selectedBuckets[i] = currentBucketList.contains(b.id); - else - selectedBuckets[i] = b.isCameraBucket; - } + selectedBuckets = new boolean[buckets.size()]; + for (int i = 0; i < this.buckets.size(); i++) { + GalleryBucketUtils.Bucket b = this.buckets.get(i); + if (!currentBucketList.isEmpty()) + selectedBuckets[i] = currentBucketList.contains(b.id); + else + selectedBuckets[i] = b.isCameraBucket; } imageAdapter = new ImageAdapter(); @@ -146,12 +110,10 @@ public void onCheckedChanged(RadioGroup radioGroup, int radioButtonId) { case R.id.cuc_local_directory_auto_scan_rb: mGridView.startAnimation(mSlideOutAnimation); mGridView.setEnabled(false); - mActivity.saveSettings(); break; case R.id.cuc_local_directory_pick_folders_rb: mGridView.startAnimation(mSlideInAnimation); mGridView.setEnabled(true); - mActivity.saveSettings(); break; } } @@ -161,14 +123,6 @@ public boolean isAutoScanSelected() { return mRadioGroup.getCheckedRadioButtonId() == R.id.cuc_local_directory_auto_scan_rb; } - private final View.OnClickListener onClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - mActivity.saveSettings(); - mActivity.finish(); - } - }; - /** * Slide out animation listener. */ @@ -180,7 +134,8 @@ public void onAnimationEnd(Animation arg0) { } @Override - public void onAnimationRepeat(Animation arg0) {} + public void onAnimationRepeat(Animation arg0) { + } @Override public void onAnimationStart(Animation arg0) { @@ -200,7 +155,8 @@ public void onAnimationEnd(Animation arg0) { } @Override - public void onAnimationRepeat(Animation arg0) {} + public void onAnimationRepeat(Animation arg0) { + } @Override public void onAnimationStart(Animation arg0) { @@ -234,9 +190,9 @@ public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { holder = new ViewHolder(); convertView = mInflater.inflate(R.layout.bucket_item, null); - holder.imageview = (ImageView) convertView.findViewById(R.id.bucket_item_thumbImage); - holder.text = (TextView) convertView.findViewById(R.id.bucket_item_name); - holder.marking = (ImageView) convertView.findViewById(R.id.bucket_item_marking); + holder.imageview = convertView.findViewById(R.id.bucket_item_thumbImage); + holder.text = convertView.findViewById(R.id.bucket_item_name); + holder.marking = convertView.findViewById(R.id.bucket_item_marking); convertView.setTag(holder); } else { @@ -255,15 +211,9 @@ public void onClick(View v) { holder.marking.setBackgroundResource(R.drawable.checkbox_unchecked); } }); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - holder.imageview.setImageBitmap(thumbnails[position]); - } else { - if (buckets.get(position).isImages != null && buckets.get(position).isImages.equals(GalleryBucketUtils.IMAGES)) { - GlideApp.with(getActivity()).load(buckets.get(position).imagePath).into(holder.imageview); - } else { - GlideApp.with(getActivity()).load(Uri.fromFile(new File(buckets.get(position).videoPath))).into(holder.imageview); - } - } + + GlideApp.with(getContext()).load(buckets.get(position).uri).into(holder.imageview); + if (selectedBuckets[position]) holder.marking.setBackgroundResource(R.drawable.checkbox_checked); else diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/ConfigWelcomeFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/ConfigWelcomeFragment.java similarity index 71% rename from app/src/main/java/com/seafile/seadroid2/cameraupload/ConfigWelcomeFragment.java rename to app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/ConfigWelcomeFragment.java index 38f71154d..74a24bf52 100644 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/ConfigWelcomeFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/ConfigWelcomeFragment.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.cameraupload; +package com.seafile.seadroid2.ui.camera_upload.config_fragment; import android.os.Bundle; import androidx.fragment.app.Fragment; @@ -15,9 +15,7 @@ public class ConfigWelcomeFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View rootView = getActivity().getLayoutInflater().inflate(R.layout.cuc_welcome_fragment, null); - - return rootView; + return getActivity().getLayoutInflater().inflate(R.layout.cuc_welcome_fragment, null); } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/HowToUploadFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/HowToUploadFragment.java new file mode 100644 index 000000000..7657be6d4 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/HowToUploadFragment.java @@ -0,0 +1,43 @@ +package com.seafile.seadroid2.ui.camera_upload.config_fragment; + +import android.os.Bundle; + +import androidx.fragment.app.Fragment; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RadioButton; +import android.widget.RadioGroup; + +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.util.sp.SettingsManager; + +/** + * How to upload fragment + */ +public class HowToUploadFragment extends Fragment { + + private RadioButton mDataPlanRadioBtn; + private RadioGroup mRadioGroup; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View rootView = LayoutInflater.from(getContext()).inflate(R.layout.cuc_how_to_upload_fragment, null); + + mRadioGroup = rootView.findViewById(R.id.cuc_wifi_radio_group); + mDataPlanRadioBtn = rootView.findViewById(R.id.cuc_wifi_or_data_plan_rb); + + if (SettingsManager.getInstance().isDataPlanAllowed()) { + mDataPlanRadioBtn.setChecked(true); + } + + return rootView; + } + + public boolean getHowToUpload() { + return mRadioGroup.getCheckedRadioButtonId() == R.id.cuc_wifi_or_data_plan_rb; + } + +} + diff --git a/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/ReadyToScanFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/ReadyToScanFragment.java new file mode 100644 index 000000000..158033780 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/ReadyToScanFragment.java @@ -0,0 +1,20 @@ +package com.seafile.seadroid2.ui.camera_upload.config_fragment; + +import android.os.Bundle; + +import androidx.fragment.app.Fragment; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.seafile.seadroid2.R; + +public class ReadyToScanFragment extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return LayoutInflater.from(getContext()).inflate(R.layout.cuc_ready_to_scan_fragment, null); + } +} + diff --git a/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/WhatToUploadFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/WhatToUploadFragment.java new file mode 100644 index 000000000..890f5d33c --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/config_fragment/WhatToUploadFragment.java @@ -0,0 +1,38 @@ +package com.seafile.seadroid2.ui.camera_upload.config_fragment; + +import android.os.Bundle; + +import androidx.fragment.app.Fragment; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RadioGroup; + +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.util.sp.SettingsManager; + +/** + * What to upload fragment for camera upload configuration helper + */ +public class WhatToUploadFragment extends Fragment { + private RadioGroup mRadioGroup; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + View rootView = LayoutInflater.from(getContext()).inflate(R.layout.cuc_what_to_upload_fragment, null); + mRadioGroup = rootView.findViewById(R.id.cuc_upload_radio_group); + if (SettingsManager.getInstance().isVideosUploadAllowed()) { + mRadioGroup.check(R.id.cuc_upload_photos_and_videos_rb); + } + + return rootView; + } + + public boolean getWhatToUpload() { + return mRadioGroup.getCheckedRadioButtonId() == R.id.cuc_upload_photos_and_videos_rb; + } + +} + diff --git a/app/src/main/java/com/seafile/seadroid2/ui/data_migrate/DataMigrationActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/data_migrate/DataMigrationActivity.java new file mode 100644 index 000000000..57ef72daf --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/data_migrate/DataMigrationActivity.java @@ -0,0 +1,760 @@ +package com.seafile.seadroid2.ui.data_migrate; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.os.Bundle; +import android.view.KeyEvent; + +import androidx.appcompat.app.AppCompatActivity; + +import com.blankj.utilcode.util.CollectionUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.ui.camera_upload.CameraUploadDBHelper; +import com.seafile.seadroid2.data.DatabaseHelper; +import com.seafile.seadroid2.data.db.AppDatabase; +import com.seafile.seadroid2.data.db.entities.CertEntity; +import com.seafile.seadroid2.data.db.entities.DirentsCacheEntity; +import com.seafile.seadroid2.data.db.entities.EncKeyCacheEntity; +import com.seafile.seadroid2.data.db.entities.FileCacheEntity; +import com.seafile.seadroid2.data.db.entities.FolderBackupCacheEntity; +import com.seafile.seadroid2.data.db.entities.FolderBackupMonitorEntity; +import com.seafile.seadroid2.data.db.entities.PhotoCacheEntity; +import com.seafile.seadroid2.data.db.entities.RepoDirEntity; +import com.seafile.seadroid2.data.db.entities.StarredFileCacheEntity; +import com.seafile.seadroid2.databinding.ActivityDataMigrationBinding; +import com.seafile.seadroid2.ui.folder_backup.FolderBackupDBHelper; +import com.seafile.seadroid2.monitor.MonitorDBHelper; +import com.seafile.seadroid2.ssl.CertsDBHelper; +import com.seafile.seadroid2.ui.folder_backup.RepoConfig; +import com.seafile.seadroid2.util.SLogs; +import com.seafile.seadroid2.util.sp.FolderBackupConfigSPs; + +import java.util.List; +import java.util.function.Consumer; + +import io.reactivex.Completable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.functions.Action; +import io.reactivex.schedulers.Schedulers; + +/** + * Migrating data from sqlite database to Room + */ +public class DataMigrationActivity extends AppCompatActivity { + private ActivityDataMigrationBinding binding; + private final CompositeDisposable compositeDisposable = new CompositeDisposable(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivityDataMigrationBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + binding.text.setText(R.string.wait); + + startMigration(); + } + + private void startMigration() { + queryPhotoDB(); + queryFolderBackupDB(); + queryRepoConfigOfFolderBackDB(); + queryDataDB(); + queryMonitorDB(); + queryCertsDB(); + + finishMigration(); + } + + private boolean checkTableExists(SQLiteDatabase database, String tableName) { + String rawQuery = "SELECT name FROM sqlite_master WHERE type='table' AND name='" + tableName + "';"; + Cursor cursor = database.rawQuery(rawQuery, null); + boolean isExists = false; + if (cursor.moveToFirst()) { + isExists = true; + } else { + //no PhotoCache table + } + cursor.close(); + return isExists; + } + + private void queryPhotoDB() { + String table = "PhotoCache"; + + CameraUploadDBHelper dbHelper = CameraUploadDBHelper.getInstance(); + SQLiteDatabase database = dbHelper.getWritableDatabase(); + boolean isExists = checkTableExists(database, table); + if (!isExists) { + return; + } + + Cursor c = database.query( + table, + null, + null, + null, + null, // don't group the rows + null, // don't filter by row groups + null // The sort order + ); + + List list = CollectionUtils.newArrayList(); + + try { + c.moveToFirst(); + while (!c.isAfterLast()) { + PhotoCacheEntity item = new PhotoCacheEntity(); + int idIndex = c.getColumnIndexOrThrow("id"); + int fileIndex = c.getColumnIndexOrThrow("file"); + int dateIndex = c.getColumnIndexOrThrow("date_added"); + + item.id = c.getLong(idIndex); + item.file = c.getString(fileIndex); + item.date_added = c.getLong(dateIndex); + item.related_account = SupportAccountManager.getInstance().getCurrentAccount().getSignature(); + + c.moveToNext(); + + list.add(item); + } + } finally { + c.close(); + } + + SLogs.d("--------------------" + table); + list.forEach(new Consumer() { + + @Override + public void accept(PhotoCacheEntity photoCacheEntity) { + SLogs.d(photoCacheEntity.toString()); + } + }); + + Completable completable = AppDatabase.getInstance().photoCacheDao().insertAll(list); + compositeDisposable.add(completable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action() { + @Override + public void run() throws Exception { + SLogs.d("--------------------" + table + " -> 完成"); + } + })); + } + + private void queryFolderBackupDB() { + //FolderBackupInfo + //RepoFig + + String table = "FolderBackupInfo"; + + FolderBackupDBHelper dbHelper = FolderBackupDBHelper.getDatabaseHelper(); + SQLiteDatabase database = dbHelper.getWritableDatabase(); + boolean isExists = checkTableExists(database, table); + if (!isExists) { + return; + } + + Cursor c = database.query( + table, + null, + null, + null, + null, // don't group the rows + null, // don't filter by row groups + null // The sort order + ); + + List list = CollectionUtils.newArrayList(); + + try { + c.moveToFirst(); + while (!c.isAfterLast()) { + FolderBackupCacheEntity item = new FolderBackupCacheEntity(); + int idIndex = c.getColumnIndexOrThrow("id"); + int repoIdIndex = c.getColumnIndexOrThrow("repo_id"); + int repoNameIndex = c.getColumnIndexOrThrow("repo_name"); + int parentFolderIndex = c.getColumnIndexOrThrow("parent_folder"); + int fileNameIndex = c.getColumnIndexOrThrow("file_name"); + int filePathIndex = c.getColumnIndexOrThrow("file_path"); + int fileSizeIndex = c.getColumnIndexOrThrow("file_size"); + + item.id = c.getLong(idIndex); + item.repo_id = c.getString(repoIdIndex); + item.repo_name = c.getString(repoNameIndex); + item.parent_folder = c.getString(parentFolderIndex); + item.file_name = c.getString(fileNameIndex); + item.file_path = c.getString(filePathIndex); + item.file_size = Long.parseLong(c.getString(fileSizeIndex)); + item.related_account = SupportAccountManager.getInstance().getCurrentAccount().getSignature(); + + c.moveToNext(); + + list.add(item); + } + } finally { + c.close(); + } + SLogs.d("--------------------" + table); + + list.forEach(new Consumer() { + @Override + public void accept(FolderBackupCacheEntity folderBackupCacheEntity) { + SLogs.d(folderBackupCacheEntity.toString()); + } + }); + + Completable completable = AppDatabase.getInstance().folderBackupCacheDao().insertAll(list); + compositeDisposable.add(completable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action() { + @Override + public void run() throws Exception { + SLogs.d("--------------------" + table + " -> 完成"); + } + })); + + } + + private void queryRepoConfigOfFolderBackDB() { + String table = "RepoConfig"; + + FolderBackupDBHelper dbHelper = FolderBackupDBHelper.getDatabaseHelper(); + SQLiteDatabase database = dbHelper.getWritableDatabase(); + boolean isRepoConfigExists = checkTableExists(database, table); + if (!isRepoConfigExists) { + return; + } + + Cursor c = database.query( + table, + null, + null, + null, + null, // don't group the rows + null, // don't filter by row groups + null // The sort order + ); + + try { + c.moveToFirst(); + while (!c.isAfterLast()) { + int idIndex = c.getColumnIndexOrThrow("id"); + int emailIndex = c.getColumnIndexOrThrow("mail");//not email,it is mail + int repoIdIndex = c.getColumnIndexOrThrow("repo_id"); + int repoNameIndex = c.getColumnIndexOrThrow("repo_name"); + + String repo_id = c.getString(repoIdIndex); + String repo_name = c.getString(repoNameIndex); + String related_account = c.getString(emailIndex); + + RepoConfig repoConfig = new RepoConfig(repo_id, repo_name, related_account); + FolderBackupConfigSPs.setBackupRepoConfigByAccount(repoConfig); + + c.moveToNext(); + } + } finally { + c.close(); + } + } + + private void queryDataDB() { + //FileCache + //StarredFileCache + //RepoDir + //DirentsCache + //EncKey + + queryFileCacheOfDataDB(); + queryStarredFileCacheOfDataDB(); + queryRepoDirOfDataDB(); + queryDirentsCacheOfDataDB(); + queryEncKeyOfDataDB(); + } + + private void queryFileCacheOfDataDB() { + String table = "FileCache"; + + DatabaseHelper dbHelper = DatabaseHelper.getDatabaseHelper(); + SQLiteDatabase database = dbHelper.getWritableDatabase(); + boolean isExists = checkTableExists(database, table); + if (!isExists) { + return; + } + + Cursor c = database.query( + table, + null, + null, + null, + null, // don't group the rows + null, // don't filter by row groups + null // The sort order + ); + + List list = CollectionUtils.newArrayList(); + + try { + c.moveToFirst(); + while (!c.isAfterLast()) { + FileCacheEntity item = new FileCacheEntity(); + int idIndex = c.getColumnIndexOrThrow("id"); + int fileIdIndex = c.getColumnIndexOrThrow("fileid"); + int pathIndex = c.getColumnIndexOrThrow("path"); + int repoIdIndex = c.getColumnIndexOrThrow("repo_id"); + int repoNameIndex = c.getColumnIndexOrThrow("repo_name"); + int accountIndex = c.getColumnIndexOrThrow("account"); + + + item.id = c.getLong(idIndex); + item.repo_id = c.getString(repoIdIndex); + item.repo_name = c.getString(repoNameIndex); + item.file_id = c.getString(fileIdIndex); + item.path = c.getString(pathIndex); + item.related_account = c.getString(accountIndex); + + c.moveToNext(); + + list.add(item); + } + } finally { + c.close(); + } + + SLogs.d("--------------------" + table); + list.forEach(new Consumer() { + @Override + public void accept(FileCacheEntity entity) { + SLogs.d(entity.toString()); + } + }); + + Completable completable = AppDatabase.getInstance().fileCacheDAO().insertAll(list); + compositeDisposable.add(completable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action() { + @Override + public void run() throws Exception { + SLogs.d("--------------------" + table + " -> 完成"); + } + })); + } + + private void queryStarredFileCacheOfDataDB() { + String table = "StarredFileCache"; + + DatabaseHelper dbHelper = DatabaseHelper.getDatabaseHelper(); + SQLiteDatabase database = dbHelper.getWritableDatabase(); + boolean isExists = checkTableExists(database, table); + if (!isExists) { + return; + } + + Cursor c = database.query( + table, + null, + null, + null, + null, // don't group the rows + null, // don't filter by row groups + null // The sort order + ); + + List list = CollectionUtils.newArrayList(); + + try { + c.moveToFirst(); + while (!c.isAfterLast()) { + StarredFileCacheEntity item = new StarredFileCacheEntity(); + int idIndex = c.getColumnIndexOrThrow("id"); + int contentIndex = c.getColumnIndexOrThrow("content"); + int accountIndex = c.getColumnIndexOrThrow("account"); + + + item.id = c.getLong(idIndex); + item.content = c.getString(contentIndex); + item.related_account = c.getString(accountIndex); + + c.moveToNext(); + + list.add(item); + } + } finally { + c.close(); + } + + SLogs.d("--------------------" + table); + list.forEach(new Consumer() { + @Override + public void accept(StarredFileCacheEntity entity) { + SLogs.d(entity.toString()); + } + }); + + Completable completable = AppDatabase.getInstance().starredFileCacheDAO().insertAll(list); + compositeDisposable.add(completable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action() { + @Override + public void run() throws Exception { + SLogs.d("--------------------" + table + " -> 完成"); + } + })); + } + + private void queryRepoDirOfDataDB() { + String table = "RepoDir"; + + DatabaseHelper dbHelper = DatabaseHelper.getDatabaseHelper(); + SQLiteDatabase database = dbHelper.getWritableDatabase(); + boolean isExists = checkTableExists(database, table); + if (!isExists) { + return; + } + + Cursor c = database.query( + table, + null, + null, + null, + null, // don't group the rows + null, // don't filter by row groups + null // The sort order + ); + + List list = CollectionUtils.newArrayList(); + + try { + c.moveToFirst(); + while (!c.isAfterLast()) { + RepoDirEntity item = new RepoDirEntity(); + int idIndex = c.getColumnIndexOrThrow("id"); + int repoIdIndex = c.getColumnIndexOrThrow("repo_id"); + int repoDirIndex = c.getColumnIndexOrThrow("repo_dir"); + int accountIndex = c.getColumnIndexOrThrow("account"); + + + item.id = c.getLong(idIndex); + item.repo_id = c.getString(repoIdIndex); + item.repo_dir = c.getString(repoDirIndex); + item.related_account = c.getString(accountIndex); + + c.moveToNext(); + + list.add(item); + } + } finally { + c.close(); + } + + SLogs.d("--------------------" + table); + list.forEach(new Consumer() { + @Override + public void accept(RepoDirEntity entity) { + SLogs.d(entity.toString()); + } + }); + + Completable completable = AppDatabase.getInstance().repoDirDAO().insertAll(list); + compositeDisposable.add(completable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action() { + @Override + public void run() throws Exception { + SLogs.d("--------------------" + table + " -> 完成"); + } + })); + + } + + private void queryDirentsCacheOfDataDB() { + String table = "DirentsCache"; + + DatabaseHelper dbHelper = DatabaseHelper.getDatabaseHelper(); + SQLiteDatabase database = dbHelper.getWritableDatabase(); + boolean isExists = checkTableExists(database, table); + if (!isExists) { + return; + } + + Cursor c = database.query( + table, + null, + null, + null, + null, // don't group the rows + null, // don't filter by row groups + null // The sort order + ); + + List list = CollectionUtils.newArrayList(); + + try { + c.moveToFirst(); + while (!c.isAfterLast()) { + DirentsCacheEntity item = new DirentsCacheEntity(); + int idIndex = c.getColumnIndexOrThrow("id"); + int repoIdIndex = c.getColumnIndexOrThrow("repo_id"); + int pathIndex = c.getColumnIndexOrThrow("path"); + int dirIdIndex = c.getColumnIndexOrThrow("dir_id"); + + item.id = c.getLong(idIndex); + item.repo_id = c.getString(repoIdIndex); + item.path = c.getString(pathIndex); + item.dir_id = c.getString(dirIdIndex); + item.related_account = SupportAccountManager.getInstance().getCurrentAccount().getSignature(); + + c.moveToNext(); + + list.add(item); + } + } finally { + c.close(); + } + + SLogs.d("--------------------" + table); + list.forEach(new Consumer() { + @Override + public void accept(DirentsCacheEntity entity) { + SLogs.d(entity.toString()); + } + }); + + Completable completable = AppDatabase.getInstance().direntsCacheDAO().insertAll(list); + compositeDisposable.add(completable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action() { + @Override + public void run() throws Exception { + SLogs.d("--------------------" + table + " -> 完成"); + } + })); + } + + private void queryEncKeyOfDataDB() { + String table = "EncKey"; + + DatabaseHelper dbHelper = DatabaseHelper.getDatabaseHelper(); + SQLiteDatabase database = dbHelper.getWritableDatabase(); + boolean isExists = checkTableExists(database, table); + if (!isExists) { + return; + } + + Cursor c = database.query( + table, + null, + null, + null, + null, // don't group the rows + null, // don't filter by row groups + null // The sort order + ); + + List list = CollectionUtils.newArrayList(); + + try { + c.moveToFirst(); + while (!c.isAfterLast()) { + EncKeyCacheEntity item = new EncKeyCacheEntity(); + int idIndex = c.getColumnIndexOrThrow("id"); + int repoIdIndex = c.getColumnIndexOrThrow("repo_id"); + int encKeyIndex = c.getColumnIndexOrThrow("enc_key"); + int encIvIndex = c.getColumnIndexOrThrow("enc_iv"); + + + item.id = c.getLong(idIndex); + item.repo_id = c.getString(repoIdIndex); + item.enc_key = c.getString(encKeyIndex); + item.enc_iv = c.getString(encIvIndex); + item.related_account = SupportAccountManager.getInstance().getCurrentAccount().getSignature(); + + c.moveToNext(); + + list.add(item); + } + } finally { + c.close(); + } + + SLogs.d("--------------------" + table); + list.forEach(new Consumer() { + @Override + public void accept(EncKeyCacheEntity entity) { + SLogs.d(entity.toString()); + } + }); + + Completable completable = AppDatabase.getInstance().encKeyCacheDAO().insertAll(list); + compositeDisposable.add(completable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action() { + @Override + public void run() throws Exception { + SLogs.d("--------------------" + table + " -> 完成"); + } + })); + } + + private void queryCertsDB() { + //Certs + String table = "Certs"; + + CertsDBHelper dbHelper = CertsDBHelper.getDatabaseHelper(); + SQLiteDatabase database = dbHelper.getWritableDatabase(); + boolean isExists = checkTableExists(database, table); + if (!isExists) { + return; + } + + Cursor c = database.query( + table, + null, + null, + null, + null, // don't group the rows + null, // don't filter by row groups + null // The sort order + ); + + List list = CollectionUtils.newArrayList(); + + try { + c.moveToFirst(); + while (!c.isAfterLast()) { + CertEntity item = new CertEntity(); + int urlIndex = c.getColumnIndexOrThrow("url"); + int certIndex = c.getColumnIndexOrThrow("cert"); + + + item.url = c.getString(urlIndex); + item.cert = c.getString(certIndex); + + c.moveToNext(); + + list.add(item); + } + } finally { + c.close(); + } + + SLogs.d("--------------------" + table); + list.forEach(new Consumer() { + @Override + public void accept(CertEntity entity) { + SLogs.d(entity.toString()); + } + }); + + Completable completable = AppDatabase.getInstance().certDAO().insertAll(list); + compositeDisposable.add(completable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action() { + @Override + public void run() throws Exception { + SLogs.d("--------------------" + table + " -> 完成"); + } + })); + + } + + private void queryMonitorDB() { + //AutoUpdateInfo + String table = "AutoUpdateInfo"; + + MonitorDBHelper dbHelper = MonitorDBHelper.getInstance(); + SQLiteDatabase database = dbHelper.getWritableDatabase(); + boolean isExists = checkTableExists(database, table); + if (!isExists) { + return; + } + + Cursor c = database.query( + table, + null, + null, + null, + null, // don't group the rows + null, // don't filter by row groups + null // The sort order + ); + + List list = CollectionUtils.newArrayList(); + + try { + c.moveToFirst(); + while (!c.isAfterLast()) { + FolderBackupMonitorEntity item = new FolderBackupMonitorEntity(); + int idIndex = c.getColumnIndexOrThrow("id"); + int repoIdIndex = c.getColumnIndexOrThrow("repo_id"); + int repoNameIndex = c.getColumnIndexOrThrow("repo_name"); + int accountIndex = c.getColumnIndexOrThrow("account"); + int parentDirIndex = c.getColumnIndexOrThrow("parent_dir"); + int localPathIndex = c.getColumnIndexOrThrow("local_path"); + int versionIndex = c.getColumnIndexOrThrow("version"); + + + item.id = c.getLong(idIndex); + item.repo_id = c.getString(repoIdIndex); + item.repo_name = c.getString(repoNameIndex); + item.related_account = c.getString(accountIndex); + item.parent_dir = c.getString(parentDirIndex); + item.local_path = c.getString(localPathIndex); + item.version = c.getString(versionIndex); + + c.moveToNext(); + + list.add(item); + } + } finally { + c.close(); + } + + SLogs.d("--------------------" + table); + list.forEach(new Consumer() { + @Override + public void accept(FolderBackupMonitorEntity entity) { + SLogs.d(entity.toString()); + } + }); + + Completable completable = AppDatabase.getInstance().folderBackupMonitorDAO().insertAll(list); + compositeDisposable.add(completable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action() { + @Override + public void run() throws Exception { + SLogs.d("--------------------" + table + " -> 完成"); + } + })); + } + + private void finishMigration() { + SLogs.d("finishMigration"); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + return true; //do not back + } + return super.onKeyDown(keyCode, event); + } + + @Override + public void onBackPressed() { + //do not super + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/AppChoiceDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/AppChoiceDialog.java index 60459fb64..9caa54f80 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/AppChoiceDialog.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog/AppChoiceDialog.java @@ -8,8 +8,10 @@ import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; import android.os.Bundle; + import androidx.fragment.app.DialogFragment; import androidx.appcompat.app.AlertDialog; + import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -32,6 +34,7 @@ public class AppChoiceDialog extends DialogFragment { public interface OnItemSelectedListener { void onAppSelected(ResolveInfo appInfo); + void onCustomActionSelected(CustomAction action); } @@ -101,12 +104,12 @@ public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { view = LayoutInflater.from(getActivity()).inflate(R.layout.app_list_item, null); - ImageView icon = (ImageView)view.findViewById(R.id.app_icon); - TextView desc = (TextView)view.findViewById(R.id.app_desc); + ImageView icon = (ImageView) view.findViewById(R.id.app_icon); + TextView desc = (TextView) view.findViewById(R.id.app_desc); viewHolder = new Viewholder(icon, desc); view.setTag(viewHolder); } else { - viewHolder = (Viewholder)convertView.getTag(); + viewHolder = (Viewholder) convertView.getTag(); } if (position < customActions.size()) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/ClearCacheTaskDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/ClearCacheTaskDialog.java deleted file mode 100644 index fdf91b484..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/ClearCacheTaskDialog.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.seafile.seadroid2.ui.dialog; - -import android.app.Dialog; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; - -import com.bumptech.glide.Glide; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeadroidApplication; -import com.seafile.seadroid2.data.DatabaseHelper; -import com.seafile.seadroid2.data.StorageManager; - -class ClearCacheTask extends TaskDialog.Task { - - @Override - protected void runTask() { - StorageManager storageManager = StorageManager.getInstance(); - storageManager.clearCache(); - - // clear cached data from database - DatabaseHelper dbHelper = DatabaseHelper.getDatabaseHelper(); - dbHelper.delCaches(); - - //clear Glide cache - Glide.get(SeadroidApplication.getAppContext()).clearDiskCache(); - } -} - -public class ClearCacheTaskDialog extends TaskDialog { - @Override - protected View createDialogContentView(LayoutInflater inflater, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.dialog_delete_cache, null); - return view; - } - - @Override - protected void onDialogCreated(Dialog dialog) { - dialog.setTitle(getString(R.string.settings_clear_cache_title)); - } - - @Override - protected ClearCacheTask prepareTask() { - ClearCacheTask task = new ClearCacheTask(); - return task; - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/ClearPasswordTaskDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/ClearPasswordTaskDialog.java deleted file mode 100644 index 3cd8ea57f..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/ClearPasswordTaskDialog.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.seafile.seadroid2.ui.dialog; - -import android.app.Dialog; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.data.DataManager; -import com.seafile.seadroid2.data.DatabaseHelper; -import com.seafile.seadroid2.data.StorageManager; - -public class ClearPasswordTaskDialog extends TaskDialog { - - @Override - protected View createDialogContentView(LayoutInflater inflater, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.dialog_delete_password, null); - return view; - } - - @Override - protected void onDialogCreated(Dialog dialog) { - dialog.setTitle(getString(R.string.clear_password_title)); - } - - @Override - protected ClearPasswordTask prepareTask() { - ClearPasswordTask task = new ClearPasswordTask(); - return task; - } -} - -class ClearPasswordTask extends TaskDialog.Task { - - @Override - protected void runTask() { - DataManager.clearPassword(); - - // clear cached data from database - DatabaseHelper dbHelper = DatabaseHelper.getDatabaseHelper(); - dbHelper.clearEnckeys(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/CopyMoveDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/CopyMoveDialog.java index ddb7cecd4..aa92b37d8 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/CopyMoveDialog.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog/CopyMoveDialog.java @@ -5,12 +5,13 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; + import com.seafile.seadroid2.R; import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.AccountManager; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.context.CopyMoveContext; import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.context.CopyMoveContext; import com.seafile.seadroid2.util.Utils; public class CopyMoveDialog extends TaskDialog { @@ -57,29 +58,30 @@ protected void onDialogCreated(Dialog dialog) { String srcDirPath = Utils.removeLastPathSeperator(srcDir); String dstDirPath = null; - AccountManager manager = new AccountManager(this.getActivity()); - SeafRepo repo = new DataManager(manager.getCurrentAccount()).getCachedRepoByID(ctx.dstRepoId); + + + SeafRepo repo = new DataManager(SupportAccountManager.getInstance().getCurrentAccount()).getCachedRepoByID(ctx.dstRepoId); if (repo != null) { String dstPath = Utils.pathJoin(repo.repo_name, ctx.dstDir); dstDirPath = Utils.removeLastPathSeperator(dstPath); } - if (ctx.batch) { - dialog.setTitle(strTitle); - if (srcDirPath != null && dstDirPath != null) { - tvMessage.setText(String.format(strMsg, srcDirPath, dstDirPath)); - } else { - tvMessage.setText(strTitle); - } - } else { - String srcFilePath = Utils.pathJoin(ctx.srcRepoName, ctx.srcDir, ctx.srcFn); - dialog.setTitle(strTitle); - if (srcFilePath != null && dstDirPath != null) { - tvMessage.setText(String.format(strMsg, srcFilePath, dstDirPath)); - } else { - tvMessage.setText(Utils.pathJoin(strTitle, ctx.srcFn)); - } - } +// if (ctx.batch) { +// dialog.setTitle(strTitle); +// if (srcDirPath != null && dstDirPath != null) { +// tvMessage.setText(String.format(strMsg, srcDirPath, dstDirPath)); +// } else { +// tvMessage.setText(strTitle); +// } +// } else { +// String srcFilePath = Utils.pathJoin(ctx.srcRepoName, ctx.srcDir, ctx.srcFn); +// dialog.setTitle(strTitle); +// if (srcFilePath != null && dstDirPath != null) { +// tvMessage.setText(String.format(strMsg, srcFilePath, dstDirPath)); +// } else { +// tvMessage.setText(Utils.pathJoin(strTitle, ctx.srcFn)); +// } +// } // dialog.setTitle(str + " " + ctx.srcFn); } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/CopyMoveTask.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/CopyMoveTask.java index e7d5baade..ea9627466 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/CopyMoveTask.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog/CopyMoveTask.java @@ -1,8 +1,6 @@ package com.seafile.seadroid2.ui.dialog; -import com.seafile.seadroid2.SeafException; import com.seafile.seadroid2.data.DataManager; -import com.seafile.seadroid2.data.SeafDirent; import com.seafile.seadroid2.context.CopyMoveContext; /** @@ -21,35 +19,35 @@ public CopyMoveTask(CopyMoveContext ctx, DataManager dataManager) { @Override protected void runTask() { - if (ctx.batch) { - String fileNames = ""; - for (SeafDirent dirent : ctx.dirents) { - fileNames += ":" + dirent.name; - } - - fileNames = fileNames.substring(1, fileNames.length()); - - try { - if (ctx.isCopy()) { - dataManager.copy(ctx.srcRepoId, ctx.srcDir, fileNames, ctx.dstRepoId, ctx.dstDir); - } else if (ctx.isMove()) { - dataManager.move(ctx.srcRepoId, ctx.srcDir, fileNames, ctx.dstRepoId, ctx.dstDir, true); - } - } catch (SeafException e) { - setTaskException(e); - } - return; - } - - try { - if (ctx.isCopy()) { - dataManager.copy(ctx.srcRepoId, ctx.srcDir, ctx.srcFn, ctx.dstRepoId, ctx.dstDir); - } else if (ctx.isMove()) { - dataManager.move(ctx.srcRepoId, ctx.srcDir, ctx.srcFn, ctx.dstRepoId, ctx.dstDir, false); - } - } catch (SeafException e) { - setTaskException(e); - } +// if (ctx.batch) { +// String fileNames = ""; +// for (DirentModel dirent : ctx.dirents) { +// fileNames += ":" + dirent.name; +// } +// +// fileNames = fileNames.substring(1, fileNames.length()); +// +// try { +// if (ctx.isCopy()) { +// dataManager.copy(ctx.srcRepoId, ctx.srcDir, fileNames, ctx.dstRepoId, ctx.dstDir); +// } else if (ctx.isMove()) { +// dataManager.move(ctx.srcRepoId, ctx.srcDir, fileNames, ctx.dstRepoId, ctx.dstDir, true); +// } +// } catch (SeafException e) { +// setTaskException(e); +// } +// return; +// } +// +// try { +// if (ctx.isCopy()) { +// dataManager.copy(ctx.srcRepoId, ctx.srcDir, ctx.srcFn, ctx.dstRepoId, ctx.dstDir); +// } else if (ctx.isMove()) { +// dataManager.move(ctx.srcRepoId, ctx.srcDir, ctx.srcFn, ctx.dstRepoId, ctx.dstDir, false); +// } +// } catch (SeafException e) { +// setTaskException(e); +// } } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/DeleteFileDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/DeleteFileDialog.java deleted file mode 100644 index 413b3987d..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/DeleteFileDialog.java +++ /dev/null @@ -1,182 +0,0 @@ -package com.seafile.seadroid2.ui.dialog; - -import android.app.Dialog; -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import com.google.common.collect.Lists; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.data.DataManager; -import com.seafile.seadroid2.data.SeafDirent; - -import java.util.List; - -/** - * * AsyncTask for deleting files - */ -class DeleteTask extends TaskDialog.Task { - public static final String DEBUG_TAG = "DeleteTask"; - - String repoID; - List dirents; - String path; - boolean isdir; - DataManager dataManager; - DeleteTaskManager manager; - - public DeleteTask(String repoID, String path, boolean isdir, DataManager dataManager) { - this.repoID = repoID; - this.path = path; - this.isdir = isdir; - this.dataManager = dataManager; - } - - public DeleteTask(String repoID, String path, List dirents, DataManager dataManager) { - this.repoID = repoID; - this.path = path; - this.dirents = dirents; - this.dataManager = dataManager; - this.manager = new DeleteTaskManager(); - } - - @Override - protected void runTask() { - try { - // batch operation - if (dirents != null) { - for (SeafDirent dirent : dirents) { - DeleteCell cell = new DeleteCell(repoID, path + "/" + dirent.name, dirent.isDir()); - manager.addTaskToQue(cell); - } - manager.doNext(); - } else - dataManager.delete(repoID, path, isdir); - } catch (SeafException e) { - setTaskException(e); - } - } - - /** - * Class for deleting files sequentially, starting one after the previous completes. - */ - class DeleteTaskManager { - - protected List waitingList = Lists.newArrayList(); - - private synchronized boolean hasInQue(DeleteCell deleteTask) { - if (waitingList.contains(deleteTask)) { - // Log.d(DEBUG_TAG, "in Que " + deleteTask.getPath() + "in waiting list"); - return true; - } - - return false; - } - - public void addTaskToQue(DeleteCell cell) { - if (!hasInQue(cell)) { - // remove the cancelled or failed cell if any - synchronized (this) { - // Log.d(DEBUG_TAG, "------ add Que " + cell.getPath()); - waitingList.add(cell); - } - } - } - - public synchronized void doNext() { - if (!waitingList.isEmpty()) { - // Log.d(DEBUG_TAG, "--- do next!"); - - DeleteCell cell = waitingList.remove(0); - - try { - dataManager.delete(cell.getRepoID(), cell.getPath(), cell.isdir); - } catch (SeafException e) { - setTaskException(e); - } - doNext(); - } - } - - } - - /** - * Class for queuing deleting tasks - */ - class DeleteCell { - private String repoID; - private String path; - private boolean isdir; - - public DeleteCell(String repoID, String path, boolean isdir) { - this.repoID = repoID; - this.path = path; - this.isdir = isdir; - } - - public String getRepoID() { - return repoID; - } - - public String getPath() { - return path; - } - - public boolean isdir() { - return isdir; - } - } -} - -public class DeleteFileDialog extends TaskDialog { - private String repoID; - private String path; - private List dirents; - private boolean isdir; - - private DataManager dataManager; - private Account account; - - public void init(String repoID, String path, boolean isdir, Account account) { - this.repoID = repoID; - this.path = path; - this.isdir = isdir; - this.account = account; - } - - public void init(String repoID, String path, List dirents, Account account) { - this.repoID = repoID; - this.path = path; - this.dirents = dirents; - this.account = account; - } - - private DataManager getDataManager() { - if (dataManager == null) { - dataManager = new DataManager(account); - } - return dataManager; - } - - @Override - protected View createDialogContentView(LayoutInflater inflater, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.dialog_delete_file, null); - return view; - } - - @Override - protected void onDialogCreated(Dialog dialog) { - String str = getActivity().getString(isdir ? R.string.delete_dir : R.string.delete_file_f); - dialog.setTitle(str); - } - - @Override - protected DeleteTask prepareTask() { - if (dirents != null) { - return new DeleteTask(repoID, path, dirents, getDataManager()); - } - return new DeleteTask(repoID, path, isdir, getDataManager()); - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/DeleteRepoDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/DeleteRepoDialog.java deleted file mode 100644 index 1e8e2a81a..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/DeleteRepoDialog.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.seafile.seadroid2.ui.dialog; - -import android.app.Dialog; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.data.DataManager; - -class DeleteRepoTask extends TaskDialog.Task { - - private String mRepoID; - private DataManager mDataManager; - - DeleteRepoTask(String repoID, DataManager dataManager) { - mRepoID = repoID; - mDataManager = dataManager; - } - - @Override - protected void runTask() { - try { - mDataManager.deleteRepo(mRepoID); - } catch (SeafException e) { - setTaskException(e); - } - } -} - -public class DeleteRepoDialog extends TaskDialog { - - private final static String STATE_REPO_ID = "delete_repo_dialog.repo_id"; - private final static String STATE_ACCOUNT = "delete_repo_dialog.account"; - - private String mRepoID; - private Account mAccount; - private DataManager mDataManager; - - public void init(String repoID, Account account) { - mRepoID = repoID; - mAccount = account; - } - - private DataManager getDataManager() { - if (mDataManager == null) { - mDataManager = new DataManager(mAccount); - } - - return mDataManager; - } - - @Override - protected View createDialogContentView(LayoutInflater inflater, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.dialog_delete_repo, null); - - if (savedInstanceState != null) { - // Restore state - mRepoID = savedInstanceState.getString(STATE_REPO_ID); - mAccount = savedInstanceState.getParcelable(STATE_ACCOUNT); - } - - return view; - } - - @Override - protected void onSaveDialogContentState(Bundle outState) { - super.onSaveDialogContentState(outState); - outState.putString(STATE_REPO_ID, mRepoID); - outState.putParcelable(STATE_ACCOUNT, mAccount); - } - - @Override - protected void onDialogCreated(Dialog dialog) { - super.onDialogCreated(dialog); - dialog.setTitle(R.string.delete_repo_title); - } - - @Override - protected DeleteRepoTask prepareTask() { - return new DeleteRepoTask(mRepoID, getDataManager()); - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/FetchFileDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/FetchFileDialog.java index 4fa1d997f..110106e01 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/FetchFileDialog.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog/FetchFileDialog.java @@ -3,11 +3,14 @@ import java.net.HttpURLConnection; import androidx.appcompat.app.AlertDialog; + import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; import android.os.Handler; + import androidx.fragment.app.DialogFragment; + import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -19,9 +22,12 @@ import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeafConnection; import com.seafile.seadroid2.SeafException; +import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.transfer.DownloadTaskInfo; import com.seafile.seadroid2.transfer.TransferService; -import com.seafile.seadroid2.ui.BrowserActivity; +import com.seafile.seadroid2.ui.dialog_fragment.PasswordDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.listener.OnRefreshDataListener; +import com.seafile.seadroid2.ui.main.MainActivity; import com.seafile.seadroid2.util.Utils; /** @@ -48,12 +54,14 @@ public class FetchFileDialog extends DialogFragment { public interface FetchFileListener { void onDismiss(); + void onSuccess(); + void onFailure(SeafException e); } - private BrowserActivity getBrowserActivity() { - return (BrowserActivity)getActivity(); + private MainActivity getBrowserActivity() { + return (MainActivity) getActivity(); } public void init(String repoName, String repoID, String path, long fileSize, FetchFileListener listener) { @@ -66,10 +74,10 @@ public void init(String repoName, String repoID, String path, long fileSize, Fet // Get the latest version of the file private void startDownloadFile() { - BrowserActivity mActivity = getBrowserActivity(); + MainActivity mActivity = getBrowserActivity(); - taskID = mActivity.getTransferService().addDownloadTask(mActivity.getAccount(), - repoName, repoID, path, fileSize); + taskID = mActivity.getTransferService().addDownloadTask(SupportAccountManager.getInstance().getCurrentAccount(), + repoName, repoID, path, fileSize); } @Override @@ -112,40 +120,35 @@ public void handleDownloadTaskInfo(DownloadTaskInfo info) { return; } switch (info.state) { - case INIT: - break; - case TRANSFERRING: - updateProgress(info.fileSize, info.finished); - break; - case CANCELLED: - break; - case FAILED: - onTaskFailed(info.err); - break; - case FINISHED: - onTaskFinished(); - break; + case INIT: + break; + case TRANSFERRING: + updateProgress(info.fileSize, info.finished); + break; + case CANCELLED: + break; + case FAILED: + onTaskFailed(info.err); + break; + case FINISHED: + onTaskFinished(); + break; } } - private TaskDialog.TaskDialogListener getPasswordDialogListener() { - return new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - startDownloadFile(); - } - + private OnRefreshDataListener getPasswordDialogListener() { + return new OnRefreshDataListener() { @Override - public void onTaskCancelled() { - getDialog().dismiss(); + public void onActionStatus(boolean isDone) { + if (isDone) { + startDownloadFile(); + } else { + getDialog().dismiss(); + } } }; } - private void handlePassword() { - getBrowserActivity().showPasswordDialog(repoName, repoID, - getPasswordDialogListener()); - } private void onTaskFailed(SeafException err) { String fileName = Utils.fileNameFromPath(path); @@ -154,7 +157,7 @@ private void onTaskFailed(SeafException err) { final String message = String.format(getActivity().getString(R.string.file_not_found), fileName); Toast.makeText(getBrowserActivity(), message, Toast.LENGTH_SHORT).show(); } else if (err.getCode() == SeafConnection.HTTP_STATUS_REPO_PASSWORD_REQUIRED) { - handlePassword(); + showPasswordDialog(repoID, repoName); } else { getDialog().dismiss(); final String message = String.format(getActivity().getString(R.string.op_exception_failed_to_download_file), fileName); @@ -165,6 +168,13 @@ private void onTaskFailed(SeafException err) { } } + private void showPasswordDialog(String repoID, String repoName) { + PasswordDialogFragment dialogFragment = PasswordDialogFragment.newInstance(); + dialogFragment.initData(repoID, repoName); + dialogFragment.setRefreshListener(getPasswordDialogListener()); + dialogFragment.show(getChildFragmentManager(), PasswordDialogFragment.class.getSimpleName()); + } + protected void onTaskFinished() { getDialog().dismiss(); if (mListener != null) { @@ -191,7 +201,7 @@ private void updateProgress(long fileSize, long finished) { if (fileSize == 0) { percent = 100; } else { - percent = (int)(finished * 100 / fileSize); + percent = (int) (finished * 100 / fileSize); } progressBar.setProgress(percent); @@ -221,10 +231,10 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { LayoutInflater inflater = getActivity().getLayoutInflater(); View view = inflater.inflate(R.layout.dialog_open_file, null); - fileIcon = (ImageView)view.findViewById(R.id.file_icon); - fileNameText = (TextView)view.findViewById(R.id.file_name); - fileSizeText = (TextView)view.findViewById(R.id.file_size); - progressBar = (ProgressBar)view.findViewById(R.id.progress_bar); + fileIcon = (ImageView) view.findViewById(R.id.file_icon); + fileNameText = (TextView) view.findViewById(R.id.file_name); + fileSizeText = (TextView) view.findViewById(R.id.file_size); + progressBar = (ProgressBar) view.findViewById(R.id.progress_bar); if (savedInstanceState != null) { repoName = savedInstanceState.getString("repoName"); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/GetShareLinkEncryptDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/GetShareLinkEncryptDialog.java deleted file mode 100644 index e6b820e88..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/GetShareLinkEncryptDialog.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.seafile.seadroid2.ui.dialog; - -import android.app.Dialog; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.EditText; - -import com.seafile.seadroid2.R; - -class GetShareLinkPasswordTask extends TaskDialog.Task { - String password; - String days; - - public GetShareLinkPasswordTask(String password, String days) { - this.password = password; - this.days = days; - } - - @Override - protected void runTask() { - - } - - public String getPassword() { - return password; - } - - public String getDays() { - return days; - } -} - - -public class GetShareLinkEncryptDialog extends TaskDialog implements CompoundButton.OnCheckedChangeListener { - private EditText passwordText; - private EditText days; - private CheckBox cbExpiration; - - - @Override - protected View createDialogContentView(LayoutInflater inflater, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.dialog_share_password, null); - passwordText = (EditText) view.findViewById(R.id.password); - days = (EditText) view.findViewById(R.id.days); - cbExpiration = (CheckBox) view.findViewById(R.id.add_expiration); - cbExpiration.setOnCheckedChangeListener(this); - return view; - } - - - @Override - protected void onDialogCreated(Dialog dialog) { - dialog.setTitle(getString(R.string.share_input_password)); - dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - } - - @Override - protected void onSaveDialogContentState(Bundle outState) { - - } - - @Override - protected void onValidateUserInput() throws Exception { - String password = passwordText.getText().toString().trim(); - String day = days.getText().toString().trim(); - - if (password.length() == 0) { - String err = getActivity().getResources().getString(R.string.password_empty); - throw new Exception(err); - } - - if (password.length() < getResources().getInteger(R.integer.minimum_password_length)) { - throw new Exception(getResources().getString(R.string.err_passwd_too_short)); - } - - if (cbExpiration.isChecked() && day.length() == 0) { - String err = getActivity().getResources().getString(R.string.input_auto_expiration); - throw new Exception(err); - } - } - - @Override - protected void disableInput() { - super.disableInput(); - passwordText.setEnabled(false); - } - - @Override - protected void enableInput() { - super.enableInput(); - passwordText.setEnabled(true); - } - - - @Override - protected boolean executeTaskImmediately() { - return false; - } - - @Override - protected GetShareLinkPasswordTask prepareTask() { - String days = null; - String password = passwordText.getText().toString().trim(); - if (cbExpiration.isChecked()) { - days = this.days.getText().toString().trim(); - } - GetShareLinkPasswordTask task = new GetShareLinkPasswordTask(password, days); - return task; - } - - - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (errorIsVisible()) { - hideError(); - } - if (isChecked) { - days.setVisibility(View.VISIBLE); - } else { - days.setVisibility(View.GONE); - - } - } - - public String getPassword() { - if (getTask() != null) { - GetShareLinkPasswordTask task = (GetShareLinkPasswordTask) getTask(); - return task.getPassword(); - } - return null; - } - - public String getDays() { - if (getTask() != null) { - GetShareLinkPasswordTask task = (GetShareLinkPasswordTask) getTask(); - return task.getDays(); - } - return null; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/NewDirDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/NewDirDialog.java deleted file mode 100644 index 358366d7c..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/NewDirDialog.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.seafile.seadroid2.ui.dialog; - -import android.app.Dialog; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.widget.EditText; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.data.DataManager; - -class NewDirTask extends TaskDialog.Task { - String repoID; - String parentDir; - String dirName; - DataManager dataManager; - - public NewDirTask(String repoID, String parentDir, - String dirName, DataManager dataManager) { - this.repoID = repoID; - this.parentDir = parentDir; - this.dirName = dirName; - this.dataManager = dataManager; - } - - @Override - protected void runTask() { - try { - dataManager.createNewDir(repoID, parentDir, dirName); - } catch (SeafException e) { - setTaskException(e); - } - } -} - -public class NewDirDialog extends TaskDialog { - - private static final String STATE_TASK_REPO_ID = "new_dir_task.repo_id"; - private static final String STATE_TASK_PARENT_DIR = "new_dir_task.parent_dir"; - private static final String STATE_ACCOUNT = "new_dir_task.account.account"; - - private EditText dirNameText; - private DataManager dataManager; - private Account account; - - private String repoID; - private String parentDir; - - public String getNewDirName() { - return dirNameText.getText().toString().trim(); - } - - public void init(String repoID, String parentDir, Account account) { - this.repoID = repoID; - this.parentDir = parentDir; - this.account = account; - } - - private DataManager getDataManager() { - if (dataManager == null) { - dataManager = new DataManager(account); - } - - return dataManager; - } - - @Override - protected View createDialogContentView(LayoutInflater inflater, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.dialog_new_dir, null); - dirNameText = (EditText) view.findViewById(R.id.new_dir_name); - - if (savedInstanceState != null) { - repoID = savedInstanceState.getString(STATE_TASK_REPO_ID); - parentDir = savedInstanceState.getString(STATE_TASK_PARENT_DIR); - account = (Account)savedInstanceState.getParcelable(STATE_ACCOUNT); - } - - return view; - } - - @Override - protected void onDialogCreated(Dialog dialog) { - dialog.setTitle(getResources().getString(R.string.create_new_dir)); - dirNameText.setFocusable(true); - dirNameText.setFocusableInTouchMode(true); - dirNameText.requestFocus(); - dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - } - - @Override - protected void onValidateUserInput() throws Exception { - String dirName = dirNameText.getText().toString().trim(); - - if (dirName.length() == 0) { - String err = getActivity().getResources().getString(R.string.dir_name_empty); - throw new Exception(err); - } - } - - @Override - protected void onSaveDialogContentState(Bundle outState) { - outState.putString(STATE_TASK_PARENT_DIR, parentDir); - outState.putString(STATE_TASK_REPO_ID, repoID); - outState.putParcelable(STATE_ACCOUNT, account); - } - - @Override - protected void disableInput() { - super.disableInput(); - dirNameText.setEnabled(false); - } - - @Override - protected void enableInput() { - super.enableInput(); - dirNameText.setEnabled(true); - } - - @Override - protected NewDirTask prepareTask() { - EditText dirNameText = (EditText)getContentView().findViewById(R.id.new_dir_name); - String dirName = dirNameText.getText().toString().trim(); - NewDirTask task = new NewDirTask(repoID, parentDir, dirName, getDataManager()); - return task; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/NewFileDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/NewFileDialog.java deleted file mode 100644 index 7031b2570..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/NewFileDialog.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.seafile.seadroid2.ui.dialog; - -import android.app.Dialog; -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.widget.EditText; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.data.DataManager; -import com.seafile.seadroid2.ui.dialog.TaskDialog.Task; - -class NewFileTask extends TaskDialog.Task { - String repoID; - String parentDir; - String fileName; - DataManager dataManager; - - public NewFileTask(String repoID, String parentDir, - String fileName, DataManager dataManager) { - this.repoID = repoID; - this.parentDir = parentDir; - this.fileName = fileName; - this.dataManager = dataManager; - } - - @Override - protected void runTask() { - try { - dataManager.createNewFile(repoID, parentDir, fileName); - } catch (SeafException e) { - setTaskException(e); - } - } -} - -public class NewFileDialog extends TaskDialog { - private static final String STATE_TASK_REPO_ID = "new_file_task.repo_id"; - private static final String STATE_TASK_PARENT_DIR = "new_file_task.parent_dir"; - private static final String STATE_ACCOUNT = "new_file_task.account.account"; - - private EditText fileNameText; - private String repoID; - private String parentDir; - - private DataManager dataManager; - private Account account; - - public void init(String repoID, String parentDir, Account account) { - this.repoID = repoID; - this.parentDir = parentDir; - this.account = account; - } - - private DataManager getDataManager() { - if (dataManager == null) { - dataManager = new DataManager(account); - } - - return dataManager; - } - - public String getNewFileName() { - return fileNameText.getText().toString().trim(); - } - - @Override - protected View createDialogContentView(LayoutInflater inflater, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.dialog_new_file, null); - fileNameText = (EditText) view.findViewById(R.id.new_file_name); - - if (savedInstanceState != null) { - repoID = savedInstanceState.getString(STATE_TASK_REPO_ID); - parentDir = savedInstanceState.getString(STATE_TASK_PARENT_DIR); - account = (Account)savedInstanceState.getParcelable(STATE_ACCOUNT); - } - - return view; - } - - @Override - protected void onSaveDialogContentState(Bundle outState) { - outState.putString(STATE_TASK_PARENT_DIR, parentDir); - outState.putString(STATE_TASK_REPO_ID, repoID); - outState.putParcelable(STATE_ACCOUNT, account); - } - - @Override - protected void onDialogCreated(Dialog dialog) { - dialog.setTitle(getResources().getString(R.string.create_new_file)); - dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - } - - @Override - protected void onValidateUserInput() throws Exception { - String fileName = fileNameText.getText().toString().trim(); - - if (fileName.length() == 0) { - String err = getActivity().getResources().getString(R.string.file_name_empty); - throw new Exception(err); - } - } - - @Override - protected NewFileTask prepareTask() { - String fileName = fileNameText.getText().toString().trim(); - NewFileTask task = new NewFileTask(repoID, parentDir, fileName, getDataManager()); - return task; - } - - @Override - protected void disableInput() { - super.disableInput(); - fileNameText.setEnabled(false); - } - - @Override - protected void enableInput() { - super.enableInput(); - fileNameText.setEnabled(true); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/NewRepoDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/NewRepoDialog.java deleted file mode 100644 index fa4b21319..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/NewRepoDialog.java +++ /dev/null @@ -1,163 +0,0 @@ -package com.seafile.seadroid2.ui.dialog; - -import android.app.Dialog; -import android.os.Bundle; -import androidx.core.widget.NestedScrollView; -import androidx.appcompat.widget.SwitchCompat; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.widget.CompoundButton; -import android.widget.EditText; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.data.DataManager; - -class NewRepoTask extends TaskDialog.Task { - - private String mRepoName; - private String mPassword; - private DataManager mDataManager; - - public NewRepoTask(String repoName, String password, DataManager dataManager) { - mRepoName = repoName; - mPassword = password; - mDataManager = dataManager; - } - - @Override - protected void runTask() { - try { - mDataManager.createNewRepo(mRepoName, mPassword); - } catch (SeafException e) { - setTaskException(e); - } - } -} - -public class NewRepoDialog extends TaskDialog { - - private final static String STATE_ACCOUNT = "new_repo_dialog.account"; - - // The input fields of the dialog - private EditText mRepoNameText; - private SwitchCompat mEncryptSwitch; - // Use plain text field to avoid having to compare two obfuscated fields - private EditText mPasswordText; - private EditText mPasswordConfirmationText; - private NestedScrollView mNestedScrollView; - - private Account mAccount; - private DataManager mDataManager; - - public void init(Account account) { - // The DataManager is not parcelable, so we save the intermediate Account instead - mAccount = account; - } - - private DataManager getDataManager() { - if (mDataManager == null) { - mDataManager = new DataManager(mAccount); - } - - return mDataManager; - } - - public String getRepoName() { return mRepoNameText.getText().toString().trim(); } - private String getPassword() { return mPasswordText.getText().toString().trim(); } - private String getPasswordConfirmation() { return mPasswordConfirmationText.getText().toString().trim(); } - - @Override - protected View createDialogContentView(LayoutInflater inflater, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.dialog_new_repo, null); - mRepoNameText = (EditText) view.findViewById(R.id.new_repo_name); - mEncryptSwitch = (SwitchCompat) view.findViewById(R.id.new_repo_encrypt_switch); - mPasswordText = (EditText) view.findViewById(R.id.new_repo_password); - mPasswordText.setHint(String.format( - getResources().getString(R.string.passwd_min_len_limit_hint), - getResources().getInteger(R.integer.minimum_password_length) - )); - mPasswordConfirmationText = (EditText) view.findViewById(R.id.new_repo_password_confirmation); - mNestedScrollView = (NestedScrollView) view.findViewById(R.id.nsv_new_repo_container); - - if (savedInstanceState != null) { - // Restore state - mAccount = (Account) savedInstanceState.getParcelable(STATE_ACCOUNT); - } - - mEncryptSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked) { - mNestedScrollView.setVisibility(View.VISIBLE); - } else { - mNestedScrollView.setVisibility(View.GONE); - - // Delete entered passwords so hiding the input fields creates an unencrypted repo - mPasswordText.setText(""); - mPasswordConfirmationText.setText(""); - } - } - }); - - return view; - } - - @Override - protected void onSaveDialogContentState(Bundle outState) { - // Save state - outState.putParcelable(STATE_ACCOUNT, mAccount); - } - - @Override - protected void onDialogCreated(Dialog dialog) { - dialog.setTitle(R.string.create_new_repo); - dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - } - - @Override - protected void onValidateUserInput() throws Exception { - if (getRepoName().length() == 0) { - throw new Exception(getResources().getString(R.string.repo_name_empty)); - } - - if (mEncryptSwitch.isChecked()) { - if (getPassword().length() == 0) { - throw new Exception(getResources().getString(R.string.err_passwd_empty)); - } - - if (getPassword().length() < getResources().getInteger(R.integer.minimum_password_length)) { - throw new Exception(getResources().getString(R.string.err_passwd_too_short)); - } - - if (!getPassword().equals(getPasswordConfirmation())) { - throw new Exception(getResources().getString(R.string.err_passwd_mismatch)); - } - } - } - - @Override - protected NewRepoTask prepareTask() { - return new NewRepoTask(getRepoName(), getPassword(), getDataManager()); - } - - @Override - protected void disableInput() { - super.disableInput(); - mRepoNameText.setEnabled(false); - mEncryptSwitch.setEnabled(false); - mPasswordText.setEnabled(false); - mPasswordConfirmationText.setEnabled(false); - } - - @Override - protected void enableInput() { - super.enableInput(); - mRepoNameText.setEnabled(true); - mEncryptSwitch.setEnabled(true); - mPasswordText.setEnabled(true); - mPasswordConfirmationText.setEnabled(true); - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/PasswordDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/PasswordDialog.java deleted file mode 100644 index 6f207e4bc..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/PasswordDialog.java +++ /dev/null @@ -1,239 +0,0 @@ -package com.seafile.seadroid2.ui.dialog; - -import android.app.Dialog; -import android.os.Bundle; -import android.text.TextUtils; -import android.text.method.HideReturnsTransformationMethod; -import android.text.method.PasswordTransformationMethod; -import android.util.Pair; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.RelativeLayout; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.crypto.Crypto; -import com.seafile.seadroid2.data.DataManager; -import com.seafile.seadroid2.data.SeafRepoEncrypt; - -import java.io.UnsupportedEncodingException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; - -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; - -class SetPasswordTask extends TaskDialog.Task { - public static final String DEBUG_TAG = SetPasswordTask.class.getSimpleName(); - - String repoID; - String password; - DataManager dataManager; - - public SetPasswordTask(String repoID, String password, - DataManager dataManager) { - this.repoID = repoID; - this.password = password; - this.dataManager = dataManager; - } - - @Override - protected void runTask() { - SeafRepoEncrypt repo = dataManager.getCachedRepoEncryptByID(repoID); - try { - if (repo == null || !repo.canLocalDecrypt()) { - dataManager.setPassword(repoID, password); - } else { - Crypto.verifyRepoPassword(repoID, password, repo.encVersion, repo.magic); - } - } catch (SeafException e) { - setTaskException(e); - } catch (NoSuchAlgorithmException | UnsupportedEncodingException | InvalidKeySpecException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | BadPaddingException | IllegalBlockSizeException e) { - e.printStackTrace(); - } - } -} - -public class PasswordDialog extends TaskDialog { - public static final String DEBUG_TAG = PasswordDialog.class.getCanonicalName(); - - private static final String STATE_TASK_REPO_NAME = "set_password_task.repo_name"; - private static final String STATE_TASK_REPO_ID = "set_password_task.repo_id"; - private static final String STATE_TASK_PASSWORD = "set_password_task.password"; - private static final String STATE_ACCOUNT = "set_password_task.account"; - - private EditText passwordEt; - private RelativeLayout eyeContainerRl; - private ImageView eyeClickIv; - private String repoID, repoName; - private DataManager dataManager; - private Account account; - private String password; - - private boolean isPasswordVisible; - - - public void setRepo(String repoName, String repoID, Account account) { - this.repoName = repoName; - this.repoID = repoID; - this.account = account; - } - - private DataManager getDataManager() { - if (dataManager == null) { - dataManager = new DataManager(account); - } - - return dataManager; - } - - @Override - protected View createDialogContentView(LayoutInflater inflater, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.dialog_password, null); - passwordEt = (EditText) view.findViewById(R.id.password); - eyeContainerRl = (RelativeLayout) view.findViewById(R.id.rl_layout_eye); - eyeClickIv = (ImageView) view.findViewById(R.id.iv_eye_click); - - passwordEt.setFocusable(true); - passwordEt.setFocusableInTouchMode(true); - passwordEt.requestFocus(); - if (savedInstanceState != null) { - repoName = savedInstanceState.getString(STATE_TASK_REPO_NAME); - repoID = savedInstanceState.getString(STATE_TASK_REPO_ID); - account = (Account)savedInstanceState.getParcelable(STATE_ACCOUNT); - } - - if (password != null) { - passwordEt.setText(password); - } - - return view; - } - - @Override - protected void onDialogCreated(Dialog dialog) { - dialog.setTitle(repoName); - dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - eyeContainerRl.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (!isPasswordVisible) { - eyeClickIv.setImageResource(R.drawable.icon_eye_open); - passwordEt.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); - } else { - eyeClickIv.setImageResource(R.drawable.icon_eye_close); - passwordEt.setTransformationMethod(PasswordTransformationMethod.getInstance()); - } - isPasswordVisible = !isPasswordVisible; - passwordEt.postInvalidate(); - String input = passwordEt.getText().toString().trim(); - if (!TextUtils.isEmpty(input)) { - passwordEt.setSelection(input.length()); - } - } - }); - } - - @Override - protected void onSaveDialogContentState(Bundle outState) { - outState.putString(STATE_TASK_REPO_NAME, repoName); - outState.putString(STATE_TASK_REPO_ID, repoID); - outState.putParcelable(STATE_ACCOUNT, account); - } - - @Override - protected void onValidateUserInput() throws Exception { - String password = passwordEt.getText().toString().trim(); - - if (password.length() == 0) { - String err = getActivity().getResources().getString(R.string.password_empty); - throw new Exception(err); - } - } - - @Override - protected void disableInput() { - super.disableInput(); - passwordEt.setEnabled(false); - } - - @Override - protected void enableInput() { - super.enableInput(); - passwordEt.setEnabled(true); - } - - @Override - protected SetPasswordTask prepareTask() { - String password = passwordEt.getText().toString().trim(); - SetPasswordTask task = new SetPasswordTask(repoID, password, getDataManager()); - return task; - } - - @Override - protected void onSaveTaskState(Bundle outState) { - SetPasswordTask task = (SetPasswordTask)getTask(); - if (task != null) { - outState.putString(STATE_TASK_PASSWORD, task.password); - } - } - - @Override - protected SetPasswordTask onRestoreTaskState(Bundle outState) { - if (outState == null) { - return null; - } - - String password = outState.getString(STATE_TASK_PASSWORD); - if (password != null) { - return new SetPasswordTask(repoID, password, getDataManager()); - } else { - return null; - } - } - - public void setPassword(String password) { - this.password = password; - } - - @Override - protected boolean executeTaskImmediately() { - return password != null; - } - - @Override - public void onTaskSuccess() { - SeafRepoEncrypt repo = dataManager.getCachedRepoEncryptByID(repoID); - String password = passwordEt.getText().toString().trim(); - if (repo == null || !repo.canLocalDecrypt()) { - dataManager.setRepoPasswordSet(repoID, password); - } else { - if (TextUtils.isEmpty(repo.magic)) - return; - - try { - final Pair pair = Crypto.generateKey(password, repo.encKey, repo.encVersion); - dataManager.setRepoPasswordSet(repoID, pair.first, pair.second); - } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) { - // TODO notify error - e.printStackTrace(); - } - } - super.onTaskSuccess(); - } - - @Override - protected String getErrorFromException(SeafException e) { - if (e.getCode() == 400 || e.getCode() == SeafException.invalidPassword.getCode()) { - return getString(R.string.wrong_password); - } - return e.getMessage(); - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/PolicyDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/PolicyDialog.java deleted file mode 100644 index ef86ad33c..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/PolicyDialog.java +++ /dev/null @@ -1,149 +0,0 @@ -package com.seafile.seadroid2.ui.dialog; - -import android.app.Dialog; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.text.SpannableStringBuilder; -import android.text.TextPaint; -import android.text.method.LinkMovementMethod; -import android.text.style.ClickableSpan; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; -import android.widget.TextView; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.ui.settings.PrivacyPolicyActivity; -import com.seafile.seadroid2.util.Utils; - -public class PolicyDialog extends Dialog implements View.OnClickListener { - private TextView tv_statement, tv_details; - private TextView mConfirm, mCancel; - - private Context mContext; - private String content; - private OnCloseListener listener; - private String positiveName; - private String negativeName; - private String title; - private String policy_statement = "请你务必审慎阅读、充分理解\"隐私政策\"个条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。"; - private String policy_details = "你可阅读《隐私政策》了解详细信息。如果你同意,请点击下面按钮开始使用本应用。"; - - public PolicyDialog(Context context) { - super(context); - this.mContext = context; - } - - public PolicyDialog(@NonNull Context context, int themeResId, String content) { - super(context, themeResId); - this.mContext = context; - this.content = content; - } - - public PolicyDialog(@NonNull Context context, int themeResId, OnCloseListener listener) { - super(context, themeResId); - this.mContext = context; - this.listener = listener; - } - - public PolicyDialog(@NonNull Context context, int themeResId, String content, OnCloseListener listener) { - super(context, themeResId); - this.mContext = context; - this.content = content; - this.listener = listener; - } - - protected PolicyDialog(@NonNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener) { - super(context, cancelable, cancelListener); - this.mContext = context; - } - - public PolicyDialog setTitle(String title) { - this.title = title; - return this; - } - - public PolicyDialog setContent(String content) { - this.content = content; - return this; - } - - public PolicyDialog setPositiveButton(String name) { - this.positiveName = name; - return this; - } - - public PolicyDialog setNegativeButton(String name) { - this.negativeName = name; - return this; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.policy_dialog); - - - Window win = getWindow(); - WindowManager.LayoutParams lp = win.getAttributes(); - lp.height = Utils.dip2px(mContext, 500); - lp.width = Utils.dip2px(mContext, 300); - win.setAttributes(lp); - setCanceledOnTouchOutside(true); - tv_statement = findViewById(R.id.tv_policy_statement); - tv_details = findViewById(R.id.tv_policy_details); - mCancel = findViewById(R.id.cancel); - mConfirm = findViewById(R.id.confirm); - tv_statement.setText(policy_statement); - SpannableStringBuilder ssb = new SpannableStringBuilder(); - ssb.append(policy_details); - final int start = policy_details.indexOf("《"); - ssb.setSpan(new ClickableSpan() { - - @Override - public void onClick(View widget) { - Intent intent = new Intent(mContext, PrivacyPolicyActivity.class); - mContext.startActivity(intent); - } - - @Override - public void updateDrawState(TextPaint ds) { - super.updateDrawState(ds); - ds.setColor(mContext.getResources().getColor(R.color.blue_700)); - ds.setUnderlineText(false); - } - - }, start, start + 6, 0); - tv_details.setMovementMethod(LinkMovementMethod.getInstance()); - tv_details.setText(ssb, TextView.BufferType.SPANNABLE); - - - mConfirm.setOnClickListener(this); - mCancel.setOnClickListener(this); - } - - @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.cancel: - if (listener != null) { - listener.onClick(false); - } - this.dismiss(); - break; - case R.id.confirm: - if (listener != null) { - listener.onClick(true); - } - this.dismiss(); - break; - } - } - - public interface OnCloseListener { - void onClick(boolean confirm); - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/RenameFileDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/RenameFileDialog.java deleted file mode 100644 index 7047ab2cd..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/RenameFileDialog.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.seafile.seadroid2.ui.dialog; - -import android.app.Dialog; -import android.os.Bundle; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.widget.EditText; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.data.DataManager; -import com.seafile.seadroid2.ui.dialog.TaskDialog.Task; -import com.seafile.seadroid2.util.Utils; - -class RenameTask extends TaskDialog.Task { - String repoID; - String path; - String newName; - boolean isdir; - DataManager dataManager; - - public RenameTask(String repoID, String path, - String newName, boolean isdir, DataManager dataManager) { - this.repoID = repoID; - this.path = path; - this.newName = newName; - this.isdir = isdir; - this.dataManager = dataManager; - } - - @Override - protected void runTask() { - if (newName.equals(Utils.fileNameFromPath(path))) { - return; - } - try { - dataManager.rename(repoID, path, newName, isdir); - } catch (SeafException e) { - setTaskException(e); - } - } -} - -public class RenameFileDialog extends TaskDialog { - private EditText fileNameText; - private String repoID; - private String path; - private boolean isdir; - - private DataManager dataManager; - private Account account; - - private static final String STATE_REPO_ID = "rename_task.repo_name"; - private static final String STATE_PATH = "rename_task.repo_id"; - private static final String STATE_ISDIR = "rename_task.account"; - private static final String STATE_ACCOUNT = "rename_task.account"; - - public void init(String repoID, String path, boolean isdir, Account account) { - this.repoID = repoID; - this.path = path; - this.isdir = isdir; - this.account = account; - } - - private DataManager getDataManager() { - if (dataManager == null) { - dataManager = new DataManager(account); - } - - return dataManager; - } - - public String getNewFileName() { - return fileNameText.getText().toString().trim(); - } - - @Override - protected View createDialogContentView(LayoutInflater inflater, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.dialog_new_file, null); - fileNameText = (EditText) view.findViewById(R.id.new_file_name); - - if (savedInstanceState != null) { - repoID = savedInstanceState.getString(STATE_REPO_ID); - path = savedInstanceState.getString(STATE_PATH); - isdir = savedInstanceState.getBoolean(STATE_ISDIR); - account = (Account)savedInstanceState.getParcelable(STATE_ACCOUNT); - } - - final String fileName = Utils.fileNameFromPath(path); - if (!TextUtils.isEmpty(fileName)) { - fileNameText.setText(fileName); - fileNameText.setSelection(fileName.length()); - } - - return view; - } - - @Override - protected void onDialogCreated(Dialog dialog) { - String str = getActivity().getString(isdir ? R.string.rename_dir : R.string.rename_file); - dialog.setTitle(str); - dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - } - - @Override - protected void onValidateUserInput() throws Exception { - String fileName = fileNameText.getText().toString().trim(); - - if (fileName.length() == 0) { - String err = getActivity().getResources().getString(R.string.file_name_empty); - throw new Exception(err); - } - } - - @Override - protected RenameTask prepareTask() { - String newName = fileNameText.getText().toString().trim(); - - RenameTask task = new RenameTask(repoID, path, newName, isdir, getDataManager()); - return task; - } - - @Override - protected void disableInput() { - super.disableInput(); - fileNameText.setEnabled(false); - } - - @Override - protected void enableInput() { - super.enableInput(); - fileNameText.setEnabled(true); - } - - @Override - protected void onSaveDialogContentState(Bundle outState) { - outState.putString(STATE_REPO_ID, repoID); - outState.putString(STATE_PATH, path); - outState.putBoolean(STATE_ISDIR, isdir); - outState.putParcelable(STATE_ACCOUNT, account); - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/RenameRepoDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/RenameRepoDialog.java deleted file mode 100644 index 32011a750..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/RenameRepoDialog.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.seafile.seadroid2.ui.dialog; - -import android.app.Dialog; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.widget.EditText; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.data.DataManager; - -class RenameRepoTask extends TaskDialog.Task { - - private String mRepoID; - private String mCurrentName; - private String mNewName; - private DataManager mDataManager; - - RenameRepoTask(String repoID, String currentName, String newName, DataManager dataManager) { - mRepoID = repoID; - mCurrentName = currentName; - mNewName = newName; - mDataManager = dataManager; - } - - @Override - protected void runTask() { - if (mNewName.equals(mCurrentName)) { return; } - - try { - mDataManager.renameRepo(mRepoID, mNewName); - } catch (SeafException e) { - setTaskException(e); - } - } -} - -public class RenameRepoDialog extends TaskDialog { - - private final static String STATE_REPO_ID = "rename_repo_dialog.repo_id"; - private final static String STATE_CURRENT_NAME = "rename_repo_dialog.current_name"; - private final static String STATE_ACCOUNT = "rename_repo_dialog.account"; - - private String mRepoID; - private String mCurrentName; - private Account mAccount; - private DataManager mDataManager; - - private EditText mRepoNameText; - - public void init(String repoID, String currentName, Account account) { - mRepoID = repoID; - mCurrentName = currentName; - mAccount = account; - } - - private DataManager getDataManager() { - if (mDataManager == null) { - mDataManager = new DataManager(mAccount); - } - - return mDataManager; - } - - private String getNewName() { return mRepoNameText.getText().toString().trim(); } - - @Override - protected View createDialogContentView(LayoutInflater inflater, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.dialog_new_file, null); - mRepoNameText = (EditText) view.findViewById(R.id.new_file_name); - - if (savedInstanceState != null) { - mRepoID = savedInstanceState.getString(STATE_REPO_ID); - mCurrentName = savedInstanceState.getString(STATE_CURRENT_NAME); - mAccount = savedInstanceState.getParcelable(STATE_ACCOUNT); - } - - mRepoNameText.setText(mCurrentName); - mRepoNameText.setSelection(mCurrentName.length()); - - return view; - } - - @Override - protected void onDialogCreated(Dialog dialog) { - super.onDialogCreated(dialog); - dialog.setTitle(R.string.rename_repo); - dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - } - - @Override - protected void onSaveDialogContentState(Bundle outState) { - super.onSaveDialogContentState(outState); - outState.putString(STATE_REPO_ID, mRepoID); - outState.putString(STATE_CURRENT_NAME, mCurrentName); - outState.putParcelable(STATE_ACCOUNT, mAccount); - } - - @Override - protected void onValidateUserInput() throws Exception { - super.onValidateUserInput(); - - if (getNewName().length() == 0) { - throw new Exception(getResources().getString(R.string.repo_name_empty)); - } - - } - - @Override - protected void disableInput() { - super.disableInput(); - mRepoNameText.setEnabled(false); - } - - @Override - protected void enableInput() { - super.enableInput(); - mRepoNameText.setEnabled(true); - } - - @Override - protected Task prepareTask() { - return new RenameRepoTask(mRepoID, mCurrentName, getNewName(), getDataManager()); - } -} - diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/SortFilesDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/SortFilesDialogFragment.java deleted file mode 100644 index 2ce5658ef..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/SortFilesDialogFragment.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.seafile.seadroid2.ui.dialog; - -import android.app.Activity; -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; -import androidx.fragment.app.DialogFragment; -import androidx.appcompat.app.AlertDialog; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SettingsManager; -import com.seafile.seadroid2.ui.adapter.SeafItemAdapter; - -public class SortFilesDialogFragment extends DialogFragment { - - /** - * The activity that creates an instance of this dialog fragment must - * implement this interface in order to receive event callbacks. - * Each method passes the DialogFragment in case the host needs to query it. - */ - public interface SortItemClickListener { - void onSortFileItemClick(DialogFragment dialog, int position); - } - - // Use this instance of the interface to deliver action events - SortItemClickListener mListener; - - // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - // Verify that the host activity implements the callback interface - try { - // Instantiate the NoticeDialogListener so we can send events to the host - mListener = (SortItemClickListener) activity; - } catch (ClassCastException e) { - // The activity doesn't implement the interface, throw exception - throw new ClassCastException(activity.toString() + " must implement NoticeDialogListener"); - } - } - - private int calculateCheckedItem() { - switch (SettingsManager.instance().getSortFilesTypePref()) { - case SeafItemAdapter.SORT_BY_NAME: - if (SettingsManager.instance().getSortFilesOrderPref() == SeafItemAdapter.SORT_ORDER_ASCENDING) - return 0; - else if (SettingsManager.instance().getSortFilesOrderPref() == SeafItemAdapter.SORT_ORDER_DESCENDING) - return 1; - break; - case SeafItemAdapter.SORT_BY_LAST_MODIFIED_TIME: - if (SettingsManager.instance().getSortFilesOrderPref() == SeafItemAdapter.SORT_ORDER_ASCENDING) - return 2; - else if (SettingsManager.instance().getSortFilesOrderPref() == SeafItemAdapter.SORT_ORDER_DESCENDING) - return 3; - break; - } - return 0; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) - .setTitle(getString(R.string.sort_files)) - .setSingleChoiceItems(R.array.sort_files_options_array, - calculateCheckedItem(), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - mListener.onSortFileItemClick(SortFilesDialogFragment.this, i); - dismiss(); - - } - - }); - return builder.create(); - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/UploadChoiceDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/UploadChoiceDialog.java index 9fbdc7d63..c631a5ed0 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/UploadChoiceDialog.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog/UploadChoiceDialog.java @@ -3,17 +3,13 @@ import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; -import android.content.Intent; import android.os.Bundle; -import androidx.fragment.app.DialogFragment; + import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeadroidApplication; -import com.seafile.seadroid2.fileschooser.MultiFileChooserActivity; -import com.seafile.seadroid2.gallery.MultipleImageSelectionActivity; -import com.seafile.seadroid2.ui.BrowserActivity; -import com.seafile.seadroid2.util.Utils; public class UploadChoiceDialog extends DialogFragment { private Context ctx = SeadroidApplication.getAppContext(); @@ -25,29 +21,29 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { setTitle(getResources().getString(R.string.pick_upload_type)). setItems(R.array.pick_upload_array, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case 0: - Intent intent = new Intent(ctx, MultiFileChooserActivity.class); - getActivity().startActivityForResult(intent, BrowserActivity.PICK_FILES_REQUEST); - break; - case 1: - // photos - intent = new Intent(ctx, MultipleImageSelectionActivity.class); - getActivity().startActivityForResult(intent, BrowserActivity.PICK_PHOTOS_VIDEOS_REQUEST); - break; - case 2: - // thirdparty file chooser - Intent target = Utils.createGetContentIntent(); - intent = Intent.createChooser(target, getString(R.string.choose_file)); - getActivity().startActivityForResult(intent, BrowserActivity.PICK_FILE_REQUEST); - break; - default: - return; - } - } - }); + @Override + public void onClick(DialogInterface dialog, int which) { +// switch (which) { +// case 0: +// Intent intent = new Intent(ctx, MultiFileChooserActivity.class); +// getActivity().startActivityForResult(intent, BrowserActivity.PICK_FILES_REQUEST); +// break; +// case 1: +// // photos +// intent = new Intent(ctx, MultipleImageSelectionActivity.class); +// getActivity().startActivityForResult(intent, BrowserActivity.PICK_PHOTOS_VIDEOS_REQUEST); +// break; +// case 2: +// // thirdparty file chooser +// Intent target = Utils.createGetContentIntent(); +// intent = Intent.createChooser(target, getString(R.string.choose_file)); +// getActivity().startActivityForResult(intent, BrowserActivity.PICK_FILE_REQUEST); +// break; +// default: +// return; +// } + } + }); return builder.show(); } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/ClearCacheDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/ClearCacheDialogFragment.java new file mode 100644 index 000000000..4073ff123 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/ClearCacheDialogFragment.java @@ -0,0 +1,53 @@ +package com.seafile.seadroid2.ui.dialog_fragment; + +import android.os.Bundle; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.fragment.RequestCustomDialogFragmentWithVM; +import com.seafile.seadroid2.ui.dialog_fragment.viewmodel.ClearCacheViewModel; + +import io.reactivex.functions.Consumer; + +public class ClearCacheDialogFragment extends RequestCustomDialogFragmentWithVM { + public static ClearCacheDialogFragment newInstance() { + + Bundle args = new Bundle(); + + ClearCacheDialogFragment fragment = new ClearCacheDialogFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + protected int getLayoutId() { + return R.layout.view_dialog_message_textview; + } + + @Override + protected void onPositiveClick() { + getViewModel().clear(new Consumer() { + @Override + public void accept(Boolean aBoolean) throws Exception { + // + refreshData(aBoolean); + dismiss(); + } + }); + } + + @Override + public int getDialogTitleRes() { + return R.string.settings_clear_cache_title; + } + + @Override + protected void initView(LinearLayout containerView) { + super.initView(containerView); + + //set message + TextView textView = containerView.findViewById(R.id.message_view); + textView.setText(R.string.settings_clear_cache_hint); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/ClearPasswordDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/ClearPasswordDialogFragment.java new file mode 100644 index 000000000..9faa663be --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/ClearPasswordDialogFragment.java @@ -0,0 +1,50 @@ +package com.seafile.seadroid2.ui.dialog_fragment; + +import android.os.Bundle; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.fragment.CustomDialogFragment; +import com.seafile.seadroid2.data.DataManager; +import com.seafile.seadroid2.data.DatabaseHelper; + +public class ClearPasswordDialogFragment extends CustomDialogFragment { + public static ClearPasswordDialogFragment newInstance() { + + Bundle args = new Bundle(); + + ClearPasswordDialogFragment fragment = new ClearPasswordDialogFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + protected int getLayoutId() { + return R.layout.view_dialog_message_textview; + } + + @Override + public int getDialogTitleRes() { + return R.string.clear_password_title; + } + + @Override + protected void onPositiveClick() { + DataManager.clearPassword(); + + // clear cached data from database + DatabaseHelper dbHelper = DatabaseHelper.getDatabaseHelper(); + dbHelper.clearEnckeys(); + } + + @Override + protected void initView(LinearLayout containerView) { + super.initView(containerView); + + //set message + TextView textView = containerView.findViewById(R.id.message_view); + textView.setText(R.string.clear_password_warning); + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/CopyMoveDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/CopyMoveDialogFragment.java new file mode 100644 index 000000000..65a5aa652 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/CopyMoveDialogFragment.java @@ -0,0 +1,137 @@ +package com.seafile.seadroid2.ui.dialog_fragment; + +import android.os.Bundle; +import android.text.TextUtils; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.blankj.utilcode.util.CollectionUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.fragment.RequestCustomDialogFragmentWithVM; +import com.seafile.seadroid2.context.CopyMoveContext; +import com.seafile.seadroid2.ui.dialog_fragment.viewmodel.CopyMoveViewModel; +import com.seafile.seadroid2.util.Utils; + +public class CopyMoveDialogFragment extends RequestCustomDialogFragmentWithVM { + private CopyMoveContext ctx; + + private Boolean isOping = false; + + public static CopyMoveDialogFragment newInstance() { + + Bundle args = new Bundle(); + + CopyMoveDialogFragment fragment = new CopyMoveDialogFragment(); + fragment.setArguments(args); + return fragment; + } + + public void initData(CopyMoveContext context) { + ctx = context; + } + + @Override + protected int getLayoutId() { + return R.layout.view_dialog_message_textview; + } + + @Override + public int getDialogTitleRes() { + if (ctx != null) { + if (ctx.isdir) { + return ctx.isCopy() ? R.string.copy_folder_ing : R.string.move_folder_ing; + } else { + return ctx.isCopy() ? R.string.copy_file_ing : R.string.move_file_ing; + } + } + return super.getDialogTitleRes(); + + } + + @Override + protected void initView(LinearLayout containerView) { + super.initView(containerView); + + int strMsgId; + if (ctx.isdir) { + strMsgId = ctx.isCopy() ? R.string.copy_file_from : R.string.move_file_from; + } else { + strMsgId = ctx.isCopy() ? R.string.copy_file_from : R.string.move_file_from; + } + + String strMsg = getString(strMsgId); + + String srcDir = Utils.pathJoin(ctx.srcRepoName, ctx.srcDir); + String srcDirPath = Utils.removeLastPathSeperator(srcDir); + + String dstPath = Utils.pathJoin(ctx.dstRepoName, ctx.dstDir); + String dstDirPath = Utils.removeLastPathSeperator(dstPath); + + //set message + TextView tvMessage = containerView.findViewById(R.id.message_view); + if (srcDirPath != null && dstDirPath != null) { + tvMessage.setText(String.format(strMsg, srcDirPath, dstDirPath)); + } else { + tvMessage.setText(getDialogTitleRes()); + } + + } + + + @Override + public void onResume() { + super.onResume(); + + if (!isOping) { + onPositiveClick(); + isOping = true; + } + } + + @Override + protected void onPositiveClick() { + if (!checkData()) { + return; + } + + if (ctx.isCopy()) { + getViewModel().copy(ctx.dstDir, ctx.dstRepoId, ctx.srcDir, ctx.srcRepoId, ctx.dirents); + } else { + getViewModel().move(ctx.dstDir, ctx.dstRepoId, ctx.srcDir, ctx.srcRepoId, ctx.dirents); + } + } + + @Override + protected void initViewModel() { + super.initViewModel(); + + getViewModel().getResultLiveData().observe(this, resultModel -> { + refreshData(); + + dismiss(); + }); + + getViewModel().getRefreshLiveData().observe(this, this::showLoading); + + } + + private boolean checkData() { + if (ctx == null) { + return false; + } + + if (CollectionUtils.isEmpty(ctx.dirents)) { + return false; + } + + if (TextUtils.isEmpty(ctx.srcRepoId) + || TextUtils.isEmpty(ctx.srcRepoName) + || TextUtils.isEmpty(ctx.srcDir) + || TextUtils.isEmpty(ctx.dstRepoId) + || TextUtils.isEmpty(ctx.dstDir)) { + return false; + } + + return true; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/DeleteFileDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/DeleteFileDialogFragment.java new file mode 100644 index 000000000..e46b6dfb1 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/DeleteFileDialogFragment.java @@ -0,0 +1,80 @@ +package com.seafile.seadroid2.ui.dialog_fragment; + +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.lifecycle.Observer; + +import com.blankj.utilcode.util.CollectionUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.fragment.RequestCustomDialogFragmentWithVM; +import com.seafile.seadroid2.data.db.entities.DirentModel; +import com.seafile.seadroid2.ui.dialog_fragment.viewmodel.DeleteDirentsViewModel; + +import java.util.ArrayList; +import java.util.List; + +public class DeleteFileDialogFragment extends RequestCustomDialogFragmentWithVM { + private List dirents; + private boolean isDir; + + public static DeleteFileDialogFragment newInstance() { + return new DeleteFileDialogFragment(); + } + + public void initData(DirentModel dirent) { + this.dirents = new ArrayList<>(); + dirents.add(dirent); + isDir = dirent.isDir(); + } + + public void initData(List dirents) { + this.dirents = dirents; + isDir = false; + } + + @Override + protected int getLayoutId() { + return R.layout.view_dialog_message_textview; + } + + @Override + protected void onPositiveClick() { + + if (CollectionUtils.isEmpty(dirents)) { + return; + } + + getViewModel().deleteDirents(dirents); + } + + @Override + protected void initViewModel() { + super.initViewModel(); + + getViewModel().getRefreshLiveData().observe(this, this::showLoading); + + getViewModel().getActionLiveData().observe(this, new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + refreshData(); + + dismiss(); + } + }); + } + + @Override + public int getDialogTitleRes() { + return isDir ? R.string.delete_dir : R.string.delete_file_f; + } + + @Override + protected void initView(LinearLayout containerView) { + super.initView(containerView); + + //set message + TextView textView = containerView.findViewById(R.id.message_view); + textView.setText(R.string.delete_file); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/DeleteRepoDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/DeleteRepoDialogFragment.java new file mode 100644 index 000000000..1ba0a85ce --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/DeleteRepoDialogFragment.java @@ -0,0 +1,92 @@ +package com.seafile.seadroid2.ui.dialog_fragment; + +import android.os.Bundle; +import android.text.TextUtils; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.lifecycle.Observer; + +import com.blankj.utilcode.util.ToastUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.fragment.RequestCustomDialogFragmentWithVM; +import com.seafile.seadroid2.data.model.ResultModel; +import com.seafile.seadroid2.ui.dialog_fragment.viewmodel.DeleteRepoViewModel; + +public class DeleteRepoDialogFragment extends RequestCustomDialogFragmentWithVM { + private String repoId; + + public static DeleteRepoDialogFragment newInstance(String repoId) { + Bundle args = new Bundle(); + args.putString("repoId", repoId); + DeleteRepoDialogFragment fragment = new DeleteRepoDialogFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() == null) { + throw new IllegalArgumentException("this dialogFragment need Arguments"); + } + + if (!getArguments().containsKey("repoId")) { + throw new IllegalArgumentException("this dialogFragment need repoId param"); + } + + repoId = getArguments().getString("repoId", null); + + if (TextUtils.isEmpty(repoId)) { + throw new IllegalArgumentException("this dialogFragment need repoId param"); + } + } + + @Override + protected int getLayoutId() { + return R.layout.view_dialog_message_textview; + } + + @Override + public int getDialogTitleRes() { + return R.string.delete_repo_title; + } + + @Override + protected void onPositiveClick() { + getViewModel().deleteRepo(repoId); + } + + @Override + protected void initViewModel() { + super.initViewModel(); + + getViewModel().getActionLiveData().observe(this, new Observer() { + @Override + public void onChanged(ResultModel resultModel) { + + if (!TextUtils.isEmpty(resultModel.error_msg)) { + ToastUtils.showLong(resultModel.error_msg); + dismiss(); + return; + } + + refreshData(); + + dismiss(); + } + }); + + getViewModel().getRefreshLiveData().observe(this, this::showLoading); + } + + @Override + protected void initView(LinearLayout containerView) { + super.initView(containerView); + + //set message + TextView textView = containerView.findViewById(R.id.message_view); + textView.setText(R.string.delete_repo); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/DialogService.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/DialogService.java new file mode 100644 index 000000000..bc72d8817 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/DialogService.java @@ -0,0 +1,66 @@ +package com.seafile.seadroid2.ui.dialog_fragment; + +import com.seafile.seadroid2.data.model.ResultModel; +import com.seafile.seadroid2.data.model.dirents.DeleteDirentModel; +import com.seafile.seadroid2.data.model.dirents.FileCreateModel; +import com.seafile.seadroid2.data.db.entities.RepoModel; + +import java.util.Map; + +import io.reactivex.Observable; +import io.reactivex.Single; +import okhttp3.RequestBody; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.Multipart; +import retrofit2.http.POST; +import retrofit2.http.PartMap; +import retrofit2.http.Path; +import retrofit2.http.Query; + +public interface DialogService { + + @Multipart + @POST("api2/repos/") + Single createRepo(@PartMap Map map); + + + @DELETE("api/v2.1/repos/{repo_id}/") + Single deleteRepo(@Path("repo_id") String repoId); + + @Multipart + @POST("api2/repos/{repo_id}/dir/") + Single createDir(@Path("repo_id") String repoId, @Query("p") String path, @PartMap Map map); + + @Multipart + @POST("api/v2.1/repos/{repo_id}/file/") + Single createFile(@Path("repo_id") String repoId, @Query("p") String fileName, @PartMap Map map); + + @Multipart + @POST("api2/repos/{repo_id}/?op=rename") + Single renameRepo(@Path("repo_id") String repoId, @PartMap Map map); + + @Multipart + @POST("api2/repos/{repo_id}/dir/") + Single renameDir(@Path("repo_id") String repoId, @Query("p") String path, @PartMap Map map); + + @Multipart + @POST("api/v2.1/repos/{repo_id}/file/") + Single renameFile(@Path("repo_id") String repoId, @Query("p") String path, @PartMap Map map); + + @Multipart + @POST("api/v2.1/repos/{repo_id}/set-password/") + Single setPassword(@Path("repo_id") String repoId, @PartMap Map map); + + @DELETE("api/v2.1/repos/{repo_id}/dir/") + Observable deleteDir(@Path("repo_id") String repoId, @Query("p") String path); + + @DELETE("api/v2.1/repos/{repo_id}/file/") + Observable deleteFile(@Path("repo_id") String repoId, @Query("p") String path); + + @POST("api/v2.1/repos/sync-batch-move-item/") + Single moveDirents(@Body Map map); + + @POST("api/v2.1/repos/sync-batch-copy-item/") + Single copyDirents(@Body Map map); +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/GetShareLinkPasswordDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/GetShareLinkPasswordDialogFragment.java new file mode 100644 index 000000000..1ea2a74aa --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/GetShareLinkPasswordDialogFragment.java @@ -0,0 +1,92 @@ +package com.seafile.seadroid2.ui.dialog_fragment; + +import android.text.TextUtils; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.LinearLayout; + +import com.blankj.utilcode.util.ToastUtils; +import com.google.android.material.switchmaterial.SwitchMaterial; +import com.google.android.material.textfield.TextInputLayout; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.fragment.RequestCustomDialogFragmentWithVM; +import com.seafile.seadroid2.ui.dialog_fragment.viewmodel.GetShareLinkPasswordViewModel; + +public class GetShareLinkPasswordDialogFragment extends RequestCustomDialogFragmentWithVM { + + @Override + protected int getLayoutId() { + return R.layout.view_dialog_share_password; + } + + @Override + protected void onPositiveClick() { + dismiss(); + } + + @Override + public int getDialogTitleRes() { + return R.string.share_input_password; + } + + public String getPassword() { + EditText editText = getDialogView().findViewById(R.id.password); + String password = editText.getText().toString(); + return password; + } + + public String getDays() { + EditText daysEditText = getDialogView().findViewById(R.id.days); + String daysText = daysEditText.getText().toString(); + return daysText; + } + + @Override + protected void initView(LinearLayout containerView) { + super.initView(containerView); + + TextInputLayout daysTextInput = getDialogView().findViewById(R.id.days_text_input); + + SwitchMaterial switchMaterial = getDialogView().findViewById(R.id.add_expiration); + switchMaterial.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + daysTextInput.setVisibility(View.VISIBLE); + } else { + daysTextInput.setVisibility(View.GONE); + } + } + }); + } + + private boolean checkData() { + EditText editText = getDialogView().findViewById(R.id.password); + String password = editText.getText().toString(); + + if (TextUtils.isEmpty(password)) { + ToastUtils.showLong(R.string.password_empty); + return false; + } + + //TODO 密码长度待确定 + if (password.length() < getResources().getInteger(R.integer.minimum_password_length)) { + ToastUtils.showLong(R.string.err_passwd_too_short); + return false; + } + + SwitchMaterial switchMaterial = getDialogView().findViewById(R.id.add_expiration); + if (switchMaterial.isChecked()) { + EditText daysEditText = getDialogView().findViewById(R.id.days); + String daysText = daysEditText.getText().toString(); + + if (TextUtils.isEmpty(daysText)) { + ToastUtils.showLong(R.string.input_auto_expiration); + return false; + } + } + + return true; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/NewDirFileDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/NewDirFileDialogFragment.java new file mode 100644 index 000000000..dd4b5e472 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/NewDirFileDialogFragment.java @@ -0,0 +1,136 @@ +package com.seafile.seadroid2.ui.dialog_fragment; + +import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.widget.EditText; +import android.widget.LinearLayout; + +import androidx.lifecycle.Observer; + +import com.blankj.utilcode.util.ToastUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.fragment.RequestCustomDialogFragmentWithVM; +import com.seafile.seadroid2.data.model.ResultModel; +import com.seafile.seadroid2.data.model.dirents.FileCreateModel; +import com.seafile.seadroid2.ui.dialog_fragment.viewmodel.NewDirViewModel; + +public class NewDirFileDialogFragment extends RequestCustomDialogFragmentWithVM { + private String parentDir, repoId; + private boolean isDir; + + public static NewDirFileDialogFragment newInstance() { + + Bundle args = new Bundle(); + + NewDirFileDialogFragment fragment = new NewDirFileDialogFragment(); + fragment.setArguments(args); + return fragment; + } + + public void initData(String repoId, String parentDir, boolean isDir) { + this.repoId = repoId; + this.parentDir = parentDir; + this.isDir = isDir; + } + + @Override + protected int getLayoutId() { + return R.layout.view_dialog_new_file; + } + + @Override + public int getDialogTitleRes() { + return isDir ? R.string.create_new_dir : R.string.create_new_file; + } + + @Override + protected void onPositiveClick() { + if (!checkData()) { + return; + } + + EditText name = getDialogView().findViewById(R.id.new_file_name); + String pathName = name.getText().toString(); + pathName = parentDir + "/" + pathName; + if (isDir) { + getViewModel().createNewDir(pathName, repoId); + } else { + getViewModel().createNewFile(pathName, repoId); + } + } + + @Override + protected void initView(LinearLayout containerView) { + super.initView(containerView); + + if (TextUtils.isEmpty(parentDir)) { + throw new IllegalArgumentException("this dialogFragment need parentDir param"); + } + + if (TextUtils.isEmpty(repoId)) { + throw new IllegalArgumentException("this dialogFragment need repoId param"); + } + } + + @Override + protected void initViewModel() { + super.initViewModel(); + + if (isDir) { + getViewModel().getCreateDirLiveData().observe(this, new Observer() { + @Override + public void onChanged(ResultModel resultModel) { + if (!TextUtils.isEmpty(resultModel.error_msg)) { + setInputError(R.id.text_input, resultModel.error_msg); + } else { + + EditText name = getDialogView().findViewById(R.id.new_file_name); + String pathName = name.getText().toString(); + + ToastUtils.showLong(getString(R.string.create_new_folder_success, pathName)); + + refreshData(); + + dismiss(); + } + } + }); + } else { + getViewModel().getCreateFileLiveData().observe(this, new Observer() { + @Override + public void onChanged(FileCreateModel fileCreateModel) { + if (!TextUtils.isEmpty(fileCreateModel.error_msg)) { + setInputError(R.id.text_input, fileCreateModel.error_msg); + } else { + EditText name = getDialogView().findViewById(R.id.new_file_name); + String pathName = name.getText().toString(); + + ToastUtils.showLong(getString(R.string.create_new_file_success, pathName)); + + refreshData(); + + dismiss(); + } + } + }); + } + + + getViewModel().getRefreshLiveData().observe(this, aBoolean -> showLoading(aBoolean)); + } + + private boolean checkData() { + EditText editText = getDialogView().findViewById(R.id.new_file_name); + Editable editable = editText.getText(); + if (editable == null || editable.length() == 0) { + if (isDir) { + ToastUtils.showLong(R.string.dir_name_empty); + } else { + ToastUtils.showLong(R.string.file_name_empty); + } + return false; + } + return true; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/NewRepoDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/NewRepoDialogFragment.java new file mode 100644 index 000000000..9c190e6ae --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/NewRepoDialogFragment.java @@ -0,0 +1,124 @@ +package com.seafile.seadroid2.ui.dialog_fragment; + +import android.text.Editable; +import android.text.TextUtils; +import android.view.View; +import android.widget.LinearLayout; + +import com.blankj.utilcode.util.ToastUtils; +import com.google.android.material.switchmaterial.SwitchMaterial; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.fragment.RequestCustomDialogFragmentWithVM; +import com.seafile.seadroid2.config.Constants; +import com.seafile.seadroid2.ui.dialog_fragment.viewmodel.NewRepoViewModel; + +public class NewRepoDialogFragment extends RequestCustomDialogFragmentWithVM { + + @Override + protected int getLayoutId() { + return R.layout.view_dialog_new_repo; + } + + @Override + public int getDialogTitleRes() { + return R.string.create_new_repo; + } + + @Override + public void initView(LinearLayout containerView) { + super.initView(containerView); + + SwitchMaterial materialSwitch = getDialogView().findViewById(R.id.widget_switch); + TextInputLayout pwd1 = getDialogView().findViewById(R.id.new_repo_input_layout_pwd_1); + TextInputLayout pwd2 = getDialogView().findViewById(R.id.new_repo_input_layout_pwd_2); + pwd1.setHint(String.format( + getResources().getString(R.string.passwd_min_len_limit_hint), + getResources().getInteger(R.integer.minimum_password_length) + )); + + TextInputEditText pwdt1 = getDialogView().findViewById(R.id.new_repo_edit_pwd_1); + TextInputEditText pwdt2 = getDialogView().findViewById(R.id.new_repo_edit_pwd_2); + + materialSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { + pwd1.setVisibility(isChecked ? View.VISIBLE : View.GONE); + pwd2.setVisibility(isChecked ? View.VISIBLE : View.GONE); + + pwdt1.setText(null); + pwdt2.setText(null); + }); + } + + @Override + public void initViewModel() { + getViewModel().getCreateRepoLiveData().observe(this, repoModel -> { + String d = String.format(getResources().getString(R.string.create_new_repo_success), repoModel.repo_name); + ToastUtils.showLong(d); + + refreshData(); + + dismiss(); + }); + + getViewModel().getRefreshLiveData().observe(this, this::showLoading); + } + + @Override + protected void onPositiveClick() { + if (!checkData()) { + return; + } + + TextInputEditText name = getDialogView().findViewById(R.id.new_repo_edit_name); + SwitchMaterial materialSwitch = getDialogView().findViewById(R.id.widget_switch); + if (materialSwitch.isChecked()) { + TextInputEditText pwd1 = getDialogView().findViewById(R.id.new_repo_edit_pwd_1); + String pwd1Str = pwd1.getText() == null ? "" : pwd1.getText().toString(); + String nameStr = name.getText() == null ? "" : name.getText().toString(); + getViewModel().createNewRepo(nameStr, "", pwd1Str); + } else { + String nameStr = name.getText() == null ? "" : name.getText().toString(); + getViewModel().createNewRepo(nameStr, "", ""); + } + } + + private boolean checkData() { + + TextInputEditText name = getDialogView().findViewById(R.id.new_repo_edit_name); + Editable editable = name.getText(); + if (editable == null || editable.length() == 0 || TextUtils.isEmpty(editable.toString().trim())) { + ToastUtils.showLong(R.string.repo_name_empty); + return false; + } + + SwitchMaterial materialSwitch = getDialogView().findViewById(R.id.widget_switch); + if (!materialSwitch.isChecked()) { + return true; + } + + TextInputEditText pwd1 = getDialogView().findViewById(R.id.new_repo_edit_pwd_1); + TextInputEditText pwd2 = getDialogView().findViewById(R.id.new_repo_edit_pwd_2); + + Editable editable1 = pwd1.getText(); + Editable editable2 = pwd2.getText(); + boolean editableBool1 = editable1 == null || editable1.length() == 0 || TextUtils.isEmpty(editable1.toString().trim()); + boolean editableBool2 = editable2 == null || editable2.length() == 0 || TextUtils.isEmpty(editable2.toString().trim()); + + if (editableBool1 || editableBool2) { + ToastUtils.showLong(R.string.err_passwd_empty); + return false; + } + + if (editable1.length() < Constants.PASSWORD_MINIMUM_LENGTH) { + ToastUtils.showLong(R.string.err_passwd_too_short); + return false; + } + + if (!TextUtils.equals(editable1, editable2)) { + ToastUtils.showLong(R.string.err_passwd_mismatch); + return false; + } + return true; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/PasswordDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/PasswordDialogFragment.java new file mode 100644 index 000000000..b80ae4844 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/PasswordDialogFragment.java @@ -0,0 +1,143 @@ +package com.seafile.seadroid2.ui.dialog_fragment; + +import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.text.method.HideReturnsTransformationMethod; +import android.text.method.PasswordTransformationMethod; +import android.widget.EditText; +import android.widget.LinearLayout; + +import androidx.lifecycle.Observer; + +import com.google.android.material.textfield.TextInputLayout; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.fragment.RequestCustomDialogFragmentWithVM; +import com.seafile.seadroid2.config.Constants; +import com.seafile.seadroid2.data.model.ResultModel; +import com.seafile.seadroid2.data.db.entities.RepoModel; +import com.seafile.seadroid2.ui.dialog_fragment.viewmodel.PasswordViewModel; + +import io.reactivex.functions.Consumer; + +public class PasswordDialogFragment extends RequestCustomDialogFragmentWithVM { + private String repoId; + private String repoName; + + public static PasswordDialogFragment newInstance() { + + Bundle args = new Bundle(); + + PasswordDialogFragment fragment = new PasswordDialogFragment(); + fragment.setArguments(args); + return fragment; + } + + public void initData(String repoId, String repoName) { + this.repoId = repoId; + this.repoName = repoName; + } + + @Override + protected int getLayoutId() { + return R.layout.view_dialog_password; + } + + @Override + public String getDialogTitleString() { + return repoName; + } + + @Override + protected void onPositiveClick() { + if (!checkData()) { + return; + } + + + EditText editText = getDialogView().findViewById(R.id.password); + String password = editText.getText().toString(); + + getViewModel().getRepoModelFromLocal(repoId, new Consumer() { + @Override + public void accept(RepoModel repoModel) throws Exception { + + if (repoModel == null || !repoModel.canLocalDecrypt()) { + getViewModel().setPassword(repoId, password); + } else { + //TODO 本地解密 +// Crypto.verifyRepoPassword(repoId, password, repo.encVersion, repo.magic); + } + } + }); + + } + + @Override + protected void initView(LinearLayout containerView) { + super.initView(containerView); + + if (TextUtils.isEmpty(repoId)) { + throw new IllegalArgumentException("this dialogFragment need repoId param"); + } + + EditText editText = getDialogView().findViewById(R.id.password); + TextInputLayout passwordInputLayout = getDialogView().findViewById(R.id.password_hint); + + passwordInputLayout.setEndIconOnClickListener(v -> { + if (editText.getTransformationMethod() instanceof PasswordTransformationMethod) { + editText.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); + passwordInputLayout.setEndIconDrawable(R.drawable.icon_eye_open); + } else { + editText.setTransformationMethod(PasswordTransformationMethod.getInstance()); + passwordInputLayout.setEndIconDrawable(R.drawable.icon_eye_close); + } + + String input = editText.getText().toString().trim(); + if (!TextUtils.isEmpty(input)) { + editText.setSelection(input.length()); + } + }); + } + + @Override + protected void initViewModel() { + super.initViewModel(); + getViewModel().getActionLiveData().observe(this, new Observer() { + @Override + public void onChanged(ResultModel resultModel) { + if (resultModel.success) { + refreshData(); + dismiss(); + } else if (!TextUtils.isEmpty(resultModel.error_msg)) { + setInputError(R.id.password_hint, resultModel.error_msg); + } + } + }); + getViewModel().getRefreshLiveData().observe(this, new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + showLoading(aBoolean); + } + }); + } + + + private boolean checkData() { + EditText editText = getDialogView().findViewById(R.id.password); + Editable editable = editText.getText(); + if (editable == null || editable.length() == 0 || TextUtils.isEmpty(editable.toString().trim())) { + setInputError(R.id.password_hint, getString(R.string.password_empty)); + return false; + } + + if (editable.length() < Constants.PASSWORD_MINIMUM_LENGTH) { + setInputError(R.id.password_hint, getString(R.string.err_passwd_too_short)); + return false; + } + + return true; + } + + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/PolicyDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/PolicyDialogFragment.java new file mode 100644 index 000000000..d95101801 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/PolicyDialogFragment.java @@ -0,0 +1,95 @@ +package com.seafile.seadroid2.ui.dialog_fragment; + +import android.app.Dialog; +import android.os.Bundle; +import android.text.SpannableStringBuilder; +import android.text.TextPaint; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.DialogFragment; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.config.Constants; +import com.seafile.seadroid2.listener.OnCallback; +import com.seafile.seadroid2.ui.webview.SeaWebViewActivity; + + +public class PolicyDialogFragment extends DialogFragment { + + private OnCallback onCallback; + + public void setOnCallback(OnCallback onCallback) { + this.onCallback = onCallback; + } + + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + + View rootView = LayoutInflater.from(requireContext()).inflate(R.layout.layout_dialog_policy, null); + TextView messageView = rootView.findViewById(R.id.text_view_message); + + TextView positiveView = rootView.findViewById(R.id.text_view_positive); + TextView negativeView = rootView.findViewById(R.id.text_view_negative); + positiveView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onCallback != null) { + onCallback.onSuccess(); + } + dismiss(); + } + }); + negativeView.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + if (onCallback != null) { + onCallback.onFailed(); + } + dismiss(); + } + }); + + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity()); + builder.setTitle("隐私政策"); + String message = "请你务必审慎阅读、充分理解“隐私政策”个条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。\n\n你可阅读《隐私政策》了解详细信息。如果你同意,请点击下面按钮开始使用本应用。"; + + SpannableStringBuilder ssb = new SpannableStringBuilder(); + ssb.append(message); + final int start = message.indexOf("《"); + ssb.setSpan(new ClickableSpan() { + + @Override + public void onClick(View widget) { + SeaWebViewActivity.openUrl(requireContext(), Constants.URL_PRIVACY); + } + + @Override + public void updateDrawState(TextPaint ds) { + super.updateDrawState(ds); + ds.setColor(ContextCompat.getColor(requireContext(), R.color.blue_700)); + ds.setUnderlineText(false); + } + + }, start, start + 6, 0); + messageView.setMovementMethod(LinkMovementMethod.getInstance()); + messageView.setText(ssb, TextView.BufferType.SPANNABLE); + + builder.setView(rootView); + + final AlertDialog dialog = builder.create(); + return dialog; + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/RenameDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/RenameDialogFragment.java new file mode 100644 index 000000000..3a9927621 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/RenameDialogFragment.java @@ -0,0 +1,136 @@ +package com.seafile.seadroid2.ui.dialog_fragment; + +import android.text.Editable; +import android.text.TextUtils; +import android.widget.EditText; +import android.widget.LinearLayout; + +import androidx.lifecycle.Observer; + +import com.blankj.utilcode.util.ToastUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.fragment.RequestCustomDialogFragmentWithVM; +import com.seafile.seadroid2.data.model.dirents.FileCreateModel; +import com.seafile.seadroid2.ui.dialog_fragment.viewmodel.RenameRepoViewModel; + +public class RenameDialogFragment extends RequestCustomDialogFragmentWithVM { + private String curName, repoId, curPath, type; + + public static RenameDialogFragment newInstance() { + return new RenameDialogFragment(); + } + + /** + * @param type "repo" or "dir" or "file" + */ + public void initData(String curName, String curPath, String repoId, String type) { + this.curName = curName; + this.curPath = curPath; + this.repoId = repoId; + this.type = type; + } + + @Override + protected int getLayoutId() { + return R.layout.view_dialog_new_file; + } + + @Override + public int getDialogTitleRes() { + if (TextUtils.equals("repo", type)) { + return R.string.rename_repo; + } else if (TextUtils.equals("dir", type)) { + return R.string.rename_dir; + } else if (TextUtils.equals("file", type)) { + return R.string.rename_file; + } + return 0; + } + + @Override + protected void onPositiveClick() { + if (!checkData()) { + return; + } + + EditText editText = getDialogView().findViewById(R.id.new_file_name); + String newName = editText.getText().toString(); + + if (TextUtils.equals("repo", type)) { + getViewModel().renameRepo(newName, repoId); + } else if (TextUtils.equals("dir", type)) { + getViewModel().renameDir(repoId, curPath, newName); + } else if (TextUtils.equals("file", type)) { + getViewModel().renameFile(repoId, curPath, newName); + } + } + + @Override + protected void initView(LinearLayout containerView) { + super.initView(containerView); + + if (TextUtils.isEmpty(repoId)) { + throw new IllegalArgumentException("this dialogFragment need a repoId param"); + } + + EditText editText = getDialogView().findViewById(R.id.new_file_name); + editText.setText(curName); + } + + @Override + protected void initViewModel() { + super.initViewModel(); + + getViewModel().getActionLiveData().observe(this, new Observer() { + @Override + public void onChanged(String s) { + if (TextUtils.equals("success", s)) { + ToastUtils.showLong(R.string.rename_successful); + + refreshData(); + + dismiss(); + } else { + setInputError(R.id.text_input, s); + } + } + }); + + getViewModel().getRenameFileLiveData().observe(this, new Observer() { + @Override + public void onChanged(FileCreateModel fileCreateModel) { + if (TextUtils.isEmpty(fileCreateModel.error_msg)) { + ToastUtils.showLong(R.string.rename_successful); + + refreshData(); + + dismiss(); + } else { + setInputError(R.id.text_input, fileCreateModel.error_msg); + } + } + }); + + getViewModel().getRefreshLiveData().observe(this, new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + showLoading(aBoolean); + } + }); + } + + private boolean checkData() { + EditText editText = getDialogView().findViewById(R.id.new_file_name); + Editable editable = editText.getText(); + if (editable == null || editable.length() == 0) { + return false; + } + + String newName = editable.toString(); + if (TextUtils.equals(curName, newName)) { + return false; + } + + return true; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SignOutDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SignOutDialogFragment.java new file mode 100644 index 000000000..b844f3b80 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SignOutDialogFragment.java @@ -0,0 +1,53 @@ +package com.seafile.seadroid2.ui.dialog_fragment; + +import android.os.Bundle; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.data.DataManager; +import com.seafile.seadroid2.data.DatabaseHelper; +import com.seafile.seadroid2.ui.base.fragment.CustomDialogFragment; + +public class SignOutDialogFragment extends CustomDialogFragment { + public static SignOutDialogFragment newInstance() { + + Bundle args = new Bundle(); + + SignOutDialogFragment fragment = new SignOutDialogFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + protected int getLayoutId() { + return R.layout.view_dialog_message_textview; + } + + @Override + public int getDialogTitleRes() { + return R.string.settings_account_sign_out_title; + } + + @Override + protected void onPositiveClick() { + Account account = SupportAccountManager.getInstance().getCurrentAccount(); + + // sign out operations + SupportAccountManager.getInstance().signOutAccount(account); + + refreshData(); + } + + @Override + protected void initView(LinearLayout containerView) { + super.initView(containerView); + + //set message + TextView textView = containerView.findViewById(R.id.message_view); + textView.setText(R.string.settings_account_sign_out_confirm); + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SortFilesDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SortFilesDialogFragment.java new file mode 100644 index 000000000..d5c3d01ac --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SortFilesDialogFragment.java @@ -0,0 +1,40 @@ +package com.seafile.seadroid2.ui.dialog_fragment; + +import android.app.Dialog; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.dialog_fragment.listener.OnSortItemClickListener; +import com.seafile.seadroid2.util.sp.Sorts; + +public class SortFilesDialogFragment extends DialogFragment { + + // Use this instance of the interface to deliver action events + private OnSortItemClickListener mListener; + + public void setOnSortItemClickListener(OnSortItemClickListener mListener) { + this.mListener = mListener; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity()) + .setTitle(getString(R.string.sort_files)) + .setSingleChoiceItems(R.array.sort_files_options_array, + Sorts.getSortType(), + (dialogInterface, i) -> { + Sorts.setSortType(i); + + if (mListener != null) { + mListener.onSortFileItemClick(SortFilesDialogFragment.this, i); + } + dismiss(); + }); + return builder.create(); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/SwitchStorageTaskDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SwitchStorageDialogFragment.java similarity index 51% rename from app/src/main/java/com/seafile/seadroid2/ui/dialog/SwitchStorageTaskDialog.java rename to app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SwitchStorageDialogFragment.java index 76b85e333..cdab80e80 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/SwitchStorageTaskDialog.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SwitchStorageDialogFragment.java @@ -1,108 +1,130 @@ -package com.seafile.seadroid2.ui.dialog; +package com.seafile.seadroid2.ui.dialog_fragment; -import android.app.Dialog; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; +import android.widget.LinearLayout; import android.widget.RadioButton; import android.widget.RadioGroup; +import androidx.annotation.Nullable; + import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeadroidApplication; import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.cameraupload.CameraUploadManager; +import com.seafile.seadroid2.ui.base.fragment.CustomDialogFragment; +import com.seafile.seadroid2.ui.camera_upload.CameraUploadManager; import com.seafile.seadroid2.data.StorageManager; import com.seafile.seadroid2.transfer.TransferService; +import com.seafile.seadroid2.util.SLogs; import java.util.ArrayList; import java.util.List; -class SwitchStorageTask extends TaskDialog.Task { +public class SwitchStorageDialogFragment extends CustomDialogFragment { - private static final String DEBUG_TAG = "SwitchStorageTask"; + private List buttonList = new ArrayList<>(); + private int currentLocationId = -1; + private RadioGroup group; - private TransferService txService; - private StorageManager.Location location = null; + public static SwitchStorageDialogFragment newInstance() { + + Bundle args = new Bundle(); + + SwitchStorageDialogFragment fragment = new SwitchStorageDialogFragment(); + fragment.setArguments(args); + return fragment; + } - SwitchStorageTask() { - Intent bIntent = new Intent(SeadroidApplication.getAppContext(), TransferService.class); - SeadroidApplication.getAppContext().bindService(bIntent, mConnection, Context.BIND_AUTO_CREATE); + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent bIntent = new Intent(requireContext(), TransferService.class); + requireContext().bindService(bIntent, mConnection, Context.BIND_AUTO_CREATE); + } + + @Override + protected int getLayoutId() { + return R.layout.view_dialog_switch_storage; } - public void setNewLocation(StorageManager.Location loc) { - location = loc; + @Override + protected void onPositiveClick() { + int selectedId = group.getCheckedRadioButtonId(); + for (RadioButton b : buttonList) { + if (b.getId() == selectedId) { + location = (StorageManager.Location) b.getTag(); + break; + } + } + + run(); } + + private TransferService txService; + private StorageManager.Location location = null; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { + SLogs.d("txService connected"); + TransferService.TransferBinder binder = (TransferService.TransferBinder) service; txService = binder.getService(); } @Override public void onServiceDisconnected(ComponentName arg0) { + SLogs.d("txService disconnected"); + txService = null; } }; - @Override - protected void runTask() { + private void run() { if (location == null) return; if (txService != null) { - Log.d(DEBUG_TAG, "Cancel all TransferService tasks"); + SLogs.d("Cancel all TransferService tasks"); txService.cancelAllUploadTasks(); txService.cancelAllDownloadTasks(); } else { return; } - Context context = SeadroidApplication.getAppContext(); - - CameraUploadManager camera = new CameraUploadManager(context); + CameraUploadManager camera = new CameraUploadManager(); Account camAccount = camera.getCameraAccount(); if (camera.isCameraUploadEnabled()) { - Log.d(DEBUG_TAG, "Temporarily disable camera upload"); + SLogs.d("Temporarily disable camera upload"); camera.disableCameraUpload(); } - Log.i(DEBUG_TAG, "Switching storage to " + location.description); + SLogs.d("Switching storage to " + location.description); StorageManager.getInstance().setStorageDir(location.id); if (camAccount != null) { - Log.d(DEBUG_TAG, "Reenable camera upload"); + SLogs.d("reEnable camera upload"); camera.setCameraAccount(camAccount); } } -} - -public class SwitchStorageTaskDialog extends TaskDialog { - RadioGroup group; - List buttonList = new ArrayList<>(); - int currentLocationId = -1; - - SwitchStorageTask task; @Override - protected View createDialogContentView(LayoutInflater inflater, Bundle savedInstanceState) { - - // create task early to allow the service to connect - task = new SwitchStorageTask(); + public int getDialogTitleRes() { + return R.string.settings_cache_location_title; + } - View view = inflater.inflate(R.layout.dialog_switch_storage, null); - group = (RadioGroup) view.findViewById(R.id.storage_options); + @Override + protected void initView(LinearLayout containerView) { + super.initView(containerView); + group = getDialogView().findViewById(R.id.storage_options); ArrayList options = StorageManager.getInstance().getStorageLocations(); - for (StorageManager.Location location: options) { + for (StorageManager.Location location : options) { RadioButton b = new RadioButton(getContext()); b.setText(location.description); b.setTag(location); @@ -116,27 +138,5 @@ protected View createDialogContentView(LayoutInflater inflater, Bundle savedInst } group.check(currentLocationId); - return view; - } - - @Override - protected void onDialogCreated(Dialog dialog) { - dialog.setTitle(getResources().getString(R.string.settings_cache_location_title)); - } - - @Override - protected Task prepareTask() { - // we can't stop the storage switch once it's started. - disableCancel(); - - int selectedId = group.getCheckedRadioButtonId(); - for (RadioButton b: buttonList) { - if (b.getId() == selectedId) { - StorageManager.Location location = (StorageManager.Location) b.getTag(); - task.setNewLocation(location); - break; - } - } - return task; } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/listener/OnRefreshDataListener.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/listener/OnRefreshDataListener.java new file mode 100644 index 000000000..bd4d0ff1b --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/listener/OnRefreshDataListener.java @@ -0,0 +1,5 @@ +package com.seafile.seadroid2.ui.dialog_fragment.listener; + +public interface OnRefreshDataListener { + void onActionStatus(boolean isDone); +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/listener/OnSortItemClickListener.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/listener/OnSortItemClickListener.java new file mode 100644 index 000000000..82af15175 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/listener/OnSortItemClickListener.java @@ -0,0 +1,12 @@ +package com.seafile.seadroid2.ui.dialog_fragment.listener; + +import androidx.fragment.app.DialogFragment; + +/** + * The activity that creates an instance of this dialog fragment must + * implement this interface in order to receive event callbacks. + * Each method passes the DialogFragment in case the host needs to query it. + */ +public interface OnSortItemClickListener { + void onSortFileItemClick(DialogFragment dialog, int position); +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/ClearCacheViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/ClearCacheViewModel.java new file mode 100644 index 000000000..16a7de1b2 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/ClearCacheViewModel.java @@ -0,0 +1,50 @@ +package com.seafile.seadroid2.ui.dialog_fragment.viewmodel; + +import com.bumptech.glide.Glide; +import com.seafile.seadroid2.SeadroidApplication; +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.data.DatabaseHelper; +import com.seafile.seadroid2.data.StorageManager; + +import io.reactivex.Single; +import io.reactivex.SingleEmitter; +import io.reactivex.SingleOnSubscribe; +import io.reactivex.functions.Consumer; + +public class ClearCacheViewModel extends BaseViewModel { + + public void clear(Consumer consumer) { + Single s = Single.create(new SingleOnSubscribe() { + @Override + public void subscribe(SingleEmitter emitter) throws Exception { + StorageManager storageManager = StorageManager.getInstance(); + storageManager.clearCache(); + + // clear cached data from database + //TODO 为什么要清理数据库? + DatabaseHelper dbHelper = DatabaseHelper.getDatabaseHelper(); + dbHelper.delCaches(); + + //clear Glide cache + Glide.get(SeadroidApplication.getAppContext()).clearDiskCache(); + + emitter.onSuccess(true); + } + }); + addSingleDisposable(s, new Consumer() { + @Override + public void accept(Boolean o) throws Exception { + if (consumer != null) { + consumer.accept(o); + } + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + if (consumer != null) { + consumer.accept(false); + } + } + }); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/CopyMoveViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/CopyMoveViewModel.java new file mode 100644 index 000000000..2a517555b --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/CopyMoveViewModel.java @@ -0,0 +1,69 @@ +package com.seafile.seadroid2.ui.dialog_fragment.viewmodel; + +import androidx.lifecycle.MutableLiveData; + +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.data.model.ResultModel; +import com.seafile.seadroid2.data.db.entities.DirentModel; +import com.seafile.seadroid2.io.http.IO; +import com.seafile.seadroid2.ui.dialog_fragment.DialogService; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import io.reactivex.Single; +import io.reactivex.functions.Consumer; + +public class CopyMoveViewModel extends BaseViewModel { + private final MutableLiveData ResultLiveData = new MutableLiveData<>(); + + public MutableLiveData getResultLiveData() { + return ResultLiveData; + } + + public void move(String dstParentDir, String dstRepoId, String srcParentDir, String srcRepoId, List list) { + getRefreshLiveData().setValue(true); + + List nameList = list.stream().map(m -> m.name).collect(Collectors.toList()); + + Map requestDataMap = new HashMap<>(); + requestDataMap.put("dst_parent_dir", dstParentDir); + requestDataMap.put("dst_repo_id", dstRepoId); + requestDataMap.put("src_parent_dir", srcParentDir); + requestDataMap.put("src_repo_id", srcRepoId); + requestDataMap.put("src_dirents", nameList); + + Single single = IO.getSingleton().execute(DialogService.class).moveDirents(requestDataMap); + addSingleDisposable(single, new Consumer() { + @Override + public void accept(ResultModel resultModel) throws Exception { + getRefreshLiveData().setValue(false); + getResultLiveData().setValue(resultModel); + } + }); + } + + public void copy(String dstParentDir, String dstRepoId, String srcParentDir, String srcRepoId, List list) { + getRefreshLiveData().setValue(true); + + List nameList = list.stream().map(m -> m.name).collect(Collectors.toList()); + + Map requestDataMap = new HashMap<>(); + requestDataMap.put("dst_parent_dir", dstParentDir); + requestDataMap.put("dst_repo_id", dstRepoId); + requestDataMap.put("src_parent_dir", srcParentDir); + requestDataMap.put("src_repo_id", srcRepoId); + requestDataMap.put("src_dirents", nameList); + + Single single = IO.getSingleton().execute(DialogService.class).copyDirents(requestDataMap); + addSingleDisposable(single, new Consumer() { + @Override + public void accept(ResultModel resultModel) throws Exception { + getRefreshLiveData().setValue(false); + getResultLiveData().setValue(resultModel); + } + }); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/DeleteDirentsViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/DeleteDirentsViewModel.java new file mode 100644 index 000000000..0f74a7833 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/DeleteDirentsViewModel.java @@ -0,0 +1,97 @@ +package com.seafile.seadroid2.ui.dialog_fragment.viewmodel; + +import android.text.TextUtils; + +import androidx.lifecycle.MutableLiveData; + +import com.blankj.utilcode.util.ToastUtils; +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.data.model.dirents.DeleteDirentModel; +import com.seafile.seadroid2.data.db.entities.DirentModel; +import com.seafile.seadroid2.io.http.IO; +import com.seafile.seadroid2.ui.dialog_fragment.DialogService; +import com.seafile.seadroid2.util.SLogs; + +import java.util.ArrayList; +import java.util.List; + +import io.reactivex.Observable; +import io.reactivex.Observer; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +public class DeleteDirentsViewModel extends BaseViewModel { + private final MutableLiveData ActionLiveData = new MutableLiveData<>(); + + public MutableLiveData getActionLiveData() { + return ActionLiveData; + } + + private Disposable disposable; + + public void cancelAllTask() { + if (disposable != null) { + disposable.dispose(); + } + } + + public void deleteDirents(List dirents) { + //cancel last task + if (disposable != null && !disposable.isDisposed()) { + disposable.dispose(); + } + + getRefreshLiveData().setValue(true); + + List> singleList = new ArrayList<>(); + for (DirentModel dirent : dirents) { + if (dirent.isDir()) { + Observable dirSingle = IO.getSingleton().execute(DialogService.class).deleteDir(dirent.repo_id, dirent.full_path); + singleList.add(dirSingle); + } else { + Observable fileSingle = IO.getSingleton().execute(DialogService.class).deleteFile(dirent.repo_id, dirent.full_path); + singleList.add(fileSingle); + } + } + + Observable.concat(singleList) + .onErrorResumeNext(throwable -> { + String string = getErrorMsgByThrowable(throwable); + DeleteDirentModel d = new DeleteDirentModel(); + d.error_msg = string; + return Observable.just(d); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer() { + @Override + public void onSubscribe(Disposable d) { + disposable = d; + } + + @Override + public void onNext(DeleteDirentModel resultModel) { + SLogs.d(resultModel.toString()); + //TODO + if (!TextUtils.isEmpty(resultModel.error_msg)) { + ToastUtils.showLong(resultModel.error_msg); + } + } + + @Override + public void onError(Throwable t) { + SLogs.e(t); + } + + @Override + public void onComplete() { + getRefreshLiveData().setValue(false); + getActionLiveData().setValue(true); + } + }); + + addDisposable(disposable); + } +} +//https://dev.seafile.com/seahub/api/v2.1/repos/4809a6f3-250c-4435-bdd8-b68f34c128d1/dir/?p=%2F%E5%85%94%E5%85%94 diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/DeleteRepoViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/DeleteRepoViewModel.java new file mode 100644 index 000000000..25915c4a2 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/DeleteRepoViewModel.java @@ -0,0 +1,53 @@ +package com.seafile.seadroid2.ui.dialog_fragment.viewmodel; + +import android.text.TextUtils; + +import androidx.lifecycle.MutableLiveData; + +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.data.model.ResultModel; +import com.seafile.seadroid2.io.http.IO; +import com.seafile.seadroid2.ui.dialog_fragment.DialogService; + +import io.reactivex.Single; +import io.reactivex.functions.Consumer; + +public class DeleteRepoViewModel extends BaseViewModel { + private final MutableLiveData ActionLiveData = new MutableLiveData<>(); + + public MutableLiveData getActionLiveData() { + return ActionLiveData; + } + + public void deleteRepo(String repoId) { + getRefreshLiveData().setValue(true); + + Single single = IO.getSingleton().execute(DialogService.class).deleteRepo(repoId); + addSingleDisposable(single, new Consumer() { + @Override + public void accept(String resultModel) throws Exception { + getRefreshLiveData().setValue(false); + + ResultModel resultModel1 = new ResultModel(); + if (TextUtils.equals("success", resultModel)) { + resultModel1.success = true; + } else { + resultModel1.error_msg = resultModel; + } + + getActionLiveData().setValue(resultModel1); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getRefreshLiveData().setValue(false); + + ResultModel resultModel = new ResultModel(); + resultModel.error_msg = getErrorMsgByThrowable(throwable); + getActionLiveData().setValue(resultModel); + } + }); + } + + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/GetShareLinkPasswordViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/GetShareLinkPasswordViewModel.java new file mode 100644 index 000000000..6e43d6c04 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/GetShareLinkPasswordViewModel.java @@ -0,0 +1,6 @@ +package com.seafile.seadroid2.ui.dialog_fragment.viewmodel; + +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; + +public class GetShareLinkPasswordViewModel extends BaseViewModel { +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/NewDirViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/NewDirViewModel.java new file mode 100644 index 000000000..65e35027e --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/NewDirViewModel.java @@ -0,0 +1,103 @@ +package com.seafile.seadroid2.ui.dialog_fragment.viewmodel; + +import android.text.TextUtils; + +import androidx.lifecycle.MutableLiveData; + +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.data.model.ResultModel; +import com.seafile.seadroid2.data.model.dirents.FileCreateModel; +import com.seafile.seadroid2.io.http.IO; +import com.seafile.seadroid2.ui.dialog_fragment.DialogService; + +import java.util.HashMap; +import java.util.Map; + +import io.reactivex.Single; +import io.reactivex.functions.Consumer; +import okhttp3.RequestBody; + +public class NewDirViewModel extends BaseViewModel { + private final MutableLiveData createDirLiveData = new MutableLiveData<>(); + private final MutableLiveData createFileLiveData = new MutableLiveData<>(); + + public MutableLiveData getCreateDirLiveData() { + return createDirLiveData; + } + + public MutableLiveData getCreateFileLiveData() { + return createFileLiveData; + } + + public void createNewDir(String p, String repo_id) { + if (TextUtils.isEmpty(p)) { + return; + } + + getRefreshLiveData().setValue(true); + + Map requestDataMap = new HashMap<>(); + requestDataMap.put("operation", "mkdir"); + + Map bodyMap = generateRequestBody(requestDataMap); + + Single single = IO.getSingleton().execute(DialogService.class).createDir(repo_id, p, bodyMap); + addSingleDisposable(single, new Consumer() { + @Override + public void accept(String resultModel) throws Exception { + getRefreshLiveData().setValue(false); + + if (TextUtils.equals("success", resultModel)) { + ResultModel resultModel1 = new ResultModel(); + resultModel1.success = true; + getCreateDirLiveData().setValue(resultModel1); + } else { + getCreateDirLiveData().setValue(null); + } + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getRefreshLiveData().setValue(false); + + ResultModel resultModel = new ResultModel(); + resultModel.error_msg = getErrorMsgByThrowable(throwable); + getCreateDirLiveData().setValue(resultModel); + } + }); + } + + public void createNewFile(String filePathName, String repo_id) { + if (TextUtils.isEmpty(filePathName)) { + return; + } + + getRefreshLiveData().setValue(true); + + Map requestDataMap = new HashMap<>(); + requestDataMap.put("operation", "create"); + + Map bodyMap = generateRequestBody(requestDataMap); + + Single single = IO.getSingleton().execute(DialogService.class).createFile(repo_id, filePathName, bodyMap); + addSingleDisposable(single, new Consumer() { + @Override + public void accept(FileCreateModel resultModel) throws Exception { + getRefreshLiveData().setValue(false); + + if (resultModel != null) { + getCreateFileLiveData().setValue(resultModel); + } + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getRefreshLiveData().setValue(false); + + FileCreateModel createModel = new FileCreateModel(); + createModel.error_msg = getErrorMsgByThrowable(throwable); + getCreateFileLiveData().setValue(createModel); + } + }); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/NewRepoViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/NewRepoViewModel.java new file mode 100644 index 000000000..c57ed4824 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/NewRepoViewModel.java @@ -0,0 +1,55 @@ +package com.seafile.seadroid2.ui.dialog_fragment.viewmodel; + +import android.text.TextUtils; + +import androidx.lifecycle.MutableLiveData; + +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.data.db.entities.RepoModel; +import com.seafile.seadroid2.io.http.IO; +import com.seafile.seadroid2.ui.dialog_fragment.DialogService; + +import java.util.HashMap; +import java.util.Map; + +import io.reactivex.Single; +import io.reactivex.functions.Consumer; +import okhttp3.RequestBody; + +public class NewRepoViewModel extends BaseViewModel { + private final MutableLiveData createRepoLiveData = new MutableLiveData<>(); + + public MutableLiveData getCreateRepoLiveData() { + return createRepoLiveData; + } + + public void createNewRepo(String repoName, String description, String password) { + + if (TextUtils.isEmpty(repoName)) { + return; + } + + getRefreshLiveData().setValue(true); + + Map requestDataMap = new HashMap<>(); + requestDataMap.put("name", repoName); + + if (description.length() > 0) { + requestDataMap.put("desc", description); + } + if (!TextUtils.isEmpty(password)) { + requestDataMap.put("passwd", password); + } + Map bodyMap = generateRequestBody(requestDataMap); + Single single = IO.getSingleton().execute(DialogService.class).createRepo(bodyMap); + addSingleDisposable(single, new Consumer() { + @Override + public void accept(RepoModel repoModel) throws Exception { + + getRefreshLiveData().setValue(false); + + getCreateRepoLiveData().setValue(repoModel); + } + }); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/PasswordViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/PasswordViewModel.java new file mode 100644 index 000000000..ff36f6ab1 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/PasswordViewModel.java @@ -0,0 +1,98 @@ +package com.seafile.seadroid2.ui.dialog_fragment.viewmodel; + +import android.text.TextUtils; + +import androidx.lifecycle.MutableLiveData; + +import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.TimeUtils; +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.config.Constants; +import com.seafile.seadroid2.data.db.AppDatabase; +import com.seafile.seadroid2.data.model.ResultModel; +import com.seafile.seadroid2.data.db.entities.ObjsModel; +import com.seafile.seadroid2.data.db.entities.RepoModel; +import com.seafile.seadroid2.io.http.IO; +import com.seafile.seadroid2.ui.dialog_fragment.DialogService; +import com.seafile.seadroid2.util.SLogs; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.reactivex.Completable; +import io.reactivex.Single; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; +import okhttp3.RequestBody; + +public class PasswordViewModel extends BaseViewModel { + private final MutableLiveData ActionLiveData = new MutableLiveData<>(); + + public MutableLiveData getActionLiveData() { + return ActionLiveData; + } + + public void setPassword(String repoId, String password) { + if (TextUtils.isEmpty(password) || TextUtils.isEmpty(repoId)) { + return; + } + + getRefreshLiveData().setValue(true); + + Map requestDataMap = new HashMap<>(); + requestDataMap.put("password", password); + Map bodyMap = generateRequestBody(requestDataMap); + + Single single = IO.getSingleton().execute(DialogService.class).setPassword(repoId, bodyMap); + addSingleDisposable(single, resultModel -> { + + long now = TimeUtils.getNowMills(); + now += 1000 * 60 * 60 * 24 * 1;//1 days + + //TODO 更新指定字段 + ObjsModel objsModel = new ObjsModel(); + objsModel.path = repoId; + objsModel.decrypt_expire_time_long = now; + objsModel.type = Constants.ObjType.REPO; + + Completable updateCompletable = AppDatabase.getInstance().objDao().insert(objsModel); + addCompletableDisposable(updateCompletable, new Action() { + @Override + public void run() throws Exception { + getRefreshLiveData().setValue(false); + getActionLiveData().setValue(resultModel); + } + }); + + + }, throwable -> { + getRefreshLiveData().setValue(false); + + ResultModel resultModel = new ResultModel(); + resultModel.error_msg = getErrorMsgByThrowable(throwable); + getActionLiveData().setValue(resultModel); + }); + } + + public void getRepoModelFromLocal(String repoId, Consumer consumer) { + Single> singleDb = AppDatabase.getInstance().repoDao().getRepoById(repoId); + addSingleDisposable(singleDb, new Consumer>() { + @Override + public void accept(List repoModels) throws Exception { + if (consumer != null) { + if (CollectionUtils.isEmpty(repoModels)) { + //no data in sqlite + } else { + consumer.accept(repoModels.get(0)); + } + } + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + SLogs.e(throwable); + } + }); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/RenameRepoViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/RenameRepoViewModel.java new file mode 100644 index 000000000..4119019dd --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/RenameRepoViewModel.java @@ -0,0 +1,115 @@ +package com.seafile.seadroid2.ui.dialog_fragment.viewmodel; + +import android.text.TextUtils; + +import androidx.lifecycle.MutableLiveData; + +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.data.model.dirents.FileCreateModel; +import com.seafile.seadroid2.io.http.IO; +import com.seafile.seadroid2.ui.dialog_fragment.DialogService; + +import java.util.HashMap; +import java.util.Map; + +import io.reactivex.Single; +import io.reactivex.functions.Consumer; +import okhttp3.RequestBody; + +public class RenameRepoViewModel extends BaseViewModel { + private final MutableLiveData actionLiveData = new MutableLiveData<>(); + private final MutableLiveData renameFileLiveData = new MutableLiveData<>(); + + public MutableLiveData getRenameFileLiveData() { + return renameFileLiveData; + } + + public MutableLiveData getActionLiveData() { + return actionLiveData; + } + + public void renameRepo(String repoName, String repoId) { + + if (TextUtils.isEmpty(repoName)) { + return; + } + + getRefreshLiveData().setValue(true); + + Map requestDataMap = new HashMap<>(); + requestDataMap.put("repo_name", repoName); + Map bodyMap = generateRequestBody(requestDataMap); + + Single single = IO.getSingleton().execute(DialogService.class).renameRepo(repoId, bodyMap); + + addSingleDisposable(single, new Consumer() { + @Override + public void accept(String resultModel) throws Exception { + getRefreshLiveData().setValue(false); + + getActionLiveData().setValue(resultModel); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getRefreshLiveData().setValue(false); + getActionLiveData().setValue(getErrorMsgByThrowable(throwable)); + } + }); + } + + public void renameDir(String repoId, String curPath, String newName) { + getRefreshLiveData().setValue(true); + + Map requestDataMap = new HashMap<>(); + requestDataMap.put("operation", "rename"); + requestDataMap.put("newname", newName); + Map bodyMap = generateRequestBody(requestDataMap); + + Single single = IO.getSingleton().execute(DialogService.class).renameDir(repoId, curPath, bodyMap); + + addSingleDisposable(single, new Consumer() { + @Override + public void accept(String resultModel) throws Exception { + getRefreshLiveData().setValue(false); + + getActionLiveData().setValue(resultModel); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getRefreshLiveData().setValue(false); + getActionLiveData().setValue(getErrorMsgByThrowable(throwable)); + } + }); + } + + public void renameFile(String repoId, String curPath, String newName) { + getRefreshLiveData().setValue(true); + + Map requestDataMap = new HashMap<>(); + requestDataMap.put("operation", "rename"); + requestDataMap.put("newname", newName); + Map bodyMap = generateRequestBody(requestDataMap); + + Single single = IO.getSingleton().execute(DialogService.class).renameFile(repoId, curPath, bodyMap); + + addSingleDisposable(single, new Consumer() { + @Override + public void accept(FileCreateModel resultModel) throws Exception { + getRefreshLiveData().setValue(false); + + getRenameFileLiveData().setValue(resultModel); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getRefreshLiveData().setValue(false); + String msg = getErrorMsgByThrowable(throwable); + FileCreateModel model = new FileCreateModel(); + model.error_msg = msg; + getRenameFileLiveData().setValue(model); + } + }); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupConfigActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupConfigActivity.java new file mode 100644 index 000000000..1fc3f1af8 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupConfigActivity.java @@ -0,0 +1,236 @@ +package com.seafile.seadroid2.ui.folder_backup; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.text.TextUtils; +import android.util.Pair; +import android.view.View; + +import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.FragmentUtils; +import com.blankj.utilcode.util.GsonUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.data.db.entities.RepoModel; +import com.seafile.seadroid2.databinding.FolderBackupActivityLayoutBinding; +import com.seafile.seadroid2.ui.BaseActivity; +import com.seafile.seadroid2.ui.selector.ObjSelectorActivity; +import com.seafile.seadroid2.ui.selector.ObjSelectorFragment; +import com.seafile.seadroid2.ui.selector.folder_selector.FolderSelectorFragment; +import com.seafile.seadroid2.ui.selector.folder_selector.StringTools; +import com.seafile.seadroid2.util.SLogs; +import com.seafile.seadroid2.util.sp.FolderBackupConfigSPs; +import com.seafile.seadroid2.util.sp.SettingsManager; + +import java.util.ArrayList; +import java.util.List; + +public class FolderBackupConfigActivity extends BaseActivity { + public static final String FOLDER_BACKUP_SELECT_MODE = "folder_backup_select_mode"; + public static final String BACKUP_SELECT_PATHS = "backup_select_paths"; + private RepoModel repoModel; + private Account mAccount; + private boolean isChooseFolderPage; + private boolean isChooseRepoPage; + + private FolderBackupService mBackupService; + private List selectFolderPaths; + private Activity mActivity; + private String originalBackupPaths; + + private FolderBackupActivityLayoutBinding binding; + private FolderSelectorFragment folderSelectorFragment; + private ObjSelectorFragment objSelectorFragment; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = FolderBackupActivityLayoutBinding.inflate(getLayoutInflater()); + + setContentView(binding.getRoot()); + + if (getSupportActionBar() != null) + getSupportActionBar().hide(); + + String selectMode = getIntent().getStringExtra(FOLDER_BACKUP_SELECT_MODE); + isChooseFolderPage = "folder".equals(selectMode); + isChooseRepoPage = "repo".equals(selectMode); + + //bind service + Intent bindIntent = new Intent(this, FolderBackupService.class); + bindService(bindIntent, mFolderBackupConnection, Context.BIND_AUTO_CREATE); + + mActivity = this; + + originalBackupPaths = FolderBackupConfigSPs.getBackupPaths(); + + if (isChooseFolderPage && !TextUtils.isEmpty(originalBackupPaths)) { + selectFolderPaths = StringTools.getJsonToList(originalBackupPaths); + } + + if (isChooseRepoPage) { + objSelectorFragment = new ObjSelectorFragment(); + FragmentUtils.add(getSupportFragmentManager(), objSelectorFragment, R.id.container); + } else if (isChooseFolderPage) { + folderSelectorFragment = new FolderSelectorFragment(); + FragmentUtils.add(getSupportFragmentManager(), folderSelectorFragment, R.id.container); + } + + binding.confirmButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (folderSelectorFragment != null) { + saveFolderConfig(); + } else if (objSelectorFragment != null) { + saveRepoConfig(); + } + } + }); + } + + + public List getSelectFolderPath() { + return selectFolderPaths; + } + + public void saveRepoConfig() { + if (!isChooseRepoPage) { + return; + } + + Pair pair = objSelectorFragment.getCameraUploadInfo(); + mAccount = pair.first; + repoModel = pair.second; + + //FIX an issue: When no folder or library is selected, a crash occurs + if (null == repoModel || null == mAccount) { + SLogs.d("----------No repo is selected"); + return; + } + + + FolderBackupConfigSPs.saveBackupEmail(mAccount.getEmail()); + + //update sp + RepoConfig repoConfig = FolderBackupConfigSPs.getBackupConfigByAccount(mAccount.getEmail()); + if (repoConfig != null) { + repoConfig.setRepoName(repoModel.repo_name); + repoConfig.setRepoID(repoModel.repo_id); + } else { + repoConfig = new RepoConfig(repoModel.repo_id, repoModel.repo_name, mAccount.getEmail()); + } + + FolderBackupConfigSPs.setBackupRepoConfigByAccount(repoConfig); + + boolean automaticBackup = SettingsManager.getInstance().isFolderAutomaticBackup(); + if (automaticBackup && mBackupService != null) { + mBackupService.backupFolder(mAccount.getEmail()); + } + + + Intent intent = new Intent(); + intent.putExtra(ObjSelectorActivity.DATA_REPO_NAME, repoModel.repo_name); + intent.putExtra(ObjSelectorActivity.DATA_REPO_ID, repoModel.repo_id); + intent.putExtra(ObjSelectorActivity.DATA_ACCOUNT, mAccount); + + String selectMode = getIntent().getStringExtra(FOLDER_BACKUP_SELECT_MODE); + intent.putExtra(FOLDER_BACKUP_SELECT_MODE, selectMode); + + setResult(RESULT_OK, intent); + finish(); + } + + public void saveFolderConfig() { + if (!isChooseFolderPage) { + return; + } + selectFolderPaths = folderSelectorFragment.getSelectedPath(); + + if (CollectionUtils.isEmpty(selectFolderPaths)) { + SLogs.d("----------No folder is selected"); + + //clear local storage + FolderBackupConfigSPs.saveBackupPaths(""); + + Intent intent = new Intent(); + String selectMode = getIntent().getStringExtra(FOLDER_BACKUP_SELECT_MODE); + intent.putExtra(FOLDER_BACKUP_SELECT_MODE, selectMode); + setResult(RESULT_OK, intent); + return; + } + + String backupEmail = FolderBackupConfigSPs.getBackupEmail(); + + String strJsonPath = GsonUtils.toJson(selectFolderPaths); + + if ((TextUtils.isEmpty(originalBackupPaths) && !TextUtils.isEmpty(strJsonPath)) || !originalBackupPaths.equals(strJsonPath)) { + mBackupService.startFolderMonitor(selectFolderPaths); + + boolean folderAutomaticBackup = SettingsManager.getInstance().isFolderAutomaticBackup(); + if (folderAutomaticBackup) { + mBackupService.backupFolder(backupEmail); + } + + SLogs.d("----------Restart monitoring FolderMonitor"); + } else if (!TextUtils.isEmpty(originalBackupPaths) && TextUtils.isEmpty(strJsonPath)) { + mBackupService.stopFolderMonitor(); + SLogs.d("----------stop monitoring FolderMonitor"); + } + + FolderBackupConfigSPs.saveBackupPaths(strJsonPath); + + Intent intent = new Intent(); + intent.putStringArrayListExtra(BACKUP_SELECT_PATHS, (ArrayList) selectFolderPaths); + + String selectMode = getIntent().getStringExtra(FOLDER_BACKUP_SELECT_MODE); + intent.putExtra(FOLDER_BACKUP_SELECT_MODE, selectMode); + + setResult(RESULT_OK, intent); + + finish(); + } + + @Override + public void onBackPressed() { + + if (folderSelectorFragment != null && folderSelectorFragment.onBackPressed()) { + return; + } + + setResult(RESULT_CANCELED); + super.onBackPressed(); + } + + public boolean isChooseDirPage() { + return isChooseFolderPage; + } + + @Override + protected void onDestroy() { + if (mBackupService != null) { + unbindService(mFolderBackupConnection); + mBackupService = null; + } + super.onDestroy(); + } + + private final ServiceConnection mFolderBackupConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder binder) { + FolderBackupService.FileBackupBinder fileBackupBinder = (FolderBackupService.FileBackupBinder) binder; + mBackupService = fileBackupBinder.getService(); + } + + @Override + public void onServiceDisconnected(ComponentName className) { + mBackupService = null; + } + + }; +} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupDBHelper.java b/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupDBHelper.java similarity index 99% rename from app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupDBHelper.java rename to app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupDBHelper.java index ba309a6c9..f5f7031d1 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupDBHelper.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupDBHelper.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.folderbackup; +package com.seafile.seadroid2.ui.folder_backup; import android.content.ContentValues; import android.content.Context; diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupEvent.java b/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupEvent.java similarity index 82% rename from app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupEvent.java rename to app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupEvent.java index aff13b103..28d8c084a 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupEvent.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupEvent.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.folderbackup; +package com.seafile.seadroid2.ui.folder_backup; public class FolderBackupEvent { private String backupInfo; diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupInfo.java b/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupInfo.java similarity index 97% rename from app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupInfo.java rename to app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupInfo.java index e71798d5f..e188d79fa 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupInfo.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupInfo.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.folderbackup; +package com.seafile.seadroid2.ui.folder_backup; import com.google.common.base.Objects; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupSelectedPathActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupSelectedPathActivity.java new file mode 100644 index 000000000..a73030e83 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupSelectedPathActivity.java @@ -0,0 +1,132 @@ +package com.seafile.seadroid2.ui.folder_backup; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; + +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.blankj.utilcode.util.CollectionUtils; +import com.chad.library.adapter4.QuickAdapterHelper; +import com.google.gson.Gson; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.bottomsheetmenu.BottomSheetHelper; +import com.seafile.seadroid2.bottomsheetmenu.BottomSheetMenuFragment; +import com.seafile.seadroid2.ui.BaseActivity; +import com.seafile.seadroid2.util.sp.FolderBackupConfigSPs; + +import java.util.ArrayList; +import java.util.List; + +public class FolderBackupSelectedPathActivity extends BaseActivity { + private RecyclerView mRecyclerView; + private FolderBackupSelectedPathAdapter mAdapter; + private QuickAdapterHelper helper; + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + overridePendingTransition(R.anim.fade_in, R.anim.fade_out); + setContentView(R.layout.folder_backup_selected_path_activity); + + + findViewById(R.id.add_backup_folder).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(FolderBackupSelectedPathActivity.this, FolderBackupConfigActivity.class); + intent.putExtra(FolderBackupConfigActivity.FOLDER_BACKUP_SELECT_MODE, "folder"); + folderBackupConfigLauncher.launch(intent); + } + }); + + + setSupportActionBar(getActionBarToolbar()); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setTitle(R.string.settings_folder_backup_select_title); + + mRecyclerView = findViewById(R.id.lv_search); + mRecyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false)); + + initAdapter(); + + initData(); + } + + private void initData() { + List backupSelectPaths = FolderBackupConfigSPs.getBackupPathList(); + mAdapter.submitList(backupSelectPaths); + } + + private void initAdapter() { + mAdapter = new FolderBackupSelectedPathAdapter(); + View t = findViewById(R.id.ll_message_content); + mAdapter.setStateView(t); + mAdapter.setStateViewEnable(true); + mAdapter.setOnItemClickListener((baseQuickAdapter, view, i) -> showBottomDialog(mAdapter.getItems().get(i))); + mAdapter.addOnItemChildClickListener(R.id.more, (baseQuickAdapter, view, i) -> showRepoBottomSheet(i)); + + helper = new QuickAdapterHelper.Builder(mAdapter).build(); + mRecyclerView.setAdapter(helper.getAdapter()); + } + + private void showRepoBottomSheet(int position) { + BottomSheetHelper.showSheet(this, R.menu.folder_backup_bottom_sheet_delete, menuItem -> { + if (menuItem.getItemId() == R.id.delete) { + mAdapter.removeAt(position); + + String strJsonPath = new Gson().toJson(mAdapter.getItems()); + FolderBackupConfigSPs.saveBackupPaths(strJsonPath); + } + }); + } + + private void showBottomDialog(String text) { + new BottomSheetMenuFragment.Builder(this) + .setTitle(text) + .setCancelable(true) + .show(getSupportFragmentManager()); + } + + @Override + public void onBackPressed() { + setFinishPage(); + + super.onBackPressed(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + setFinishPage(); + + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + public void setFinishPage() { + Intent intent = new Intent(); + if (!CollectionUtils.isEmpty(mAdapter.getItems())) { + intent.putStringArrayListExtra(FolderBackupConfigActivity.BACKUP_SELECT_PATHS, (ArrayList) mAdapter.getItems()); + } + setResult(RESULT_OK, intent); + } + + private final ActivityResultLauncher folderBackupConfigLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback() { + @Override + public void onActivityResult(ActivityResult o) { + if (o.getResultCode() != Activity.RESULT_OK) { + return; + } + + initData(); + } + }); +} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackSelectedPathRecyclerViewAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupSelectedPathAdapter.java similarity index 69% rename from app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackSelectedPathRecyclerViewAdapter.java rename to app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupSelectedPathAdapter.java index ee86668a7..48c40080f 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackSelectedPathRecyclerViewAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupSelectedPathAdapter.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.folderbackup; +package com.seafile.seadroid2.ui.folder_backup; import android.content.Context; import android.view.LayoutInflater; @@ -9,12 +9,12 @@ import androidx.annotation.Nullable; import com.seafile.seadroid2.R; -import com.seafile.seadroid2.ui.base.adapter.BaseViewHolder; -import com.seafile.seadroid2.ui.base.adapter.ParentAdapter; +import com.seafile.seadroid2.ui.base.adapter.BaseAdapter; +import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; import org.jetbrains.annotations.NotNull; -public class FolderBackSelectedPathRecyclerViewAdapter extends ParentAdapter { +public class FolderBackupSelectedPathAdapter extends BaseAdapter { @Override protected void onBindViewHolder(@NotNull SearchItemViewHolder viewHolder, int i, @Nullable String s) { viewHolder.title.setText(s); @@ -35,8 +35,8 @@ public static class SearchItemViewHolder extends BaseViewHolder { public SearchItemViewHolder(View view) { super(view); - title = (TextView) view.findViewById(R.id.title); - icon = (View) view.findViewById(R.id.more); + title = view.findViewById(R.id.title); + icon = view.findViewById(R.id.more); } } diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupService.java b/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupService.java similarity index 91% rename from app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupService.java rename to app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupService.java index f0c1dd732..eedaea158 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupService.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/FolderBackupService.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.folderbackup; +package com.seafile.seadroid2.ui.folder_backup; import android.app.Service; import android.content.BroadcastReceiver; @@ -10,24 +10,22 @@ import android.os.AsyncTask; import android.os.Binder; import android.os.IBinder; - -import androidx.localbroadcastmanager.content.LocalBroadcastManager; - import android.text.TextUtils; import android.util.Log; -import com.seafile.seadroid2.SeadroidApplication; -import com.seafile.seadroid2.SettingsManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import com.seafile.seadroid2.ui.selector.folder_selector.FileBean; +import com.seafile.seadroid2.util.FileTools; +import com.seafile.seadroid2.util.sp.FolderBackupConfigSPs; +import com.seafile.seadroid2.util.sp.SettingsManager; import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.AccountManager; +import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.data.DataManager; -import com.seafile.seadroid2.folderbackup.selectfolder.FileBean; -import com.seafile.seadroid2.folderbackup.selectfolder.FileTools; -import com.seafile.seadroid2.folderbackup.selectfolder.StringTools; +import com.seafile.seadroid2.ui.selector.folder_selector.StringTools; import com.seafile.seadroid2.transfer.TransferManager; import com.seafile.seadroid2.transfer.TransferService; import com.seafile.seadroid2.transfer.UploadTaskManager; -import com.seafile.seadroid2.util.CameraSyncStatus; import com.seafile.seadroid2.util.ConcurrentAsyncTask; import com.seafile.seadroid2.util.SLogs; import com.seafile.seadroid2.util.Utils; @@ -51,7 +49,6 @@ public class FolderBackupService extends Service { private TransferService txService = null; private DataManager dataManager; private FolderBackupDBHelper databaseHelper; - private AccountManager accountManager; private Account currentAccount; private RepoConfig repoConfig; private List backupPathsList; @@ -81,7 +78,6 @@ public void onCreate() { databaseHelper = FolderBackupDBHelper.getDatabaseHelper(); Intent bIntent = new Intent(this, TransferService.class); - accountManager = new AccountManager(this); bindService(bIntent, mConnection, Context.BIND_AUTO_CREATE); if (mFolderBackupReceiver == null) { @@ -91,7 +87,7 @@ public void onCreate() { IntentFilter filter = new IntentFilter(TransferManager.BROADCAST_ACTION); LocalBroadcastManager.getInstance(this).registerReceiver(mFolderBackupReceiver, filter); - String backupPaths = SettingsManager.instance().getBackupPaths(); + String backupPaths = FolderBackupConfigSPs.getBackupPaths(); if (!TextUtils.isEmpty(backupPaths)) { List pathsList = StringTools.getJsonToList(backupPaths); if (pathsList != null) { @@ -143,14 +139,12 @@ public void backupFolder(String email) { } } - String backupPaths = SettingsManager.instance().getBackupPaths(); + String backupPaths = FolderBackupConfigSPs.getBackupPaths(); if (repoConfig == null || TextUtils.isEmpty(backupPaths)) { return; } - if (accountManager == null) { - accountManager = new AccountManager(this); - } - currentAccount = accountManager.getCurrentAccount(); + + currentAccount = SupportAccountManager.getInstance().getCurrentAccount(); backupPathsList = StringTools.getJsonToList(backupPaths); dataManager = new DataManager(currentAccount); @@ -185,7 +179,7 @@ private void startBackupFolder(String parentPath, String filePath) { File file = FileTools.getFileByPath(filePath); File[] files = file.listFiles(); - boolean isJumpHiddenFile = SettingsManager.instance().isFolderBackupJumpHiddenFiles(); + boolean isJumpHiddenFile = SettingsManager.getInstance().isFolderBackupJumpHiddenFiles(); if (files != null) { for (File value : files) { @@ -323,7 +317,7 @@ public void onStop(FileAlterationObserver observer) { } public void backupFolders() { - String backupEmail = SettingsManager.instance().getBackupEmail(); + String backupEmail = FolderBackupConfigSPs.getBackupEmail(); if (backupEmail != null) { backupFolder(backupEmail); } diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/RepoConfig.java b/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/RepoConfig.java similarity index 94% rename from app/src/main/java/com/seafile/seadroid2/folderbackup/RepoConfig.java rename to app/src/main/java/com/seafile/seadroid2/ui/folder_backup/RepoConfig.java index 60bdb4753..b89d85fad 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/RepoConfig.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/folder_backup/RepoConfig.java @@ -1,7 +1,6 @@ -package com.seafile.seadroid2.folderbackup; +package com.seafile.seadroid2.ui.folder_backup; import com.google.common.base.Objects; -import com.seafile.seadroid2.SettingsManager; import java.io.Serializable; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/gesture/CreateGesturePasswordActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/gesture/CreateGesturePasswordActivity.java index 5a2cd94ff..4b98dc6f8 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/gesture/CreateGesturePasswordActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/gesture/CreateGesturePasswordActivity.java @@ -11,8 +11,9 @@ import android.widget.TextView; import android.widget.Toast; +import com.blankj.utilcode.util.ToastUtils; import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.util.sp.SettingsManager; import com.seafile.seadroid2.gesturelock.LockPatternUtils; import com.seafile.seadroid2.gesturelock.LockPatternView; import com.seafile.seadroid2.gesturelock.LockPatternView.Cell; @@ -149,7 +150,7 @@ protected enum Stage { final boolean patternEnabled; } - SettingsManager settingsMgr = SettingsManager.instance(); + SettingsManager settingsMgr = SettingsManager.getInstance(); @Override public void onCreate(Bundle savedInstanceState) { @@ -417,7 +418,7 @@ private void saveChosenPatternAndFinish() { LockPatternUtils mLockPatternUtils = new LockPatternUtils(this); mLockPatternUtils.saveLockPattern(mChosenPattern); settingsMgr.setupGestureLock(); - showShortToast(this, getResources().getString(R.string.lockpattern_pattern_toast_saved)); + ToastUtils.showLong(R.string.lockpattern_pattern_toast_saved); setResult(RESULT_OK); finish(); } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/gesture/UnlockGesturePasswordActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/gesture/UnlockGesturePasswordActivity.java index be06515d9..b4ea71e78 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/gesture/UnlockGesturePasswordActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/gesture/UnlockGesturePasswordActivity.java @@ -15,13 +15,14 @@ import android.view.animation.AnimationUtils; import android.widget.TextView; +import com.blankj.utilcode.util.ToastUtils; import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.util.sp.SettingsManager; import com.seafile.seadroid2.gesturelock.LockPatternUtils; import com.seafile.seadroid2.gesturelock.LockPatternView; import com.seafile.seadroid2.gesturelock.LockPatternView.Cell; import com.seafile.seadroid2.ui.BaseActivity; -import com.seafile.seadroid2.ui.BrowserActivity; +import com.seafile.seadroid2.ui.main.MainActivity; import java.util.List; @@ -55,7 +56,7 @@ public void onCreate(Bundle savedInstanceState) { setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(false); getSupportActionBar().setTitle(R.string.gesture_lock); - settingsMgr = SettingsManager.instance(); + settingsMgr = SettingsManager.getInstance(); } @Override @@ -69,7 +70,7 @@ protected void onDestroy() { public void onBackPressed() { // stop default action (finishing the current activity) to be executed. // super.onBackPressed(); - Intent i = new Intent(this, BrowserActivity.class); + Intent i = new Intent(this, MainActivity.class); i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(i); finish(); @@ -107,13 +108,13 @@ public void onPatternDetected(List pattern) { int retry = LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT - mFailedPatternAttemptsSinceLastTimeout; if (retry >= 0) { if (retry == 0) - showShortToast(UnlockGesturePasswordActivity.this, getResources().getString(R.string.lockscreen_access_pattern_failure)); + ToastUtils.showLong(R.string.lockscreen_access_pattern_failure); mHeadTextView.setText(getResources().getQuantityString(R.plurals.lockscreen_access_pattern_failure_left_try_times, retry, retry)); mHeadTextView.setTextColor(Color.RED); mHeadTextView.startAnimation(mShakeAnim); } } else { - showShortToast(UnlockGesturePasswordActivity.this, getResources().getString(R.string.lockscreen_access_pattern_failure_not_long_enough)); + ToastUtils.showLong(getResources().getString(R.string.lockscreen_access_pattern_failure_not_long_enough)); } if (mFailedPatternAttemptsSinceLastTimeout >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/main/MainActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/main/MainActivity.java new file mode 100644 index 000000000..07c5fad32 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/main/MainActivity.java @@ -0,0 +1,1137 @@ +package com.seafile.seadroid2.ui.main; + +import android.Manifest; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; + +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.FileProvider; +import androidx.fragment.app.DialogFragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.TimeUtils; +import com.blankj.utilcode.util.ToastUtils; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; +import com.google.common.collect.Lists; +import com.seafile.seadroid2.BuildConfig; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.context.NavContext; +import com.seafile.seadroid2.data.DataManager; +import com.seafile.seadroid2.data.ServerInfo; +import com.seafile.seadroid2.data.db.entities.DirentModel; +import com.seafile.seadroid2.data.db.entities.ObjsModel; +import com.seafile.seadroid2.data.db.entities.RepoModel; +import com.seafile.seadroid2.databinding.ActivityMainBinding; +import com.seafile.seadroid2.monitor.FileMonitorService; +import com.seafile.seadroid2.notification.UploadNotificationProvider; +import com.seafile.seadroid2.transfer.DownloadTaskManager; +import com.seafile.seadroid2.transfer.PendingUploadInfo; +import com.seafile.seadroid2.transfer.TransferManager; +import com.seafile.seadroid2.transfer.TransferService; +import com.seafile.seadroid2.transfer.UploadTaskManager; +import com.seafile.seadroid2.ui.account.AccountsActivity; +import com.seafile.seadroid2.ui.adapter.ViewPager2Adapter; +import com.seafile.seadroid2.ui.base.BaseQuickActivity; +import com.seafile.seadroid2.ui.camera_upload.MediaObserverService; +import com.seafile.seadroid2.ui.dialog_fragment.NewDirFileDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.NewRepoDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.PasswordDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.SortFilesDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.listener.OnRefreshDataListener; +import com.seafile.seadroid2.ui.dialog_fragment.listener.OnSortItemClickListener; +import com.seafile.seadroid2.ui.folder_backup.FolderBackupService; +import com.seafile.seadroid2.ui.gesture.UnlockGesturePasswordActivity; +import com.seafile.seadroid2.ui.repo.RepoQuickFragment; +import com.seafile.seadroid2.ui.search.Search2Activity; +import com.seafile.seadroid2.ui.settings.SettingsActivity; +import com.seafile.seadroid2.ui.transfer.TransferActivity; +import com.seafile.seadroid2.util.PermissionUtil; +import com.seafile.seadroid2.util.SLogs; +import com.seafile.seadroid2.util.TakeCameras; +import com.seafile.seadroid2.util.Utils; +import com.seafile.seadroid2.util.sp.FolderBackupConfigSPs; +import com.seafile.seadroid2.util.sp.SettingsManager; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import io.reactivex.functions.Consumer; +import kotlin.Pair; + +public class MainActivity extends BaseQuickActivity implements Toolbar.OnMenuItemClickListener { + + private ActivityMainBinding binding; + public static final int INDEX_LIBRARY_TAB = 0; + + private Intent monitorIntent; + private TransferService txService = null; + private FolderBackupService folderBackupService = null; + private TransferReceiver mTransferReceiver; + + + private MainViewModel mainViewModel; + + private Menu overFlowMenu; + private MenuItem menuSearch; + + private Account curAccount; + + private ArrayList pendingUploads = Lists.newArrayList(); + + private NavContext getNavContext() { + return mainViewModel.getNavContext(); + } + + public TransferService getTransferService() { + return txService; + } + + private RepoQuickFragment getReposFragment() { + return (RepoQuickFragment) mainViewModel.getFragments().get(0); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (!isTaskRoot()) { + final Intent intent = getIntent(); + final String intentAction = getIntent().getAction(); + if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && intentAction != null && intentAction.equals(Intent.ACTION_MAIN)) { + finish(); + return; + } + } + + mainViewModel = new ViewModelProvider(this).get(MainViewModel.class); + binding = ActivityMainBinding.inflate(getLayoutInflater()); + + setContentView(binding.getRoot()); + + setSupportActionBar(getActionBarToolbar()); + // enable ActionBar app icon to behave as action back + enableUpButton(false); + + curAccount = SupportAccountManager.getInstance().getCurrentAccount(); + if (curAccount == null || !curAccount.hasValidToken()) { + finishAndStartAccountsActivity(); + return; + } + + initTabLayout(); + initViewPager(); + initViewModel(); + + //service +// bindService(); + +// requestServerInfo(true); + + //job +// Utils.startCameraSyncJob(this); + +// syncCamera(); + } + + + @Override + public void onStart() { + super.onStart(); + SLogs.d("onStart"); + + if (SettingsManager.getInstance().isGestureLockRequired()) { + Intent intent = new Intent(this, UnlockGesturePasswordActivity.class); + startActivity(intent); + } + + registerReceiver(); + } + + @Override + public void onRestart() { + super.onRestart(); + SLogs.d("onRestart"); + + if (curAccount == null + || !curAccount.equals(SupportAccountManager.getInstance().getCurrentAccount()) + || !SupportAccountManager.getInstance().getCurrentAccount().getToken().equals(curAccount.getToken())) { + finishAndStartAccountsActivity(); + } + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + SLogs.d("onNewIntent"); + + // if the user started the Seadroid app from the Launcher, keep the old Activity + final String intentAction = intent.getAction(); + if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) + && intentAction != null + && intentAction.equals(Intent.ACTION_MAIN)) { + return; + } + + Account selectedAccount = SupportAccountManager.getInstance().getCurrentAccount(); + + SLogs.d("Current account: " + curAccount); + if (selectedAccount == null + || !curAccount.equals(selectedAccount) + || !curAccount.getToken().equals(selectedAccount.getToken())) { + SLogs.d("Account switched, restarting activity."); + finishAndStartAccountsActivity(); + return; + } + + String repoId = intent.getStringExtra("repoID"); + String repoName = intent.getStringExtra("repoName"); + String path = intent.getStringExtra("path"); + + if (TextUtils.isEmpty(repoId) || TextUtils.isEmpty(path)) { + return; + } + + // + navToPath(repoId, path); + } + + private void navToPath(String repoId, String path) { + mainViewModel.requestRepoModel(repoId, new Consumer() { + @Override + public void accept(RepoModel repoModel) throws Exception { + if (repoModel.encrypted) { + mainViewModel.getObjFromDB(repoId, new Consumer() { + @Override + public void accept(ObjsModel objsModel) throws Exception { + //check encrypt and decrypt_expire_time + long now = TimeUtils.getNowMills(); + if (objsModel == null || (repoModel.encrypted && now > objsModel.decrypt_expire_time_long)) { + // expired + showPasswordDialog(repoModel, path); + } else { + getNavContext().navToPath(repoModel, path); + binding.pager.setCurrentItem(0); + getReposFragment().loadData(); + refreshToolbarTitle(); + } + } + }); + } else { + getNavContext().navToPath(repoModel, path); + binding.pager.setCurrentItem(0); + getReposFragment().loadData(); + refreshToolbarTitle(); + } + } + }); + } + + private void initTabLayout() { + binding.slidingTabs.setTabIndicatorAnimationMode(TabLayout.INDICATOR_ANIMATION_MODE_ELASTIC); + binding.slidingTabs.setSelectedTabIndicator(R.drawable.cat_tabs_rounded_line_indicator); + binding.slidingTabs.setTabIndicatorFullWidth(false); + binding.slidingTabs.setTabGravity(TabLayout.GRAVITY_CENTER); + + binding.slidingTabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { + @Override + public void onTabSelected(TabLayout.Tab tab) { + onTabLayoutSelected(); + } + + @Override + public void onTabUnselected(TabLayout.Tab tab) { + + } + + @Override + public void onTabReselected(TabLayout.Tab tab) { + + } + }); + } + + private void onTabLayoutSelected() { + //Invalidate menu + supportInvalidateOptionsMenu(); + + //tab + if (binding.slidingTabs.getSelectedTabPosition() == 0) { + if (getNavContext().isInRepo()) { + setActionbarTitle(getNavContext().getNameInCurPath()); + enableUpButton(true); + } else { + enableUpButton(false); + setActionbarTitle(getString(R.string.tabs_library).toUpperCase()); + } + } else if (binding.slidingTabs.getSelectedTabPosition() == 1) { + enableUpButton(false); + setActionbarTitle(getString(R.string.tabs_starred).toUpperCase()); + } else if (binding.slidingTabs.getSelectedTabPosition() == 2) { + enableUpButton(false); + setActionbarTitle(getString(R.string.tabs_activity).toUpperCase()); + } + } + + private void initViewPager() { + ViewPager2Adapter viewPager2Adapter = new ViewPager2Adapter(this); + viewPager2Adapter.addFragments(mainViewModel.getFragments()); + binding.pager.setAdapter(viewPager2Adapter); + binding.pager.setOffscreenPageLimit(3); + + String[] tabArray = getResources().getStringArray(R.array.main_fragment_titles); + TabLayoutMediator mediator = new TabLayoutMediator(binding.slidingTabs, binding.pager, new TabLayoutMediator.TabConfigurationStrategy() { + @Override + public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) { + tab.setText(tabArray[position]); + } + }); + + mediator.attach(); + } + + private void initViewModel() { + mainViewModel.getOnForceRefreshRepoListLiveData().observe(this, new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + if (binding.pager.getCurrentItem() == 0) { + RepoQuickFragment fragment = (RepoQuickFragment) mainViewModel.getFragments().get(0); + fragment.clearExpireRefreshMap(); + fragment.loadData(); + } + } + }); + + mainViewModel.getOnNewFileDownloadLiveData().observe(this, new Observer>() { + @Override + public void onChanged(Pair pair) { + if (pair == null) { + return; + } + + String repoId = pair.getFirst(); + String path = pair.getSecond(); + + + } + }); + + mainViewModel.getServerInfoLiveData().observe(this, new Observer() { + @Override + public void onChanged(ServerInfo serverInfo) { + requestServerInfo(false); + } + }); + + mainViewModel.getOnNavContextChangeListenerLiveData().observe(this, new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + refreshActionbar(); + } + }); + } + + private void refreshToolbarTitle() { + if (getNavContext().isInRepoList()) { + getActionBarToolbar().setTitle(R.string.libraries); + } else if (getNavContext().isInRepoRoot()) { + getActionBarToolbar().setTitle(getNavContext().getRepoModel().repo_name); + } else { + String toolbarTitle = getNavContext().getLastPathName(); + getActionBarToolbar().setTitle(toolbarTitle); + } + } + + @Override + public void onBackPressed() { + if (binding.pager.getCurrentItem() == INDEX_LIBRARY_TAB) { + RepoQuickFragment fragment = (RepoQuickFragment) mainViewModel.getFragments().get(0); + boolean canBack = fragment.backTo(); + if (canBack) { + + } else { + super.onBackPressed(); + } + } else { + super.onBackPressed(); + } + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + + // handle notification permission on API level >= 33 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // request notification permission first and then prompt for storage permissions + // storage permissions handled in onRequestPermissionsResult + PermissionUtil.requestNotificationPermission(this); + } else { + PermissionUtil.requestExternalStoragePermission(this); + } + } + + /////////////////////service + private void bindService() { + // restart service should it have been stopped for some reason + Intent mediaObserver = new Intent(this, MediaObserverService.class); + startService(mediaObserver); + SLogs.d("start MediaObserverService"); + + Intent dIntent = new Intent(this, FolderBackupService.class); + startService(dIntent); + SLogs.d("start FolderBackupService"); + + Intent dirIntent = new Intent(this, FolderBackupService.class); + bindService(dirIntent, folderBackupConnection, Context.BIND_AUTO_CREATE); + SLogs.d("try bind FolderBackupService"); + + Intent txIntent = new Intent(this, TransferService.class); + startService(txIntent); + SLogs.d("start TransferService"); + + // bind transfer service + Intent bIntent = new Intent(this, TransferService.class); + bindService(bIntent, mConnection, Context.BIND_AUTO_CREATE); + SLogs.d("try bind TransferService"); + + monitorIntent = new Intent(this, FileMonitorService.class); + startService(monitorIntent); + SLogs.d("start FileMonitorService"); + } + + private void registerReceiver() { + + if (mTransferReceiver == null) { + mTransferReceiver = new TransferReceiver(); + } + + IntentFilter filter = new IntentFilter(TransferManager.BROADCAST_ACTION); + LocalBroadcastManager.getInstance(this).registerReceiver(mTransferReceiver, filter); + + } + + private final ServiceConnection mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + TransferService.TransferBinder binder = (TransferService.TransferBinder) service; + txService = binder.getService(); + SLogs.d("bond TransferService"); + + for (PendingUploadInfo info : pendingUploads) { + txService.addTaskToUploadQue(curAccount, + info.repoID, + info.repoName, + info.targetDir, + info.localFilePath, + info.isUpdate, + info.isCopyToLocal); + } + pendingUploads.clear(); + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + txService = null; + } + }; + + + private final ServiceConnection folderBackupConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + FolderBackupService.FileBackupBinder binder = (FolderBackupService.FileBackupBinder) service; + folderBackupService = binder.getService(); + SLogs.d("bond FolderBackupService"); + + boolean dirAutomaticUpload = SettingsManager.getInstance().isFolderAutomaticBackup(); + String backupEmail = FolderBackupConfigSPs.getBackupEmail(); + if (dirAutomaticUpload && folderBackupService != null && !TextUtils.isEmpty(backupEmail)) { + folderBackupService.backupFolder(backupEmail); + } + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + folderBackupService = null; + } + }; + + + // for receive broadcast from TransferService + private static class TransferReceiver extends BroadcastReceiver { + + private TransferReceiver() { + } + + public void onReceive(Context context, Intent intent) { + String type = intent.getStringExtra("type"); + if (TextUtils.isEmpty(type)) { + return; + } + + if (type.equals(DownloadTaskManager.BROADCAST_FILE_DOWNLOAD_FAILED)) { + int taskID = intent.getIntExtra("taskID", 0); +// onFileDownloadFailed(taskID); + } else if (type.equals(UploadTaskManager.BROADCAST_FILE_UPLOAD_SUCCESS)) { + int taskID = intent.getIntExtra("taskID", 0); +// onFileUploaded(taskID); + } else if (type.equals(UploadTaskManager.BROADCAST_FILE_UPLOAD_FAILED)) { + int taskID = intent.getIntExtra("taskID", 0); +// onFileUploadFailed(taskID); + } + } + + } + + //////////////////////////check server info + private void requestServerInfo(boolean loadFromNet) { + if (!checkServerProEdition()) { + // hide Activity tab + ViewPager2Adapter adapter = (ViewPager2Adapter) binding.pager.getAdapter(); + if (adapter != null) { + adapter.removeFragment(2); + binding.slidingTabs.removeTabAt(2); + adapter.notifyDataSetChanged(); + } + } + +// if (!checkSearchEnabled()) { +// // hide search menu +// if (menuSearch != null) +// menuSearch.setVisible(false); +// } + + if (loadFromNet) { + mainViewModel.getServerInfo(); + } + } + + /** + * check if server is pro edition + * + * @return true, if server is pro edition + * false, otherwise. + */ + private boolean checkServerProEdition() { + if (curAccount == null) + return false; + + ServerInfo serverInfo = SupportAccountManager.getInstance().getServerInfo(curAccount); + return serverInfo.isProEdition(); + } + + /** + * check if server supports searching feature + * + * @return true, if search enabled + * false, otherwise. + */ + private boolean checkSearchEnabled() { + if (curAccount == null) + return false; + + ServerInfo serverInfo = SupportAccountManager.getInstance().getServerInfo(curAccount); + + return serverInfo.isSearchEnabled(); + } + + + private void finishAndStartAccountsActivity() { + Intent newIntent = new Intent(this, AccountsActivity.class); + newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + finish(); + startActivity(newIntent); + } + + + /** + * Callback received when a permissions request has been completed. + */ + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + // Log.i(DEBUG_TAG, "Received response for permission request."); + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + switch (requestCode) { + case PermissionUtil.PERMISSIONS_POST_NOTIFICATIONS: + // handle notification permission on API level >= 33 + // dialogue was dismissed -> prompt for storage permissions + PermissionUtil.requestExternalStoragePermission(this); + break; + case PermissionUtil.PERMISSIONS_EXTERNAL_STORAGE: + // Check if the only required permission has been granted + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // permission was granted + SLogs.i("PERMISSIONS_EXTERNAL_STORAGE has been granted"); + } + break; + } + } + + +// @Override +// public void onSaveInstanceState(Bundle outState) { +// super.onSaveInstanceState(outState); +// //outState.putInt("tab", getActionBarToolbar().getSelectedNavigationIndex()); +// if (navContext.getRepoID() != null) { +// outState.putString("repoID", navContext.getRepoID()); +// outState.putString("repoName", navContext.getRepoName()); +// outState.putString("path", navContext.getDirPath()); +// outState.putString("permission", navContext.getDirPermission()); +// } +// } + + ////////////////////////menu//////////////////////// + private void refreshActionbar() { + if (getNavContext().isInRepo()) { + + //refresh back btn state + enableUpButton(true); + + setActionbarTitle(getNavContext().getNameInCurPath()); + } else { + enableUpButton(false); + + setActionbarTitle(null); + } + + //refresh toolbar menu + supportInvalidateOptionsMenu(); + } + + public void setActionbarTitle(String title) { + if (getSupportActionBar() != null) { + if (TextUtils.isEmpty(title)) { + getSupportActionBar().setTitle(R.string.libraries); + } else { + getSupportActionBar().setTitle(title); + } + } + + } + + private void enableUpButton(boolean isEnable) { + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(isEnable); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + overFlowMenu = menu; + + Toolbar toolbar = getActionBarToolbar(); + toolbar.inflateMenu(R.menu.browser_menu); + toolbar.setOnMenuItemClickListener(this); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + if (getNavContext().isInRepo() && binding.pager.getCurrentItem() == INDEX_LIBRARY_TAB) { + onBackPressed(); + } + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + menuSearch = menu.findItem(R.id.search); + MenuItem menuSort = menu.findItem(R.id.sort); + MenuItem menuAdd = menu.findItem(R.id.add); + MenuItem menuCreateRepo = menu.findItem(R.id.create_repo); + MenuItem menuEdit = menu.findItem(R.id.edit); + + // Libraries Tab + if (binding.pager.getCurrentItem() == INDEX_LIBRARY_TAB) { + if (getNavContext().isInRepo()) { + menuCreateRepo.setVisible(false); + menuAdd.setVisible(true); + menuEdit.setVisible(true); + if (getNavContext().hasWritePermissionWithRepo()) { + menuAdd.setEnabled(true); + menuEdit.setEnabled(true); + } else { + menuAdd.setEnabled(false); + menuEdit.setEnabled(false); + } + + } else { + menuCreateRepo.setVisible(true); + menuAdd.setVisible(false); + menuEdit.setVisible(false); + } + + menuSort.setVisible(true); + } else { + menuSort.setVisible(false); + menuCreateRepo.setVisible(false); + menuAdd.setVisible(false); + menuEdit.setVisible(false); + } + + // Global menus, e.g. Accounts, TransferTasks, Settings, are visible by default. + // So nothing need to be done here. + + // Though search menu is also a global menu, its state was maintained dynamically at runtime. +// if (!checkServerProEdition()) +// menuSearch.setVisible(false); + + return true; + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + if (item.getItemId() == R.id.sort) { + showSortFilesDialog(); + } else if (item.getItemId() == R.id.search) { + Intent searchIntent = new Intent(this, Search2Activity.class); + startActivity(searchIntent); + } else if (item.getItemId() == R.id.create_repo) { + showNewRepoDialog(); + } else if (item.getItemId() == R.id.add) { + showAddFileDialog(); + } else if (item.getItemId() == R.id.transfer_tasks) { + Intent newIntent = new Intent(this, TransferActivity.class); + newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(newIntent); + } else if (item.getItemId() == R.id.accounts) { + Intent newIntent = new Intent(this, AccountsActivity.class); + newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(newIntent); + } else if (item.getItemId() == R.id.edit) { + if (binding.pager.getCurrentItem() == INDEX_LIBRARY_TAB) { + getReposFragment().startContextualActionMode(); + } + } else if (item.getItemId() == R.id.settings) { + Intent settingsIntent = new Intent(MainActivity.this, SettingsActivity.class); + settingsIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(settingsIntent); + } else { + return super.onOptionsItemSelected(item); + } + return true; + } + + @Override + public boolean onKeyUp(int keycode, KeyEvent e) { + if (keycode == KeyEvent.KEYCODE_MENU) { + if (overFlowMenu != null) { + overFlowMenu.performIdentifierAction(R.id.menu_overflow, 0); + } + } + + return super.onKeyUp(keycode, e); + } + + /** + * onConfigurationChanged + */ + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + } + + ////////////////////////////////////////////////////////////////// + private void showSortFilesDialog() { + SortFilesDialogFragment dialog = new SortFilesDialogFragment(); + dialog.setOnSortItemClickListener(new OnSortItemClickListener() { + @Override + public void onSortFileItemClick(DialogFragment dialog, int position) { + mainViewModel.getOnResortListLiveData().setValue(position); + } + }); + dialog.show(getSupportFragmentManager(), SortFilesDialogFragment.class.getSimpleName()); + } + + private void showPasswordDialog(RepoModel repoModel, String path) { + PasswordDialogFragment dialogFragment = PasswordDialogFragment.newInstance(); + dialogFragment.initData(repoModel.repo_id, repoModel.repo_name); + dialogFragment.setRefreshListener(new OnRefreshDataListener() { + @Override + public void onActionStatus(boolean isDone) { + if (isDone) { + getNavContext().navToPath(repoModel, path); + binding.pager.setCurrentItem(0); + getReposFragment().loadData(); + refreshToolbarTitle(); + } + } + }); + dialogFragment.show(getSupportFragmentManager(), PasswordDialogFragment.class.getSimpleName()); + } + + /** + * create a new repo + */ + private void showNewRepoDialog() { + NewRepoDialogFragment dialogFragment = new NewRepoDialogFragment(); + dialogFragment.setRefreshListener(new OnRefreshDataListener() { + @Override + public void onActionStatus(boolean isDone) { + if (isDone) { + mainViewModel.getOnForceRefreshRepoListLiveData().setValue(true); + } + } + }); + dialogFragment.show(getSupportFragmentManager(), NewRepoDialogFragment.class.getSimpleName()); + } + + /** + * add new file/files + */ + private void showAddFileDialog() { + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); + builder.setTitle(getString(R.string.add_file)); + builder.setItems(R.array.add_file_options_array, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == 0) // create file + showNewFileDialog(); + else if (which == 1) // create folder + showNewDirDialog(); + else if (which == 2) // upload file + pickFile(); + else if (which == 3) // take a photo + cameraTakePhoto(); + } + }).show(); + } + + // + private void showNewDirDialog() { + if (!getNavContext().hasWritePermissionWithRepo()) { + ToastUtils.showLong(R.string.library_read_only); + return; + } + String parentPath = getNavContext().getNavPath(); + NewDirFileDialogFragment dialogFragment = NewDirFileDialogFragment.newInstance(); + dialogFragment.initData(getNavContext().getRepoModel().repo_id, parentPath, true); + dialogFragment.setRefreshListener(new OnRefreshDataListener() { + @Override + public void onActionStatus(boolean isDone) { + if (isDone) { + mainViewModel.getOnForceRefreshRepoListLiveData().setValue(true); + } + } + }); + dialogFragment.show(getSupportFragmentManager(), NewDirFileDialogFragment.class.getSimpleName()); + } + + private void showNewFileDialog() { + if (!getNavContext().hasWritePermissionWithRepo()) { + ToastUtils.showLong(R.string.library_read_only); + return; + } + + String parentPath = getNavContext().getNavPath(); + NewDirFileDialogFragment dialogFragment = NewDirFileDialogFragment.newInstance(); + dialogFragment.initData(getNavContext().getRepoModel().repo_id, parentPath, true); + dialogFragment.setRefreshListener(new OnRefreshDataListener() { + @Override + public void onActionStatus(boolean isDone) { + if (isDone) { + mainViewModel.getOnForceRefreshRepoListLiveData().setValue(true); + } + } + }); + dialogFragment.show(getSupportFragmentManager(), NewDirFileDialogFragment.class.getSimpleName()); + } + + void pickFile() { + if (!getNavContext().hasWritePermissionWithRepo()) { + ToastUtils.showLong(R.string.library_read_only); + return; + } + + takeFile(true); + } + + private void cameraTakePhoto() { + try { + File ImgDir = DataManager.createTempDir(); + + String fileName = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(new Date()) + ".jpg"; + tempTakePhotoFile = new File(ImgDir, fileName); + tempTakePhotoUri = FileProvider.getUriForFile(this, BuildConfig.FILE_PROVIDER_AUTHORITIES, tempTakePhotoFile); + + takePhoto(); + } catch (IOException e) { + ToastUtils.showLong(R.string.unknow_error); + } + } + + ////////////////Launcher/////////////// + + //0 camera + //1 video + private int permission_media_select_type = -1; + + private void takePhoto() { + permission_media_select_type = 0; + cameraPermissionLauncher.launch(Manifest.permission.CAMERA); + } + + private void takeVideo() { + permission_media_select_type = 1; + cameraPermissionLauncher.launch(Manifest.permission.CAMERA); + } + + private void takeFile(boolean isSingleSelect) { + String[] mimeTypes = new String[]{"*/*"}; + if (isSingleSelect) { + singleFileAndImageChooseLauncher.launch(mimeTypes); + } else { + multiFileAndImageChooserLauncher.launch(mimeTypes); + } + } + + private final ActivityResultLauncher singleFileAndImageChooseLauncher = registerForActivityResult(new ActivityResultContracts.OpenDocument(), new ActivityResultCallback() { + @Override + public void onActivityResult(Uri o) { + if (null == o) { + ToastUtils.showLong(R.string.saf_upload_path_not_available); + return; + } + + doSelectSingleFile(o); + } + }); + + private final ActivityResultLauncher multiFileAndImageChooserLauncher = registerForActivityResult(new ActivityResultContracts.OpenMultipleDocuments(), new ActivityResultCallback>() { + @Override + public void onActivityResult(List o) { + if (CollectionUtils.isEmpty(o)) { + return; + } + + Uri[] uris = o.toArray(new Uri[0]); + + } + }); + + private final ActivityResultLauncher cameraPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), new ActivityResultCallback() { + @Override + public void onActivityResult(Boolean result) { + if (Boolean.FALSE.equals(result)) { + ToastUtils.showLong(R.string.permission_camera); +// PermissionUtils.launchAppDetailsSettings(); + return; + } + + if (permission_media_select_type == 0) { + tempTakePhotoUri = TakeCameras.buildTakePhotoUriAfterCleanOldCacheFiles(MainActivity.this); + takePhotoLauncher.launch(tempTakePhotoUri); + } else if (permission_media_select_type == 1) { + tempTakePhotoUri = TakeCameras.buildTakePhotoUriAfterCleanOldCacheFiles(MainActivity.this); + takeVideoLauncher.launch(tempTakePhotoUri); + } + } + }); + + private Uri tempTakePhotoUri; + private File tempTakePhotoFile; + private final ActivityResultLauncher takePhotoLauncher = registerForActivityResult(new ActivityResultContracts.TakePicture(), new ActivityResultCallback() { + @Override + public void onActivityResult(Boolean result) { + if (!result) { + return; + } + SLogs.d("take photo complete"); + + if (tempTakePhotoFile == null) { + ToastUtils.showLong(R.string.saf_upload_path_not_available); + return; + } + + ToastUtils.showLong(R.string.added_to_upload_tasks); + RepoModel repoModel = getNavContext().getRepoModel(); + if (repoModel.canLocalDecrypt()) { + addUploadBlocksTask(repoModel.repo_id, repoModel.repo_name, getNavContext().getNavPath(), tempTakePhotoFile.getAbsolutePath()); + } else { + addUploadTask(repoModel.repo_id, repoModel.repo_name, getNavContext().getNavPath(), tempTakePhotoFile.getAbsolutePath()); + } + } + }); + + private final ActivityResultLauncher takeVideoLauncher = registerForActivityResult(new ActivityResultContracts.CaptureVideo(), new ActivityResultCallback() { + @Override + public void onActivityResult(Boolean o) { + if (!o) { + return; + } + + SLogs.d("take video complete"); + } + }); + + + ////////////////add task///////////// + private int addUploadTask(String repoID, String repoName, String targetDir, String localFilePath) { + ToastUtils.showLong("TODO: 上传文件"); +// if (txService != null) { +// return txService.addTaskToUploadQue(curAccount, repoID, repoName, targetDir, localFilePath, false, false); +// } else { +// PendingUploadInfo info = new PendingUploadInfo(repoID, repoName, targetDir, localFilePath, false, false); +// pendingUploads.add(info); +// return 0; +// } + return 0; + } + + private int addUploadBlocksTask(String repoID, String repoName, String targetDir, String localFilePath) { + ToastUtils.showLong("TODO: 上传文件"); + +// if (txService != null) { +// return txService.addTaskToUploadQueBlock(curAccount, repoID, repoName, targetDir, localFilePath, false, false); +// } else { +// PendingUploadInfo info = new PendingUploadInfo(repoID, repoName, targetDir, localFilePath, false, false); +// pendingUploads.add(info); +// return 0; +// } + return 0; + } + + public void addUpdateTask(String repoID, String repoName, String targetDir, String localFilePath) { + ToastUtils.showLong("TODO: 更新文件"); + +// if (txService != null) { +// txService.addTaskToUploadQue(curAccount, repoID, repoName, targetDir, localFilePath, true, false); +// } else { +// PendingUploadInfo info = new PendingUploadInfo(repoID, repoName, targetDir, localFilePath, true, false); +// pendingUploads.add(info); +// } + } + + public void addUpdateBlocksTask(String repoID, String repoName, String targetDir, String localFilePath) { + ToastUtils.showLong("TODO: 更新文件"); + +// if (txService != null) { +// txService.addTaskToUploadQueBlock(curAccount, repoID, repoName, targetDir, localFilePath, true, false); +// } else { +// PendingUploadInfo info = new PendingUploadInfo(repoID, repoName, targetDir, localFilePath, true, false); +// pendingUploads.add(info); +// } + } + + ///////////////////////////// + private void doSelectSingleFile(Uri o) { + try { + File tempDir = DataManager.createTempDir(); + File tempFile = new File(tempDir, Utils.getFilenamefromUri(MainActivity.this, o)); + + if (!tempFile.createNewFile()) { + throw new RuntimeException("could not create temporary file"); + } + + RepoModel repoModel = getNavContext().getRepoModel(); + mainViewModel.getDirentsFromServer(repoModel.repo_id, getNavContext().getNavPath(), new Consumer>() { + @Override + public void accept(List list) { + boolean duplicate = false; + for (DirentModel dirent : list) { + if (dirent.name.equals(tempFile.getName())) { + duplicate = true; + break; + } + } + if (!duplicate) { + ToastUtils.showLong(R.string.added_to_upload_tasks); + if (repoModel.canLocalDecrypt()) { + addUploadBlocksTask(repoModel.repo_id, repoModel.repo_name, getNavContext().getNavPath(), tempFile.getAbsolutePath()); + } else { + addUploadTask(repoModel.repo_id, repoModel.repo_name, getNavContext().getNavPath(), tempFile.getAbsolutePath()); + } + } else { + showFileExistDialog(tempFile); + } + + + if (txService == null) + return; + + if (!txService.hasUploadNotifProvider()) { + UploadNotificationProvider provider = new UploadNotificationProvider( + txService.getUploadTaskManager(), + txService); + txService.saveUploadNotifProvider(provider); + } + } + }); + + } catch (Exception e) { + SLogs.e("Could not open requested document", e); + } + } + + private void showFileExistDialog(final File file) { + RepoModel repoModel = getNavContext().getRepoModel(); + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); + builder.setTitle(getString(R.string.upload_file_exist)); + builder.setMessage(String.format(getString(R.string.upload_duplicate_found), file.getName())); + builder.setPositiveButton(getString(R.string.upload_replace), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ToastUtils.showLong(R.string.added_to_upload_tasks); + + if (repoModel.canLocalDecrypt()) { + addUpdateBlocksTask(repoModel.repo_id, repoModel.repo_name, getNavContext().getNavPath(), file.getAbsolutePath()); + } else { + addUpdateTask(repoModel.repo_id, repoModel.repo_name, getNavContext().getNavPath(), file.getAbsolutePath()); + } + } + }); + builder.setNeutralButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + builder.setNegativeButton(getString(R.string.upload_keep_both), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (repoModel.canLocalDecrypt()) { + addUploadBlocksTask(repoModel.repo_id, repoModel.repo_name, getNavContext().getNavPath(), file.getAbsolutePath()); + } else { + addUploadTask(repoModel.repo_id, repoModel.repo_name, getNavContext().getNavPath(), file.getAbsolutePath()); + } + } + }); + builder.show(); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/main/MainViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/main/MainViewModel.java new file mode 100644 index 000000000..fbdbae7c6 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/main/MainViewModel.java @@ -0,0 +1,227 @@ +package com.seafile.seadroid2.ui.main; + +import android.text.TextUtils; + +import androidx.fragment.app.Fragment; +import androidx.lifecycle.MutableLiveData; + +import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.ToastUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.data.model.repo.DirentWrapperModel; +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.config.Constants; +import com.seafile.seadroid2.context.NavContext; +import com.seafile.seadroid2.data.ServerInfo; +import com.seafile.seadroid2.data.db.AppDatabase; +import com.seafile.seadroid2.data.db.entities.DirentModel; +import com.seafile.seadroid2.data.db.entities.ObjsModel; +import com.seafile.seadroid2.data.db.entities.RepoModel; +import com.seafile.seadroid2.data.model.repo.RepoWrapperModel; +import com.seafile.seadroid2.data.model.server.ServerInfoModel; +import com.seafile.seadroid2.data.remote.api.MainService; +import com.seafile.seadroid2.data.remote.api.RepoService; +import com.seafile.seadroid2.io.http.IO; +import com.seafile.seadroid2.ui.activities.AllActivitiesFragment; +import com.seafile.seadroid2.ui.repo.RepoQuickFragment; +import com.seafile.seadroid2.ui.star.StarredQuickFragment; +import com.seafile.seadroid2.util.SLogs; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import io.reactivex.Single; +import io.reactivex.functions.Consumer; +import kotlin.Pair; + +public class MainViewModel extends BaseViewModel { + private final MutableLiveData> OnNewFileDownloadLiveData = new MutableLiveData<>(); + private final MutableLiveData OnResortListLiveData = new MutableLiveData<>(); + + //force refresh repo/dirents + private final MutableLiveData OnForceRefreshRepoListLiveData = new MutableLiveData<>(); + + //show swipeRefresh in Repo Fragment + private final MutableLiveData OnShowRefreshLoadingInRepoLiveData = new MutableLiveData<>(); + + + private final MutableLiveData OnNavChangeListenerLiveData = new MutableLiveData<>(); + + + private final MutableLiveData ServerInfoLiveData = new MutableLiveData<>(); + + public MutableLiveData> getOnNewFileDownloadLiveData() { + return OnNewFileDownloadLiveData; + } + + public MutableLiveData getOnForceRefreshRepoListLiveData() { + return OnForceRefreshRepoListLiveData; + } + + public MutableLiveData getOnShowRefreshLoadingInRepoLiveData() { + return OnShowRefreshLoadingInRepoLiveData; + } + + public MutableLiveData getOnNavContextChangeListenerLiveData() { + return OnNavChangeListenerLiveData; + } + + + public MutableLiveData getServerInfoLiveData() { + return ServerInfoLiveData; + } + + public MutableLiveData getOnResortListLiveData() { + return OnResortListLiveData; + } + + private NavContext navContext = null; + + public NavContext getNavContext() { + if (navContext == null) { + navContext = new NavContext(); + } + return navContext; + } + + private final List fragments = new ArrayList<>(); + + public List getFragments() { + return fragments; + } + + public MainViewModel() { + getNavContext(); + + fragments.add(RepoQuickFragment.newInstance()); + fragments.add(StarredQuickFragment.newInstance()); + fragments.add(AllActivitiesFragment.newInstance()); + } + + public void getServerInfo() { + Single single = IO.getSingleton().execute(MainService.class).getServerInfo(); + addSingleDisposable(single, new Consumer() { + @Override + public void accept(ServerInfoModel serverInfo) throws Exception { + Account account = SupportAccountManager.getInstance().getCurrentAccount(); + if (account == null) { + return; + } + + ServerInfo serverInfo1 = new ServerInfo(account.server, serverInfo.version, serverInfo.getFeaturesString()); + SupportAccountManager.getInstance().setServerInfo(account, serverInfo1); + + getServerInfoLiveData().setValue(serverInfo1); + } + }); + } + + public void requestRepoModel(String repoId, Consumer consumer) { + getOnShowRefreshLoadingInRepoLiveData().setValue(true); + + //from db + Single> singleDb = AppDatabase.getInstance().repoDao().getRepoById(repoId); + addSingleDisposable(singleDb, new Consumer>() { + @Override + public void accept(List repoModels) throws Exception { + if (consumer != null) { + if (CollectionUtils.isEmpty(repoModels)) { + //no data in sqlite, request RepoApi again + requestRepoModelFromServer(repoId, consumer); + } else { + consumer.accept(repoModels.get(0)); + getOnShowRefreshLoadingInRepoLiveData().setValue(false); + } + } else { + getOnShowRefreshLoadingInRepoLiveData().setValue(false); + } + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getOnShowRefreshLoadingInRepoLiveData().setValue(false); + SLogs.e(throwable); + } + }); + } + + private void requestRepoModelFromServer(String repoId, Consumer consumer) { + //from net + Single singleNet = IO.getSingleton().execute(RepoService.class).getRepos(); + addSingleDisposable(singleNet, new Consumer() { + @Override + public void accept(RepoWrapperModel repoWrapperModel) throws Exception { + getOnShowRefreshLoadingInRepoLiveData().setValue(false); + + if (repoWrapperModel == null || CollectionUtils.isEmpty(repoWrapperModel.repos)) { + ToastUtils.showLong(R.string.search_library_not_found); + return; + } + + Optional optionalRepoModel = repoWrapperModel.repos + .stream() + .filter(f -> TextUtils.equals(f.repo_id, repoId)) + .findFirst(); + if (optionalRepoModel.isPresent()) { + if (consumer != null) { + consumer.accept(optionalRepoModel.get()); + } + } else { + ToastUtils.showLong(R.string.search_library_not_found); + } + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getOnShowRefreshLoadingInRepoLiveData().setValue(false); + String msg = getErrorMsgByThrowable(throwable); + ToastUtils.showLong(msg); + } + }); + } + + public void getObjFromDB(String repoName, Consumer consumer) { + Single> single = AppDatabase.getInstance().objDao().getByPath(repoName, Constants.ObjType.REPO); + addSingleDisposable(single, new Consumer>() { + @Override + public void accept(List objsModels) throws Exception { + if (!CollectionUtils.isEmpty(objsModels)) { + consumer.accept(objsModels.get(0)); + } + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + SLogs.e(throwable); + } + }); + } + + public void getDirentsFromLocal(String repoId, String parent_dir, Consumer> consumer) { + Single> singleDB = AppDatabase.getInstance().direntDao().getAllByParentPath(repoId, parent_dir); + addSingleDisposable(singleDB, new Consumer>() { + @Override + public void accept(List direntModels) throws Exception { + if (consumer != null) { + consumer.accept(direntModels); + } + } + }); + } + + public void getDirentsFromServer(String repoId, String parent_dir, Consumer> consumer) { + Single singleServer = IO.getSingleton().execute(RepoService.class).getDirents(repoId, parent_dir); + addSingleDisposable(singleServer, new Consumer() { + @Override + public void accept(DirentWrapperModel direntWrapperModel) throws Exception { + if (consumer != null) { + consumer.accept(direntWrapperModel.dirent_list); + } + } + }); + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/markdown/MarkdownActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/markdown/MarkdownActivity.java index 730229c7f..06443ac85 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/markdown/MarkdownActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/markdown/MarkdownActivity.java @@ -10,6 +10,8 @@ import android.view.Menu; import android.view.MenuItem; +import com.blankj.utilcode.util.ToastUtils; +import com.seafile.seadroid2.BuildConfig; import com.seafile.seadroid2.R; import com.seafile.seadroid2.editor.EditorActivity; import com.seafile.seadroid2.ui.BaseActivity; @@ -101,7 +103,7 @@ private void edit() { // First try to find an activity who can handle markdown edit Intent editAsMarkDown = new Intent(Intent.ACTION_EDIT); - Uri uri = FileProvider.getUriForFile(this, getPackageName(), new File(path)); + Uri uri = FileProvider.getUriForFile(this, BuildConfig.FILE_PROVIDER_AUTHORITIES, new File(path)); editAsMarkDown.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); String mime = FileMimeUtils.getMimeType(new File(path)); @@ -123,7 +125,7 @@ private void edit() { try { startActivity(editAsText); } catch (ActivityNotFoundException e) { - showShortToast(this, getString(R.string.activity_not_found)); + ToastUtils.showLong(R.string.activity_not_found); } } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/AccountViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/AccountViewHolder.java new file mode 100644 index 000000000..1fb894211 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/AccountViewHolder.java @@ -0,0 +1,16 @@ +package com.seafile.seadroid2.ui.repo; + +import androidx.annotation.NonNull; + +import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; +import com.seafile.seadroid2.databinding.ItemAccountBinding; + +public class AccountViewHolder extends BaseViewHolder { + public ItemAccountBinding binding; + + public AccountViewHolder(@NonNull ItemAccountBinding binding) { + super(binding.getRoot()); + + this.binding = binding; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/DirentViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/DirentViewHolder.java new file mode 100644 index 000000000..373fbabd0 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/DirentViewHolder.java @@ -0,0 +1,16 @@ +package com.seafile.seadroid2.ui.repo; + +import androidx.annotation.NonNull; + +import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; +import com.seafile.seadroid2.databinding.ItemDirentBinding; + +public class DirentViewHolder extends BaseViewHolder { + public ItemDirentBinding binding; + + public DirentViewHolder(@NonNull ItemDirentBinding binding) { + super(binding.getRoot()); + + this.binding = binding; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/DirentsAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/DirentsAdapter.java index e55b06139..092af9031 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/DirentsAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/DirentsAdapter.java @@ -12,6 +12,7 @@ import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeadroidApplication; import com.seafile.seadroid2.data.SeafDirent; +import com.seafile.seadroid2.util.sp.Sorts; import java.util.Collections; import java.util.List; @@ -68,7 +69,7 @@ public void setDirents(List dirents) { notifyDataSetChanged(); } - public void sortFiles(int type, int order) { + public void sortFiles() { List folders = Lists.newArrayList(); List files = Lists.newArrayList(); @@ -83,23 +84,25 @@ public void sortFiles(int type, int order) { dirents.clear(); // sort SeafDirents - if (type == SORT_BY_NAME) { + + int sortType = Sorts.getSortType(); + if (sortType <= Sorts.SORT_BY_NAME_DESC) { // sort by name, in ascending order folders.sort(new SeafDirent.DirentNameComparator()); files.sort(new SeafDirent.DirentNameComparator()); - if (order == SORT_ORDER_DESCENDING) { + if (sortType == Sorts.SORT_BY_NAME_DESC) { Collections.reverse(folders); Collections.reverse(files); } - } else if (type == SORT_BY_LAST_MODIFIED_TIME) { - // sort by last modified time, in ascending order + } else { folders.sort(new SeafDirent.DirentLastMTimeComparator()); files.sort(new SeafDirent.DirentLastMTimeComparator()); - if (order == SORT_ORDER_DESCENDING) { + if (sortType == Sorts.SORT_BY_MODIFIED_TIME_DESC) { Collections.reverse(folders); Collections.reverse(files); } } + // Adds the objects in the specified collection to this ArrayList dirents.addAll(folders); dirents.addAll(files); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java new file mode 100644 index 000000000..893ab7f38 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java @@ -0,0 +1,507 @@ +package com.seafile.seadroid2.ui.repo; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.DiffUtil; + +import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.SizeUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.ui.base.adapter.BaseMultiAdapter; +import com.seafile.seadroid2.config.AbsLayoutItemType; +import com.seafile.seadroid2.config.Constants; +import com.seafile.seadroid2.config.GlideLoadConfig; +import com.seafile.seadroid2.data.db.entities.DirentModel; +import com.seafile.seadroid2.data.db.entities.RepoModel; +import com.seafile.seadroid2.data.model.BaseModel; +import com.seafile.seadroid2.data.model.GroupItemModel; +import com.seafile.seadroid2.databinding.ItemAccountBinding; +import com.seafile.seadroid2.databinding.ItemDirentBinding; +import com.seafile.seadroid2.databinding.ItemGroupItemBinding; +import com.seafile.seadroid2.databinding.ItemRepoBinding; +import com.seafile.seadroid2.databinding.ItemUnsupportedBinding; +import com.seafile.seadroid2.ui.viewholder.GroupItemViewHolder; +import com.seafile.seadroid2.util.GlideApp; + +import java.util.ArrayList; +import java.util.List; + +public class RepoQuickAdapter extends BaseMultiAdapter { + private boolean actionModeOn; + + private Drawable starDrawable; + + /** + * -1 no select + * 0 only select account + * 1 only select repo + */ + private int selectorMode = -1; + + /** + * -1 no limited + * 0 no this value + * >=1 max count + */ + private int selectedMaxCount = 1; + + public void setSelectorMode(int selectorMode) { + this.selectorMode = selectorMode; + } + + public void setSelectData(int selectorMode, int selectedMaxCount) { + this.selectorMode = selectorMode; + this.selectedMaxCount = selectedMaxCount; + } + + public RepoQuickAdapter() { + addItemType(AbsLayoutItemType.GROUP_ITEM, new OnMultiItemAdapterListener() { + @NonNull + @Override + public GroupItemViewHolder onCreate(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) { + ItemGroupItemBinding binding = ItemGroupItemBinding.inflate(LayoutInflater.from(context), viewGroup, false); + return new GroupItemViewHolder(binding); + } + + @Override + public void onBind(@NonNull GroupItemViewHolder holder, int i, @Nullable BaseModel groupTimeModel) { + onBindGroup(holder, (GroupItemModel) groupTimeModel); + } + }).addItemType(AbsLayoutItemType.ACCOUNT, new OnMultiItem() { + @NonNull + @Override + public AccountViewHolder onCreate(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) { + ItemAccountBinding binding = ItemAccountBinding.inflate(LayoutInflater.from(context), viewGroup, false); + return new AccountViewHolder(binding); + } + + @Override + public void onBind(@NonNull AccountViewHolder viewHolder, int i, @Nullable BaseModel baseModel) { + onBindAccount(viewHolder, baseModel); + } + }).addItemType(AbsLayoutItemType.REPO, new OnMultiItem() { + @NonNull + @Override + public RepoViewHolder onCreate(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) { + ItemRepoBinding binding = ItemRepoBinding.inflate(LayoutInflater.from(context), viewGroup, false); + return new RepoViewHolder(binding); + } + + @Override + public void onBind(@NonNull RepoViewHolder viewHolder, int i, @Nullable BaseModel baseModel) { + onBindRepos(viewHolder, (RepoModel) baseModel); + } + }).addItemType(AbsLayoutItemType.DIRENT, new OnMultiItem() { + @NonNull + @Override + public DirentViewHolder onCreate(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) { + ItemDirentBinding binding = ItemDirentBinding.inflate(LayoutInflater.from(context), viewGroup, false); + return new DirentViewHolder(binding); + } + + @Override + public void onBind(@NonNull DirentViewHolder viewHolder, int i, @Nullable BaseModel baseModel) { + onBindDirents(viewHolder, (DirentModel) baseModel); + } + }).addItemType(AbsLayoutItemType.UNSUPPORTED, new OnMultiItem() { + @NonNull + @Override + public UnsupportedViewHolder onCreate(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) { + ItemUnsupportedBinding binding = ItemUnsupportedBinding.inflate(LayoutInflater.from(context), viewGroup, false); + return new UnsupportedViewHolder(binding); + } + + @Override + public void onBind(@NonNull UnsupportedViewHolder viewHolder, int i, @Nullable BaseModel baseModel) { + } + }).onItemViewType(new OnItemViewTypeListener() { + @Override + public int onItemViewType(int i, @NonNull List list) { + if (list.get(i) instanceof GroupItemModel) { + return AbsLayoutItemType.GROUP_ITEM; + } else if (list.get(i) instanceof RepoModel) { + return AbsLayoutItemType.REPO; + } else if (list.get(i) instanceof DirentModel) { + return AbsLayoutItemType.DIRENT; + } else if (list.get(i) instanceof Account) { + return AbsLayoutItemType.ACCOUNT; + } + return AbsLayoutItemType.UNSUPPORTED; + } + }); + } + + public Drawable getStarDrawable() { + if (null == starDrawable) { + int DP_16 = SizeUtils.dp2px(16); + starDrawable = ContextCompat.getDrawable(getContext(), R.drawable.baseline_star_24); + starDrawable.setBounds(0, 0, DP_16, DP_16); + starDrawable.setTint(ContextCompat.getColor(getContext(), R.color.light_grey)); + } + return starDrawable; + } + + + private void onBindAccount(AccountViewHolder holder, BaseModel model) { + Account account = (Account) model; + + holder.binding.listItemAccountTitle.setText(account.getServerHost()); + holder.binding.listItemAccountSubtitle.setText(account.getName()); + + if (TextUtils.isEmpty(account.avatar_url)) { + holder.binding.listItemAccountIcon.setImageResource(R.drawable.default_avatar); + } else { + GlideApp.with(getContext()) + .load(GlideLoadConfig.getGlideUrl(account.avatar_url)) + .apply(GlideLoadConfig.getAvatarOptions()) + .into(holder.binding.listItemAccountIcon); + } + + + if (selectorMode >= 0) { + holder.binding.itemSelectView.setVisibility(account.is_selected ? View.VISIBLE : View.INVISIBLE); + } else { + holder.binding.itemSelectView.setVisibility(View.INVISIBLE); + } + + } + + private void onBindGroup(GroupItemViewHolder holder, GroupItemModel model) { + if (model.name != 0) { + holder.binding.itemGroupTitle.setText(model.name); + } else if (!TextUtils.isEmpty(model.title)) { + if ("Organization".equals(model.title)) { + holder.binding.itemGroupTitle.setText(R.string.shared_with_all); + } else { + holder.binding.itemGroupTitle.setText(model.title); + } + } + } + + private void onBindRepos(RepoViewHolder holder, RepoModel model) { + holder.binding.itemTitle.setText(model.repo_name); + holder.binding.itemSubtitle.setText(model.getSubtitle()); + holder.binding.itemIcon.setImageResource(model.getIcon()); + + if (selectorMode >= 1) { + holder.binding.itemSelectView.setVisibility(model.is_selected ? View.VISIBLE : View.INVISIBLE); + holder.binding.expandableToggleButton.setVisibility(View.INVISIBLE); + } else if (!model.hasWritePermission()) { + holder.binding.expandableToggleButton.setVisibility(View.INVISIBLE); + holder.binding.itemSelectView.setVisibility(View.GONE); + } else { + holder.binding.expandableToggleButton.setVisibility(View.VISIBLE); + holder.binding.itemSelectView.setVisibility(View.GONE); + } + + holder.binding.itemTitle.setCompoundDrawablePadding(Constants.DP.DP_4); + if (model.starred) { + holder.binding.itemTitle.setCompoundDrawables(null, null, getStarDrawable(), null); + } else { + holder.binding.itemTitle.setCompoundDrawables(null, null, null, null); + } + } + + + private void onBindDirents(DirentViewHolder holder, DirentModel model) { + holder.binding.itemTitle.setText(model.name); + holder.binding.itemSubtitle.setText(model.getSubtitle()); + holder.binding.itemIcon.setImageResource(model.getIcon()); + + //action mode + if (actionModeOn) { + holder.binding.itemMultiSelect.setVisibility(View.VISIBLE); + if (model.is_selected) { + holder.binding.itemMultiSelect.setImageResource(R.drawable.multi_select_item_checked); + } else { + holder.binding.itemMultiSelect.setImageResource(R.drawable.multi_select_item_unchecked); + } + } else { + holder.binding.itemMultiSelect.setVisibility(View.GONE); + holder.binding.itemMultiSelect.setImageResource(R.drawable.multi_select_item_unchecked); + } + + holder.binding.itemDownloadStatusProgressbar.setVisibility(View.GONE); + holder.binding.itemDownloadStatus.setVisibility(View.GONE); + + if (selectorMode < 0) { + holder.binding.itemTitle.setTextColor(ContextCompat.getColor(getContext(), R.color.item_repo_title_color)); + holder.binding.itemSubtitle.setTextColor(ContextCompat.getColor(getContext(), R.color.item_repo_subtitle_color)); + + holder.binding.expandableToggleButton.setVisibility(View.VISIBLE); + } else { + + if (!model.isDir()) { + holder.binding.itemTitle.setTextColor(ContextCompat.getColor(getContext(), R.color.light_grey)); + holder.binding.itemSubtitle.setTextColor(ContextCompat.getColor(getContext(), R.color.light_grey)); + } else { + holder.binding.itemTitle.setTextColor(ContextCompat.getColor(getContext(), R.color.item_repo_title_color)); + holder.binding.itemSubtitle.setTextColor(ContextCompat.getColor(getContext(), R.color.item_repo_subtitle_color)); + } + + holder.binding.expandableToggleButton.setVisibility(View.INVISIBLE); + } + +// if (model.isDir()) { +// holder.binding.itemDownloadStatusProgressbar.setVisibility(View.GONE); +// holder.binding.itemDownloadStatus.setVisibility(View.GONE); +// } else { +// holder.binding.itemDownloadStatusProgressbar.setVisibility(View.VISIBLE); +// holder.binding.itemDownloadStatus.setVisibility(View.VISIBLE); +// } + + holder.binding.itemTitle.setCompoundDrawablePadding(Constants.DP.DP_4); + if (model.starred) { + holder.binding.itemTitle.setCompoundDrawables(null, null, getStarDrawable(), null); + } else { + holder.binding.itemTitle.setCompoundDrawables(null, null, null, null); + } + } + + public void setActionModeOn(boolean actionModeOn) { + this.actionModeOn = actionModeOn; + + if (!actionModeOn) { + setItemSelected(false); + } + + notifyItemRangeChanged(0, getItemCount()); + } + + public boolean getActionMode() { + return actionModeOn; + } + + public void setItemSelected(boolean itemSelected) { + for (BaseModel item : getItems()) { + if (item instanceof DirentModel) { + DirentModel model = (DirentModel) item; + model.is_selected = itemSelected; + } + } + + notifyItemRangeChanged(0, getItemCount()); + } + + public List getSelectedList() { + List list = new ArrayList<>(); + for (BaseModel item : getItems()) { + if (item instanceof DirentModel) { + DirentModel model = (DirentModel) item; + if (model.is_selected) { + list.add(model); + } + } + } + return list; + } + + /** + * @return is selected? + */ + public boolean selectItemByMode(int position) { + BaseModel item = getItems().get(position); + + //single + if (selectedMaxCount == 1) { + int selectedPosition = getSelectedPositionByMode(); + if (selectedPosition == position){ + item.is_selected = !item.is_selected; + notifyItemChanged(selectedPosition); + return item.is_selected; + + }else if (selectedPosition > -1) { + //Deselect an item that has already been selected + getItems().get(selectedPosition).is_selected = false; + notifyItemChanged(selectedPosition); + + item.is_selected = true; + notifyItemChanged(position); + }else { + item.is_selected = true; + notifyItemChanged(position); + } + }else { + long selectedCount = getSelectedCountByMode(); + if (selectedCount >= selectedMaxCount) { + return false; + } + + item.is_selected = !item.is_selected; + notifyItemChanged(position); + + return item.is_selected; + } + + return true; + } + + private long getSelectedCountByMode() { + if (selectorMode == 0) { + return getItems().stream() + .filter(Account.class::isInstance) + .filter(f -> f.is_selected) + .count(); + } else if (selectorMode == 1) { + return getItems().stream() + .filter(RepoModel.class::isInstance) + .filter(f -> f.is_selected) + .count(); + } + return 0; + } + + private int getSelectedPositionByMode() { + for (int i = 0; i < getItems().size(); i++) { + if (getItems().get(i).is_selected) { + return i; + } + } + return -1; + } + + public void notifyDataChanged(List list) { + if (CollectionUtils.isEmpty(list)) { + submitList(list); + return; + } + + if (CollectionUtils.isEmpty(getItems())) { + submitList(list); + return; + } + + DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return getItems().size(); + } + + @Override + public int getNewListSize() { + return list.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + String oldClassName = getItems().get(oldItemPosition).getClass().getName(); + String newClassName = list.get(newItemPosition).getClass().getName(); + if (!oldClassName.equals(newClassName)) { + return false; + } + if (getItems().get(oldItemPosition) instanceof Account) { + Account newT = (Account) getItems().get(oldItemPosition); + Account oldT = (Account) list.get(newItemPosition); + if (!TextUtils.equals(newT.email, oldT.email)) { + return false; + } + } + + if (getItems().get(oldItemPosition) instanceof RepoModel) { + RepoModel newT = (RepoModel) getItems().get(oldItemPosition); + RepoModel oldT = (RepoModel) list.get(newItemPosition); + if (!TextUtils.equals(newT.repo_id, oldT.repo_id) || newT.group_id != oldT.group_id) { + return false; + } + } + + if (getItems().get(oldItemPosition) instanceof DirentModel) { + DirentModel newT = (DirentModel) getItems().get(oldItemPosition); + DirentModel oldT = (DirentModel) list.get(newItemPosition); + return TextUtils.equals(newT.full_path, oldT.full_path); + } + + return true; + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + String oldClassName = getItems().get(oldItemPosition).getClass().getName(); + String newClassName = list.get(newItemPosition).getClass().getName(); + if (!oldClassName.equals(newClassName)) { + return false; + } + + if (getItems().get(oldItemPosition) instanceof RepoModel) { + RepoModel newT = (RepoModel) getItems().get(oldItemPosition); + RepoModel oldT = (RepoModel) list.get(newItemPosition); + + return TextUtils.equals(newT.repo_id, oldT.repo_id) + && TextUtils.equals(newT.repo_name, oldT.repo_name) + && TextUtils.equals(newT.type, oldT.type) + && TextUtils.equals(newT.group_name, oldT.group_name) + && TextUtils.equals(newT.owner_name, oldT.owner_name) + && TextUtils.equals(newT.owner_email, oldT.owner_email) + && TextUtils.equals(newT.owner_contact_email, oldT.owner_contact_email) + && TextUtils.equals(newT.modifier_email, oldT.modifier_email) + && TextUtils.equals(newT.modifier_name, oldT.modifier_name) + && TextUtils.equals(newT.modifier_contact_email, oldT.modifier_contact_email) + && TextUtils.equals(newT.permission, oldT.permission) + && TextUtils.equals(newT.salt, oldT.salt) + && TextUtils.equals(newT.status, oldT.status) + && TextUtils.equals(newT.last_modified, oldT.last_modified) + && newT.group_id == oldT.group_id + && newT.encrypted == oldT.encrypted + && newT.size == oldT.size + && newT.starred == oldT.starred + && newT.monitored == oldT.monitored + && newT.is_admin == oldT.is_admin; + } + if (getItems().get(oldItemPosition) instanceof Account) { + Account newT = (Account) getItems().get(oldItemPosition); + Account oldT = (Account) list.get(newItemPosition); + return TextUtils.equals(newT.email, oldT.email) + && TextUtils.equals(newT.name, oldT.name) + && TextUtils.equals(newT.avatar_url, oldT.avatar_url) + && TextUtils.equals(newT.server, oldT.server); + } + + if (getItems().get(oldItemPosition) instanceof DirentModel) { + DirentModel newT = (DirentModel) getItems().get(oldItemPosition); + DirentModel oldT = (DirentModel) list.get(newItemPosition); + return TextUtils.equals(newT.full_path, oldT.full_path) + && TextUtils.equals(newT.name, oldT.name) + && TextUtils.equals(newT.parent_dir, oldT.parent_dir) + && TextUtils.equals(newT.id, oldT.id) + && TextUtils.equals(newT.type, oldT.type) + && TextUtils.equals(newT.permission, oldT.permission) + && TextUtils.equals(newT.dir_id, oldT.dir_id) + && TextUtils.equals(newT.related_account_email, oldT.related_account_email) + && TextUtils.equals(newT.repo_id, oldT.repo_id) + && TextUtils.equals(newT.repo_name, oldT.repo_name) +// && TextUtils.equals(newT.lock_owner, oldT.lock_owner) +// && TextUtils.equals(newT.lock_owner_name, oldT.lock_owner_name) +// && TextUtils.equals(newT.lock_owner_contact_email, oldT.lock_owner_contact_email) +// && TextUtils.equals(newT.modifier_email, oldT.modifier_email) +// && TextUtils.equals(newT.modifier_name, oldT.modifier_name) +// && TextUtils.equals(newT.modifier_contact_email, oldT.modifier_contact_email) +// && TextUtils.equals(newT.encoded_thumbnail_src, oldT.encoded_thumbnail_src) + && TextUtils.equals(newT.repo_name, oldT.repo_name) + && newT.mtime == oldT.mtime + && newT.starred == oldT.starred + && newT.size == oldT.size + && newT.is_locked == oldT.is_locked + && newT.is_freezed == oldT.is_freezed +// && newT.locked_by_me == oldT.locked_by_me +// && newT.lock_time == oldT.lock_time + ; + + } + + return true; + } + }); + + setItems(list); + diffResult.dispatchUpdatesTo(this); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickFragment.java new file mode 100644 index 000000000..95011eb8c --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickFragment.java @@ -0,0 +1,993 @@ +package com.seafile.seadroid2.ui.repo; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.view.ActionMode; +import androidx.core.content.FileProvider; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.TimeUtils; +import com.blankj.utilcode.util.ToastUtils; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.common.collect.Maps; +import com.seafile.seadroid2.BuildConfig; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.SeafException; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.account.SupportDataManager; +import com.seafile.seadroid2.ui.base.fragment.BaseFragmentWithVM; +import com.seafile.seadroid2.bottomsheetmenu.BottomSheetHelper; +import com.seafile.seadroid2.bottomsheetmenu.BottomSheetMenuFragment; +import com.seafile.seadroid2.bottomsheetmenu.OnMenuClickListener; +import com.seafile.seadroid2.config.Constants; +import com.seafile.seadroid2.context.CopyMoveContext; +import com.seafile.seadroid2.context.NavContext; +import com.seafile.seadroid2.data.db.entities.DirentModel; +import com.seafile.seadroid2.data.db.entities.RepoModel; +import com.seafile.seadroid2.data.model.BaseModel; +import com.seafile.seadroid2.databinding.LayoutFrameSwipeRvBinding; +import com.seafile.seadroid2.play.exoplayer.CustomExoVideoPlayerActivity; +import com.seafile.seadroid2.ui.WidgetUtils; +import com.seafile.seadroid2.ui.dialog.AppChoiceDialog; +import com.seafile.seadroid2.ui.dialog.FetchFileDialog; +import com.seafile.seadroid2.ui.dialog_fragment.CopyMoveDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.DeleteFileDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.DeleteRepoDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.PasswordDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.RenameDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.listener.OnRefreshDataListener; +import com.seafile.seadroid2.ui.main.MainViewModel; +import com.seafile.seadroid2.ui.selector.ObjSelectorActivity; +import com.seafile.seadroid2.ui.webview.SeaWebViewActivity; +import com.seafile.seadroid2.util.SLogs; +import com.seafile.seadroid2.util.Utils; +import com.seafile.seadroid2.view.TipsViews; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import kotlin.Pair; + +public class RepoQuickFragment extends BaseFragmentWithVM { + private static final String KEY_REPO_SCROLL_POSITION = "repo_scroll_position"; + private static final String KEY_REPO_LIST = "key_in_repo_list"; + + private final HashMap mRefreshStatusExpireTimeMap = new HashMap(); + private boolean isFirstLoadData = true; + private LayoutFrameSwipeRvBinding binding; + private RepoQuickAdapter adapter; + private LinearLayoutManager rvManager; + + private MainViewModel mainViewModel; + private final Map scrollPositions = Maps.newHashMap(); + private AppCompatActivity activity; + private ActionMode actionMode; + + public static RepoQuickFragment newInstance() { + Bundle args = new Bundle(); + RepoQuickFragment fragment = new RepoQuickFragment(); + fragment.setArguments(args); + return fragment; + } + + private NavContext getNavContext() { + return mainViewModel.getNavContext(); + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + activity = (AppCompatActivity) context; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mainViewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = LayoutFrameSwipeRvBinding.inflate(inflater, container, false); + binding.swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + loadData(true); + } + }); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + init(); + + initAdapter(); + + initViewModel(); + } + + @Override + public void onResume() { + super.onResume(); + d("load data:onResume"); + if (isFirstLoadData) { + isFirstLoadData = false; + d("load data:isFirstLoadData"); + loadData(true); + } else { + if (isForce()) { + loadData(true); + } + } + } + + private void init() { + rvManager = (LinearLayoutManager) binding.rv.getLayoutManager(); + } + + private void initAdapter() { + adapter = new RepoQuickAdapter(); + + adapter.setOnItemClickListener((baseQuickAdapter, view, i) -> { + if (adapter.getActionMode()) { + //toggle + toggleAdapterItemSelectedOnLongClick(i); + + //update bar title + updateContextualActionBar(); + } else { + BaseModel baseModel = adapter.getItems().get(i); + if (baseModel instanceof RepoModel) { + RepoModel repoModel = (RepoModel) baseModel; + if (repoModel.encrypted) { + doEncrypt(repoModel); + } else { + navTo(baseModel); + } + } else { + navTo(baseModel); + } + } + }); + + adapter.setOnItemLongClickListener((baseQuickAdapter, view, i) -> { + if (getNavContext().isInRepo()) { + + //return + if (adapter.getActionMode()) { + return true; + } + + //toggle + toggleAdapterItemSelectedOnLongClick(i); + + //start action mode + startContextualActionMode(); + + //It's actually updating the title of the ActionBar + updateContextualActionBar(); + + return true; + } else { + return false; + } + }); + + adapter.addOnItemChildClickListener(R.id.expandable_toggle_button, (baseQuickAdapter, view, i) -> { + + //when ActionMode is On, return + if (adapter.getActionMode()) { + return; + } + + //open bottom sheet dialog + if (getNavContext().isInRepo()) { + DirentModel direntModel = (DirentModel) adapter.getItems().get(i); + if (direntModel.isDir()) { + showDirNewBottomSheet(direntModel); + } else { + showFileBottomSheet(direntModel); + } + } else { + showRepoBottomSheet((RepoModel) adapter.getItems().get(i)); + } + }); + + binding.rv.setAdapter(createMuiltAdapterHelper(adapter).getAdapter()); + } + + private void initViewModel() { + getViewModel().getRefreshLiveData().observe(getViewLifecycleOwner(), aBoolean -> binding.swipeRefreshLayout.setRefreshing(aBoolean)); + + getViewModel().getExceptionLiveData().observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(Pair exceptionPair) { + showErrorTip(); + } + }); + + getViewModel().getStarLiveData().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + if (aBoolean) { + loadData(true); + } + } + }); + + getViewModel().getObjsListLiveData().observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(List repoModels) { + + notifyDataChanged(repoModels); + + restoreScrollPosition(); + } + }); + + mainViewModel.getOnResortListLiveData().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Integer a) { + SLogs.d("resort: " + a); + loadData(); + } + }); + + mainViewModel.getOnForceRefreshRepoListLiveData().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + loadData(true); + } + }); + + } + + public void clearExpireRefreshMap() { + mRefreshStatusExpireTimeMap.clear(); + } + + private boolean isForce() { + long now = TimeUtils.getNowMills(); + Long expire; + if (getNavContext().isInRepoList()) { + expire = mRefreshStatusExpireTimeMap.get(KEY_REPO_LIST); + } else { + String k = getNavContext().getRepoModel().repo_id + getNavContext().getNavPath(); + expire = mRefreshStatusExpireTimeMap.get(k); + } + return expire == null || now > expire; + } + + public void loadData() { + loadData(false); + } + + public void loadData(boolean forceRefresh) { + + if (forceRefresh) { + long now = TimeUtils.getNowMills(); + now += 1000 * 60 * 10;//10min + if (getNavContext().isInRepoList()) { + mRefreshStatusExpireTimeMap.put(KEY_REPO_LIST, now); + } else { + String k = getNavContext().getRepoModel().repo_id + getNavContext().getNavPath(); + mRefreshStatusExpireTimeMap.put(k, now); + } + } + + getViewModel().loadData(getNavContext(), forceRefresh); + } + + + private void notifyDataChanged(List repoModels) { + if (CollectionUtils.isEmpty(repoModels)) { + showEmptyTip(); + } else { + adapter.notifyDataChanged(repoModels); + } + } + + private void showEmptyTip() { + showAdapterTip(R.string.no_repo); + } + + private void showErrorTip() { + int strInt = !getNavContext().isInRepo() ? R.string.error_when_load_repos : R.string.error_when_load_dirents; + showAdapterTip(strInt); + } + + private void showAdapterTip(int textRes) { + adapter.submitList(null); + TextView tipView = TipsViews.getTipTextView(requireContext()); + tipView.setText(textRes); + tipView.setOnClickListener(v -> loadData(true)); + adapter.setStateView(tipView); + adapter.setStateViewEnable(true); + } + + private void navTo(BaseModel model) { + //save + saveScrollPosition(); + + if (getNavContext().isInRepoList()) { + getNavContext().push(model); + loadData(isForce()); + } else if (model instanceof DirentModel) { + DirentModel direntModel = (DirentModel) model; + if (direntModel.isDir()) { + getNavContext().push(direntModel); + loadData(isForce()); + } else { + openDirent(direntModel, true); + } + } + + //notify navContext changed + mainViewModel.getOnNavContextChangeListenerLiveData().setValue(true); + } + + public boolean backTo() { + if (getNavContext().isInRepo()) { + if (adapter.getActionMode()) { + adapter.setActionModeOn(false); + } else { + // + removeScrollPosition(); + // + getNavContext().pop(); + getViewModel().loadData(getNavContext(), false); + + //notify navContext changed + mainViewModel.getOnNavContextChangeListenerLiveData().setValue(true); + } + return true; + } + return false; + } + + private void doEncrypt(RepoModel repoModel) { + getViewModel().getObjFromDB(repoModel.repo_id, objsModel -> { + if (objsModel == null) { + showPasswordDialog(repoModel); + } else { + long now = TimeUtils.getNowMills(); + if (objsModel.decrypt_expire_time_long == 0) { + showPasswordDialog(repoModel); + } else if (now < objsModel.decrypt_expire_time_long) { + navTo(repoModel); + } else { + showPasswordDialog(repoModel); + } + } + }); + } + + private void showPasswordDialog(RepoModel repoModel) { + PasswordDialogFragment dialogFragment = PasswordDialogFragment.newInstance(); + dialogFragment.initData(repoModel.repo_id, repoModel.repo_name); + dialogFragment.setRefreshListener(new OnRefreshDataListener() { + @Override + public void onActionStatus(boolean isDone) { + if (isDone) { + navTo(repoModel); + } + } + }); + dialogFragment.show(getChildFragmentManager(), PasswordDialogFragment.class.getSimpleName()); + } + + + private void saveScrollPosition() { + View vi = binding.rv.getChildAt(0); + int top = (vi == null) ? 0 : vi.getTop(); + final int index = rvManager.findFirstVisibleItemPosition(); + final ScrollState state = new ScrollState(index, top); + + removeScrollPosition(); + + if (!getNavContext().isInRepo()) { + scrollPositions.put(KEY_REPO_SCROLL_POSITION, state); + } else { + String k = getNavContext().getNavPath(); + scrollPositions.put(k, state); + } + } + + private void removeScrollPosition() { + if (!getNavContext().isInRepo()) { + scrollPositions.remove(KEY_REPO_SCROLL_POSITION); + } else { + String k = getNavContext().getNavPath(); + scrollPositions.remove(k); + } + } + + private void restoreScrollPosition() { + ScrollState state; + if (!getNavContext().isInRepo()) { + state = scrollPositions.get(KEY_REPO_SCROLL_POSITION); + } else { + state = scrollPositions.get(getNavContext().getNavPath()); + } + + if (state != null) { + rvManager.scrollToPositionWithOffset(state.index, state.top); + } else { + rvManager.scrollToPosition(0); + } + } + + @Override + public void onDetach() { + super.onDetach(); + + if (actionMode != null) { + actionMode.finish(); + } + } + + private void toggleAdapterItemSelectedOnLongClick(int i) { + //action mode on + if (!adapter.getActionMode()) { + adapter.setActionModeOn(true); + } + + DirentModel direntModel = (DirentModel) adapter.getItems().get(i); + direntModel.is_selected = !direntModel.is_selected; + adapter.getItems().set(i, direntModel); + adapter.notifyItemChanged(i); + } + + public void closeActionMode(){ + if (adapter.getActionMode()) { + adapter.setActionModeOn(false); + + actionMode.finish(); + actionMode = null; + } + } + + public void startContextualActionMode() { + if (!getNavContext().isInRepo()) return; + + if (actionMode == null) { + // start the actionMode + actionMode = activity.startSupportActionMode(new ActionModeCallback()); + } + } + + /** + * update state of contextual action bar (CAB) + */ + public void updateContextualActionBar() { + if (!getNavContext().isInRepo()) return; + + if (actionMode == null) { + // there are some selected items, start the actionMode + actionMode = activity.startSupportActionMode(new ActionModeCallback()); + } else { + int count = adapter.getSelectedList().size(); + actionMode.setTitle(getResources().getQuantityString(R.plurals.transfer_list_items_selected, count, count)); + } + + } + + /** + * Represents a contextual mode of the user interface. + * Action modes can be used to provide alternative interaction modes and replace parts of the normal UI until finished. + * A Callback configures and handles events raised by a user's interaction with an action mode. + */ + private final class ActionModeCallback implements ActionMode.Callback { + private boolean allItemsSelected; + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // Inflate the menu for the contextual action bar (CAB) + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.repos_fragment_menu, menu); + if (adapter == null) return true; + + // to hidden "r" permissions files or folder + if (!getNavContext().hasWritePermissionWithRepo()) { + menu.findItem(R.id.action_mode_delete).setVisible(false); + menu.findItem(R.id.action_mode_move).setVisible(false); + } + return true; + } + + @SuppressLint("NewApi") + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + /* + * The ActionBarPolicy determines how many action button to place in the ActionBar + * and the default amount is 2. + */ + menu.findItem(R.id.action_mode_delete).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + menu.findItem(R.id.action_mode_copy).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + menu.findItem(R.id.action_mode_select_all).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + + // Here you can perform updates to the contextual action bar (CAB) due to + // an invalidate() request + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + //check data + List selectedDirents = adapter.getSelectedList(); + if (CollectionUtils.isEmpty(selectedDirents) || !getNavContext().isInRepo()) { + if (item.getItemId() != R.id.action_mode_select_all) { + ToastUtils.showLong(R.string.action_mode_no_items_selected); + return true; + } + } + int itemId = item.getItemId(); + if (itemId == R.id.action_mode_select_all) { + adapter.setItemSelected(!allItemsSelected); + updateContextualActionBar(); + + allItemsSelected = !allItemsSelected; + } else if (itemId == R.id.action_mode_delete) { + deleteDirens(selectedDirents); + } else if (itemId == R.id.action_mode_copy) { + DirentModel dirent = selectedDirents.get(0); + copyFiles(dirent.repo_id, dirent.repo_name, dirent.parent_dir, selectedDirents); + } else if (itemId == R.id.action_mode_move) { + DirentModel dirent = selectedDirents.get(0); + moveFiles(dirent.repo_id, dirent.repo_name, dirent.parent_dir, selectedDirents); + } else if (itemId == R.id.action_mode_download) { + ToastUtils.showLong("action: download"); + // mActivity.downloadFiles(repoID, repoName, dirPath, selectedDirents); + } else { + return false; + } + + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + if (adapter == null) return; + + adapter.setActionModeOn(false); + + // Here you can make any necessary updates to the activity when + // the contextual action bar (CAB) is removed. By default, selected items are deselected/unchecked. + if (actionMode != null) { + actionMode = null; + } + } + + } + + public void showRepoBottomSheet(RepoModel model) { + int rid = model.starred ? R.menu.bottom_sheet_op_repo_with_unstar : R.menu.bottom_sheet_op_repo; + BottomSheetHelper.showSheet(getActivity(), rid, menuItem -> { + if (menuItem.getItemId() == R.id.star_repo) { + starOrNot(model); + } else if (menuItem.getItemId() == R.id.rename_repo) { + rename(model.repo_id, null, model.repo_name, "repo"); + } else if (menuItem.getItemId() == R.id.delete_repo) { + deleteRepo(model.repo_id); + } + }); + } + + public void showFileBottomSheet(DirentModel dirent) { + BottomSheetHelper.showSheet(getActivity(), R.menu.bottom_sheet_op_file, new OnMenuClickListener() { + @Override + public void onMenuClick(MenuItem menuItem) { + int itemId = menuItem.getItemId(); + + if (itemId == R.id.share) { + showShareDialog(dirent); + } else if (itemId == R.id.open) { + openDirent(dirent, true); + } else if (itemId == R.id.delete) { + deleteDirent(dirent); + } else if (itemId == R.id.copy) { + copyFiles(dirent.repo_id, dirent.repo_name, dirent.parent_dir, CollectionUtils.newArrayList(dirent)); + } else if (itemId == R.id.move) { + moveFiles(dirent.repo_id, dirent.repo_name, dirent.parent_dir, CollectionUtils.newArrayList(dirent)); + } else if (itemId == R.id.rename) { + rename(dirent.repo_id, dirent.full_path, dirent.name, "file"); + } else if (itemId == R.id.update) { + // mActivity.addUpdateTask(repoID, repoName, dir, localPath); + //TODO: 文件上传 + ToastUtils.showLong("TODO: 文件上传"); + } else if (itemId == R.id.download) { + // mActivity.downloadFile(dir, dirent.name); + //TODO: 文件下载 + ToastUtils.showLong("TODO: 文件下载"); + } else if (itemId == R.id.export) { + chooseExportApp(dirent); + } else if (itemId == R.id.star) { + starOrNot(dirent); + } + } + }); + } + + public void showDirNewBottomSheet(DirentModel dirent) { + int rid = dirent.starred ? R.menu.bottom_sheet_op_dir_with_unstar : R.menu.bottom_sheet_op_dir; + BottomSheetMenuFragment.Builder builder1 = BottomSheetHelper.buildSheet(getActivity(), rid, new OnMenuClickListener() { + @Override + public void onMenuClick(MenuItem menuItem) { + int itemId = menuItem.getItemId(); + + if (itemId == R.id.star) { + starOrNot(dirent); + } else if (itemId == R.id.share) { + showShareDialog(dirent); + } else if (itemId == R.id.delete) { + deleteDirent(dirent); + } else if (itemId == R.id.copy) { + copyFiles(dirent.repo_id, dirent.repo_name, dirent.parent_dir, CollectionUtils.newArrayList(dirent)); + } else if (itemId == R.id.move) { + moveFiles(dirent.repo_id, dirent.repo_name, dirent.parent_dir, CollectionUtils.newArrayList(dirent)); + } else if (itemId == R.id.rename) { + rename(dirent.repo_id, dirent.full_path, dirent.name, "dir"); + } else if (itemId == R.id.download) { + // mActivity.downloadDir(dir, dirent.name, true); + //TODO: 文件夹下载 + ToastUtils.showLong("TODO: 文件夹下载"); + } + } + }); + + if (!dirent.hasWritePermission()) { + builder1.removeMenu(R.id.rename); + builder1.removeMenu(R.id.delete); + builder1.removeMenu(R.id.move); + } + +// SeafRepo repo = getDataManager().getCachedRepoByID(repoID); +// if (repo != null && repo.encrypted) { +// builder1.removeMenu(R.id.share); +// } + builder1.show(getChildFragmentManager()); + } + + + /************ Files ************/ + + /** + * Export a file. + * 1. first ask the user to choose an app + * 2. then download the latest version of the file + * 3. start the choosen app + */ + private void chooseExportApp(DirentModel direntModel) { + + final File file = SupportDataManager + .getInstance() + .getDataManager() + .getLocalRepoFile(direntModel.repo_name, direntModel.repo_id, direntModel.full_path); + Uri uri = FileProvider.getUriForFile(requireContext(), BuildConfig.FILE_PROVIDER_AUTHORITIES, file); + + final Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.setType(Utils.getFileMimeType(file)); + sendIntent.putExtra(Intent.EXTRA_STREAM, uri); + + // Get a list of apps + List infos = Utils.getAppsByIntent(sendIntent); + + if (infos.isEmpty()) { + ToastUtils.showLong(R.string.no_app_available); + return; + } + + AppChoiceDialog dialog = new AppChoiceDialog(); + dialog.init(getString(R.string.export_file), infos, new AppChoiceDialog.OnItemSelectedListener() { + @Override + public void onCustomActionSelected(AppChoiceDialog.CustomAction action) { + } + + @Override + public void onAppSelected(ResolveInfo appInfo) { + String className = appInfo.activityInfo.name; + String packageName = appInfo.activityInfo.packageName; + sendIntent.setClassName(packageName, className); + + if (!Utils.isNetworkOn() && file.exists()) { + startActivity(sendIntent); + return; + } + fetchFileAndExport(sendIntent, direntModel.repo_name, direntModel.repo_id, direntModel.full_path, direntModel.size); + } + + }); + dialog.show(getChildFragmentManager(), AppChoiceDialog.class.getSimpleName()); + } + + public void fetchFileAndExport(Intent intent, String repoName, String repoID, String path, long fileSize) { + + FetchFileDialog fetchFileDialog = new FetchFileDialog(); + fetchFileDialog.init(repoName, repoID, path, fileSize, new FetchFileDialog.FetchFileListener() { + @Override + public void onSuccess() { + startActivity(intent); + } + + @Override + public void onDismiss() { + } + + @Override + public void onFailure(SeafException err) { + } + }); + fetchFileDialog.show(getChildFragmentManager(), FetchFileDialog.class.getSimpleName()); + } + + public void openDirent(DirentModel dirent, boolean isOpenWith) { + String fileName = dirent.name; + String filePath = Utils.pathJoin(getNavContext().getNavPath(), fileName); + + RepoModel repoModel = getNavContext().getRepoModel(); + + // Encrypted repo does not support gallery, + // because pic thumbnail under encrypted repo was not supported at the server side + if (Utils.isViewableImage(fileName) && !repoModel.encrypted) { + WidgetUtils.startGalleryActivity(getActivity(), + repoModel.repo_name, + repoModel.repo_id, + getNavContext().getNavPath(), + fileName, + SupportAccountManager.getInstance().getCurrentAccount()); + } else if (fileName.endsWith(Constants.Format.DOT_SDOC)) { + SeaWebViewActivity.openSdoc(getContext(), repoModel.repo_name, repoModel.repo_id, dirent.parent_dir + dirent.name); + } else if (Utils.isVideoFile(fileName)) { + AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()); + builder.setItems(R.array.video_download_array, (dialog, which) -> { + if (which == 0) { + CustomExoVideoPlayerActivity.startThis(getContext(), fileName, repoModel.repo_id, filePath, SupportAccountManager.getInstance().getCurrentAccount()); + } else if (which == 1) { + startFileActivity(repoModel.repo_name, repoModel.repo_id, filePath, dirent.size, isOpenWith); + } + }).show(); + } else { + File localFile = SupportDataManager.getInstance().getDataManager() + .getLocalCachedFile(repoModel.repo_name, repoModel.repo_id, filePath, dirent.id); + if (localFile != null) { + WidgetUtils.showFile(getActivity(), localFile, isOpenWith); + } else { + startFileActivity(repoModel.repo_name, repoModel.repo_id, filePath, dirent.size, isOpenWith); + } + } + } + + private void startFileActivity(String repoName, String repoID, String path, long size, boolean isOpenWith) { + ToastUtils.showLong("启动文件下载页: 暂未完成"); + +// int taskID = txService.addDownloadTask(account, repoName, repoID, path, size); +// Intent intent = new Intent(this, FileActivity.class); +// intent.putExtra("repoName", repoName); +// intent.putExtra("repoID", repoID); +// intent.putExtra("filePath", path); +// intent.putExtra("account", account); +// intent.putExtra("taskID", taskID); +// intent.putExtra("is_open_with", isOpenWith); +// startActivityForResult(intent, DOWNLOAD_FILE_REQUEST); + } + + public void rename(String repoID, String curPath, String curName, String type) { + RenameDialogFragment dialogFragment = RenameDialogFragment.newInstance(); + dialogFragment.initData(curName, curPath, repoID, type); + dialogFragment.setRefreshListener(new OnRefreshDataListener() { + @Override + public void onActionStatus(boolean isDone) { + if (isDone) { + mainViewModel.getOnForceRefreshRepoListLiveData().setValue(true); + } + } + }); + dialogFragment.show(getChildFragmentManager(), RenameDialogFragment.class.getSimpleName()); + } + + public void deleteRepo(String repoID) { + DeleteRepoDialogFragment dialogFragment = DeleteRepoDialogFragment.newInstance(repoID); + dialogFragment.setRefreshListener(isDone -> { + if (isDone) { + ToastUtils.showLong(R.string.delete_successful); + + loadData(true); + } + }); + dialogFragment.show(getChildFragmentManager(), DeleteRepoDialogFragment.class.getSimpleName()); + } + + public void deleteDirent(DirentModel dirent) { + deleteDirens(CollectionUtils.newArrayList(dirent)); + } + + public void deleteDirens(List dirents) { + DeleteFileDialogFragment dialogFragment = DeleteFileDialogFragment.newInstance(); + dialogFragment.initData(dirents); + dialogFragment.setRefreshListener(new OnRefreshDataListener() { + @Override + public void onActionStatus(boolean isDone) { + if (isDone) { + ToastUtils.showLong(R.string.delete_successful); + + closeActionMode(); + + loadData(true); + } + } + }); + dialogFragment.show(getChildFragmentManager(), DeleteFileDialogFragment.class.getSimpleName()); + } + + /** + * Share a file. Generating a file share link and send the link or file to someone + * through some app. + */ + public void showShareDialog(DirentModel direntModel) { + MaterialAlertDialogBuilder mBuilder = new MaterialAlertDialogBuilder(requireContext()); + + boolean inChina = Utils.isInChina(); + String[] strings; + //if user in China ,system add WeChat share + if (inChina) { + strings = getResources().getStringArray(R.array.file_action_share_array_zh); + } else { + strings = getResources().getStringArray(R.array.file_action_share_array); + } + + mBuilder.setItems(strings, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (!inChina) { + which++; + } + switch (which) { + case 0: { + ToastUtils.showLong("TODO: 分享到微信"); + // WidgetUtils.ShareWeChat(requireContext(), account, repoID, path, fileName, fileSize, isDir); + } + break; + case 1: { + ToastUtils.showLong("TODO: 分享链接"); + // need input password +// WidgetUtils.chooseShareApp(BrowserActivity.this, repoID, path, isDir, account, null, null); + } + break; + case 2: { + ToastUtils.showLong("TODO: 分享高级链接"); +// WidgetUtils.inputSharePassword(BrowserActivity.this, repoID, path, isDir, account); + } + break; + } + } + }).show(); + } + + /** + * Copy multiple files + */ + public void copyFiles(String srcRepoId, String srcRepoName, String srcDir, List dirents) { + chooseCopyMoveDestForMultiFiles(srcRepoId, srcRepoName, srcDir, dirents, CopyMoveContext.OP.COPY); + } + + + /** + * Move multiple files + */ + public void moveFiles(String srcRepoId, String srcRepoName, String srcDir, List dirents) { + chooseCopyMoveDestForMultiFiles(srcRepoId, srcRepoName, srcDir, dirents, CopyMoveContext.OP.MOVE); + } + + + private CopyMoveContext copyMoveContext = null; + + /** + * Choose copy/move destination for multiple files + */ + private void chooseCopyMoveDestForMultiFiles(String repoID, String repoName, String dirPath, List dirents, CopyMoveContext.OP op) { + copyMoveContext = new CopyMoveContext(repoID, repoName, dirPath, dirents, op); + + Intent intent = new Intent(requireContext(), ObjSelectorActivity.class); + intent.putExtra(ObjSelectorActivity.DATA_ACCOUNT, SupportAccountManager.getInstance().getCurrentAccount()); + copyMoveLauncher.launch(intent); + } + + private final ActivityResultLauncher copyMoveLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback() { + @Override + public void onActivityResult(ActivityResult o) { + if (o.getResultCode() != Activity.RESULT_OK || o.getData() == null) { + return; + } + + String dstRepoId = o.getData().getStringExtra(ObjSelectorActivity.DATA_REPO_ID); + String dstDir = o.getData().getStringExtra(ObjSelectorActivity.DATA_DIR); + String disRepoName = o.getData().getStringExtra(ObjSelectorActivity.DATA_REPO_NAME); + + copyMoveContext.setDest(dstRepoId, dstDir, disRepoName); + + doCopyMove(); + } + }); + + private void doCopyMove() { + if (copyMoveContext == null) { + return; + } + + if (!copyMoveContext.checkCopyMoveToSubfolder()) { + ToastUtils.showLong(copyMoveContext.isCopy() + ? R.string.cannot_copy_folder_to_subfolder + : R.string.cannot_move_folder_to_subfolder); + return; + } + + CopyMoveDialogFragment dialogFragment = CopyMoveDialogFragment.newInstance(); + dialogFragment.initData(copyMoveContext); + dialogFragment.setRefreshListener(new OnRefreshDataListener() { + @Override + public void onActionStatus(boolean isDone) { + if (isDone) { + ToastUtils.showLong(copyMoveContext.isCopy() ? R.string.copied_successfully : R.string.moved_successfully); + + closeActionMode(); + + loadData(true); + } + } + }); + dialogFragment.show(getChildFragmentManager(), CopyMoveDialogFragment.class.getSimpleName()); + } + + private void starOrNot(BaseModel model) { + if (model instanceof RepoModel) { + RepoModel repoModel = (RepoModel) model; + if (repoModel.starred) { + getViewModel().unStar(repoModel.repo_id, "/"); + } else { + getViewModel().star(repoModel.repo_id, "/"); + } + } else if (model instanceof DirentModel) { + DirentModel direntModel = (DirentModel) model; + String path = Utils.pathJoin(getNavContext().getNavPath(), direntModel.name); + if (direntModel.starred) { + getViewModel().unStar(direntModel.repo_id, path); + } else { + getViewModel().star(direntModel.repo_id, path); + } + } + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewHolder.java new file mode 100644 index 000000000..a754fa84e --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewHolder.java @@ -0,0 +1,16 @@ +package com.seafile.seadroid2.ui.repo; + +import androidx.annotation.NonNull; + +import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; +import com.seafile.seadroid2.databinding.ItemRepoBinding; + +public class RepoViewHolder extends BaseViewHolder { + public ItemRepoBinding binding; + + public RepoViewHolder(@NonNull ItemRepoBinding binding) { + super(binding.getRoot()); + + this.binding = binding; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java new file mode 100644 index 000000000..d2eb79632 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java @@ -0,0 +1,521 @@ +package com.seafile.seadroid2.ui.repo; + +import android.text.TextUtils; + +import androidx.lifecycle.MutableLiveData; + +import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.EncryptUtils; +import com.blankj.utilcode.util.TimeUtils; +import com.blankj.utilcode.util.ToastUtils; +import com.google.common.collect.Lists; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.SeafException; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.config.Constants; +import com.seafile.seadroid2.data.db.AppDatabase; +import com.seafile.seadroid2.data.model.BaseModel; +import com.seafile.seadroid2.data.model.GroupItemModel; +import com.seafile.seadroid2.data.model.ResultModel; +import com.seafile.seadroid2.data.db.entities.ObjsModel; +import com.seafile.seadroid2.data.model.repo.DirentMiniModel; +import com.seafile.seadroid2.data.remote.api.RepoService; +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.context.NavContext; +import com.seafile.seadroid2.data.StorageManager; +import com.seafile.seadroid2.io.http.IO; +import com.seafile.seadroid2.data.db.entities.DirentModel; +import com.seafile.seadroid2.data.model.repo.DirentWrapperModel; +import com.seafile.seadroid2.data.db.entities.RepoModel; +import com.seafile.seadroid2.data.model.repo.RepoWrapperModel; +import com.seafile.seadroid2.util.SLogs; +import com.seafile.seadroid2.util.sp.Sorts; +import com.seafile.seadroid2.util.Times; + +import java.io.File; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import io.reactivex.Completable; +import io.reactivex.Single; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; +import kotlin.Pair; +import okhttp3.RequestBody; + +public class RepoViewModel extends BaseViewModel { + + private final MutableLiveData> ObjsListLiveData = new MutableLiveData<>(); + private final MutableLiveData StarLiveData = new MutableLiveData<>(); + + public MutableLiveData getStarLiveData() { + return StarLiveData; + } + + public MutableLiveData> getObjsListLiveData() { + return ObjsListLiveData; + } + + public void getObjFromDB(String repoId, Consumer consumer) { + Single> single = AppDatabase.getInstance().objDao().getByPath(repoId, Constants.ObjType.REPO); + addSingleDisposable(single, new Consumer>() { + @Override + public void accept(List objsModels) throws Exception { + if (!CollectionUtils.isEmpty(objsModels)) { + consumer.accept(objsModels.get(0)); + } else { + consumer.accept(null); + } + } + }); + } + + public void loadData(NavContext context, boolean forceRefresh) { + Account account = SupportAccountManager.getInstance().getCurrentAccount(); + if (account == null) { + return; + } + + if (!context.isInRepo()) { + loadReposFromDB(account, forceRefresh); + } else { + loadDirentsFromDb(account, context, forceRefresh); + } + } + + private void loadReposFromDB(Account account, boolean isForce) { + Single> singleDB = AppDatabase.getInstance().repoDao().getAllByAccount(account.email); + addSingleDisposable(singleDB, new Consumer>() { + @Override + public void accept(List repoModels) throws Exception { + if (!CollectionUtils.isEmpty(repoModels)) { + Pair, List> pair = parseRepos(repoModels, account.email); + + getObjsListLiveData().setValue(null == pair ? null : pair.getFirst()); + + if (isForce) { + loadReposFromNet(account); + } + } else { + loadReposFromNet(account); + } + } + }); + } + + private void loadReposFromNet(Account account) { + getRefreshLiveData().setValue(true); + + Single singleNet = IO.getSingleton().execute(RepoService.class).getRepos(); + addSingleDisposable(singleNet, new Consumer() { + @Override + public void accept(RepoWrapperModel repoWrapperModel) throws Exception { + getRefreshLiveData().setValue(false); + + if (repoWrapperModel == null || CollectionUtils.isEmpty(repoWrapperModel.repos)) { + getObjsListLiveData().setValue(null); + return; + } + + Pair, List> pair = parseRepos(repoWrapperModel.repos, account.email); + + if (null == pair) { + getObjsListLiveData().setValue(null); + return; + } + + Completable completableDelete = AppDatabase.getInstance().repoDao().deleteAllByAccount(account.email); + Completable completableInsert = AppDatabase.getInstance().repoDao().insertAll(pair.getSecond()); + Completable completable = Completable.mergeArray(completableDelete, completableInsert); + addCompletableDisposable(completable, new Action() { + @Override + public void run() throws Exception { + SLogs.d("Dirents本地数据库已更新"); + } + }); + + getObjsListLiveData().setValue(pair.getFirst()); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + SLogs.d(throwable.getMessage()); + } + }); + } + + private void loadDirentsFromDb(Account account, NavContext context, boolean isForce) { + String repoId = context.getRepoModel().repo_id; + String parentDir = context.getNavPath(); + + Single> singleDB = AppDatabase.getInstance().direntDao().getAllByParentPath(repoId, parentDir); + addSingleDisposable(singleDB, new Consumer>() { + @Override + public void accept(List direntModels) throws Exception { + if (!CollectionUtils.isEmpty(direntModels)) { + getObjsListLiveData().setValue(parseDirentsWithLocal(direntModels)); + + if (isForce) { + loadDirentsFromNet(account, context); + } + } else { + loadDirentsFromNet(account, context); + } + } + }); + } + + private void loadDirentsFromNet(Account account, NavContext context) { + getRefreshLiveData().setValue(true); + + String repoId = context.getRepoModel().repo_id; + String parentDir = context.getNavPath(); + + Single singleNet = IO.getSingleton().execute(RepoService.class).getDirents(repoId, parentDir); + addSingleDisposable(singleNet, new Consumer() { + @Override + public void accept(DirentWrapperModel direntWrapperModel) throws Exception { + Pair, List> pair = parseDirents( + direntWrapperModel.dirent_list, + direntWrapperModel.dir_id, + account.email, + context.getRepoModel()); + + if (null == pair) { + getObjsListLiveData().setValue(null); + getRefreshLiveData().setValue(false); + return; + } + + Completable completableDelete = AppDatabase.getInstance().direntDao().deleteAllByPath(repoId, parentDir); + Completable completableInsert = AppDatabase.getInstance().direntDao().insertAll(pair.getSecond()); + Completable completable = Completable.concat(CollectionUtils.newArrayList(completableDelete, completableInsert)); + + addCompletableDisposable(completable, new Action() { + @Override + public void run() throws Exception { + SLogs.d("Dirents本地数据库已更新"); + } + }); + + getObjsListLiveData().setValue(pair.getFirst()); + getRefreshLiveData().setValue(false); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getRefreshLiveData().setValue(false); + getExceptionLiveData().setValue(new Pair<>(400, SeafException.networkException)); + String msg = getErrorMsgByThrowable(throwable); + ToastUtils.showLong(msg); + } + }); + } + + private Pair, List> parseRepos(List list, String email) { + if (CollectionUtils.isEmpty(list)) { + return null; + } + for (int i = 0; i < list.size(); i++) { + list.get(i).related_account_email = email; + } + + List newRvList = CollectionUtils.newArrayList(); + List newDbList = CollectionUtils.newArrayList(); + + TreeMap> treeMap = groupRepos(list); + + //mine + List mineList = treeMap.get("mine"); + if (!CollectionUtils.isEmpty(mineList)) { + newRvList.add(new GroupItemModel(R.string.personal)); + for (RepoModel repoModel : mineList) { + repoModel.last_modified_long = Times.convertMtime2Long(repoModel.last_modified); + } + + List sortedList = sortRepos(mineList); + newRvList.addAll(sortedList); + newDbList.addAll(mineList); + } + + //shared + List sharedList = treeMap.get("shared"); + if (!CollectionUtils.isEmpty(sharedList)) { + newRvList.add(new GroupItemModel(R.string.shared)); + for (RepoModel repoModel : sharedList) { + repoModel.last_modified_long = Times.convertMtime2Long(repoModel.last_modified); + } + + List sortedList = sortRepos(sharedList); + newRvList.addAll(sortedList); + newDbList.addAll(sharedList); + } + + for (String key : treeMap.keySet()) { + if (TextUtils.equals(key, "mine")) { + } else if (TextUtils.equals(key, "shared")) { + } else { + List groupList = treeMap.get(key); + if (!CollectionUtils.isEmpty(groupList)) { + newRvList.add(new GroupItemModel(key)); + for (RepoModel repoModel : groupList) { + repoModel.last_modified_long = Times.convertMtime2Long(repoModel.last_modified); + } + + List sortedList = sortRepos(groupList); + newRvList.addAll(sortedList); + newDbList.addAll(groupList); + } + } + } + return new Pair<>(newRvList, newDbList); + } + + private Pair, List> parseDirents(List list, String dir_id, String email, RepoModel repoModel) { + if (CollectionUtils.isEmpty(list)) { + return null; + } + + TreeMap> treeMap = groupDirents(list); + List dirModels = treeMap.get("dir"); + List fileModels = treeMap.get("file"); + + List newDbList = new ArrayList<>(); + long now = TimeUtils.getNowMills(); + if (!CollectionUtils.isEmpty(dirModels)) { + for (int i = 0; i < dirModels.size(); i++) { + // + dirModels.get(i).last_sync_time = now; + dirModels.get(i).dir_id = dir_id; + dirModels.get(i).related_account_email = email; + dirModels.get(i).repo_id = repoModel.repo_id; + dirModels.get(i).repo_name = repoModel.repo_name; + dirModels.get(i).full_path = dirModels.get(i).parent_dir + dirModels.get(i).name; + dirModels.get(i).hash_path = EncryptUtils.encryptMD5ToString(dirModels.get(i).repo_id + list.get(i).full_path); + } + newDbList.addAll(sortDirents(dirModels)); + } + + if (!CollectionUtils.isEmpty(fileModels)) { + for (int i = 0; i < fileModels.size(); i++) { + // + fileModels.get(i).repo_id = repoModel.repo_id; + fileModels.get(i).repo_name = repoModel.repo_name; + fileModels.get(i).last_sync_time = now; + fileModels.get(i).dir_id = dir_id; + fileModels.get(i).related_account_email = email; + fileModels.get(i).full_path = fileModels.get(i).parent_dir + fileModels.get(i).name; + fileModels.get(i).hash_path = EncryptUtils.encryptMD5ToString(repoModel.repo_id + fileModels.get(i).full_path); + } + newDbList.addAll(sortDirents(fileModels)); + } + + List newRvList = new ArrayList<>(newDbList); + + return new Pair<>(newRvList, newDbList); + } + + private List parseDirentsWithLocal(List list) { + if (CollectionUtils.isEmpty(list)) { + return null; + } + + TreeMap> treeMap = groupDirents(list); + List dirModels = treeMap.get("dir"); + List fileModels = treeMap.get("file"); + + List newList = new ArrayList<>(); + if (!CollectionUtils.isEmpty(dirModels)) { + newList.addAll(sortDirents(dirModels)); + } + + if (!CollectionUtils.isEmpty(fileModels)) { + newList.addAll(sortDirents(fileModels)); + } + + return new ArrayList<>(newList); + } + + + private List sortRepos(List repos) { + List newRepos = new ArrayList<>(); + + int sortType = Sorts.getSortType(); + switch (sortType) { + case 0: // sort by name, ascending + newRepos = repos.stream().sorted(new Comparator() { + @Override + public int compare(RepoModel o1, RepoModel o2) { + return o1.repo_name.compareTo(o2.repo_name); + } + }).collect(Collectors.toList()); + + break; + case 1: // sort by name, descending + newRepos = repos.stream().sorted(new Comparator() { + @Override + public int compare(RepoModel o1, RepoModel o2) { + return -o1.repo_name.compareTo(o2.repo_name); + } + }).collect(Collectors.toList()); + break; + case 2: // sort by last modified time, ascending + newRepos = repos.stream().sorted(new Comparator() { + @Override + public int compare(RepoModel o1, RepoModel o2) { + return o1.last_modified_long < o2.last_modified_long ? -1 : 1; + } + }).collect(Collectors.toList()); + break; + case 3: // sort by last modified time, descending + newRepos = repos.stream().sorted(new Comparator() { + @Override + public int compare(RepoModel o1, RepoModel o2) { + return o1.last_modified_long > o2.last_modified_long ? -1 : 1; + } + }).collect(Collectors.toList()); + break; + } + return newRepos; + } + + private List sortDirents(List list) { + List newList = new ArrayList<>(); + + int sortType = Sorts.getSortType(); + switch (sortType) { + case 0: // sort by name, ascending + newList = list.stream().sorted(new Comparator() { + @Override + public int compare(DirentModel o1, DirentModel o2) { + return o1.name.compareTo(o2.name); + } + }).collect(Collectors.toList()); + + break; + case 1: // sort by name, descending + newList = list.stream().sorted(new Comparator() { + @Override + public int compare(DirentModel o1, DirentModel o2) { + return -o1.name.compareTo(o2.name); + } + }).collect(Collectors.toList()); + break; + case 2: // sort by last modified time, ascending + newList = list.stream().sorted(new Comparator() { + @Override + public int compare(DirentModel o1, DirentModel o2) { + return o1.mtime < o2.mtime ? -1 : 1; + } + }).collect(Collectors.toList()); + break; + case 3: // sort by last modified time, descending + newList = list.stream().sorted(new Comparator() { + @Override + public int compare(DirentModel o1, DirentModel o2) { + return o1.mtime > o2.mtime ? -1 : 1; + } + }).collect(Collectors.toList()); + break; + } + return newList; + } + + public TreeMap> groupRepos(List repos) { + TreeMap> map = new TreeMap>(); + for (RepoModel repo : repos) { + if (TextUtils.equals(repo.type, "group")) { + List l = map.computeIfAbsent(repo.group_name, k -> Lists.newArrayList()); + l.add(repo); + } else { + List l = map.computeIfAbsent(repo.type, k -> Lists.newArrayList()); + l.add(repo); + } + } + return map; + } + + public TreeMap> groupDirents(List list) { + TreeMap> map = new TreeMap>(); + for (DirentModel repo : list) { + List l = map.computeIfAbsent(repo.type, k -> Lists.newArrayList()); + l.add(repo); + } + return map; + } + + private final StorageManager storageManager = StorageManager.getInstance(); + + private File getFileForReposCache() { + Account account = SupportAccountManager.getInstance().getCurrentAccount(); + String filename = "repos-" + (account.server + account.email).hashCode() + ".dat"; + return new File(storageManager.getJsonCacheDir(), filename); + } + + private File getFileForDirentCache(String dirID) { + String filename = "dirent-" + dirID + ".dat"; + return new File(storageManager.getJsonCacheDir() + "/" + filename); + } + + private File getFileForBlockCache(String blockId) { + String filename = "block-" + blockId + ".dat"; + return new File(storageManager.getTempDir() + "/" + filename); + } + + + //star + public void star(String repoId, String path) { + getRefreshLiveData().setValue(true); + + Map requestDataMap = new HashMap<>(); + requestDataMap.put("repo_id", repoId); + requestDataMap.put("path", path); + Map bodyMap = generateRequestBody(requestDataMap); + + Single single = IO.getSingleton().execute(RepoService.class).star(bodyMap); + addSingleDisposable(single, new Consumer() { + @Override + public void accept(DirentMiniModel resultModel) throws Exception { + getRefreshLiveData().setValue(false); + + getStarLiveData().setValue(true); + ToastUtils.showLong(R.string.success); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getRefreshLiveData().setValue(false); + String errMsg = getErrorMsgByThrowable(throwable); + ToastUtils.showLong(errMsg); + } + }); + } + + public void unStar(String repoId, String path) { + getRefreshLiveData().setValue(true); + + Single single = IO.getSingleton().execute(RepoService.class).unStar(repoId, path); + addSingleDisposable(single, new Consumer() { + @Override + public void accept(ResultModel resultModel) throws Exception { + getRefreshLiveData().setValue(false); + + getStarLiveData().setValue(true); + ToastUtils.showLong(R.string.success); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getRefreshLiveData().setValue(false); + String errMsg = getErrorMsgByThrowable(throwable); + ToastUtils.showLong(errMsg); + } + }); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/ReposAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/ReposAdapter.java index aa14864ea..25cd1ca19 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/ReposAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/ReposAdapter.java @@ -6,6 +6,7 @@ import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; + import com.google.common.collect.Lists; import com.seafile.seadroid2.SeadroidApplication; import com.seafile.seadroid2.data.SeafRepo; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/ReposFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/ReposFragment.java deleted file mode 100644 index 259c309ad..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/ReposFragment.java +++ /dev/null @@ -1,1243 +0,0 @@ -package com.seafile.seadroid2.ui.repo; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.DialogInterface; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Handler; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; -import android.widget.AdapterView; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.TextView; - -import androidx.appcompat.view.ActionMode; -import androidx.fragment.app.ListFragment; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; - -import com.cocosw.bottomsheet.BottomSheet; -import com.google.common.collect.Maps; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeafConnection; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.SettingsManager; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.data.DataManager; -import com.seafile.seadroid2.data.SeafCachedFile; -import com.seafile.seadroid2.data.SeafDirent; -import com.seafile.seadroid2.data.SeafGroup; -import com.seafile.seadroid2.data.SeafItem; -import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.listener.OnCallback; -import com.seafile.seadroid2.ssl.CertsManager; -import com.seafile.seadroid2.task.StarItemsTask; -import com.seafile.seadroid2.transfer.TransferService; -import com.seafile.seadroid2.context.CopyMoveContext; -import com.seafile.seadroid2.context.NavContext; -import com.seafile.seadroid2.ui.BrowserActivity; -import com.seafile.seadroid2.ui.adapter.SeafItemAdapter; -import com.seafile.seadroid2.ui.dialog.SslConfirmDialog; -import com.seafile.seadroid2.ui.dialog.TaskDialog; -import com.seafile.seadroid2.util.ConcurrentAsyncTask; -import com.seafile.seadroid2.util.Utils; - -import java.net.HttpURLConnection; -import java.util.List; -import java.util.Map; - - -public class ReposFragment extends ListFragment { - - private static final String DEBUG_TAG = "ReposFragment"; - private static final String KEY_REPO_SCROLL_POSITION = "repo_scroll_position"; - - private static final int REFRESH_ON_RESUME = 0; - private static final int REFRESH_ON_PULL = 1; - private static final int REFRESH_ON_CLICK = 2; - private static final int REFRESH_ON_OVERFLOW_MENU = 3; - private static int mRefreshType = -1; - /** - * flag to stop refreshing when nav to other directory - */ - private static int mPullToRefreshStopRefreshing = 0; - - private SeafItemAdapter adapter; - private BrowserActivity mActivity = null; - private ActionMode mActionMode; - private CopyMoveContext copyMoveContext; - private Map scrollPositions; - - public static final int FILE_ACTION_EXPORT = 0; - public static final int FILE_ACTION_COPY = 1; - public static final int FILE_ACTION_MOVE = 2; - public static final int FILE_ACTION_STAR = 3; - - private SwipeRefreshLayout refreshLayout; - private ListView mListView; - private ImageView mEmptyView; - private View mProgressContainer; - private View mListContainer; - private TextView mErrorText; - - private boolean isTimerStarted; - private final Handler mTimer = new Handler(); - - private DataManager getDataManager() { - return mActivity.getDataManager(); - } - - private NavContext getNavContext() { - return mActivity.getNavContext(); - } - - public SeafItemAdapter getAdapter() { - return adapter; - } - - public ImageView getEmptyView() { - return mEmptyView; - } - - public interface OnFileSelectedListener { - void onFileSelected(SeafDirent fileName); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - Log.d(DEBUG_TAG, "ReposFragment Attached"); - mActivity = (BrowserActivity) activity; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View root = inflater.inflate(R.layout.repos_fragment, container, false); - refreshLayout = (SwipeRefreshLayout) root.findViewById(R.id.swiperefresh); - mListView = (ListView) root.findViewById(android.R.id.list); - mEmptyView = (ImageView) root.findViewById(R.id.empty); - mListContainer = root.findViewById(R.id.listContainer); - mErrorText = (TextView) root.findViewById(R.id.error_message); - mProgressContainer = root.findViewById(R.id.progressContainer); - - mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { - @Override - public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { - startContextualActionMode(position); - return true; - } - }); - - refreshLayout.setColorSchemeResources(R.color.fancy_orange); - refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - mRefreshType = REFRESH_ON_PULL; - refreshView(true, true); - } - }); - - return root; - } - - /** - * Start action mode for selecting and process multiple files/folders. - * The contextual action mode is a system implementation of ActionMode - * that focuses user interaction toward performing contextual actions. - * When a user enables this mode by selecting an item, - * a contextual action bar appears at the top of the screen - * to present actions the user can perform on the currently selected item(s). - *

- * While this mode is enabled, - * the user can select multiple items (if you allow it), deselect items, - * and continue to navigate within the activity (as much as you're willing to allow). - *

- * The action mode is disabled and the contextual action bar disappears - * when the user deselects all items, presses the BACK button, or selects the Done action on the left side of the bar. - *

- * see http://developer.android.com/guide/topics/ui/menus.html#CAB - */ - public void startContextualActionMode(int position) { - startContextualActionMode(); - - NavContext nav = getNavContext(); - if (adapter == null || !nav.inRepo()) return; - - adapter.toggleSelection(position); - updateContextualActionBar(); - - } - - public void startContextualActionMode() { - NavContext nav = getNavContext(); - if (!nav.inRepo()) return; - - if (mActionMode == null) { - // start the actionMode - mActionMode = mActivity.startSupportActionMode(new ActionModeCallback()); - } - - } - - public void showRepoBottomSheet(final SeafRepo repo) { - final BottomSheet.Builder builder = new BottomSheet.Builder(mActivity); - int rid = !repo.isStarred() ? R.menu.bottom_sheet_op_repo : R.menu.bottom_sheet_op_repo_with_unstar; - builder.title(repo.getRepoName()) - .sheet(rid) - .listener(new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case R.id.star_repo: - // "/":root path - starRepoOrUnstar(repo); - break; - case R.id.rename_repo: - mActivity.renameRepo(repo.getRepoId(), repo.getRepoName()); - break; - case R.id.delete_repo: - mActivity.deleteRepo(repo.getRepoId()); - break; - } - } - }).show(); - } - - public void showFileBottomSheet(String title, final SeafDirent dirent) { - final String repoName = getNavContext().getRepoName(); - final String repoID = getNavContext().getRepoID(); - final String dir = getNavContext().getDirPath(); - final String path = Utils.pathJoin(dir, dirent.name); - final String filename = dirent.name; - final String localPath = getDataManager().getLocalRepoFile(repoName, repoID, path).getPath(); - final BottomSheet.Builder builder = new BottomSheet.Builder(mActivity); - builder.title(title).sheet(R.menu.bottom_sheet_op_file).listener(new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case R.id.share: - mActivity.showShareDialog(repoID, path, false, dirent.size, dirent.name); - break; - case R.id.open: - mActivity.onFileSelected(dirent, true); - break; - case R.id.delete: - mActivity.deleteFile(repoID, repoName, path); - break; - case R.id.copy: - mActivity.copyFile(repoID, repoName, dir, filename, false); - break; - case R.id.move: - mActivity.moveFile(repoID, repoName, dir, filename, false); - break; - case R.id.rename: - mActivity.renameFile(repoID, repoName, path); - break; - case R.id.update: - mActivity.addUpdateTask(repoID, repoName, dir, localPath); - break; - case R.id.download: - mActivity.downloadFile(dir, dirent.name); - break; - case R.id.export: - mActivity.exportFile(dirent.name, dirent.size); - break; - case R.id.star: - mActivity.starFile(repoID, dir, filename); - break; - } - } - }); - if (!dirent.hasWritePermission()) { - Menu menu = builder.build().getMenu(); - menu.findItem(R.id.rename).setVisible(false); - menu.findItem(R.id.delete).setVisible(false); - menu.findItem(R.id.move).setVisible(false); - } - if (!Utils.isTextMimeType(filename)) { - Menu menu = builder.build().getMenu(); - menu.findItem(R.id.open).setVisible(false); - } - builder.show(); - SeafRepo repo = getDataManager().getCachedRepoByID(repoID); - if (repo != null && repo.encrypted) { - builder.remove(R.id.share); - } - - SeafCachedFile cf = getDataManager().getCachedFile(repoName, repoID, path); - if (cf != null) { - builder.remove(R.id.download); - } else { - builder.remove(R.id.update); - } - - } - - public void showDirBottomSheet(String title, final SeafDirent dirent) { - final String repoName = getNavContext().getRepoName(); - final String repoID = getNavContext().getRepoID(); - final String dir = getNavContext().getDirPath(); - final String path = Utils.pathJoin(dir, dirent.name); - final String filename = dirent.name; - final BottomSheet.Builder builder = new BottomSheet.Builder(mActivity); - - int rid = !dirent.isStarred() ? R.menu.bottom_sheet_op_dir : R.menu.bottom_sheet_op_dir_with_unstar; - builder.title(title).sheet(rid).listener(new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case R.id.star: - starDirOrUnstar(repoID, dirent); - break; - case R.id.share: - mActivity.showShareDialog(repoID, path, true, dirent.size, dirent.name); - break; - case R.id.delete: - mActivity.deleteDir(repoID, repoName, path); - break; - case R.id.copy: - mActivity.copyFile(repoID, repoName, dir, filename, false); - break; - case R.id.move: - mActivity.moveFile(repoID, repoName, dir, filename, false); - break; - case R.id.rename: - mActivity.renameDir(repoID, repoName, path); - break; - case R.id.download: - mActivity.downloadDir(dir, dirent.name, true); - break; - } - } - }); - - Menu menu = builder.build().getMenu(); - if (!dirent.hasWritePermission()) { - menu.findItem(R.id.rename).setVisible(false); - menu.findItem(R.id.delete).setVisible(false); - menu.findItem(R.id.move).setVisible(false); - } - builder.show(); - SeafRepo repo = getDataManager().getCachedRepoByID(repoID); - if (repo != null && repo.encrypted) { - builder.remove(R.id.share); - } - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - Log.d(DEBUG_TAG, "ReposFragment onActivityCreated"); - scrollPositions = Maps.newHashMap(); - adapter = new SeafItemAdapter(mActivity); - - mListView.setAdapter(adapter); - } - - @Override - public void onStart() { - // Log.d(DEBUG_TAG, "ReposFragment onStart"); - super.onStart(); - } - - @Override - public void onStop() { - // Log.d(DEBUG_TAG, "ReposFragment onStop"); - super.onStop(); - stopTimer(); - } - - @Override - public void onResume() { - super.onResume(); - // Log.d(DEBUG_TAG, "ReposFragment onResume"); - // refresh the view (loading data) - refreshView(true); - mRefreshType = REFRESH_ON_RESUME; - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - } - - @Override - public void onDetach() { - mActivity = null; - // Log.d(DEBUG_TAG, "ReposFragment detached"); - super.onDetach(); - } - - public void refresh() { - mRefreshType = REFRESH_ON_OVERFLOW_MENU; - refreshView(true, false); - } - - public void refreshView() { - refreshView(false, false); - } - - public void refreshView(boolean restorePosition) { - refreshView(false, restorePosition); - } - - public void refreshView(boolean forceRefresh, boolean restorePosition) { - if (mActivity == null) - return; - - mErrorText.setVisibility(View.GONE); - mListContainer.setVisibility(View.VISIBLE); - - NavContext navContext = getNavContext(); - if (navContext.inRepo()) { - if (mActivity.getCurrentPosition() == BrowserActivity.INDEX_LIBRARY_TAB) { - mActivity.enableUpButton(); - } - navToDirectory(forceRefresh, restorePosition); - } else { - mActivity.disableUpButton(); - navToReposView(forceRefresh, restorePosition); - } - mActivity.supportInvalidateOptionsMenu(); - } - - public void navToReposView(boolean forceRefresh, boolean restorePosition) { - //stopTimer(); - - mPullToRefreshStopRefreshing++; - - if (mPullToRefreshStopRefreshing > 1) { - refreshLayout.setRefreshing(false); - mPullToRefreshStopRefreshing = 0; - } - - forceRefresh = forceRefresh || isReposRefreshTimeOut(); - if (!Utils.isNetworkOn() || !forceRefresh) { - List repos = getDataManager().getReposFromCache(); - if (repos != null) { - if (mRefreshType == REFRESH_ON_PULL) { - refreshLayout.setRefreshing(false); - mPullToRefreshStopRefreshing = 0; - } - - updateAdapterWithRepos(repos, restorePosition); - return; - } - } - - ConcurrentAsyncTask.execute(new LoadRepoTask(getDataManager())); - } - - public void navToDirectory(boolean forceRefresh, boolean restorePosition) { - startTimer(); - - mPullToRefreshStopRefreshing++; - - if (mPullToRefreshStopRefreshing > 1) { - refreshLayout.setRefreshing(false); - mPullToRefreshStopRefreshing = 0; - } - - NavContext nav = getNavContext(); - DataManager dataManager = getDataManager(); - - SeafRepo repo = getDataManager().getCachedRepoByID(nav.getRepoID()); - if (repo != null) { - adapter.setEncryptedRepo(repo.encrypted); - if (nav.getDirPath().equals(BrowserActivity.ACTIONBAR_PARENT_PATH)) { - mActivity.setUpButtonTitle(nav.getRepoName()); - } else - mActivity.setUpButtonTitle(nav.getDirPath().substring( - nav.getDirPath().lastIndexOf(BrowserActivity.ACTIONBAR_PARENT_PATH) + 1)); - } - - forceRefresh = forceRefresh || isDirentsRefreshTimeOut(nav.getRepoID(), nav.getDirPath()); - if (!Utils.isNetworkOn() || !forceRefresh) { - List dirents = dataManager.getCachedDirents(nav.getRepoID(), nav.getDirPath()); - if (dirents != null) { - if (mRefreshType == REFRESH_ON_PULL) { - refreshLayout.setRefreshing(false); - mPullToRefreshStopRefreshing = 0; - } - - updateAdapterWithDirents(dirents, restorePosition); - return; - } - } - - ConcurrentAsyncTask.execute(new LoadDirTask(getDataManager()), - nav.getRepoName(), - nav.getRepoID(), - nav.getDirPath()); - } - - // refresh list by mTimer - public void startTimer() { - if (isTimerStarted) - return; - - isTimerStarted = true; - Log.d(DEBUG_TAG, "timer started"); - mTimer.postDelayed(new Runnable() { - @Override - public void run() { - if (mActivity == null) return; - - TransferService ts = mActivity.getTransferService(); - String repoID = getNavContext().getRepoID(); - String repoName = getNavContext().getRepoName(); - String currentDir = getNavContext().getDirPath(); - if (ts != null) { - adapter.setDownloadTaskList(ts.getDownloadTaskInfosByPath(repoID, currentDir)); - } - // Log.d(DEBUG_TAG, "timer post refresh signal " + System.currentTimeMillis()); - mTimer.postDelayed(this, 1 * 3500); - } - }, 1 * 3500); - } - - public void stopTimer() { - Log.d(DEBUG_TAG, "timer stopped"); - mTimer.removeCallbacksAndMessages(null); - isTimerStarted = false; - } - - /** - * calculate if repo refresh time is expired, the expiration is 10 mins - */ - private boolean isReposRefreshTimeOut() { - if (getDataManager().isReposRefreshTimeout()) { - return true; - } - - return false; - - } - - /** - * calculate if dirent refresh time is expired, the expiration is 10 mins - * - * @param repoID - * @param path - * @return true if refresh time expired, false otherwise - */ - private boolean isDirentsRefreshTimeOut(String repoID, String path) { - if (getDataManager().isDirentsRefreshTimeout(repoID, path)) { - return true; - } - - return false; - } - - public void sortFiles(int type, int order) { - adapter.sortFiles(type, order); - adapter.notifyDataSetChanged(); - // persist sort settings - SettingsManager.instance().saveSortFilesPref(type, order); - } - - private void updateAdapterWithRepos(List repos, boolean restoreScrollPosition) { - adapter.clear(); - if (repos.size() > 0) { - addReposToAdapter(repos); - adapter.sortFiles(SettingsManager.instance().getSortFilesTypePref(), - SettingsManager.instance().getSortFilesOrderPref()); - adapter.notifyChanged(); - mListView.setVisibility(View.VISIBLE); - restoreRepoScrollPosition(restoreScrollPosition); - mEmptyView.setVisibility(View.GONE); - } else { - mListView.setVisibility(View.GONE); - mEmptyView.setVisibility(View.VISIBLE); - } - // Collapses the currently open view - //mListView.collapse(); - } - - private void updateAdapterWithDirents(final List dirents, boolean restoreScrollPosition) { - adapter.clear(); - if (dirents.size() > 0) { - for (SeafDirent dirent : dirents) { - adapter.add(dirent); - } - NavContext nav = getNavContext(); - final String repoName = nav.getRepoName(); - final String repoID = nav.getRepoID(); - final String dirPath = nav.getDirPath(); - - adapter.sortFiles(SettingsManager.instance().getSortFilesTypePref(), - SettingsManager.instance().getSortFilesOrderPref()); - adapter.notifyChanged(); - mListView.setVisibility(View.VISIBLE); - restoreDirentScrollPosition(restoreScrollPosition, repoID, dirPath); - mEmptyView.setVisibility(View.GONE); - } else { - // Directory is empty - mListView.setVisibility(View.GONE); - mEmptyView.setVisibility(View.VISIBLE); - } - // Collapses the currently open view - //mListView.collapse(); - } - - /** - * update state of contextual action bar (CAB) - */ - public void updateContextualActionBar() { - - if (mActionMode == null) { - // there are some selected items, start the actionMode - mActionMode = mActivity.startSupportActionMode(new ActionModeCallback()); - } else { - // Log.d(DEBUG_TAG, "mActionMode.setTitle " + adapter.getCheckedItemCount()); - mActionMode.setTitle(getResources().getQuantityString( - R.plurals.transfer_list_items_selected, - adapter.getCheckedItemCount(), - adapter.getCheckedItemCount())); - } - - } - - @Override - public void onListItemClick(final ListView l, final View v, final int position, final long id) { - if (Utils.isFastTapping()) return; - - // handle action mode selections - if (mActionMode != null) { - // add or remove selection for current list item - if (adapter == null) return; - - adapter.toggleSelection(position); - updateContextualActionBar(); - return; - } - - SeafRepo repo = null; - final NavContext nav = getNavContext(); - if (nav.inRepo()) { - repo = getDataManager().getCachedRepoByID(nav.getRepoID()); - mActivity.setUpButtonTitle(repo.getRepoName()); - } else { - SeafItem item = adapter.getItem(position); - if (item instanceof SeafRepo) { - repo = (SeafRepo) item; - } - } - - if (repo == null) { - return; - } - - if (repo.encrypted && !getDataManager().getRepoPasswordSet(repo.repo_id)) { - String password = getDataManager().getRepoPassword(repo.repo_id); - mActivity.showPasswordDialog(repo.repo_name, repo.repo_id, - new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - onListItemClick(l, v, position, id); - } - }, password); - - return; - } - - mRefreshType = REFRESH_ON_CLICK; - if (nav.inRepo()) { - if (adapter.getItem(position) instanceof SeafDirent) { - final SeafDirent dirent = (SeafDirent) adapter.getItem(position); - if (dirent.isDir()) { - String currentPath = nav.getDirPath(); - String newPath = currentPath.endsWith("/") - ? currentPath + dirent.name - : currentPath + "/" + dirent.name; - nav.setDirPath(newPath); - nav.setDirPermission(dirent.permission); - saveDirentScrollPosition(repo.getRepoId(), currentPath); - refreshView(); - mActivity.setUpButtonTitle(dirent.name); - } else { - String currentPath = nav.getDirPath(); - saveDirentScrollPosition(repo.getRepoId(), currentPath); - mActivity.onFileSelected(dirent); - } - } - } else { - nav.setDirPermission(repo.permission); - nav.setRepoID(repo.repo_id); - nav.setRepoName(repo.getRepoName()); - nav.setDirPath("/"); - saveRepoScrollPosition(); - refreshView(); - } - } - - private class ScrollState { - public int index; - public int top; - - public ScrollState(int index, int top) { - this.index = index; - this.top = top; - } - } - - private void saveDirentScrollPosition(String repoId, String currentPath) { - final String pathJoin = Utils.pathJoin(repoId, currentPath); - final int index = mListView.getFirstVisiblePosition(); - final View v = mListView.getChildAt(0); - final int top = (v == null) ? 0 : (v.getTop() - mListView.getPaddingTop()); - final ScrollState state = new ScrollState(index, top); - scrollPositions.put(pathJoin, state); - } - - private void saveRepoScrollPosition() { - final int index = mListView.getFirstVisiblePosition(); - final View v = mListView.getChildAt(0); - final int top = (v == null) ? 0 : (v.getTop() - mListView.getPaddingTop()); - final ScrollState state = new ScrollState(index, top); - scrollPositions.put(KEY_REPO_SCROLL_POSITION, state); - } - - private void restoreDirentScrollPosition(boolean restore, String repoId, String dirPath) { - final String pathJoin = Utils.pathJoin(repoId, dirPath); - if (restore) { - ScrollState state = scrollPositions.get(pathJoin); - if (state != null) { - mListView.setSelectionFromTop(state.index, state.top); - } else { - mListView.setSelectionAfterHeaderView(); - } - } else { - mListView.setSelectionAfterHeaderView(); - } - } - - private void restoreRepoScrollPosition(boolean restore) { - if (restore) { - ScrollState state = scrollPositions.get(KEY_REPO_SCROLL_POSITION); - if (state != null) { - mListView.setSelectionFromTop(state.index, state.top); - } else { - mListView.setSelectionAfterHeaderView(); - } - } else { - mListView.setSelectionAfterHeaderView(); - } - } - - private void addReposToAdapter(List repos) { - if (repos == null) - return; - Map> map = Utils.groupRepos(repos); - List personalRepos = map.get(Utils.PERSONAL_REPO); - if (personalRepos != null) { - SeafGroup personalGroup = new SeafGroup(mActivity.getResources().getString(R.string.personal)); - adapter.add(personalGroup); - for (SeafRepo repo : personalRepos) - adapter.add(repo); - } - - List sharedRepos = map.get(Utils.SHARED_REPO); - if (sharedRepos != null) { - SeafGroup sharedGroup = new SeafGroup(mActivity.getResources().getString(R.string.shared)); - adapter.add(sharedGroup); - for (SeafRepo repo : sharedRepos) - adapter.add(repo); - } - - for (Map.Entry> entry : map.entrySet()) { - String key = entry.getKey(); - if (!key.equals(Utils.PERSONAL_REPO) - && !key.endsWith(Utils.SHARED_REPO)) { - SeafGroup group = new SeafGroup(key); - adapter.add(group); - for (SeafRepo repo : entry.getValue()) { - adapter.add(repo); - } - } - } - } - - private class LoadRepoTask extends AsyncTask> { - SeafException err = null; - DataManager dataManager; - - public LoadRepoTask(DataManager dataManager) { - this.dataManager = dataManager; - } - - @Override - protected void onPreExecute() { - if (mRefreshType == REFRESH_ON_CLICK - || mRefreshType == REFRESH_ON_OVERFLOW_MENU - || mRefreshType == REFRESH_ON_RESUME) { - showLoading(true); - } else if (mRefreshType == REFRESH_ON_PULL) { - - } - } - - @Override - protected List doInBackground(Void... params) { - try { - return dataManager.getReposFromServer(); - } catch (SeafException e) { - err = e; - return null; - } - } - - private void displaySSLError() { - if (mActivity == null) - return; - - if (getNavContext().inRepo()) { - return; - } - - showError(R.string.ssl_error); - } - - private void resend() { - if (mActivity == null) - return; - - if (getNavContext().inRepo()) { - return; - } - ConcurrentAsyncTask.execute(new LoadRepoTask(dataManager)); - } - - // onPostExecute displays the results of the AsyncTask. - @Override - protected void onPostExecute(List rs) { - if (mActivity == null) - // this occurs if user navigation to another activity - return; - - if (mRefreshType == REFRESH_ON_CLICK - || mRefreshType == REFRESH_ON_OVERFLOW_MENU - || mRefreshType == REFRESH_ON_RESUME) { - showLoading(false); - } else if (mRefreshType == REFRESH_ON_PULL) { - String lastUpdate = getDataManager().getLastPullToRefreshTime(DataManager.PULL_TO_REFRESH_LAST_TIME_FOR_REPOS_FRAGMENT); - //mListView.onRefreshComplete(lastUpdate); - refreshLayout.setRefreshing(false); - getDataManager().saveLastPullToRefreshTime(System.currentTimeMillis(), DataManager.PULL_TO_REFRESH_LAST_TIME_FOR_REPOS_FRAGMENT); - mPullToRefreshStopRefreshing = 0; - } - - if (getNavContext().inRepo()) { - // this occurs if user already navigate into a repo - return; - } - - // Prompt the user to accept the ssl certificate - if (err == SeafException.sslException) { - SslConfirmDialog dialog = new SslConfirmDialog(dataManager.getAccount(), - new SslConfirmDialog.Listener() { - @Override - public void onAccepted(boolean rememberChoice) { - Account account = dataManager.getAccount(); - CertsManager.instance().saveCertForAccount(account, rememberChoice); - resend(); - } - - @Override - public void onRejected() { - displaySSLError(); - } - }); - dialog.show(getFragmentManager(), SslConfirmDialog.FRAGMENT_TAG); - return; - } else if (err == SeafException.remoteWipedException) { - mActivity.completeRemoteWipe(); - } - - if (err != null) { - if (err.getCode() == HttpURLConnection.HTTP_UNAUTHORIZED) { - // Token expired, should login again - mActivity.showShortToast(mActivity, R.string.err_token_expired); - mActivity.logoutWhenTokenExpired(); - } else { - Log.e(DEBUG_TAG, "failed to load repos: " + err.getMessage()); - showError(R.string.error_when_load_repos); - return; - } - } - - if (rs != null) { - getDataManager().setReposRefreshTimeStamp(); - updateAdapterWithRepos(rs, false); - } else { - Log.i(DEBUG_TAG, "failed to load repos"); - showError(R.string.error_when_load_repos); - } - } - } - - - private class LoadDirTask extends AsyncTask> { - - SeafException err = null; - String myRepoName; - String myRepoID; - String myPath; - - DataManager dataManager; - - public LoadDirTask(DataManager dataManager) { - this.dataManager = dataManager; - } - - @Override - protected void onPreExecute() { - if (mRefreshType == REFRESH_ON_CLICK - || mRefreshType == REFRESH_ON_OVERFLOW_MENU - || mRefreshType == REFRESH_ON_RESUME) { - showLoading(true); - } else if (mRefreshType == REFRESH_ON_PULL) { - // mHeadProgress.setVisibility(ProgressBar.VISIBLE); - } - } - - @Override - protected List doInBackground(String... params) { - if (params.length != 3) { - Log.d(DEBUG_TAG, "Wrong params to LoadDirTask"); - return null; - } - - myRepoName = params[0]; - myRepoID = params[1]; - myPath = params[2]; - try { - List dirents = dataManager.getDirentsFromServer(myRepoID, myPath); - String repoName = getNavContext().getRepoName(); - String repoID = getNavContext().getRepoID(); - for (SeafDirent sd : dirents) { - if (!sd.isDir()) { - String path = Utils.pathJoin(getNavContext().getDirPath(), sd.name); - SeafCachedFile scf = dataManager.getCachedFile(repoName, repoID, path); - if (scf != null && scf.getSize() != sd.getFileSize()) { - dataManager.removeCachedFile(scf); - } - } - } - return dirents; - } catch (SeafException e) { - err = e; - return null; - } - - } - - private void resend() { - if (mActivity == null) - return; - NavContext nav = mActivity.getNavContext(); - if (!myRepoID.equals(nav.getRepoID()) || !myPath.equals(nav.getDirPath())) { - return; - } - - ConcurrentAsyncTask.execute(new LoadDirTask(dataManager), myRepoName, myRepoID, myPath); - } - - private void displaySSLError() { - if (mActivity == null) - return; - - NavContext nav = mActivity.getNavContext(); - if (!myRepoID.equals(nav.getRepoID()) || !myPath.equals(nav.getDirPath())) { - return; - } - showError(R.string.ssl_error); - } - - // onPostExecute displays the results of the AsyncTask. - @Override - protected void onPostExecute(List dirents) { - if (mActivity == null) - // this occurs if user navigation to another activity - return; - - if (mRefreshType == REFRESH_ON_CLICK - || mRefreshType == REFRESH_ON_OVERFLOW_MENU - || mRefreshType == REFRESH_ON_RESUME) { - showLoading(false); - } else if (mRefreshType == REFRESH_ON_PULL) { - String lastUpdate = getDataManager().getLastPullToRefreshTime(DataManager.PULL_TO_REFRESH_LAST_TIME_FOR_REPOS_FRAGMENT); - //mListView.onRefreshComplete(lastUpdate); - refreshLayout.setRefreshing(false); - getDataManager().saveLastPullToRefreshTime(System.currentTimeMillis(), DataManager.PULL_TO_REFRESH_LAST_TIME_FOR_REPOS_FRAGMENT); - mPullToRefreshStopRefreshing = 0; - } - - NavContext nav = mActivity.getNavContext(); - if (!myRepoID.equals(nav.getRepoID()) || !myPath.equals(nav.getDirPath())) { - return; - } - - if (err == SeafException.sslException) { - SslConfirmDialog dialog = new SslConfirmDialog(dataManager.getAccount(), - new SslConfirmDialog.Listener() { - @Override - public void onAccepted(boolean rememberChoice) { - Account account = dataManager.getAccount(); - CertsManager.instance().saveCertForAccount(account, rememberChoice); - resend(); - } - - @Override - public void onRejected() { - displaySSLError(); - } - }); - dialog.show(getFragmentManager(), SslConfirmDialog.FRAGMENT_TAG); - return; - } else if (err == SeafException.remoteWipedException) { - mActivity.completeRemoteWipe(); - } - - if (err != null) { - if (err.getCode() == SeafConnection.HTTP_STATUS_REPO_PASSWORD_REQUIRED) { - showPasswordDialog(); - } else if (err.getCode() == HttpURLConnection.HTTP_UNAUTHORIZED) { - // Token expired, should login again - mActivity.showShortToast(mActivity, R.string.err_token_expired); - mActivity.logoutWhenTokenExpired(); - } else if (err.getCode() == HttpURLConnection.HTTP_NOT_FOUND) { - final String message = String.format(getString(R.string.op_exception_folder_deleted), myPath); - mActivity.showShortToast(mActivity, message); - } else { - Log.d(DEBUG_TAG, "failed to load dirents: " + err.getMessage()); - err.printStackTrace(); - showError(R.string.error_when_load_dirents); - } - return; - } - - if (dirents == null) { - showError(R.string.error_when_load_dirents); - Log.i(DEBUG_TAG, "failed to load dir"); - return; - } - getDataManager().setDirsRefreshTimeStamp(myRepoID, myPath); - updateAdapterWithDirents(dirents, false); - } - } - - private void showError(int strID) { - showError(mActivity.getResources().getString(strID)); - } - - private void showError(String msg) { - mProgressContainer.setVisibility(View.GONE); - mListContainer.setVisibility(View.GONE); - - adapter.clear(); - adapter.notifyChanged(); - - mErrorText.setText(msg); - mErrorText.setVisibility(View.VISIBLE); - mErrorText.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - refresh(); - } - }); - } - - public void showLoading(boolean show) { - mErrorText.setVisibility(View.GONE); - if (show) { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation( - mActivity, android.R.anim.fade_in)); - mListContainer.startAnimation(AnimationUtils.loadAnimation( - mActivity, android.R.anim.fade_out)); - - mProgressContainer.setVisibility(View.VISIBLE); - mListContainer.setVisibility(View.INVISIBLE); - } else { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation( - mActivity, android.R.anim.fade_out)); - mListContainer.startAnimation(AnimationUtils.loadAnimation( - mActivity, android.R.anim.fade_in)); - - mProgressContainer.setVisibility(View.GONE); - mListContainer.setVisibility(View.VISIBLE); - } - } - - private void showPasswordDialog() { - NavContext nav = mActivity.getNavContext(); - String repoName = nav.getRepoName(); - String repoID = nav.getRepoID(); - - mActivity.showPasswordDialog(repoName, repoID, new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - refreshView(); - } - }); - } - - /** - * Represents a contextual mode of the user interface. - * Action modes can be used to provide alternative interaction modes and replace parts of the normal UI until finished. - * A Callback configures and handles events raised by a user's interaction with an action mode. - */ - class ActionModeCallback implements ActionMode.Callback { - private boolean allItemsSelected; - - public ActionModeCallback() { - } - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - // Inflate the menu for the contextual action bar (CAB) - MenuInflater inflater = mode.getMenuInflater(); - inflater.inflate(R.menu.repos_fragment_menu, menu); - if (adapter == null) return true; - adapter.setActionModeOn(true); - // to hidden "r" permissions files or folder - String permission = getNavContext().getDirPermission(); - if (permission != null && permission.indexOf("w") == -1) { - menu.findItem(R.id.action_mode_delete).setVisible(false); - menu.findItem(R.id.action_mode_move).setVisible(false); - } - adapter.notifyDataSetChanged(); - return true; - } - - @SuppressLint("NewApi") - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - /* - * The ActionBarPolicy determines how many action button to place in the ActionBar - * and the default amount is 2. - */ - menu.findItem(R.id.action_mode_delete).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - menu.findItem(R.id.action_mode_copy).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - menu.findItem(R.id.action_mode_select_all).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - - // Here you can perform updates to the contextual action bar (CAB) due to - // an invalidate() request - return true; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - // Respond to clicks on the actions in the contextual action bar (CAB) - NavContext nav = mActivity.getNavContext(); - String repoID = nav.getRepoID(); - String repoName = nav.getRepoName(); - String dirPath = nav.getDirPath(); - final List selectedDirents = adapter.getSelectedItemsValues(); - if (selectedDirents.size() == 0 - || repoID == null - || dirPath == null) { - if (item.getItemId() != R.id.action_mode_select_all) { - mActivity.showShortToast(mActivity, R.string.action_mode_no_items_selected); - return true; - } - } - - switch (item.getItemId()) { - case R.id.action_mode_select_all: - if (!allItemsSelected) { - if (adapter == null) return true; - - adapter.selectAllItems(); - updateContextualActionBar(); - } else { - if (adapter == null) return true; - - adapter.deselectAllItems(); - updateContextualActionBar(); - } - - allItemsSelected = !allItemsSelected; - break; - case R.id.action_mode_delete: - mActivity.deleteFiles(repoID, dirPath, selectedDirents); - break; - case R.id.action_mode_copy: - mActivity.copyFiles(repoID, repoName, dirPath, selectedDirents); - break; - case R.id.action_mode_move: - mActivity.moveFiles(repoID, repoName, dirPath, selectedDirents); - break; - case R.id.action_mode_download: - mActivity.downloadFiles(repoID, repoName, dirPath, selectedDirents); - break; - - default: - return false; - } - - return true; - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - if (adapter == null) return; - - adapter.setActionModeOn(false); - adapter.deselectAllItems(); - - // Here you can make any necessary updates to the activity when - // the contextual action bar (CAB) is removed. By default, selected items are deselected/unchecked. - mActionMode = null; - } - - } - - public void clearAdapterData() { - if (adapter != null && mListView != null) { - adapter.clear(); - mListView.setAdapter(adapter); - } - } - - private void starRepoOrUnstar(SeafRepo repo) { - // "/":root path - StarItemsTask task = new StarItemsTask(mActivity, repo.repo_id, "/", repo.isStarred()); - task.setOnCallback(new OnCallback() { - @Override - public void onFailed() { - - } - - @Override - public void onSuccess() { - repo.setStarred(!repo.isStarred()); - } - }); - ConcurrentAsyncTask.execute(task); - } - - private void starDirOrUnstar(String repo_id, SeafDirent dirent) { - String path = Utils.pathJoin(getNavContext().getDirPath(), dirent.name); - StarItemsTask task = new StarItemsTask(mActivity, repo_id, path, dirent.isStarred()); - task.setOnCallback(new OnCallback() { - @Override - public void onFailed() { - - } - - @Override - public void onSuccess() { - dirent.setStarred(!dirent.isStarred()); - } - }); - ConcurrentAsyncTask.execute(task); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/ScrollState.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/ScrollState.java new file mode 100644 index 000000000..9951c5e6e --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/ScrollState.java @@ -0,0 +1,11 @@ +package com.seafile.seadroid2.ui.repo; + +public class ScrollState { + public int index; + public int top; + + public ScrollState(int index, int top) { + this.index = index; + this.top = top; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/SeafReposAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/SeafReposAdapter.java index a43a4a5a2..b57f4e6d3 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/SeafReposAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/SeafReposAdapter.java @@ -5,6 +5,7 @@ import com.google.common.collect.Lists; import com.seafile.seadroid2.R; import com.seafile.seadroid2.data.SeafRepo; +import com.seafile.seadroid2.util.sp.Sorts; import java.util.Collections; import java.util.List; @@ -18,13 +19,21 @@ public SeafReposAdapter(boolean onlyShowWritableRepos, String encryptedRepoId) { super(onlyShowWritableRepos, encryptedRepoId); } - /** sort files type */ + /** + * sort files type + */ public static final int SORT_BY_NAME = 9; - /** sort files type */ + /** + * sort files type + */ public static final int SORT_BY_LAST_MODIFIED_TIME = 10; - /** sort files order */ + /** + * sort files order + */ public static final int SORT_ORDER_ASCENDING = 11; - /** sort files order */ + /** + * sort files order + */ public static final int SORT_ORDER_DESCENDING = 12; @@ -56,7 +65,7 @@ public void clearRepos() { repos.clear(); } - public void sortFiles(int type, int order) { + public void sortFiles() { List folders = Lists.newArrayList(); for (SeafRepo item : repos) { @@ -64,19 +73,19 @@ public void sortFiles(int type, int order) { } repos.clear(); - if (type == SORT_BY_NAME) { - // sort by name, in ascending order + int sortType = Sorts.getSortType(); + if (sortType <= Sorts.SORT_BY_NAME_DESC) { Collections.sort(folders, new SeafRepo.RepoNameComparator()); - if (order == SORT_ORDER_DESCENDING) { + if (sortType == Sorts.SORT_BY_NAME_DESC) { Collections.reverse(folders); } - } else if (type == SORT_BY_LAST_MODIFIED_TIME) { - // sort by last modified time, in ascending order + } else { Collections.sort(folders, new SeafRepo.RepoLastMTimeComparator()); - if (order == SORT_ORDER_DESCENDING) { + if (sortType == Sorts.SORT_BY_MODIFIED_TIME_DESC) { Collections.reverse(folders); } } + // Adds the objects in the specified collection to this ArrayList repos.addAll(folders); } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/UnsupportedViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/UnsupportedViewHolder.java new file mode 100644 index 000000000..0481652b6 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/UnsupportedViewHolder.java @@ -0,0 +1,16 @@ +package com.seafile.seadroid2.ui.repo; + +import androidx.annotation.NonNull; + +import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; +import com.seafile.seadroid2.databinding.ItemUnsupportedBinding; + +public class UnsupportedViewHolder extends BaseViewHolder { + public ItemUnsupportedBinding binding; + + public UnsupportedViewHolder(@NonNull ItemUnsupportedBinding binding) { + super(binding.getRoot()); + + this.binding = binding; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/search/Search2Activity.java b/app/src/main/java/com/seafile/seadroid2/ui/search/Search2Activity.java index 166b2169f..8a882ae5a 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/search/Search2Activity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/search/Search2Activity.java @@ -27,23 +27,24 @@ import androidx.recyclerview.widget.RecyclerView; import com.blankj.utilcode.util.CollectionUtils; -import com.chad.library.adapter.base.BaseQuickAdapter; -import com.chad.library.adapter.base.QuickAdapterHelper; -import com.chad.library.adapter.base.loadState.LoadState; -import com.chad.library.adapter.base.loadState.trailing.TrailingLoadStateAdapter; +import com.blankj.utilcode.util.ToastUtils; +import com.chad.library.adapter4.BaseQuickAdapter; +import com.chad.library.adapter4.QuickAdapterHelper; +import com.chad.library.adapter4.loadState.LoadState; +import com.chad.library.adapter4.loadState.trailing.TrailingLoadStateAdapter; import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeafException; import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.AccountManager; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.ui.base.adapter.CustomLoadMoreAdapter; import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.data.SeafRepo; import com.seafile.seadroid2.data.SearchedFile; import com.seafile.seadroid2.play.exoplayer.CustomExoVideoPlayerActivity; import com.seafile.seadroid2.transfer.TransferService; -import com.seafile.seadroid2.ui.WidgetUtils; import com.seafile.seadroid2.ui.BaseActivity; +import com.seafile.seadroid2.ui.WidgetUtils; import com.seafile.seadroid2.ui.activity.FileActivity; -import com.seafile.seadroid2.ui.base.adapter.CustomLoadMoreAdapter; import com.seafile.seadroid2.util.ConcurrentAsyncTask; import com.seafile.seadroid2.util.Utils; @@ -106,8 +107,8 @@ protected void onCreate(Bundle savedInstanceState) { private void initAdapter() { mAdapter = new SearchRecyclerViewAdapter(this); View t = findViewById(R.id.ll_message_content); - mAdapter.setEmptyView(t); - mAdapter.setEmptyViewEnable(true); + mAdapter.setStateView(t); + mAdapter.setStateViewEnable(true); mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() { @Override public void onClick(@NotNull BaseQuickAdapter baseQuickAdapter, @NotNull View view, int i) { @@ -178,8 +179,7 @@ protected void onDestroy() { } private void initData() { - AccountManager accountManager = new AccountManager(this); - account = accountManager.getCurrentAccount(); + account = SupportAccountManager.getInstance().getCurrentAccount(); dataManager = new DataManager(account); // bind transfer service @@ -240,7 +240,7 @@ public void increasePage() { private void loadNext(boolean isRefresh) { if (!Utils.isNetworkOn()) { - showShortToast(this, R.string.network_down); + ToastUtils.showLong(R.string.network_down); return; } @@ -255,7 +255,7 @@ private void loadNext(boolean isRefresh) { Utils.hideSoftKeyboard(mTextField); } else { - showShortToast(this, R.string.search_txt_empty); + ToastUtils.showLong(R.string.search_txt_empty); } } @@ -305,7 +305,7 @@ protected void onPostExecute(ArrayList result) { if (result == null) { if (seafException != null) { if (seafException.getCode() == 404) - showShortToast(Search2Activity.this, R.string.search_server_not_support); + ToastUtils.showLong(R.string.search_server_not_support); Log.d(DEBUG_TAG, seafException.getMessage() + " code " + seafException.getCode()); } @@ -314,7 +314,7 @@ protected void onPostExecute(ArrayList result) { } if (result.size() == 0) { - showShortToast(Search2Activity.this, R.string.search_content_empty); + ToastUtils.showLong(R.string.search_content_empty); } if (page == 1) { @@ -338,8 +338,7 @@ protected void onPostExecute(ArrayList result) { public DataManager getDataManager() { if (dataManager == null) { - AccountManager accountManager = new AccountManager(this); - account = accountManager.getCurrentAccount(); + account = SupportAccountManager.getInstance().getCurrentAccount(); dataManager = new DataManager(account); } return dataManager; @@ -389,7 +388,7 @@ public void onSearchedFileSelected(SearchedFile searchedFile) { if (searchedFile.isDir()) { if (repo == null) { - showShortToast(this, R.string.search_library_not_found); + ToastUtils.showLong(R.string.search_library_not_found); return; } WidgetUtils.showRepo(this, repoID, repoName, filePath, null); @@ -398,8 +397,7 @@ public void onSearchedFileSelected(SearchedFile searchedFile) { // Encrypted repo doesn\`t support gallery, // because pic thumbnail under encrypted repo was not supported at the server side - if (Utils.isViewableImage(searchedFile.getTitle()) - && repo != null && !repo.encrypted) { + if (Utils.isViewableImage(searchedFile.getTitle()) && !repo.encrypted) { WidgetUtils.startGalleryActivity(this, repoName, repoID, Utils.getParentPath(filePath), searchedFile.getTitle(), account); return; } @@ -409,6 +407,7 @@ public void onSearchedFileSelected(SearchedFile searchedFile) { WidgetUtils.showFile(this, localFile); return; } + boolean videoFile = Utils.isVideoFile(fileName); if (videoFile) { // is video file final AlertDialog.Builder builder = new AlertDialog.Builder(this); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/search/SearchRecyclerViewAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/search/SearchRecyclerViewAdapter.java index 20cad7f72..9d321ef8e 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/search/SearchRecyclerViewAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/search/SearchRecyclerViewAdapter.java @@ -10,15 +10,15 @@ import androidx.annotation.Nullable; import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.adapter.BaseAdapter; +import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; import com.seafile.seadroid2.data.SeafRepo; import com.seafile.seadroid2.data.SearchedFile; -import com.seafile.seadroid2.ui.base.adapter.BaseViewHolder; -import com.seafile.seadroid2.ui.base.adapter.ParentAdapter; import com.seafile.seadroid2.util.Utils; import org.jetbrains.annotations.NotNull; -public class SearchRecyclerViewAdapter extends ParentAdapter { +public class SearchRecyclerViewAdapter extends BaseAdapter { private Context context; public SearchRecyclerViewAdapter(Context context) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorActivity.java new file mode 100644 index 000000000..0aee21608 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorActivity.java @@ -0,0 +1,291 @@ +package com.seafile.seadroid2.ui.selector; + +import android.content.Intent; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; + +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; + +import com.blankj.utilcode.util.CollectionUtils; +import com.chad.library.adapter4.QuickAdapterHelper; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.context.NavContext; +import com.seafile.seadroid2.data.db.entities.DirentModel; +import com.seafile.seadroid2.data.db.entities.RepoModel; +import com.seafile.seadroid2.data.model.BaseModel; +import com.seafile.seadroid2.databinding.ActivitySelectorObjBinding; +import com.seafile.seadroid2.ui.BaseActivity; +import com.seafile.seadroid2.ui.repo.RepoQuickAdapter; +import com.seafile.seadroid2.view.TipsViews; + +import java.util.List; + +/** + * can select repo、dir、account + */ +public class ObjSelectorActivity extends BaseActivity { + private static final int STEP_CHOOSE_ACCOUNT = 1; + private static final int STEP_CHOOSE_REPO = 2; + private static final int STEP_CHOOSE_DIR = 3; + private int mStep = 1; + + public static final String DATA_ACCOUNT = "account"; + public static final String DATA_REPO_ID = "repoID"; + public static final String DATA_REPO_NAME = "repoNAME"; + public static final String DATA_REPO_PERMISSION = "permission"; + public static final String DATA_DIRECTORY_PATH = "dirPath"; + public static final String DATA_DIR = "dir"; + + + private boolean canChooseAccount; + private boolean isOnlyChooseRepo; + + private ActivitySelectorObjBinding binding; + private NavContext mNavContext = new NavContext(); + + private RepoQuickAdapter adapter; + private ObjSelectorViewModel viewModel; + private Account mAccount; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivitySelectorObjBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + //view model + viewModel = new ViewModelProvider(this).get(ObjSelectorViewModel.class); + + Intent intent = getIntent(); + if (intent != null) { + Account account = intent.getParcelableExtra(DATA_ACCOUNT); + if (account == null) { + canChooseAccount = true; + } else { + mAccount = account; + } + } + + + Toolbar toolbar = getActionBarToolbar(); + setSupportActionBar(toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setTitle(R.string.app_name); + } + + initView(); + initViewModel(); + initAdapter(); + + if (canChooseAccount) { + mStep = STEP_CHOOSE_ACCOUNT; + } else { + mStep = STEP_CHOOSE_REPO; + } + + loadData(); + } + + private void initView() { + binding.swipeRefreshLayout.setOnRefreshListener(this::loadData); + + if (isOnlyChooseRepo) { + binding.ok.setVisibility(View.GONE); + binding.newFolder.setVisibility(View.GONE); + } + + binding.ok.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String repoName = mNavContext.getRepoModel().repo_name; + String repoID = mNavContext.getRepoModel().repo_id; + String dir = mNavContext.getNavPath(); + + Intent intent = new Intent(); + intent.putExtra(DATA_REPO_NAME, repoName); + intent.putExtra(DATA_REPO_ID, repoID); + intent.putExtra(DATA_DIR, dir); + intent.putExtra(DATA_ACCOUNT, mAccount); + setResult(RESULT_OK, intent); + finish(); + } + }); + + binding.newFolder.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { +// createNewFolder(); + } + }); + + binding.cancel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + setResult(RESULT_CANCELED); + finish(); + } + }); + } + + + private void initViewModel() { + viewModel.getRefreshLiveData().observe(this, new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + binding.swipeRefreshLayout.setRefreshing(aBoolean); + } + }); + + viewModel.getObjsListLiveData().observe(this, new Observer>() { + @Override + public void onChanged(List baseModels) { + notifyDataChanged(baseModels); + } + }); + } + + private void initAdapter() { + adapter = new RepoQuickAdapter(); + adapter.setSelectorMode(2); + + adapter.setOnItemClickListener((baseQuickAdapter, view, i) -> { + BaseModel baseModel = adapter.getItems().get(i); + onItemClick(baseModel); + }); + + QuickAdapterHelper helper = new QuickAdapterHelper.Builder(adapter).build(); + binding.rv.setAdapter(helper.getAdapter()); + } + + private void notifyDataChanged(List models) { + if (CollectionUtils.isEmpty(models)) { + showEmptyTip(); + } else { + adapter.notifyDataChanged(models); + } + } + + private void onItemClick(BaseModel baseModel) { + if (baseModel instanceof Account) { + mAccount = (Account) baseModel; + mStep = STEP_CHOOSE_REPO; + } else if (baseModel instanceof RepoModel) { + RepoModel model = (RepoModel) baseModel; + mStep = STEP_CHOOSE_DIR; + mNavContext.push(model); + + } else if (baseModel instanceof DirentModel) { + DirentModel model = (DirentModel) baseModel; + if (!model.isDir()) { + return; + } + + mNavContext.push(model); + + } + + loadData(); + } + + private void loadData() { + // update action bar + ActionBar bar = getSupportActionBar(); + if (bar == null) { + return; + } + + if (mStep == STEP_CHOOSE_ACCOUNT) { + + bar.setDisplayHomeAsUpEnabled(false); + bar.setTitle(R.string.choose_an_account); + + viewModel.loadAccount(); + } else if (mStep == STEP_CHOOSE_REPO) { + + //TODO 校验密码 + + bar.setDisplayHomeAsUpEnabled(true); + bar.setTitle(R.string.choose_a_library); + + viewModel.loadReposFromNet(mAccount); + } else if (mStep == STEP_CHOOSE_DIR) { + + bar.setDisplayHomeAsUpEnabled(true); + bar.setTitle(R.string.choose_a_folder); + + viewModel.loadDirentsFromNet(mAccount, mNavContext); + } + } + + private void showEmptyTip() { + if (mStep == STEP_CHOOSE_ACCOUNT) { + showAdapterTip(R.string.no_account); + } else if (mStep == STEP_CHOOSE_REPO) { + showAdapterTip(R.string.no_repo); + } else if (mStep == STEP_CHOOSE_DIR) { + showAdapterTip(R.string.dir_empty); + } + } + + private void showAdapterTip(int textRes) { + adapter.submitList(null); + TextView tipView = TipsViews.getTipTextView(this); + tipView.setText(textRes); + adapter.setStateView(tipView); + adapter.setStateViewEnable(true); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + stepBack(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onBackPressed() { + stepBack(); + } + + private void stepBack() { + switch (mStep) { + case STEP_CHOOSE_ACCOUNT: { + setResult(RESULT_CANCELED); + finish(); + } + break; + case STEP_CHOOSE_REPO: { + if (canChooseAccount) { + mStep = STEP_CHOOSE_ACCOUNT; + loadData(); + } else { + setResult(RESULT_CANCELED); + finish(); + } + } + break; + case STEP_CHOOSE_DIR: { + if (mNavContext.isInRepoRoot()) { + mStep = STEP_CHOOSE_REPO; + } else { + mNavContext.pop(); + } + loadData(); + } + break; + } + } + + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorFragment.java new file mode 100644 index 000000000..d5311d000 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorFragment.java @@ -0,0 +1,211 @@ +package com.seafile.seadroid2.ui.selector; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; + +import com.blankj.utilcode.util.CollectionUtils; +import com.chad.library.adapter4.QuickAdapterHelper; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.context.NavContext; +import com.seafile.seadroid2.data.db.entities.RepoModel; +import com.seafile.seadroid2.data.model.BaseModel; +import com.seafile.seadroid2.databinding.FragmentRemoteLibraryFragmentBinding; +import com.seafile.seadroid2.ui.base.fragment.BaseFragment; +import com.seafile.seadroid2.ui.repo.RepoQuickAdapter; +import com.seafile.seadroid2.view.TipsViews; + +import java.util.List; + +/** + * Choose account and library for camera upload + */ +public class ObjSelectorFragment extends BaseFragment { + private static final int STEP_CHOOSE_ACCOUNT = 1; + private static final int STEP_CHOOSE_REPO = 2; + private static final int STEP_CHOOSE_DIR = 3; + private int mStep = 1; + + private FragmentRemoteLibraryFragmentBinding binding; + private RepoQuickAdapter adapter; + private NavContext mNavContext = new NavContext(); + private ObjSelectorViewModel viewModel; + private Account mAccount; + private boolean canChooseAccount; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + viewModel = new ViewModelProvider(this).get(ObjSelectorViewModel.class); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + AppCompatActivity mActivity = (AppCompatActivity) getActivity(); + + Intent intent = mActivity.getIntent(); + Account account = intent.getParcelableExtra(ObjSelectorActivity.DATA_ACCOUNT); + if (account == null) { + canChooseAccount = true; + } else { + mAccount = account; + } + + binding = FragmentRemoteLibraryFragmentBinding.inflate(getLayoutInflater(), container, false); + + initView(); + initViewModel(); + initAdapter(); + + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + if (canChooseAccount) { + mStep = STEP_CHOOSE_ACCOUNT; + } else { + mStep = STEP_CHOOSE_REPO; + } + + loadData(); + } + + private void initView() { + binding.swipeRefreshLayout.setOnRefreshListener(this::loadData); + + binding.cucMultiSelectionUpLayout.setOnClickListener(v -> { + try { + stepBack(); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + private void initViewModel() { + viewModel.getRefreshLiveData().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + binding.swipeRefreshLayout.setRefreshing(aBoolean); + } + }); + + viewModel.getObjsListLiveData().observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(List baseModels) { + notifyDataChanged(baseModels); + } + }); + } + + private void initAdapter() { + adapter = new RepoQuickAdapter(); + adapter.setSelectorMode(1); + + adapter.setOnItemClickListener((baseQuickAdapter, view, i) -> { + BaseModel baseModel = adapter.getItems().get(i); + onItemClick(baseModel, i); + }); + + QuickAdapterHelper helper = new QuickAdapterHelper.Builder(adapter).build(); + binding.rv.setAdapter(helper.getAdapter()); + } + + private void notifyDataChanged(List models) { + if (CollectionUtils.isEmpty(models)) { + showEmptyTip(); + } else { + adapter.notifyDataChanged(models); + } + } + + private void onItemClick(BaseModel baseModel, int position) { + if (baseModel instanceof Account) { + + mAccount = (Account) baseModel; + mStep = STEP_CHOOSE_REPO; + + binding.title.setText(mAccount.getDisplayName()); + + loadData(); + + } else if (baseModel instanceof RepoModel) { + + //TODO 校验密码 + boolean status = adapter.selectItemByMode(position); + if (status) { + RepoModel model = (RepoModel) baseModel; + mNavContext.push(model); + } else { + mNavContext.pop(); + } + } + } + + private void loadData() { + if (mStep == STEP_CHOOSE_ACCOUNT) { + viewModel.loadAccount(); + } else if (mStep == STEP_CHOOSE_REPO) { + viewModel.loadReposFromNet(mAccount); + } else if (mStep == STEP_CHOOSE_DIR) { + viewModel.loadDirentsFromNet(mAccount, mNavContext); + } + } + + private void showEmptyTip() { + if (mStep == STEP_CHOOSE_ACCOUNT) { + showAdapterTip(R.string.no_account); + } else if (mStep == STEP_CHOOSE_REPO) { + showAdapterTip(R.string.no_repo); + } else if (mStep == STEP_CHOOSE_DIR) { + showAdapterTip(R.string.dir_empty); + } + } + + private void showAdapterTip(int textRes) { + adapter.submitList(null); + TextView tipView = TipsViews.getTipTextView(requireContext()); + tipView.setText(textRes); + adapter.setStateView(tipView); + adapter.setStateViewEnable(true); + } + + private void stepBack() { + switch (mStep) { + case STEP_CHOOSE_ACCOUNT: { + } + break; + case STEP_CHOOSE_REPO: { + if (canChooseAccount) { + mStep = STEP_CHOOSE_ACCOUNT; + + binding.title.setText(R.string.choose_a_library); + + loadData(); + } + } + break; + } + } + + + public Pair getCameraUploadInfo() { + return new Pair<>(mAccount, mNavContext.getRepoModel()); + } +} + diff --git a/app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorViewModel.java new file mode 100644 index 000000000..3e072ba0f --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorViewModel.java @@ -0,0 +1,403 @@ +package com.seafile.seadroid2.ui.selector; + +import android.text.TextUtils; + +import androidx.lifecycle.MutableLiveData; + +import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.EncryptUtils; +import com.blankj.utilcode.util.TimeUtils; +import com.blankj.utilcode.util.ToastUtils; +import com.google.common.collect.Lists; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.SeafException; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.context.NavContext; +import com.seafile.seadroid2.data.db.AppDatabase; +import com.seafile.seadroid2.data.db.entities.DirentModel; +import com.seafile.seadroid2.data.db.entities.RepoModel; +import com.seafile.seadroid2.data.model.BaseModel; +import com.seafile.seadroid2.data.model.GroupItemModel; +import com.seafile.seadroid2.data.model.repo.DirentWrapperModel; +import com.seafile.seadroid2.data.model.repo.RepoWrapperModel; +import com.seafile.seadroid2.data.remote.api.RepoService; +import com.seafile.seadroid2.io.http.IO; +import com.seafile.seadroid2.util.SLogs; +import com.seafile.seadroid2.util.Times; +import com.seafile.seadroid2.util.sp.Sorts; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import io.reactivex.Single; +import io.reactivex.functions.Consumer; +import kotlin.Pair; + +public class ObjSelectorViewModel extends BaseViewModel { + private final MutableLiveData> ObjsListLiveData = new MutableLiveData<>(); + + public MutableLiveData> getObjsListLiveData() { + return ObjsListLiveData; + } + + public void loadAccount() { + List list = SupportAccountManager.getInstance().getSignedInAccountList(); + getObjsListLiveData().setValue(new ArrayList<>(list)); + getRefreshLiveData().setValue(false); + } + + public void loadReposFromNet(Account account) { + getRefreshLiveData().setValue(true); + + Single singleNet = IO.getNewInstance(account.server,account.token).execute(RepoService.class).getRepos(); + addSingleDisposable(singleNet, new Consumer() { + @Override + public void accept(RepoWrapperModel repoWrapperModel) throws Exception { + if (repoWrapperModel == null || CollectionUtils.isEmpty(repoWrapperModel.repos)) { + getObjsListLiveData().setValue(null); + getRefreshLiveData().setValue(false); + return; + } + + List list = parseRepos(repoWrapperModel.repos, account.email); + getObjsListLiveData().setValue(list); + getRefreshLiveData().setValue(false); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + SLogs.d(throwable.getMessage()); + } + }); + } + + public void loadDirentsFromNet(Account account, NavContext context) { + getRefreshLiveData().setValue(true); + + String repoId = context.getRepoModel().repo_id; + String parentDir = context.getNavPath(); + + Single singleNet = IO.getNewInstance(account.server,account.token).execute(RepoService.class).getDirents(repoId, parentDir); + addSingleDisposable(singleNet, new Consumer() { + @Override + public void accept(DirentWrapperModel direntWrapperModel) throws Exception { + List list = parseDirents( + direntWrapperModel.dirent_list, + direntWrapperModel.dir_id, + account.email, + context.getRepoModel()); + + getObjsListLiveData().setValue(list); + getRefreshLiveData().setValue(false); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getRefreshLiveData().setValue(false); + getExceptionLiveData().setValue(new Pair<>(400, SeafException.networkException)); + String msg = getErrorMsgByThrowable(throwable); + ToastUtils.showLong(msg); + } + }); + } + + private List parseRepos(List list, String email) { + if (CollectionUtils.isEmpty(list)) { + return null; + } + for (int i = 0; i < list.size(); i++) { + list.get(i).related_account_email = email; + } + + List newRvList = CollectionUtils.newArrayList(); + + TreeMap> treeMap = groupRepos(list); + + //mine + List mineList = treeMap.get("mine"); + if (!CollectionUtils.isEmpty(mineList)) { + newRvList.add(new GroupItemModel(R.string.personal)); + + List l = new ArrayList<>(); + for (RepoModel repoModel : mineList) { + repoModel.last_modified_long = Times.convertMtime2Long(repoModel.last_modified); + + //check permission + if (repoModel.hasWritePermission()) { + l.add(repoModel); + } + } + + List sortedList = sortRepos(l); + newRvList.addAll(sortedList); + } + + //shared + List sharedList = treeMap.get("shared"); + if (!CollectionUtils.isEmpty(sharedList)) { + newRvList.add(new GroupItemModel(R.string.shared)); + + List l = new ArrayList<>(); + + for (RepoModel repoModel : sharedList) { + repoModel.last_modified_long = Times.convertMtime2Long(repoModel.last_modified); + + //check permission + if (repoModel.hasWritePermission()) { + l.add(repoModel); + } + } + + List sortedList = sortRepos(l); + newRvList.addAll(sortedList); + } + + for (String key : treeMap.keySet()) { + if (TextUtils.equals(key, "mine")) { + } else if (TextUtils.equals(key, "shared")) { + } else { + List groupList = treeMap.get(key); + if (!CollectionUtils.isEmpty(groupList)) { + newRvList.add(new GroupItemModel(key)); + + List l = new ArrayList<>(); + + for (RepoModel repoModel : groupList) { + repoModel.last_modified_long = Times.convertMtime2Long(repoModel.last_modified); + + //check permission + if (repoModel.hasWritePermission()) { + l.add(repoModel); + } + } + + List sortedList = sortRepos(l); + newRvList.addAll(sortedList); + } + } + } + return newRvList; + } + + private List parseDirents(List list, String dir_id, String email, RepoModel repoModel) { + if (CollectionUtils.isEmpty(list)) { + return null; + } + + TreeMap> treeMap = groupDirents(list); + List dirModels = treeMap.get("dir"); + List fileModels = treeMap.get("file"); + + List newRvList = new ArrayList<>(); + + long now = TimeUtils.getNowMills(); + if (!CollectionUtils.isEmpty(dirModels)) { + for (int i = 0; i < dirModels.size(); i++) { + // + dirModels.get(i).last_sync_time = now; + dirModels.get(i).dir_id = dir_id; + dirModels.get(i).related_account_email = email; + dirModels.get(i).repo_id = repoModel.repo_id; + dirModels.get(i).repo_name = repoModel.repo_name; + dirModels.get(i).full_path = dirModels.get(i).parent_dir + dirModels.get(i).name; + dirModels.get(i).hash_path = EncryptUtils.encryptMD5ToString(dirModels.get(i).repo_id + list.get(i).full_path); + } + newRvList.addAll(sortDirents(dirModels)); + } + + + if (!CollectionUtils.isEmpty(fileModels)) { + for (int i = 0; i < fileModels.size(); i++) { + // + fileModels.get(i).repo_id = repoModel.repo_id; + fileModels.get(i).repo_name = repoModel.repo_name; + fileModels.get(i).last_sync_time = now; + fileModels.get(i).dir_id = dir_id; + fileModels.get(i).related_account_email = email; + fileModels.get(i).full_path = fileModels.get(i).parent_dir + fileModels.get(i).name; + fileModels.get(i).hash_path = EncryptUtils.encryptMD5ToString(repoModel.repo_id + fileModels.get(i).full_path); + } + newRvList.addAll(sortDirents(fileModels)); + } + + + return newRvList; + } + + private List sortRepos(List repos) { + List newRepos = new ArrayList<>(); + + int sortType = Sorts.getSortType(); + switch (sortType) { + case 0: // sort by name, ascending + newRepos = repos.stream().sorted(new Comparator() { + @Override + public int compare(RepoModel o1, RepoModel o2) { + return o1.repo_name.compareTo(o2.repo_name); + } + }).collect(Collectors.toList()); + + break; + case 1: // sort by name, descending + newRepos = repos.stream().sorted(new Comparator() { + @Override + public int compare(RepoModel o1, RepoModel o2) { + return -o1.repo_name.compareTo(o2.repo_name); + } + }).collect(Collectors.toList()); + break; + case 2: // sort by last modified time, ascending + newRepos = repos.stream().sorted(new Comparator() { + @Override + public int compare(RepoModel o1, RepoModel o2) { + return o1.last_modified_long < o2.last_modified_long ? -1 : 1; + } + }).collect(Collectors.toList()); + break; + case 3: // sort by last modified time, descending + newRepos = repos.stream().sorted(new Comparator() { + @Override + public int compare(RepoModel o1, RepoModel o2) { + return o1.last_modified_long > o2.last_modified_long ? -1 : 1; + } + }).collect(Collectors.toList()); + break; + } + return newRepos; + } + + private List sortDirents(List list) { + List newList = new ArrayList<>(); + + int sortType = Sorts.getSortType(); + switch (sortType) { + case 0: // sort by name, ascending + newList = list.stream().sorted(new Comparator() { + @Override + public int compare(DirentModel o1, DirentModel o2) { + return o1.name.compareTo(o2.name); + } + }).collect(Collectors.toList()); + + break; + case 1: // sort by name, descending + newList = list.stream().sorted(new Comparator() { + @Override + public int compare(DirentModel o1, DirentModel o2) { + return -o1.name.compareTo(o2.name); + } + }).collect(Collectors.toList()); + break; + case 2: // sort by last modified time, ascending + newList = list.stream().sorted(new Comparator() { + @Override + public int compare(DirentModel o1, DirentModel o2) { + return o1.mtime < o2.mtime ? -1 : 1; + } + }).collect(Collectors.toList()); + break; + case 3: // sort by last modified time, descending + newList = list.stream().sorted(new Comparator() { + @Override + public int compare(DirentModel o1, DirentModel o2) { + return o1.mtime > o2.mtime ? -1 : 1; + } + }).collect(Collectors.toList()); + break; + } + return newList; + } + + public TreeMap> groupRepos(List repos) { + TreeMap> map = new TreeMap>(); + for (RepoModel repo : repos) { + if (TextUtils.equals(repo.type, "group")) { + List l = map.computeIfAbsent(repo.group_name, k -> Lists.newArrayList()); + l.add(repo); + } else { + List l = map.computeIfAbsent(repo.type, k -> Lists.newArrayList()); + l.add(repo); + } + } + return map; + } + + public TreeMap> groupDirents(List list) { + TreeMap> map = new TreeMap>(); + for (DirentModel repo : list) { + List l = map.computeIfAbsent(repo.type, k -> Lists.newArrayList()); + l.add(repo); + } + return map; + } + + public void requestRepoModel(String repoId, Consumer consumer) { + getRefreshLiveData().setValue(true); + + //from db + Single> singleDb = AppDatabase.getInstance().repoDao().getRepoById(repoId); + addSingleDisposable(singleDb, new Consumer>() { + @Override + public void accept(List repoModels) throws Exception { + if (consumer != null) { + if (CollectionUtils.isEmpty(repoModels)) { + //no data in sqlite, request RepoApi again + requestRepoModelFromNet(repoId, consumer); + } else { + consumer.accept(repoModels.get(0)); + getRefreshLiveData().setValue(false); + } + } else { + getRefreshLiveData().setValue(false); + } + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getRefreshLiveData().setValue(false); + SLogs.e(throwable); + } + }); + } + + private void requestRepoModelFromNet(String repoId, Consumer consumer) { + //from net + Single singleNet = IO.getSingleton().execute(RepoService.class).getRepos(); + addSingleDisposable(singleNet, new Consumer() { + @Override + public void accept(RepoWrapperModel repoWrapperModel) throws Exception { + getRefreshLiveData().setValue(false); + + if (repoWrapperModel == null || CollectionUtils.isEmpty(repoWrapperModel.repos)) { + ToastUtils.showLong(R.string.search_library_not_found); + return; + } + + Optional optionalRepoModel = repoWrapperModel.repos + .stream() + .filter(f -> TextUtils.equals(f.repo_id, repoId)) + .findFirst(); + if (optionalRepoModel.isPresent()) { + if (consumer != null) { + consumer.accept(optionalRepoModel.get()); + } + } else { + ToastUtils.showLong(R.string.search_library_not_found); + } + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getRefreshLiveData().setValue(false); + String msg = getErrorMsgByThrowable(throwable); + ToastUtils.showLong(msg); + } + }); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/BeanListManager.java b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/BeanListManager.java similarity index 62% rename from app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/BeanListManager.java rename to app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/BeanListManager.java index 5785eb9fd..ead560b0f 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/BeanListManager.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/BeanListManager.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.folderbackup.selectfolder; +package com.seafile.seadroid2.ui.selector.folder_selector; import android.app.Activity; import android.app.ProgressDialog; @@ -6,22 +6,21 @@ import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeadroidApplication; -import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.util.FileTools; +import com.seafile.seadroid2.util.sp.SettingsManager; import java.io.File; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; import java.util.List; -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.annotations.NonNull; -import io.reactivex.rxjava3.core.Observable; -import io.reactivex.rxjava3.core.Observer; -import io.reactivex.rxjava3.disposables.Disposable; -import io.reactivex.rxjava3.functions.Function; -import io.reactivex.rxjava3.schedulers.Schedulers; +import io.reactivex.Observable; +import io.reactivex.Observer; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Function; +import io.reactivex.schedulers.Schedulers; + public class BeanListManager { public static final int TYPE_ADD_TAB_BAR = 0; @@ -30,11 +29,11 @@ public class BeanListManager { public static void upDataFileBeanListByAsyn(Activity at, List selectFilePath, List fileBeanList, FileListAdapter fileListAdapter, - String path, List fileTypes, int sortType) { + String path) { if (fileBeanList == null) { fileBeanList = new ArrayList<>(); - } else if (fileBeanList.size() != 0) { + } else if (!fileBeanList.isEmpty()) { fileBeanList.clear(); } @@ -42,7 +41,7 @@ public static void upDataFileBeanListByAsyn(Activity at, List selectFile @Override public List apply(List fileBeanList) { - boolean isJumpHiddenFile = SettingsManager.instance().isFolderBackupJumpHiddenFiles(); + boolean isJumpHiddenFile = SettingsManager.getInstance().isFolderBackupJumpHiddenFiles(); File file = FileTools.getFileByPath(path); @@ -69,7 +68,7 @@ public List apply(List fileBeanList) { } } } - sortFileBeanList(fileBeanList, sortType); +// sortFileBeanList(fileBeanList, sortType); return fileBeanList; } }) @@ -87,7 +86,7 @@ public void onSubscribe(@NonNull Disposable d) { @Override public void onNext(@NonNull List fileBeans) { if (fileListAdapter != null) { - fileListAdapter.updateListData(fileBeans); +// fileListAdapter.updateListData(fileBeans); fileListAdapter.notifyDataSetChanged(); } } @@ -104,59 +103,6 @@ public void onComplete() { }); } - public static void sortFileBeanList(List fileBeanList, int sortType) { - Collections.sort(fileBeanList, new Comparator() { - @Override - public int compare(FileBean file1, FileBean file2) { - - if (file1.isDir() && file2.isFile()) - return -1; - if (file1.isFile() && file2.isDir()) - return 1; - - switch (sortType) { - case Constants.SORT_NAME_ASC: - return file1.getFileName().compareToIgnoreCase(file2.getFileName()); - case Constants.SORT_NAME_DESC: - return file2.getFileName().compareToIgnoreCase(file1.getFileName()); - case Constants.SORT_TIME_ASC: - long diff = file1.getModifyTime() - file2.getModifyTime(); - if (diff > 0) - return 1; - else if (diff == 0) - return 0; - else - return -1; - case Constants.SORT_TIME_DESC: - diff = file2.getModifyTime() - file1.getModifyTime(); - if (diff > 0) - return 1; - else if (diff == 0) - return 0; - else - return -1; - case Constants.SORT_SIZE_ASC: - diff = file1.getSimpleSize() - file2.getSimpleSize(); - if (diff > 0) - return 1; - else if (diff == 0) - return 0; - else - return -1; - case Constants.SORT_SIZE_DESC: - diff = file2.getSimpleSize() - file1.getSimpleSize(); - if (diff > 0) - return 1; - else if (diff == 0) - return 0; - else - return -1; - default: - return 0; - } - } - }); - } public static void getTabbarFileBeanList(List tabbarList, String path, List allPathsList) { diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/Constants.java b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/Constants.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/Constants.java rename to app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/Constants.java index 9fe4cc619..e4e0c0402 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/Constants.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/Constants.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.folderbackup.selectfolder; +package com.seafile.seadroid2.ui.selector.folder_selector; import android.os.Environment; diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileBean.java b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FileBean.java similarity index 90% rename from app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileBean.java rename to app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FileBean.java index 780bee7e5..fc0331f05 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileBean.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FileBean.java @@ -1,7 +1,8 @@ -package com.seafile.seadroid2.folderbackup.selectfolder; +package com.seafile.seadroid2.ui.selector.folder_selector; import com.seafile.seadroid2.R; +import com.seafile.seadroid2.util.FileTools; import com.seafile.seadroid2.util.Utils; import java.io.Serializable; @@ -54,7 +55,7 @@ public int setImageResourceByExtension(String extension) { switch (extension) { default: if (dir) { - resourceId = R.drawable.folder; + resourceId = R.drawable.folders; } else { resourceId = Utils.getFileIconSuffix(extension); } @@ -107,12 +108,12 @@ public String getParentName() { return parentName; } - public int getChildrenFileNumber() { - return childrenFileNumber; + public String getChildrenFileNumber() { + return String.valueOf(childrenFileNumber); } - public int getChildrenDirNumber() { - return childrenDirNumber; + public String getChildrenDirNumber() { + return String.valueOf(childrenDirNumber); } public String getSize() { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FileListAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FileListAdapter.java new file mode 100644 index 000000000..f95ff25e2 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FileListAdapter.java @@ -0,0 +1,68 @@ +package com.seafile.seadroid2.ui.selector.folder_selector; + + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CompoundButton; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.adapter.BaseAdapter; +import com.seafile.seadroid2.listener.OnFileItemChangeListener; + +public class FileListAdapter extends BaseAdapter { + + private OnFileItemChangeListener onFileItemChangeListener; + + public void setOnFileItemChangeListener(OnFileItemChangeListener onFileItemChangeListener) { + this.onFileItemChangeListener = onFileItemChangeListener; + } + + @NonNull + @Override + protected FileListViewHolder onCreateViewHolder(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) { + View view = LayoutInflater.from(context).inflate(R.layout.item_files_list, viewGroup, false); + return new FileListViewHolder(view); + } + + @Override + protected void onBindViewHolder(@NonNull FileListViewHolder holder, int i, @Nullable FileBean fileBean) { + + holder.checkBoxFile.setChecked(fileBean.isChecked()); + holder.tvFileName.setText(fileBean.getFileName()); + holder.imgvFiletype.setImageResource(fileBean.getFileImgType()); + boolean isFile = fileBean.isFile(); + + if (isFile) { + holder.tvFileDetail.setText(String.format(getContext().getString(R.string.folder_file_item_size), fileBean.getSize())); + } else { + String c = String.format(getContext().getString(R.string.folder_file_item_describe), fileBean.getChildrenFileNumber(), fileBean.getChildrenDirNumber()); + holder.tvFileDetail.setText(c); + } + + if (!isFile) { + holder.checkBoxFile.setVisibility(View.VISIBLE); + } else { + holder.checkBoxFile.setVisibility(View.GONE); + } + + final int p = i; + holder.checkBoxFile.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + onCheckBoxChanged(p, isChecked); + } + }); + } + + private void onCheckBoxChanged(int position, boolean isChecked) { + getItems().get(position).setChecked(isChecked); + if (onFileItemChangeListener != null) { + onFileItemChangeListener.onChanged(getItems().get(position), position, isChecked); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileListViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FileListViewHolder.java similarity index 68% rename from app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileListViewHolder.java rename to app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FileListViewHolder.java index 33eb6c299..e02f34c91 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileListViewHolder.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FileListViewHolder.java @@ -1,24 +1,21 @@ -package com.seafile.seadroid2.folderbackup.selectfolder; +package com.seafile.seadroid2.ui.selector.folder_selector; -import androidx.recyclerview.widget.RecyclerView; import android.view.View; import android.widget.CheckBox; import android.widget.ImageView; -import android.widget.RelativeLayout; import android.widget.TextView; import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; -public class FileListViewHolder extends RecyclerView.ViewHolder { +public class FileListViewHolder extends BaseViewHolder { protected ImageView imgvFiletype; protected TextView tvFileName, tvFileDetail; protected CheckBox checkBoxFile; - protected RelativeLayout layoutRoot; public FileListViewHolder(View itemView) { super(itemView); - layoutRoot = (RelativeLayout) itemView.findViewById(R.id.ll_root); imgvFiletype = (ImageView) itemView.findViewById(R.id.iv_file_type_fileitem); tvFileName = (TextView) itemView.findViewById(R.id.tv_file_name_fileitem); tvFileDetail = (TextView) itemView.findViewById(R.id.tv_file_detail_fileitem); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FolderSelectorFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FolderSelectorFragment.java new file mode 100644 index 000000000..fb7c4f44c --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FolderSelectorFragment.java @@ -0,0 +1,243 @@ +package com.seafile.seadroid2.ui.selector.folder_selector; + +import android.app.Activity; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.blankj.utilcode.util.CollectionUtils; +import com.chad.library.adapter4.QuickAdapterHelper; +import com.google.common.collect.Maps; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.fragment.BaseFragmentWithVM; +import com.seafile.seadroid2.databinding.FragmentFolderSelectorBinding; +import com.seafile.seadroid2.ui.folder_backup.FolderBackupConfigActivity; +import com.seafile.seadroid2.ui.repo.ScrollState; +import com.seafile.seadroid2.util.FileTools; +import com.seafile.seadroid2.util.sp.FolderBackupConfigSPs; +import com.seafile.seadroid2.view.TipsViews; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class FolderSelectorFragment extends BaseFragmentWithVM { + + private FragmentFolderSelectorBinding binding; + private LinearLayoutManager rvManager; + private LinearLayoutManager rvPathManager; + + private List allPathsList; + + private List mTabbarFileList; + private String mCurrentPath; + private FileListAdapter mFileListAdapter; + private TabBarFileListAdapter mTabBarFileListAdapter; + private FolderBackupConfigActivity mActivity; + private boolean chooseDirPage; + private String initialPath; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + mActivity = (FolderBackupConfigActivity) getActivity(); + + binding = FragmentFolderSelectorBinding.inflate(inflater, container, false); + + rvManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false); + binding.rvOfList.setLayoutManager(rvManager); + + rvPathManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false); + binding.rvOfPath.setLayoutManager(rvPathManager); + + mTabBarFileListAdapter = new TabBarFileListAdapter(getActivity(), mTabbarFileList); + binding.rvOfPath.setAdapter(mTabBarFileListAdapter); + + chooseDirPage = mActivity.isChooseDirPage(); + + return binding.getRoot(); + } + + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + initView(); + initViewModel(); + initAdapter(); + initData(); + + loadData(); + } + + private void initView() { + binding.swipeRefreshLayout.setOnRefreshListener(this::loadData); + + mTabBarFileListAdapter.setOnItemClickListener((tabBarFileBean, position) -> { + TabBarFileBean item = mTabbarFileList.get(position); + mCurrentPath = item.getFilePath(); + + if (mTabbarFileList.size() > 1) { + refreshFileAndTabBar(BeanListManager.TYPE_DEL_TAB_BAR); + } + + loadData(); + }); + } + + private void initViewModel() { + getViewModel().getRefreshLiveData().observe(getViewLifecycleOwner(), aBoolean -> { + binding.swipeRefreshLayout.setRefreshing(aBoolean); + }); + + getViewModel().getDataListLiveData().observe(getViewLifecycleOwner(), fileBeans -> { + notifyDataChanged(fileBeans); + + restoreScrollPosition(); + }); + } + + private void initAdapter() { + mFileListAdapter = new FileListAdapter(); + mFileListAdapter.setOnFileItemChangeListener((fileBean, position, isChecked) -> { + if (isChecked) { + getViewModel().removeSpecialPath(fileBean.getFilePath()); + } else { + getViewModel().addSpecialPath(fileBean.getFilePath()); + } + }); + + mFileListAdapter.setOnItemClickListener((baseQuickAdapter, view, i) -> { + + saveScrollPosition(); + + FileBean item = mFileListAdapter.getItems().get(i); + if (item.isFile()) { + Toast.makeText(getActivity(), getActivity().getString(R.string.selection_file_type), Toast.LENGTH_SHORT).show(); + } else { + mCurrentPath = item.getFilePath(); + refreshFileAndTabBar(BeanListManager.TYPE_ADD_TAB_BAR); + } + + loadData(); + }); + + QuickAdapterHelper helper = new QuickAdapterHelper.Builder(mFileListAdapter).build(); + binding.rvOfList.setAdapter(helper.getAdapter()); + } + + private void notifyDataChanged(List list) { + if (CollectionUtils.isEmpty(list)) { + showAdapterTip(); + } else { + mFileListAdapter.submitList(list); + } + } + + private void showAdapterTip() { + mFileListAdapter.submitList(null); + TextView tipView = TipsViews.getTipTextView(requireContext()); + tipView.setText(R.string.dir_empty); + mFileListAdapter.setStateView(tipView); + mFileListAdapter.setStateViewEnable(true); + } + + private void loadData() { + getViewModel().loadData(mCurrentPath); + } + + private void initData() { + String originalBackupPaths = FolderBackupConfigSPs.getBackupPaths(); + + if (!TextUtils.isEmpty(originalBackupPaths)) { + List selectPaths = StringTools.getJsonToList(originalBackupPaths); + getViewModel().setSelectFilePathList(selectPaths); + } + + allPathsList = initRootPath(getActivity()); + mTabbarFileList = new ArrayList<>(); + refreshFileAndTabBar(BeanListManager.TYPE_INIT_TAB_BAR); + } + + private List initRootPath(Activity activity) { + List allPaths = FileTools.getAllPaths(activity); + if (allPaths.isEmpty()) { + mCurrentPath = Constants.DEFAULT_ROOTPATH; + } else { + mCurrentPath = allPaths.get(0); + } + initialPath = mCurrentPath; + return allPaths; + } + + private void refreshFileAndTabBar(int tabbarType) { + + BeanListManager.upDataTabbarFileBeanList(mTabbarFileList, mTabBarFileListAdapter, + mCurrentPath, tabbarType, allPathsList); + } + + public boolean onBackPressed() { + if (mCurrentPath.equals(initialPath) || allPathsList.contains(mCurrentPath)) { + return false; + } else { + mCurrentPath = FileTools.getParentPath(mCurrentPath); + refreshFileAndTabBar(BeanListManager.TYPE_DEL_TAB_BAR); + + loadData(); + return true; + } + } + + + private final Map scrollPositions = Maps.newHashMap(); + + private void saveScrollPosition() { + View vi = binding.rvOfList.getChildAt(0); + int top = (vi == null) ? 0 : vi.getTop(); + final int index = rvManager.findFirstVisibleItemPosition(); + final ScrollState state = new ScrollState(index, top); + + removeScrollPosition(); + + scrollPositions.put(mCurrentPath, state); + } + + private void removeScrollPosition() { + scrollPositions.remove(mCurrentPath); + } + + private void restoreScrollPosition() { + ScrollState state = scrollPositions.get(mCurrentPath); + + if (state != null) { + rvManager.scrollToPositionWithOffset(state.index, state.top); + } else { + rvManager.scrollToPosition(0); + } + } + + public List getSelectedPath() { + return mFileListAdapter + .getItems() + .stream() + .filter(FileBean::isChecked).map(new Function() { + @Override + public String apply(FileBean fileBean) { + return fileBean.getFilePath(); + } + }).collect(Collectors.toList()); + } + + +} + diff --git a/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FolderSelectorViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FolderSelectorViewModel.java new file mode 100644 index 000000000..f5813cd7e --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/FolderSelectorViewModel.java @@ -0,0 +1,196 @@ +package com.seafile.seadroid2.ui.selector.folder_selector; + +import android.text.TextUtils; + +import androidx.lifecycle.MutableLiveData; + +import com.blankj.utilcode.util.CollectionUtils; +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.util.FileTools; +import com.seafile.seadroid2.util.sp.SettingsManager; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import io.reactivex.Single; +import io.reactivex.SingleEmitter; +import io.reactivex.SingleOnSubscribe; +import io.reactivex.functions.Consumer; + +public class FolderSelectorViewModel extends BaseViewModel { + private MutableLiveData> dataListLiveData = new MutableLiveData<>(); + + public void setSelectFilePathList(List selectFilePath) { + this.selectFilePath = selectFilePath; + } + + public List getSelectFilePathList() { + return selectFilePath; + } + + private List selectFilePath = new ArrayList<>(); + + public void removeSpecialPath(String filePath) { + selectFilePath.removeIf(s -> TextUtils.equals(s, filePath)); + } + public void addSpecialPath(String filePath) { + selectFilePath.add(filePath); + } + public MutableLiveData> getDataListLiveData() { + return dataListLiveData; + } + + public void loadData(String path) { + + getRefreshLiveData().setValue(true); + boolean isJumpHiddenFile = SettingsManager.getInstance().isFolderBackupJumpHiddenFiles(); + + Single> single = Single.create(new SingleOnSubscribe>() { + @Override + public void subscribe(SingleEmitter> emitter) throws Exception { + File file = FileTools.getFileByPath(path); + if (file == null) { + emitter.onSuccess(Collections.emptyList()); + return; + } + + File[] files = file.listFiles(); + if (files == null) { + emitter.onSuccess(Collections.emptyList()); + return; + } + + List fileBeanList = new ArrayList<>(); + + for (File value : files) { + FileBean fileBean = new FileBean(value.getAbsolutePath()); + + boolean isJump = false; + if (isJumpHiddenFile) { + String fileName = fileBean.getFileName(); + if (!TextUtils.isEmpty(fileName) && fileName.startsWith(".")) { + isJump = true; + } + } + + if (!isJump) { + if (!CollectionUtils.isEmpty(getSelectFilePathList())) { + if (getSelectFilePathList().contains(fileBean.getFilePath())) { + fileBean.setChecked(true); + } + } + + fileBeanList.add(fileBean); + } + } + + sortFileBeanList(fileBeanList, Constants.SORT_NAME_ASC); + + emitter.onSuccess(fileBeanList); + } + }); + + addSingleDisposable(single, new Consumer>() { + @Override + public void accept(List fileBeans) throws Exception { + getRefreshLiveData().setValue(false); + getDataListLiveData().setValue(fileBeans); + } + }); + } + + private void sortFileBeanList(List fileBeanList, int sortType) { + Collections.sort(fileBeanList, (file1, file2) -> { + + if (file1.isDir() && file2.isFile()) + return -1; + if (file1.isFile() && file2.isDir()) + return 1; + + switch (sortType) { + case Constants.SORT_NAME_ASC: + return file1.getFileName().compareToIgnoreCase(file2.getFileName()); + case Constants.SORT_NAME_DESC: + return file2.getFileName().compareToIgnoreCase(file1.getFileName()); + case Constants.SORT_TIME_ASC: + long diff = file1.getModifyTime() - file2.getModifyTime(); + if (diff > 0) + return 1; + else if (diff == 0) + return 0; + else + return -1; + case Constants.SORT_TIME_DESC: + diff = file2.getModifyTime() - file1.getModifyTime(); + if (diff > 0) + return 1; + else if (diff == 0) + return 0; + else + return -1; + case Constants.SORT_SIZE_ASC: + diff = file1.getSimpleSize() - file2.getSimpleSize(); + if (diff > 0) + return 1; + else if (diff == 0) + return 0; + else + return -1; + case Constants.SORT_SIZE_DESC: + diff = file2.getSimpleSize() - file1.getSimpleSize(); + if (diff > 0) + return 1; + else if (diff == 0) + return 0; + else + return -1; + default: + return 0; + } + }); + } + +// private List sortRepos(List repos) { +// List list = new ArrayList<>(); +// +// int sortType = Sorts.getSortType(); +// switch (sortType) { +// case 0: // sort by name, ascending +// list = repos.stream().sorted(new Comparator() { +// @Override +// public int compare(FileBean o1, FileBean o2) { +// return o1.getFileName().compareTo(o2.getFileName()); +// } +// }).collect(Collectors.toList()); +// +// break; +// case 1: // sort by name, descending +// list = repos.stream().sorted(new Comparator() { +// @Override +// public int compare(FileBean o1, FileBean o2) { +// return -o1.getFileName().compareTo(o2.getFileName()); +// } +// }).collect(Collectors.toList()); +// break; +// case 2: // sort by last modified time, ascending +// list = repos.stream().sorted(new Comparator() { +// @Override +// public int compare(FileBean o1, FileBean o2) { +// return o1.getModifyTime() < o2.getModifyTime() ? -1 : 1; +// } +// }).collect(Collectors.toList()); +// break; +// case 3: // sort by last modified time, descending +// list = repos.stream().sorted(new Comparator() { +// @Override +// public int compare(FileBean o1, FileBean o2) { +// return o1.getModifyTime() > o2.getModifyTime() ? -1 : 1; +// } +// }).collect(Collectors.toList()); +// break; +// } +// return list; +// } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/StringTools.java b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/StringTools.java new file mode 100644 index 000000000..174c77d08 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/StringTools.java @@ -0,0 +1,31 @@ +package com.seafile.seadroid2.ui.selector.folder_selector; + +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import java.util.ArrayList; +import java.util.List; + +public class StringTools { + + public static Long getOnlyNumber(String s) { + String temp = s.replaceAll("[^0-9]", ""); + if (temp.equals("")) { + return -1L; + } + return Long.valueOf(temp); + } + + public static List getJsonToList(String strJson) { + List list = new ArrayList(); + if (TextUtils.isEmpty(strJson)) { + return list; + } + Gson gson = new Gson(); + list = gson.fromJson(strJson, new TypeToken>() { + }.getType()); + return list; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabBarFileBean.java b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/TabBarFileBean.java similarity index 91% rename from app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabBarFileBean.java rename to app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/TabBarFileBean.java index 459cbe624..d3108ee54 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabBarFileBean.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/TabBarFileBean.java @@ -1,6 +1,8 @@ -package com.seafile.seadroid2.folderbackup.selectfolder; +package com.seafile.seadroid2.ui.selector.folder_selector; +import com.seafile.seadroid2.util.FileTools; + public class TabBarFileBean { private String filePath; diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabBarFileListAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/TabBarFileListAdapter.java similarity index 75% rename from app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabBarFileListAdapter.java rename to app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/TabBarFileListAdapter.java index d4c28f14a..fced3c9e3 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabBarFileListAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/TabBarFileListAdapter.java @@ -1,13 +1,16 @@ -package com.seafile.seadroid2.folderbackup.selectfolder; +package com.seafile.seadroid2.ui.selector.folder_selector; import android.app.Activity; import android.content.Context; + import androidx.recyclerview.widget.RecyclerView; + import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.seafile.seadroid2.R; +import com.seafile.seadroid2.listener.OnItemClickListener; import java.util.List; @@ -15,9 +18,9 @@ public class TabBarFileListAdapter extends RecyclerView.Adapter mListData; private Context mContext; - private OnFileItemClickListener onItemClickListener; + private com.seafile.seadroid2.listener.OnItemClickListener onItemClickListener; - public void setOnItemClickListener(OnFileItemClickListener onItemClickListener) { + public void setOnItemClickListener(OnItemClickListener onItemClickListener) { this.onItemClickListener = onItemClickListener; } @@ -37,12 +40,9 @@ public void onBindViewHolder(final TabbarFileViewHolder holder, int positon) { final TabBarFileBean entity = mListData.get(positon); String fileName = entity.getFileName().replaceAll("[^\\p{Print}]", ""); holder.tvName.setText(fileName); - holder.llRoot.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (onItemClickListener != null) { - onItemClickListener.onItemClick(holder.getAdapterPosition()); - } + holder.llRoot.setOnClickListener(v -> { + if (onItemClickListener != null) { + onItemClickListener.onItemClick(entity, positon); } }); } diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabbarFileViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/TabbarFileViewHolder.java similarity index 90% rename from app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabbarFileViewHolder.java rename to app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/TabbarFileViewHolder.java index 1f52f140d..dfd43dbe0 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabbarFileViewHolder.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/selector/folder_selector/TabbarFileViewHolder.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.folderbackup.selectfolder; +package com.seafile.seadroid2.ui.selector.folder_selector; import androidx.recyclerview.widget.RecyclerView; import android.view.View; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/settings/PrivacyPolicyActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/settings/PrivacyPolicyActivity.java deleted file mode 100644 index 9df5ae4bc..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/settings/PrivacyPolicyActivity.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.seafile.seadroid2.ui.settings; - -import android.os.Bundle; -import androidx.appcompat.widget.Toolbar; -import android.view.MenuItem; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.ui.BaseActivity; - -public class PrivacyPolicyActivity extends BaseActivity implements Toolbar.OnMenuItemClickListener { - - private WebView mWebView; - - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.privacypolicy_activity_layout); - mWebView = (WebView) this.findViewById(R.id.privacy_policy_wv); - mWebView.loadUrl("https://www.seafile.com/privacy/"); - mWebView.setWebViewClient(new WebViewClient()); - WebSettings webSettings = mWebView.getSettings(); - webSettings.setJavaScriptEnabled(true); - mWebView.getSettings().setSupportZoom(true); - mWebView.getSettings().setBuiltInZoomControls(true); - mWebView.setWebViewClient(new WebViewClient() { - public boolean shouldOverrideUrlLoading(WebView view, String url) { - view.loadUrl(url); - return true; - } - }); - - Toolbar toolbar = getActionBarToolbar(); - setSupportActionBar(toolbar); - toolbar.setOnMenuItemClickListener(this); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - } - - @Override - public boolean onMenuItemClick(MenuItem item) { - return super.onOptionsItemSelected(item); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - this.finish(); - default: - return super.onOptionsItemSelected(item); - } - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsActivity.java index 54980277d..925d1a776 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsActivity.java @@ -6,24 +6,30 @@ import androidx.annotation.NonNull; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; +import com.blankj.utilcode.util.FragmentUtils; import com.seafile.seadroid2.R; +import com.seafile.seadroid2.databinding.SettingsActivityLayoutBinding; import com.seafile.seadroid2.ui.BaseActivity; import com.seafile.seadroid2.ui.settings.SettingsFragment; public class SettingsActivity extends BaseActivity implements Toolbar.OnMenuItemClickListener, PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { + private SettingsActivityLayoutBinding binding; + private SettingsActivityViewModel viewModel; + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.settings_activity_layout); + binding = SettingsActivityLayoutBinding.inflate(getLayoutInflater()); - getSupportFragmentManager() - .beginTransaction() - .replace(R.id.settings_fragment_container, new SettingsFragment()) - .commit(); + setContentView(binding.getRoot()); + + FragmentUtils.add(getSupportFragmentManager(), SettingsFragment.newInstance(), R.id.settings_fragment_container); Toolbar toolbar = getActionBarToolbar(); setSupportActionBar(toolbar); @@ -33,6 +39,16 @@ public void onCreate(Bundle savedInstanceState) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setTitle(R.string.settings); } + + binding.swipeRefreshLayout.setEnabled(false); + + viewModel = new ViewModelProvider(this).get(SettingsActivityViewModel.class); + viewModel.getRefreshLiveData().observe(this, new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + binding.swipeRefreshLayout.setRefreshing(aBoolean); + } + }); } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsActivityViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsActivityViewModel.java new file mode 100644 index 000000000..087f4aa8d --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsActivityViewModel.java @@ -0,0 +1,6 @@ +package com.seafile.seadroid2.ui.settings; + +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; + +public class SettingsActivityViewModel extends BaseViewModel { +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsCameraBackupAdvanceFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsCameraBackupAdvanceFragment.java index 5bdbddb56..676aa9f1b 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsCameraBackupAdvanceFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsCameraBackupAdvanceFragment.java @@ -16,12 +16,12 @@ import androidx.preference.SwitchPreferenceCompat; import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SettingsManager; import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.cameraupload.CameraUploadConfigActivity; -import com.seafile.seadroid2.cameraupload.CameraUploadManager; -import com.seafile.seadroid2.cameraupload.GalleryBucketUtils; -import com.seafile.seadroid2.ui.activity.SeafilePathChooserActivity; +import com.seafile.seadroid2.ui.camera_upload.CameraUploadConfigActivity; +import com.seafile.seadroid2.ui.camera_upload.CameraUploadManager; +import com.seafile.seadroid2.ui.camera_upload.GalleryBucketUtils; +import com.seafile.seadroid2.ui.selector.ObjSelectorActivity; +import com.seafile.seadroid2.util.sp.SettingsManager; import java.util.ArrayList; import java.util.LinkedHashSet; @@ -34,7 +34,6 @@ public class SettingsCameraBackupAdvanceFragment extends PreferenceFragmentCompa private SwitchPreferenceCompat cbVideoAllowed; private Preference mCameraBackupLocalBucketPref; - private SettingsManager settingsMgr; private CameraUploadManager cameraUploaderManager; @Override @@ -49,8 +48,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { } private void init() { - settingsMgr = SettingsManager.instance(); - cameraUploaderManager = new CameraUploadManager(requireContext()); + cameraUploaderManager = new CameraUploadManager(); } @Override @@ -69,12 +67,11 @@ private void initCameraBackupView() { mCameraBackupLocalBucketPref = findPreference(SettingsManager.CAMERA_UPLOAD_BUCKETS_KEY); cbDataPlan = findPreference(SettingsManager.CAMERA_UPLOAD_ALLOW_DATA_PLAN_SWITCH_KEY); - cbDataPlan.setChecked(settingsMgr.isDataPlanAllowed()); - + cbDataPlan.setChecked(SettingsManager.getInstance().isDataPlanAllowed()); // videos cbVideoAllowed = findPreference(SettingsManager.CAMERA_UPLOAD_ALLOW_VIDEOS_SWITCH_KEY); - cbVideoAllowed.setChecked(settingsMgr.isVideosUploadAllowed()); + cbVideoAllowed.setChecked(SettingsManager.getInstance().isVideosUploadAllowed()); mCameraBackupCustomBucketsSwitch.setOnPreferenceChangeListener((preference, newValue) -> { boolean isBool = newValue instanceof Boolean; @@ -109,7 +106,7 @@ private void scanCustomDirs(boolean isCustomScanOn) { selectLocalDirLauncher.launch(intent); } else { List selectedBuckets = new ArrayList<>(); - settingsMgr.setCameraUploadBucketList(selectedBuckets); + SettingsManager.getInstance().setCameraUploadBucketList(selectedBuckets); refreshPreferenceView(); } } @@ -117,7 +114,7 @@ private void scanCustomDirs(boolean isCustomScanOn) { private void refreshPreferenceView() { List bucketNames = new ArrayList<>(); - List bucketIds = settingsMgr.getCameraUploadBucketList(); + List bucketIds = SettingsManager.getInstance().getCameraUploadBucketList(); List tempBuckets = GalleryBucketUtils.getMediaBuckets(getActivity().getApplicationContext()); LinkedHashSet bucketsSet = new LinkedHashSet<>(tempBuckets.size()); bucketsSet.addAll(tempBuckets); @@ -148,12 +145,12 @@ public void onActivityResult(ActivityResult result) { return; } - final String repoName = result.getData().getStringExtra(SeafilePathChooserActivity.DATA_REPO_NAME); - final String repoId = result.getData().getStringExtra(SeafilePathChooserActivity.DATA_REPO_ID); - final Account account = result.getData().getParcelableExtra(SeafilePathChooserActivity.DATA_ACCOUNT); + final String repoName = result.getData().getStringExtra(ObjSelectorActivity.DATA_REPO_NAME); + final String repoId = result.getData().getStringExtra(ObjSelectorActivity.DATA_REPO_ID); + final Account account = result.getData().getParcelableExtra(ObjSelectorActivity.DATA_ACCOUNT); if (repoName != null && repoId != null) { cameraUploaderManager.setCameraAccount(account); - settingsMgr.saveCameraUploadRepoInfo(repoId, repoName); + SettingsManager.getInstance().saveCameraUploadRepoInfo(repoId, repoName); } refreshPreferenceView(); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsFragment.java index a6e121c3f..a6679c363 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsFragment.java @@ -1,21 +1,29 @@ package com.seafile.seadroid2.ui.settings; +import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY; + import android.app.Activity; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; -import android.os.AsyncTask; import android.os.Bundle; import android.text.Html; +import android.text.Spanned; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.Toast; +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.core.text.HtmlCompat; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; @@ -23,66 +31,60 @@ import com.blankj.utilcode.util.AppUtils; import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.ToastUtils; import com.bumptech.glide.Glide; -import com.google.common.collect.Maps; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.hjq.permissions.OnPermissionCallback; import com.hjq.permissions.Permission; import com.hjq.permissions.XXPermissions; import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeadroidApplication; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.SettingsManager; import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.AccountInfo; -import com.seafile.seadroid2.account.AccountManager; -import com.seafile.seadroid2.cameraupload.CameraUploadConfigActivity; -import com.seafile.seadroid2.cameraupload.CameraUploadManager; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.config.Constants; import com.seafile.seadroid2.data.CameraSyncEvent; import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.data.ServerInfo; import com.seafile.seadroid2.data.StorageManager; -import com.seafile.seadroid2.folderbackup.FolderBackupConfigActivity; -import com.seafile.seadroid2.folderbackup.FolderBackupDBHelper; -import com.seafile.seadroid2.folderbackup.FolderBackupEvent; -import com.seafile.seadroid2.folderbackup.FolderBackupSelectedPathActivity; -import com.seafile.seadroid2.folderbackup.RepoConfig; -import com.seafile.seadroid2.folderbackup.selectfolder.StringTools; import com.seafile.seadroid2.gesturelock.LockPatternUtils; -import com.seafile.seadroid2.ui.BrowserActivity; +import com.seafile.seadroid2.ui.camera_upload.CameraUploadConfigActivity; +import com.seafile.seadroid2.ui.camera_upload.CameraUploadManager; +import com.seafile.seadroid2.ui.dialog_fragment.ClearCacheDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.ClearPasswordDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.SignOutDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.SwitchStorageDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.listener.OnRefreshDataListener; +import com.seafile.seadroid2.ui.folder_backup.FolderBackupConfigActivity; +import com.seafile.seadroid2.ui.folder_backup.FolderBackupEvent; +import com.seafile.seadroid2.ui.folder_backup.FolderBackupSelectedPathActivity; +import com.seafile.seadroid2.ui.folder_backup.RepoConfig; import com.seafile.seadroid2.ui.gesture.CreateGesturePasswordActivity; -import com.seafile.seadroid2.ui.activity.SeafilePathChooserActivity; -import com.seafile.seadroid2.ui.dialog.ClearCacheTaskDialog; -import com.seafile.seadroid2.ui.dialog.ClearPasswordTaskDialog; -import com.seafile.seadroid2.ui.dialog.SwitchStorageTaskDialog; -import com.seafile.seadroid2.ui.dialog.TaskDialog.TaskDialogListener; +import com.seafile.seadroid2.ui.main.MainActivity; +import com.seafile.seadroid2.ui.selector.ObjSelectorActivity; +import com.seafile.seadroid2.ui.webview.SeaWebViewActivity; import com.seafile.seadroid2.util.CameraSyncStatus; -import com.seafile.seadroid2.util.ConcurrentAsyncTask; -import com.seafile.seadroid2.util.SLogs; import com.seafile.seadroid2.util.Utils; +import com.seafile.seadroid2.util.sp.FolderBackupConfigSPs; +import com.seafile.seadroid2.util.sp.SettingsManager; +import com.seafile.seadroid2.view.ListPreferenceCompat; -import org.apache.commons.io.FileUtils; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; -import java.util.ArrayList; import java.util.List; import java.util.Locale; -import java.util.Map; public class SettingsFragment extends PreferenceFragmentCompat { private static final String DEBUG_TAG = "SettingsFragment"; - public static final String CAMERA_UPLOAD_BOTH_PAGES = "com.seafile.seadroid2.camera.upload"; public static final String CAMERA_UPLOAD_REMOTE_LIBRARY = "com.seafile.seadroid2.camera.upload.library"; - public static final String FOLDER_BACKUP_REMOTE_PATH = "com.seafile.seadroid2.folder.backup.path"; public static final String CAMERA_UPLOAD_LOCAL_DIRECTORIES = "com.seafile.seadroid2.camera.upload.directories"; - public static final String FOLDER_BACKUP_REMOTE_LIBRARY = "com.seafile.seadroid2.folder.backup.library"; public static final int CHOOSE_CAMERA_UPLOAD_REQUEST = 2; public static final int CHOOSE_BACKUP_UPLOAD_REQUEST = 5; - // Account Info - private static Map accountInfoMap = Maps.newHashMap(); + private SettingsFragmentViewModel viewModel; + private SettingsActivityViewModel activityViewModel; // Camera upload private SwitchPreferenceCompat mCameraBackupSwitch; @@ -93,28 +95,22 @@ public class SettingsFragment extends PreferenceFragmentCompat { private SettingsActivity mActivity; - public SettingsManager settingsMgr; private CameraUploadManager cameraManager; - - private AccountManager accountMgr; private DataManager dataMgr; - private final StorageManager storageManager = StorageManager.getInstance(); //folder backup private SwitchPreferenceCompat mFolderBackupSwitch; private ListPreference mFolderBackupNetworkMode; private Preference mFolderBackupRepo; - private Preference mFolderBackupDirsPref; + private Preference mFolderBackupFolderPref; private Preference mFolderBackupState; - private List backupSelectPaths; - private FolderBackupDBHelper databaseHelper; private RepoConfig selectRepoConfig; private final SharedPreferences.OnSharedPreferenceChangeListener spChangeListener = (sharedPreferences, key) -> { switch (key) { case SettingsManager.SHARED_PREF_STORAGE_DIR: { - ConcurrentAsyncTask.execute(new UpdateStorageLocationSummaryTask()); + updateStorageLocationSummary(); } break; case SettingsManager.FOLDER_BACKUP_MODE: { @@ -126,6 +122,14 @@ public class SettingsFragment extends PreferenceFragmentCompat { } }; + public static SettingsFragment newInstance() { + + Bundle args = new Bundle(); + + SettingsFragment fragment = new SettingsFragment(); + fragment.setArguments(args); + return fragment; + } @Override public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { @@ -141,43 +145,24 @@ public void onAttach(@NonNull Context context) { mActivity = (SettingsActivity) getActivity(); } + @Override public void onCreate(Bundle savedInstanceState) { - Log.d(DEBUG_TAG, "onCreate"); super.onCreate(savedInstanceState); - if (!Utils.isNetworkOn()) { - mActivity.showShortToast(mActivity, R.string.network_down); - return; - } - + viewModel = new ViewModelProvider(this).get(SettingsFragmentViewModel.class); + activityViewModel = new ViewModelProvider(requireActivity()).get(SettingsActivityViewModel.class); init(); - - runTask(); } private void init() { //settings manager - settingsMgr = SettingsManager.instance(); - settingsMgr.registerSharedPreferencesListener(spChangeListener); + SettingsManager.getInstance().registerSharedPreferencesListener(spChangeListener); - accountMgr = new AccountManager(mActivity); - - Account act = accountMgr.getCurrentAccount(); + Account act = SupportAccountManager.getInstance().getCurrentAccount(); dataMgr = new DataManager(act); - cameraManager = new CameraUploadManager(mActivity.getApplicationContext()); - databaseHelper = FolderBackupDBHelper.getDatabaseHelper(); - - String backupPaths = SettingsManager.instance().getBackupPaths(); - if (!TextUtils.isEmpty(backupPaths)) { - backupSelectPaths = StringTools.getJsonToList(backupPaths); - } - } - - private void runTask() { - Account account = accountMgr.getCurrentAccount(); - ConcurrentAsyncTask.execute(new RequestAccountInfoTask(), account); + cameraManager = new CameraUploadManager(); } @Override @@ -185,7 +170,7 @@ public void onDestroy() { super.onDestroy(); Log.d(DEBUG_TAG, "onDestroy()"); - settingsMgr.unregisterSharedPreferencesListener(spChangeListener); + SettingsManager.getInstance().unregisterSharedPreferencesListener(spChangeListener); } @Override @@ -193,14 +178,47 @@ public void onViewCreated(View view, Bundle savedInstanceState) { Log.d(DEBUG_TAG, "onViewCreated"); super.onViewCreated(view, savedInstanceState); + initViewModel(); + initView(); + + loadData(); } + private void initViewModel() { + viewModel.getRefreshLiveData().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + activityViewModel.getRefreshLiveData().setValue(aBoolean); + } + }); + + viewModel.getAccountInfoLiveData().observe(getViewLifecycleOwner(), accountInfo -> { + // update Account info settings + findPreference(SettingsManager.SETTINGS_ACCOUNT_INFO_KEY).setSummary(accountInfo.getDisplayName()); + findPreference(SettingsManager.SETTINGS_ACCOUNT_SPACE_KEY).setSummary(accountInfo.getSpaceUsed()); + + refreshServerView(); + }); + + viewModel.getCacheSizeLiveData().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String s) { + findPreference(SettingsManager.SETTINGS_CACHE_SIZE_KEY).setSummary(s); + } + }); + } + + private void loadData() { + viewModel.getAccountInfo(); + } private void initView() { initAccountView(); + initAppView(); + initCameraBackupView(); initFolderBackupView(); @@ -212,27 +230,58 @@ private void initView() { initPolicyView(); refreshCameraUploadView(); + refreshFolderBackupView(); // Cache size calculateCacheSize(); } private void initAccountView() { - // User info - String identifier = getCurrentUserIdentifier(); - findPreference(SettingsManager.SETTINGS_ACCOUNT_INFO_KEY).setSummary(identifier); - // Space used - Account currentAccount = accountMgr.getCurrentAccount(); - if (currentAccount != null) { - String signature = currentAccount.getSignature(); - AccountInfo info = getAccountInfoBySignature(signature); - if (info != null) { - String spaceUsed = info.getSpaceUsed(); - findPreference(SettingsManager.SETTINGS_ACCOUNT_SPACE_KEY).setSummary(spaceUsed); - } + Account currentAccount = SupportAccountManager.getInstance().getCurrentAccount(); + if (currentAccount == null) { + // User info + return; + } + + findPreference(SettingsManager.SETTINGS_ACCOUNT_INFO_KEY).setSummary(currentAccount.getDisplayName()); + + refreshServerView(); + } + + private void refreshServerView() { + Account currentAccount = SupportAccountManager.getInstance().getCurrentAccount(); + if (currentAccount == null) { + // User info + return; + } + + Preference clientEncPref = findPreference(SettingsManager.CLIENT_ENC_SWITCH_KEY); + if (clientEncPref == null) { + return; + } + + ServerInfo serverInfo = SupportAccountManager.getInstance().getServerInfo(currentAccount); + if (serverInfo.canLocalDecrypt()) { + // Client side encryption for encrypted Library + clientEncPref.setVisible(true); + clientEncPref.setOnPreferenceChangeListener((preference, newValue) -> { + if (newValue instanceof Boolean) { + boolean isChecked = (Boolean) newValue; + // inverse checked status + SettingsManager.getInstance().setupEncrypt(!isChecked); + return true; + } + + return false; + }); + } else { + clientEncPref.setVisible(false); + clientEncPref.setOnPreferenceClickListener(null); } + } + private void initAppView() { //gesture lock findPreference(SettingsManager.GESTURE_LOCK_SWITCH_KEY).setOnPreferenceChangeListener((preference, newValue) -> { boolean isChecked = (Boolean) newValue; @@ -240,7 +289,7 @@ private void initAccountView() { // inverse checked status Intent newIntent = new Intent(getActivity(), CreateGesturePasswordActivity.class); newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivityForResult(newIntent, SettingsManager.GESTURE_LOCK_REQUEST); + gestureLauncher.launch(newIntent); } else { LockPatternUtils mLockPatternUtils = new LockPatternUtils(getActivity()); mLockPatternUtils.clearLock(); @@ -260,31 +309,6 @@ private void initAccountView() { clearPassword(); return true; }); - - //client encrypt - if (currentAccount == null) { - return; - } - - ServerInfo serverInfo = accountMgr.getServerInfo(currentAccount); - if (serverInfo == null || serverInfo.canLocalDecrypt()) { - // Client side encryption for encrypted Library - Preference clientEncPref = findPreference(SettingsManager.CLIENT_ENC_SWITCH_KEY); - clientEncPref.setVisible(true); - clientEncPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (newValue instanceof Boolean) { - boolean isChecked = (Boolean) newValue; - // inverse checked status - settingsMgr.setupEncrypt(!isChecked); - return true; - } - - return false; - } - }); - } } private void initCameraBackupView() { @@ -294,6 +318,9 @@ private void initCameraBackupView() { mCameraBackupRepoPref = findPreference(SettingsManager.CAMERA_UPLOAD_REPO_KEY); mCameraBackupRepoState = findPreference(SettingsManager.CAMERA_UPLOAD_STATE); + // + mCameraBackupAdvanced.setFragment(SettingsCameraBackupAdvanceFragment.class.getName()); + mCameraBackupSwitch.setOnPreferenceChangeListener((preference, newValue) -> { boolean isBool = newValue instanceof Boolean; if (!isBool) { @@ -310,7 +337,7 @@ public boolean onPreferenceClick(@NonNull Preference preference) { // choose remote library Intent intent = new Intent(mActivity, CameraUploadConfigActivity.class); intent.putExtra(CAMERA_UPLOAD_REMOTE_LIBRARY, true); - startActivityForResult(intent, CHOOSE_CAMERA_UPLOAD_REQUEST); + cameraBackupConfigLauncher.launch(intent); return true; } }); @@ -326,7 +353,7 @@ private void initFolderBackupView() { //folder backup mFolderBackupSwitch = findPreference(SettingsManager.FOLDER_BACKUP_SWITCH_KEY); mFolderBackupRepo = findPreference(SettingsManager.FOLDER_BACKUP_LIBRARY_KEY); - mFolderBackupDirsPref = findPreference(SettingsManager.SELECTED_BACKUP_FOLDERS_KEY); + mFolderBackupFolderPref = findPreference(SettingsManager.SELECTED_BACKUP_FOLDERS_KEY); mFolderBackupState = findPreference(SettingsManager.FOLDER_BACKUP_STATE); mFolderBackupNetworkMode = findPreference(SettingsManager.FOLDER_BACKUP_MODE); @@ -345,7 +372,7 @@ private void initFolderBackupView() { mFolderBackupNetworkMode.setOnPreferenceChangeListener((preference, newValue) -> { String newString = (String) newValue; int i = mFolderBackupNetworkMode.findIndexOfValue(newString); - SettingsManager.instance().saveFolderBackupDataPlanAllowed(i != 0); + SettingsManager.getInstance().saveFolderBackupDataPlanAllowed(i != 0); return true; }); @@ -367,24 +394,28 @@ private void initFolderBackupView() { @Override public boolean onPreferenceClick(@NonNull Preference preference) { Intent intent = new Intent(mActivity, FolderBackupConfigActivity.class); - intent.putExtra(FOLDER_BACKUP_REMOTE_LIBRARY, true); - startActivityForResult(intent, CHOOSE_BACKUP_UPLOAD_REQUEST); + intent.putExtra(FolderBackupConfigActivity.FOLDER_BACKUP_SELECT_MODE, "repo"); + + folderBackupConfigLauncher.launch(intent); return true; } }); } // - if (mFolderBackupDirsPref != null) { - mFolderBackupDirsPref.setOnPreferenceClickListener(preference -> { + if (mFolderBackupFolderPref != null) { + mFolderBackupFolderPref.setOnPreferenceClickListener(preference -> { + + List backupPathList = FolderBackupConfigSPs.getBackupPathList(); + Intent intent; - if (CollectionUtils.isEmpty(backupSelectPaths)) { + if (CollectionUtils.isEmpty(backupPathList)) { intent = new Intent(mActivity, FolderBackupConfigActivity.class); } else { intent = new Intent(mActivity, FolderBackupSelectedPathActivity.class); } - intent.putExtra(FOLDER_BACKUP_REMOTE_PATH, true); - startActivityForResult(intent, CHOOSE_BACKUP_UPLOAD_REQUEST); + intent.putExtra(FolderBackupConfigActivity.FOLDER_BACKUP_SELECT_MODE, "folder"); + folderBackupConfigLauncher.launch(intent); return true; }); } @@ -394,9 +425,6 @@ public boolean onPreferenceClick(@NonNull Preference preference) { } private void initCacheView() { - // Cache size - calculateCacheSize(); - // Clear cache findPreference(SettingsManager.SETTINGS_CLEAR_CACHE_KEY).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { @Override @@ -407,12 +435,13 @@ public boolean onPreferenceClick(@NonNull Preference preference) { }); // Storage selection only works on KitKat or later - if (storageManager.supportsMultipleStorageLocations()) { + if (StorageManager.getInstance().supportsMultipleStorageLocations()) { updateStorageLocationSummary(); findPreference(SettingsManager.SETTINGS_CACHE_DIR_KEY).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { @Override public boolean onPreferenceClick(@NonNull Preference preference) { - new SwitchStorageTaskDialog().show(getChildFragmentManager(), "Select cache location"); + SwitchStorageDialogFragment dialogFragment = SwitchStorageDialogFragment.newInstance(); + dialogFragment.show(getChildFragmentManager(), SwitchStorageDialogFragment.class.getSimpleName()); return true; } }); @@ -426,17 +455,13 @@ private void initAboutView() { String appVersion = AppUtils.getAppVersionName(); findPreference(SettingsManager.SETTINGS_ABOUT_VERSION_KEY).setSummary(appVersion); - // About author - findPreference(SettingsManager.SETTINGS_ABOUT_AUTHOR_KEY).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(@NonNull Preference preference) { - AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); - // builder.setIcon(R.drawable.icon); - builder.setMessage(Html.fromHtml(getString(R.string.settings_about_author_info, appVersion))); - builder.show(); - return true; - } + findPreference(SettingsManager.SETTINGS_ABOUT_AUTHOR_KEY).setOnPreferenceClickListener(preference -> { + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(mActivity); + Spanned span = HtmlCompat.fromHtml(getString(R.string.settings_about_author_info, appVersion), FROM_HTML_MODE_LEGACY); + builder.setMessage(span); + builder.show(); + return true; }); } @@ -447,8 +472,7 @@ private void initPolicyView() { findPreference(SettingsManager.SETTINGS_PRIVACY_POLICY_KEY).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { @Override public boolean onPreferenceClick(@NonNull Preference preference) { - Intent intent = new Intent(mActivity, PrivacyPolicyActivity.class); - mActivity.startActivity(intent); + SeaWebViewActivity.openUrl(requireContext(), Constants.URL_PRIVACY); return true; } }); @@ -457,41 +481,43 @@ public boolean onPreferenceClick(@NonNull Preference preference) { } } - private void onPreferenceSignOutClicked() { - // popup a dialog to confirm sign out request - final AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); - builder.setTitle(getString(R.string.settings_account_sign_out_title)); - builder.setMessage(getString(R.string.settings_account_sign_out_confirm)); - builder.setPositiveButton(getString(R.string.confirm), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Account account = accountMgr.getCurrentAccount(); + @Override + public void onDisplayPreferenceDialog(@NonNull Preference preference) { + if (preference instanceof ListPreference) { + showListPreferenceDialog(preference); + } else { + super.onDisplayPreferenceDialog(preference); + } + } - // sign out operations - accountMgr.signOutAccount(account); + private void showListPreferenceDialog(Preference preference) { + ListPreferenceCompat dialogFragment = new ListPreferenceCompat(); + Bundle bundle = new Bundle(1); + bundle.putString("key", preference.getKey()); + dialogFragment.setArguments(bundle); + dialogFragment.setTargetFragment(this, 0); +// getParentFragmentManager().setFragmentResultListener(); + dialogFragment.show(getParentFragmentManager(), "androidx.preference.PreferenceFragment.DIALOG"); + } - // restart BrowserActivity (will go to AccountsActivity) - Intent intent = new Intent(mActivity, BrowserActivity.class); + private void onPreferenceSignOutClicked() { + SignOutDialogFragment dialogFragment = new SignOutDialogFragment(); + dialogFragment.setRefreshListener(isDone -> { + if (isDone) { + Intent intent = new Intent(mActivity, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); mActivity.startActivity(intent); mActivity.finish(); } }); - builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // dismiss - dialog.dismiss(); - } - }); - builder.show(); + dialogFragment.show(getChildFragmentManager(), SignOutDialogFragment.class.getSimpleName()); } private void onPreferenceFolderBackupSwitchChanged(boolean isChecked) { setFolderPreferencesVisible(isChecked); if (!isChecked) { - SettingsManager.instance().saveFolderAutomaticBackup(false); + SettingsManager.getInstance().saveFolderAutomaticBackup(false); return; } @@ -500,7 +526,7 @@ private void onPreferenceFolderBackupSwitchChanged(boolean isChecked) { @Override public void onGranted(List permissions, boolean all) { if (all) { - SettingsManager.instance().saveFolderAutomaticBackup(true); + SettingsManager.getInstance().saveFolderAutomaticBackup(true); refreshCameraUploadView(); } } @@ -523,7 +549,6 @@ private void onPreferenceCameraBackupSwitchChanged(boolean isChecked) { if (!isChecked) { cameraManager.disableCameraUpload(); - return; } @@ -533,8 +558,7 @@ private void onPreferenceCameraBackupSwitchChanged(boolean isChecked) { public void onGranted(List permissions, boolean all) { if (all) { Intent intent = new Intent(mActivity, CameraUploadConfigActivity.class); - intent.putExtra(CAMERA_UPLOAD_BOTH_PAGES, true); - startActivityForResult(intent, CHOOSE_CAMERA_UPLOAD_REQUEST); + cameraBackupConfigLauncher.launch(intent); } } @@ -545,7 +569,7 @@ public void onDenied(List permissions, boolean never) { XXPermissions.startPermissionActivity(getActivity(), permissions); } else { Toast.makeText(getActivity(), mActivity.getString(R.string.get_storage_permission_failed), Toast.LENGTH_LONG).show(); - mFolderBackupSwitch.setChecked(false); + mCameraBackupSwitch.setChecked(false); } } }); @@ -554,7 +578,7 @@ public void onDenied(List permissions, boolean never) { private void setFolderPreferencesVisible(boolean isChecked) { mFolderBackupNetworkMode.setVisible(isChecked); mFolderBackupRepo.setVisible(isChecked); - mFolderBackupDirsPref.setVisible(isChecked); + mFolderBackupFolderPref.setVisible(isChecked); mFolderBackupState.setVisible(isChecked); } @@ -565,235 +589,83 @@ private void setCameraPreferencesVisible(boolean isChecked) { } private void clearPassword() { - ClearPasswordTaskDialog dialog = new ClearPasswordTaskDialog(); - dialog.setTaskDialogLisenter(new TaskDialogListener() { - @Override - public void onTaskSuccess() { - mActivity.showShortToast(mActivity, R.string.clear_password_successful); - } - + ClearPasswordDialogFragment dialogFragment = ClearPasswordDialogFragment.newInstance(); + dialogFragment.setRefreshListener(new OnRefreshDataListener() { @Override - public void onTaskFailed(SeafException e) { - mActivity.showShortToast(mActivity, R.string.clear_password_failed); + public void onActionStatus(boolean isDone) { + if (isDone) { + ToastUtils.showLong(R.string.clear_password_successful); + } else { + ToastUtils.showLong(R.string.clear_password_failed); + } } }); - dialog.show(getChildFragmentManager(), "DialogFragment"); + dialogFragment.show(getChildFragmentManager(), ClearPasswordDialogFragment.class.getSimpleName()); } private void updateStorageLocationSummary() { - String summary = storageManager.getStorageLocation().description; + String summary = StorageManager.getInstance().getStorageLocation().description; findPreference(SettingsManager.SETTINGS_CACHE_DIR_KEY).setSummary(summary); } private void refreshCameraUploadView() { Account camAccount = cameraManager.getCameraAccount(); - String backupEmail = SettingsManager.instance().getBackupEmail(); - if (camAccount != null && settingsMgr.getCameraUploadRepoName() != null) { - mCameraBackupRepoPref.setSummary(camAccount.getSignature() + "/" + settingsMgr.getCameraUploadRepoName()); + if (camAccount != null && SettingsManager.getInstance().getCameraUploadRepoName() != null) { + mCameraBackupRepoPref.setSummary(camAccount.getSignature() + "/" + SettingsManager.getInstance().getCameraUploadRepoName()); } mCameraBackupSwitch.setChecked(cameraManager.isCameraUploadEnabled()); - boolean isFolderAutomaticBackup = SettingsManager.instance().isFolderAutomaticBackup(); - mFolderBackupSwitch.setChecked(isFolderAutomaticBackup); - if (isFolderAutomaticBackup) { - if (CollectionUtils.isEmpty(backupSelectPaths)) { - mFolderBackupDirsPref.setSummary("0"); - } else { - mFolderBackupDirsPref.setSummary(backupSelectPaths.size() + ""); - } - - if (!TextUtils.isEmpty(backupEmail)) { - try { - selectRepoConfig = databaseHelper.getRepoConfig(backupEmail); - } catch (Exception e) { - SLogs.d("=refreshCameraUploadView=======================" + e); - } - } - - if (selectRepoConfig != null && !TextUtils.isEmpty(selectRepoConfig.getRepoName())) { - mFolderBackupRepo.setSummary(backupEmail + "/" + selectRepoConfig.getRepoName()); - } else { - mFolderBackupRepo.setSummary(getActivity().getString(R.string.folder_backup_select_repo_hint)); - } - } - } - - private void clearCache() { - ClearCacheTaskDialog dialog = new ClearCacheTaskDialog(); - dialog.setTaskDialogLisenter(new TaskDialogListener() { - @Override - public void onTaskSuccess() { - // refresh cache size - calculateCacheSize(); - //clear Glide cache - Glide.get(SeadroidApplication.getAppContext()).clearMemory(); - Toast.makeText(mActivity, getString(R.string.settings_clear_cache_success), Toast.LENGTH_SHORT).show(); - } + setCameraPreferencesVisible(mCameraBackupSwitch.isChecked()); - @Override - public void onTaskFailed(SeafException e) { - Toast.makeText(mActivity, getString(R.string.settings_clear_cache_failed), Toast.LENGTH_SHORT).show(); - } - }); - dialog.show(getFragmentManager(), "DialogFragment"); } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case SettingsManager.GESTURE_LOCK_REQUEST: - if (resultCode == Activity.RESULT_OK) { - - - } else if (resultCode == Activity.RESULT_CANCELED) { - ((SwitchPreferenceCompat) findPreference(SettingsManager.GESTURE_LOCK_SWITCH_KEY)).setChecked(false); - } - break; - case CHOOSE_CAMERA_UPLOAD_REQUEST: - if (resultCode == Activity.RESULT_OK) { - if (data == null) { - return; - } - final String repoName = data.getStringExtra(SeafilePathChooserActivity.DATA_REPO_NAME); - final String repoId = data.getStringExtra(SeafilePathChooserActivity.DATA_REPO_ID); - final Account account = data.getParcelableExtra(SeafilePathChooserActivity.DATA_ACCOUNT); - if (repoName != null && repoId != null) { - // Log.d(DEBUG_TAG, "Activating camera upload to " + account + "; " + repoName); - cameraManager.setCameraAccount(account); - settingsMgr.saveCameraUploadRepoInfo(repoId, repoName); - } - - } else if (resultCode == Activity.RESULT_CANCELED) { - - } - refreshCameraUploadView(); - break; - case CHOOSE_BACKUP_UPLOAD_REQUEST: - if (resultCode == Activity.RESULT_OK) { - if (data == null) { - return; - } - - final boolean pathOn = data.getBooleanExtra(FolderBackupConfigActivity.BACKUP_SELECT_PATHS_SWITCH, false); - final ArrayList pathListExtra = data.getStringArrayListExtra(FolderBackupConfigActivity.BACKUP_SELECT_PATHS); - if (pathOn && pathListExtra != null) { - if (backupSelectPaths == null) { - backupSelectPaths = new ArrayList<>(); - } else { - backupSelectPaths.clear(); - } - backupSelectPaths.addAll(pathListExtra); - mFolderBackupDirsPref.setSummary(pathListExtra.size() + ""); - } else if (pathListExtra == null) { - if (backupSelectPaths != null) { - backupSelectPaths.clear(); - } - mFolderBackupDirsPref.setSummary("0"); - } - } - refreshCameraUploadView(); - break; - - default: - break; + private void refreshFolderBackupView() { + boolean isFolderAutomaticBackup = SettingsManager.getInstance().isFolderAutomaticBackup(); + mFolderBackupSwitch.setChecked(isFolderAutomaticBackup); + if (!isFolderAutomaticBackup) { + return; } - } - - /** - * automatically update Account info, like space usage, total space size, from background. - */ - private class RequestAccountInfoTask extends AsyncTask { - - @Override - protected void onPreExecute() { - mActivity.setSupportProgressBarIndeterminateVisibility(true); + String backupEmail = FolderBackupConfigSPs.getBackupEmail(); + if (!TextUtils.isEmpty(backupEmail)) { + selectRepoConfig = FolderBackupConfigSPs.getBackupConfigByAccount(backupEmail); } - @Override - protected AccountInfo doInBackground(Account... params) { - AccountInfo accountInfo = null; - - if (params == null) return null; - - try { - // get account info from server - accountInfo = dataMgr.getAccountInfo(); - } catch (Exception e) { - Log.e(DEBUG_TAG, "could not get account info!", e); - } - - return accountInfo; + if (selectRepoConfig != null && !TextUtils.isEmpty(selectRepoConfig.getRepoName())) { + mFolderBackupRepo.setSummary(backupEmail + "/" + selectRepoConfig.getRepoName()); + } else { + mFolderBackupRepo.setSummary(getString(R.string.folder_backup_select_repo_hint)); } - @Override - protected void onPostExecute(AccountInfo accountInfo) { - mActivity.setSupportProgressBarIndeterminateVisibility(false); - - if (accountInfo == null) return; - - // update Account info settings - findPreference(SettingsManager.SETTINGS_ACCOUNT_INFO_KEY).setSummary(getCurrentUserIdentifier()); - String spaceUsage = accountInfo.getSpaceUsed(); - findPreference(SettingsManager.SETTINGS_ACCOUNT_SPACE_KEY).setSummary(spaceUsage); - Account currentAccount = accountMgr.getCurrentAccount(); - if (currentAccount != null) - saveAccountInfo(currentAccount.getSignature(), accountInfo); + List stringList = FolderBackupConfigSPs.getBackupPathList(); + if (CollectionUtils.isEmpty(stringList)) { + mFolderBackupFolderPref.setSummary("0"); + } else { + mFolderBackupFolderPref.setSummary(String.valueOf(stringList.size())); } } - public String getCurrentUserIdentifier() { - Account account = accountMgr.getCurrentAccount(); - - if (account == null) - return ""; - - return account.getDisplayName(); - } - - public void saveAccountInfo(String signature, AccountInfo accountInfo) { - accountInfoMap.put(signature, accountInfo); - } - - public AccountInfo getAccountInfoBySignature(String signature) { - if (accountInfoMap.containsKey(signature)) - return accountInfoMap.get(signature); - else - return null; + private void clearCache() { + ClearCacheDialogFragment dialogFragment = ClearCacheDialogFragment.newInstance(); + dialogFragment.setRefreshListener(new OnRefreshDataListener() { + @Override + public void onActionStatus(boolean isDone) { + if (isDone) { + calculateCacheSize(); + //clear Glide cache + Glide.get(SeadroidApplication.getAppContext()).clearMemory(); + ToastUtils.showLong(R.string.settings_clear_cache_success); + } else { + ToastUtils.showLong(R.string.settings_clear_cache_failed); + } + } + }); + dialogFragment.show(getChildFragmentManager(), ClearCacheDialogFragment.class.getSimpleName()); } private void calculateCacheSize() { - ConcurrentAsyncTask.execute(new CalculateCacheTask()); - } - - private class CalculateCacheTask extends AsyncTask { - - @Override - protected Long doInBackground(String... params) { - return storageManager.getUsedSpace(); - } - - @Override - protected void onPostExecute(Long aLong) { - String total = FileUtils.byteCountToDisplaySize(aLong); - findPreference(SettingsManager.SETTINGS_CACHE_SIZE_KEY).setSummary(total); - } - - } - - private class UpdateStorageLocationSummaryTask extends AsyncTask { - - @Override - protected Void doInBackground(Void... params) { - return null; - } - - @Override - protected void onPostExecute(Void ret) { - updateStorageLocationSummary(); - } - + viewModel.calculateCacheSize(); } @Override @@ -829,4 +701,49 @@ public void onEvent(FolderBackupEvent result) { } } + private final ActivityResultLauncher folderBackupConfigLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback() { + @Override + public void onActivityResult(ActivityResult o) { + if (o.getResultCode() != Activity.RESULT_OK) { + return; + } + + ToastUtils.showLong(R.string.folder_backup_select_repo_update); + refreshFolderBackupView(); + } + }); + + private final ActivityResultLauncher cameraBackupConfigLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback() { + @Override + public void onActivityResult(ActivityResult o) { + if (o.getResultCode() != Activity.RESULT_OK) { + refreshCameraUploadView(); + return; + } + + if (o.getData() == null) { + return; + } + + final String repoName = o.getData().getStringExtra(ObjSelectorActivity.DATA_REPO_NAME); + final String repoId = o.getData().getStringExtra(ObjSelectorActivity.DATA_REPO_ID); + final Account account = o.getData().getParcelableExtra(ObjSelectorActivity.DATA_ACCOUNT); + if (repoName != null && repoId != null) { + cameraManager.setCameraAccount(account); + SettingsManager.getInstance().saveCameraUploadRepoInfo(repoId, repoName); + } + + refreshCameraUploadView(); + } + }); + + private final ActivityResultLauncher gestureLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback() { + @Override + public void onActivityResult(ActivityResult o) { + if (o.getResultCode() != Activity.RESULT_OK) { + ((SwitchPreferenceCompat) findPreference(SettingsManager.GESTURE_LOCK_SWITCH_KEY)).setChecked(false); + } + } + }); + } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsFragmentViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsFragmentViewModel.java new file mode 100644 index 000000000..234bcebfb --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsFragmentViewModel.java @@ -0,0 +1,92 @@ +package com.seafile.seadroid2.ui.settings; + +import androidx.lifecycle.MutableLiveData; + +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.AccountInfo; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.data.ServerInfo; +import com.seafile.seadroid2.data.db.AppDatabase; +import com.seafile.seadroid2.data.model.server.ServerInfoModel; +import com.seafile.seadroid2.data.remote.api.MainService; +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.data.StorageManager; +import com.seafile.seadroid2.data.remote.api.AccountService; +import com.seafile.seadroid2.io.http.IO; + +import org.apache.commons.io.FileUtils; + +import io.reactivex.Single; +import io.reactivex.SingleEmitter; +import io.reactivex.SingleOnSubscribe; +import io.reactivex.functions.BiFunction; +import io.reactivex.functions.Consumer; +import kotlin.Pair; + +public class SettingsFragmentViewModel extends BaseViewModel { + private final MutableLiveData accountInfoLiveData = new MutableLiveData<>(); + private final MutableLiveData cacheSizeLiveData = new MutableLiveData<>(); + + public MutableLiveData getCacheSizeLiveData() { + return cacheSizeLiveData; + } + + public MutableLiveData getAccountInfoLiveData() { + return accountInfoLiveData; + } + + public void getAccountInfo() { + getRefreshLiveData().setValue(true); + + Single single1 = IO.getSingleton().execute(MainService.class).getServerInfo(); + Single single2 = IO.getSingleton().execute(AccountService.class).getAccountInfo(); + + Single single = Single.zip(single1, single2, new BiFunction() { + @Override + public AccountInfo apply(ServerInfoModel serverInfoModel, AccountInfo accountInfo) throws Exception { + + Account account = SupportAccountManager.getInstance().getCurrentAccount(); + if (account == null) { + return accountInfo; + } + + accountInfo.setServer(IO.getSingleton().getServerUrl()); + + ServerInfo serverInfo1 = new ServerInfo(account.server, serverInfoModel.version, serverInfoModel.getFeaturesString()); + SupportAccountManager.getInstance().setServerInfo(account, serverInfo1); + return accountInfo; + } + }); + + addSingleDisposable(single, new Consumer() { + @Override + public void accept(AccountInfo accountInfo) throws Exception { + accountInfo.setServer(IO.getSingleton().getServerUrl()); + + getRefreshLiveData().setValue(false); + + getAccountInfoLiveData().setValue(accountInfo); + } + }); + } + + public void calculateCacheSize() { + + Single single = Single.create(new SingleOnSubscribe() { + @Override + public void subscribe(SingleEmitter emitter) throws Exception { + long l = StorageManager.getInstance().getUsedSpace(); + + String total = FileUtils.byteCountToDisplaySize(l); + emitter.onSuccess(total); + } + }); + addSingleDisposable(single, new Consumer() { + @Override + public void accept(String s) throws Exception { + getCacheSizeLiveData().setValue(s); + } + }); + + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/star/StarredAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/star/StarredAdapter.java new file mode 100644 index 000000000..00f3b0f22 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/star/StarredAdapter.java @@ -0,0 +1,143 @@ +package com.seafile.seadroid2.ui.star; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.DiffUtil; + +import com.blankj.utilcode.util.CollectionUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.ui.base.adapter.BaseAdapter; +import com.seafile.seadroid2.config.GlideLoadConfig; +import com.seafile.seadroid2.databinding.ItemStarredBinding; +import com.seafile.seadroid2.io.http.IO; +import com.seafile.seadroid2.data.model.star.StarredModel; +import com.seafile.seadroid2.util.GlideApp; +import com.seafile.seadroid2.util.Utils; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.List; + +public class StarredAdapter extends BaseAdapter { + private final String SERVER = IO.getSingleton().getServerUrl(); + + @NonNull + @Override + protected StarredViewHolder onCreateViewHolder(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) { + ItemStarredBinding binding = ItemStarredBinding.inflate(LayoutInflater.from(context), viewGroup, false); + return new StarredViewHolder(binding); + } + + @Override + protected void onBindViewHolder(@NonNull StarredViewHolder holder, int i, @Nullable StarredModel model) { + if (null == model) { + return; + } + + holder.binding.itemTitle.setText(model.obj_name); + if (model.deleted) { + holder.binding.itemSubtitle.setTextColor(ContextCompat.getColor(getContext(), R.color.red)); + holder.binding.itemSubtitle.setText(R.string.deleted); + } else { + holder.binding.itemSubtitle.setTextColor(ContextCompat.getColor(getContext(), R.color.fancy_black)); + holder.binding.itemSubtitle.setText(model.getSubtitle()); + } + + + //set item_icon + if (model.is_dir) { + if (TextUtils.equals(model.path, "/")) { + if (model.repo_encrypted) { + holder.binding.itemIcon.setImageResource(R.drawable.repo_encrypted); + } else { + holder.binding.itemIcon.setImageResource(R.drawable.repo); + } + } else { + holder.binding.itemIcon.setImageResource(R.drawable.folder); + } + } else { + if (TextUtils.isEmpty(model.encoded_thumbnail_src)) { + holder.binding.itemIcon.setImageResource(Utils.getFileIcon(model.obj_name)); + } else { + String url = convertThumbnailUrl(model.repo_id, model.path); + GlideApp.with(getContext()) + .load(GlideLoadConfig.getGlideUrl(url)) + .apply(GlideLoadConfig.getOptions()) + .into(holder.binding.itemIcon); + } + } + } + + @SuppressLint("DefaultLocale") + private String convertThumbnailUrl(String repoId, String filePath) { + try { + String pathEnc = URLEncoder.encode(filePath, "UTF-8"); + return String.format("%sapi2/repos/%s/thumbnail/?p=%s&size=%d", SERVER, repoId, pathEnc, 48); + } catch (UnsupportedEncodingException e) { + return null; + } + } + + public void notifyDataChanged(List list) { + if (CollectionUtils.isEmpty(list)) { + submitList(list); + return; + } + + if (CollectionUtils.isEmpty(getItems())) { + submitList(list); + return; + } + + DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return getItems().size(); + } + + @Override + public int getNewListSize() { + return list.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + StarredModel oldModel = getItems().get(oldItemPosition); + StarredModel newModel = list.get(newItemPosition); + String oldFullPath = oldModel.path + oldModel.obj_name; + String newFullPath = newModel.path + newModel.obj_name; + + return TextUtils.equals(oldFullPath, newFullPath); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + StarredModel oldModel = getItems().get(oldItemPosition); + StarredModel newModel = list.get(newItemPosition); + + + return TextUtils.equals(oldModel.repo_id, newModel.repo_id) + && TextUtils.equals(oldModel.repo_name, newModel.repo_name) + && TextUtils.equals(oldModel.mtime, newModel.mtime) + && TextUtils.equals(oldModel.path, newModel.path) + && TextUtils.equals(oldModel.obj_name, newModel.obj_name) + && TextUtils.equals(oldModel.user_email, newModel.user_email) + && TextUtils.equals(oldModel.user_name, newModel.user_name) + && TextUtils.equals(oldModel.user_contact_email, newModel.user_contact_email) + && oldModel.repo_encrypted == newModel.repo_encrypted + && oldModel.is_dir == newModel.is_dir + && oldModel.size == newModel.size; + } + }); + + setItems(list); + diffResult.dispatchUpdatesTo(this); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/ui/star/StarredFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/star/StarredFragment.java deleted file mode 100644 index 67eb853ce..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/star/StarredFragment.java +++ /dev/null @@ -1,519 +0,0 @@ -package com.seafile.seadroid2.ui.star; - -import android.app.Activity; -import android.os.Bundle; - -import androidx.fragment.app.ListFragment; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; -import androidx.appcompat.view.ActionMode; - -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; -import android.widget.AdapterView; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.Toast; - -import com.blankj.utilcode.util.CollectionUtils; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.data.DataManager; -import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.data.SeafStarredFile; -import com.seafile.seadroid2.listener.OnCallback; -import com.seafile.seadroid2.task.StarItemsTask; -import com.seafile.seadroid2.ui.WidgetUtils; -import com.seafile.seadroid2.ui.BrowserActivity; -import com.seafile.seadroid2.ui.dialog.PasswordDialog; -import com.seafile.seadroid2.ui.dialog.TaskDialog; -import com.seafile.seadroid2.util.ConcurrentAsyncTask; -import com.seafile.seadroid2.util.SupportAsyncTask; -import com.seafile.seadroid2.util.Utils; - -import java.util.List; - -public class StarredFragment extends ListFragment { - private StarredItemAdapter adapter; - private BrowserActivity mActivity = null; - - private SwipeRefreshLayout refreshLayout; - private ListView mListView; - private TextView mNoStarredView; - private View mProgressContainer; - private View mListContainer; - private TextView mErrorText; - private ActionMode mActionMode; - /*private LinearLayout mTaskActionBar; - private RelativeLayout mUnstarFiles;*/ - private static final int REFRESH_ON_RESUME = 0; - private static final int REFRESH_ON_PULL = 1; - private static final int REFRESH_ON_OVERFLOW_MENU = 2; - private static int mRefreshType = -1; - public static final String PASSWORD_DIALOG_STARREDFRAGMENT_TAG = "password_starredfragment"; - - private DataManager getDataManager() { - return mActivity.getDataManager(); - } - - public StarredItemAdapter getAdapter() { - return adapter; - } - - public interface OnStarredFileSelectedListener { - void onStarredFileSelected(SeafStarredFile starredFile); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - mActivity = (BrowserActivity) activity; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View root = inflater.inflate(R.layout.starred_fragment, container, false); - refreshLayout = (SwipeRefreshLayout) root.findViewById(R.id.swiperefresh); - mListView = (ListView) root.findViewById(android.R.id.list); - mNoStarredView = (TextView) root.findViewById(android.R.id.empty); - mListContainer = root.findViewById(R.id.listContainer); - mErrorText = (TextView) root.findViewById(R.id.error_message); - mProgressContainer = root.findViewById(R.id.progressContainer); - - mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { - @Override - public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { - startContextualActionMode(position); - return true; - } - }); - - refreshLayout.setColorSchemeResources(R.color.fancy_orange); - // Set a listener to be invoked when the list should be refreshed. - refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - mRefreshType = REFRESH_ON_PULL; - refreshView(); - } - }); - - return root; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - adapter = new StarredItemAdapter(mActivity); - setListAdapter(adapter); - - getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); - } - - @Override - public void onStart() { - super.onStart(); - } - - @Override - public void onStop() { - super.onStop(); - } - - @Override - public void onResume() { - super.onResume(); - mRefreshType = REFRESH_ON_RESUME; - refreshView(); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - } - - @Override - public void onDetach() { - mActivity = null; - super.onDetach(); - } - - public void refresh() { - mRefreshType = REFRESH_ON_OVERFLOW_MENU; - refreshView(); - } - - public void refreshView() { - - if (mActivity == null) - return; - - mErrorText.setVisibility(View.GONE); - mListContainer.setVisibility(View.VISIBLE); - if (!Utils.isNetworkOn()) { - refreshLayout.setRefreshing(false); - Toast.makeText(mActivity, getString(R.string.network_down), Toast.LENGTH_SHORT).show(); - } - List starredFiles = getDataManager().getCachedStarredFiles(); - boolean refreshTimeout = getDataManager().isStarredFilesRefreshTimeout(); - if (mRefreshType == REFRESH_ON_PULL || mRefreshType == REFRESH_ON_OVERFLOW_MENU - || starredFiles == null || refreshTimeout) { - ConcurrentAsyncTask.execute(new LoadStarredFilesTask(getDataManager())); - } else { - updateAdapterWithStarredFiles(starredFiles); - } - //mActivity.supportInvalidateOptionsMenu(); - } - - private void showError(String msg) { - mProgressContainer.setVisibility(View.GONE); - mListContainer.setVisibility(View.GONE); - - adapter.clear(); - adapter.notifyChanged(); - - mErrorText.setText(msg); - mErrorText.setVisibility(View.VISIBLE); - mErrorText.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - refreshView(); - } - }); - } - - private void showLoading(boolean show) { - mErrorText.setVisibility(View.GONE); - if (show) { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_in)); - mListContainer.startAnimation(AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_out)); - - mProgressContainer.setVisibility(View.VISIBLE); - mListContainer.setVisibility(View.INVISIBLE); - } else { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_out)); - mListContainer.startAnimation(AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_in)); - - mProgressContainer.setVisibility(View.GONE); - mListContainer.setVisibility(View.VISIBLE); - } - } - - private void updateAdapterWithStarredFiles(List starredFiles) { - adapter.clear(); - if (starredFiles.size() > 0) { - for (SeafStarredFile starred : starredFiles) { - adapter.add(starred); - } - adapter.notifyChanged(); - mListView.setVisibility(View.VISIBLE); - mNoStarredView.setVisibility(View.GONE); - } else { - mListView.setVisibility(View.GONE); - mNoStarredView.setVisibility(View.VISIBLE); - } - } - - @Override - public void onListItemClick(final ListView l, final View v, final int position, final long id) { - // handle action mode selections - if (mActionMode != null) { - // add or remove selection for current list item - if (adapter == null) return; - - adapter.toggleSelection(position); - updateContextualActionBar(); - return; - } - - final SeafStarredFile starredFile = (SeafStarredFile) adapter.getItem(position); - if (starredFile.isDir()) { - onStarredDirSelected(starredFile); - } else { - mActivity.onStarredFileSelected(starredFile); - } - } - - public void showPasswordDialog(String repoName, String repoID, TaskDialog.TaskDialogListener listener, String password) { - PasswordDialog passwordDialog = new PasswordDialog(); - passwordDialog.setRepo(repoName, repoID, mActivity.getAccount()); - if (password != null) { - passwordDialog.setPassword(password); - } - passwordDialog.setTaskDialogLisenter(listener); - passwordDialog.show(mActivity.getSupportFragmentManager(), PASSWORD_DIALOG_STARREDFRAGMENT_TAG); - } - - public void onStarredDirSelected(SeafStarredFile starDirent) { - final String repoID = starDirent.getRepoID(); - final SeafRepo repo = getDataManager().getCachedRepoByID(repoID); - final String repoName = repo.getRepoName(); - final String filePath = starDirent.getPath(); - - if (starDirent.isRepoEncrypted()) { - if (repo.encrypted && !getDataManager().getRepoPasswordSet(repo.repo_id)) { - String password = getDataManager().getRepoPassword(repo.repo_id); - showPasswordDialog(repo.repo_name, repo.repo_id, - new TaskDialog.TaskDialogListener() { - @Override - public void onTaskSuccess() { - WidgetUtils.showStarredRepo(mActivity, repoID, repoName, filePath); - } - }, password); - - } else { - WidgetUtils.showStarredRepo(mActivity, repoID, repoName, filePath); - } - } else { - WidgetUtils.showStarredRepo(mActivity, repoID, repoName, filePath); - } - } - - private void unStarFiles(List starredFiles) { - for (SeafStarredFile seafStarredFile : starredFiles) { - doUnStarFile(seafStarredFile.getRepoID(), seafStarredFile.getPath()); - } - } - - private void doUnStarFile(String repoID, String path) { - if (!Utils.isNetworkOn()) { - mActivity.showShortToast(mActivity, R.string.network_down); - return; - } - - StarItemsTask task = new StarItemsTask(mActivity, repoID, path, true); - task.setOnCallback(new OnCallback() { - @Override - public void onFailed() { - - } - - @Override - public void onSuccess() { - mRefreshType = REFRESH_ON_RESUME; - refreshView(); - adapter.deselectAllItems(); - mActionMode.setTitle(getResources(). - getQuantityString(R.plurals.transfer_list_items_selected, 0, 0)); - } - }); - ConcurrentAsyncTask.execute(task); - } - - public void doStarFile(String repoID, String path, String filename) { - if (!Utils.isNetworkOn()) { - mActivity.showShortToast(mActivity, R.string.network_down); - return; - } - - String p = Utils.pathJoin(path, filename); - - StarItemsTask task = new StarItemsTask(mActivity, repoID, p, false); - ConcurrentAsyncTask.execute(task); - } - - private class LoadStarredFilesTask extends SupportAsyncTask> { - - SeafException err = null; - - DataManager dataManager; - - public LoadStarredFilesTask(DataManager dataManager) { - super(mActivity); - this.dataManager = dataManager; - } - - @Override - protected void onPreExecute() { - if (mRefreshType != REFRESH_ON_PULL) - showLoading(true); - } - - @Override - protected List doInBackground(Void... params) { - - try { - return dataManager.getStarredFiles(); - } catch (SeafException e) { - err = e; - return null; - } - - } - - // onPostExecute displays the results of the AsyncTask. - @Override - protected void onPostExecute(List starredFiles) { - if (getContextParam() == null) - // this occurs if user navigation to another activity - return; - - if (mRefreshType == REFRESH_ON_RESUME || mRefreshType == REFRESH_ON_OVERFLOW_MENU) { - showLoading(false); - } else if (mRefreshType == REFRESH_ON_PULL) { - // Call onRefreshComplete when the list has been refreshed. - //mListView.onRefreshComplete(getDataManager().getLastPullToRefreshTime(DataManager.PULL_TO_REFRESH_LAST_TIME_FOR_STARRED_FRAGMENT)); - getDataManager().saveLastPullToRefreshTime(System.currentTimeMillis(), DataManager.PULL_TO_REFRESH_LAST_TIME_FOR_STARRED_FRAGMENT); - refreshLayout.setRefreshing(false); - } - - - if (err != null) { - if (err == SeafException.remoteWipedException) { - if (getContextParam() != null) { - getContextParam().completeRemoteWipe(); - } - } else { - showError(getString(R.string.error_when_load_starred)); - return; - } - } - - if (starredFiles == null) { - showError(getString(R.string.error_when_load_starred)); - return; - } - - updateAdapterWithStarredFiles(starredFiles); - } - } - - /** - * Start action mode for selecting and process multiple files/folders. - * The contextual action mode is a system implementation of ActionMode - * that focuses user interaction toward performing contextual actions. - * When a user enables this mode by selecting an item, - * a contextual action bar appears at the top of the screen - * to present actions the user can perform on the currently selected item(s). - *

- * While this mode is enabled, - * the user can select multiple items (if you allow it), deselect items, - * and continue to navigate within the activity (as much as you're willing to allow). - *

- * The action mode is disabled and the contextual action bar disappears - * when the user deselects all items, presses the BACK button, or selects the Done action on the left side of the bar. - *

- * see http://developer.android.com/guide/topics/ui/menus.html#CAB - */ - public void startContextualActionMode(int position) { - startContextualActionMode(); - - if (adapter == null) return; - - adapter.toggleSelection(position); - updateContextualActionBar(); - } - - public void startContextualActionMode() { - if (mActionMode == null) { - // start the actionMode - mActionMode = mActivity.startSupportActionMode(new ActionModeCallback()); - } - - } - - /** - * update state of contextual action bar (CAB) - */ - public void updateContextualActionBar() { - - if (mActionMode == null) { - // there are some selected items, start the actionMode - mActionMode = mActivity.startSupportActionMode(new ActionModeCallback()); - } else { - // Log.d(DEBUG_TAG, "mActionMode.setTitle " + adapter.getCheckedItemCount()); - mActionMode.setTitle(getResources().getQuantityString( - R.plurals.transfer_list_items_selected, - adapter.getCheckedItemCount(), - adapter.getCheckedItemCount())); - } - - } - - /** - * Represents a contextual mode of the user interface. - * Action modes can be used to provide alternative interaction modes and replace parts of the normal UI until finished. - * A Callback configures and handles events raised by a user's interaction with an action mode. - */ - class ActionModeCallback implements ActionMode.Callback { - private boolean allItemsSelected; - - public ActionModeCallback() { - } - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - // Inflate the menu for the contextual action bar (CAB) - MenuInflater inflater = mode.getMenuInflater(); - inflater.inflate(R.menu.starred_fragment_menu, menu); - if (adapter == null) return true; - - adapter.setActionModeOn(true); - adapter.notifyDataSetChanged(); - - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - // Here you can perform updates to the contextual action bar (CAB) due to - // an invalidate() request - return false; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - // Respond to clicks on the actions in the contextual action bar (CAB) - final List starredFiles = adapter.getSelectedItemsValues(); - if (CollectionUtils.isEmpty(starredFiles)) { - if (item.getItemId() != R.id.action_mode_select_all) { - mActivity.showShortToast(mActivity, R.string.action_mode_no_items_selected); - return true; - } - } - - switch (item.getItemId()) { - case R.id.action_mode_select_all: - if (!allItemsSelected) { - if (adapter == null) return true; - - adapter.selectAllItems(); - updateContextualActionBar(); - } else { - if (adapter == null) return true; - - adapter.deselectAllItems(); - updateContextualActionBar(); - } - - allItemsSelected = !allItemsSelected; - break; - case R.id.action_mode_delete: - unStarFiles(starredFiles); - break; - - default: - return false; - } - - return true; - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - if (adapter == null) return; - - adapter.setActionModeOn(false); - adapter.deselectAllItems(); - - // Here you can make any necessary updates to the activity when - // the contextual action bar (CAB) is removed. By default, selected items are deselected/unchecked. - mActionMode = null; - } - - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/star/StarredItemAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/star/StarredItemAdapter.java deleted file mode 100644 index be329c8ec..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/star/StarredItemAdapter.java +++ /dev/null @@ -1,223 +0,0 @@ -package com.seafile.seadroid2.ui.star; - -import android.util.SparseBooleanArray; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import com.google.common.collect.Lists; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.config.GlideLoadConfig; -import com.seafile.seadroid2.data.DataManager; -import com.seafile.seadroid2.data.SeafItem; -import com.seafile.seadroid2.data.SeafStarredFile; -import com.seafile.seadroid2.ui.WidgetUtils; -import com.seafile.seadroid2.ui.BrowserActivity; -import com.seafile.seadroid2.util.GlideApp; -import com.seafile.seadroid2.util.Utils; - -import java.util.ArrayList; -import java.util.List; - -public class StarredItemAdapter extends BaseAdapter { - private static final String DEBUG_TAG = "StarredItemAdapter"; - private ArrayList items; - private BrowserActivity mActivity; - private DataManager dataManager; - - private boolean actionModeOn; - private SparseBooleanArray mSelectedItemsIds; - private List mSelectedItemsPositions = Lists.newArrayList(); - private List mSelectedItemsValues = Lists.newArrayList(); - - public StarredItemAdapter(BrowserActivity activity) { - this.mActivity = activity; - items = Lists.newArrayList(); - mSelectedItemsIds = new SparseBooleanArray(); - dataManager = mActivity.getDataManager(); - - } - - @Override - public int getCount() { - return items.size(); - } - - public void clear() { - items.clear(); - } - - public void add(SeafItem entry) { - items.add(entry); - } - - public void notifyChanged() { - notifyDataSetChanged(); - } - - @Override - public SeafItem getItem(int position) { - return items.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - public void setActionModeOn(boolean actionModeOn) { - this.actionModeOn = actionModeOn; - } - - public void toggleSelection(int position) { - if (mSelectedItemsIds.get(position)) { - // unselected - mSelectedItemsIds.delete(position); - mSelectedItemsPositions.remove(Integer.valueOf(position)); - mSelectedItemsValues.remove(items.get(position)); - } else { - mSelectedItemsIds.put(position, true); - mSelectedItemsPositions.add(position); - mSelectedItemsValues.add((SeafStarredFile) items.get(position)); - } - - mActivity.getStarredFragment().updateContextualActionBar(); - notifyDataSetChanged(); - } - - public int getCheckedItemCount() { - return mSelectedItemsIds.size(); - } - - public List getSelectedItemsValues() { - return mSelectedItemsValues; - } - - public void setItems(List starredFiles) { - items.clear(); - items.addAll(starredFiles); - this.mSelectedItemsIds.clear(); - this.mSelectedItemsPositions.clear(); - this.mSelectedItemsValues.clear(); - } - - public void deselectAllItems() { - mSelectedItemsIds.clear(); - mSelectedItemsPositions.clear(); - mSelectedItemsValues.clear(); - notifyDataSetChanged(); - } - - public void selectAllItems() { - mSelectedItemsIds.clear(); - mSelectedItemsPositions.clear(); - mSelectedItemsValues.clear(); - for (int i = 0; i < items.size(); i++) { - mSelectedItemsIds.put(i, true); - mSelectedItemsPositions.add(i); - mSelectedItemsValues.add((SeafStarredFile) items.get(i)); - } - notifyDataSetChanged(); - } - - @Override - public View getView(final int position, View convertView, ViewGroup parent) { - final SeafItem item = items.get(position); - View view = convertView; - final ViewHolder viewHolder; - - if (convertView == null) { - view = LayoutInflater.from(mActivity).inflate(R.layout.starred_list_item, null); - ImageView multiSelect = view.findViewById(R.id.list_item_multi_select_btn); - TextView title = view.findViewById(R.id.starred_list_item_title); - TextView subtitle = view.findViewById(R.id.starred_list_item_subtitle); - ImageView icon = view.findViewById(R.id.starred_list_item_icon); - ImageView action = view.findViewById(R.id.starred_list_item_action); - viewHolder = new ViewHolder(title, subtitle, multiSelect, icon, action); - view.setTag(viewHolder); - } else { - viewHolder = (ViewHolder) convertView.getTag(); - } - viewHolder.title.setText(item.getTitle()); - viewHolder.subtitle.setText(item.getSubtitle()); - viewHolder.icon.setTag(R.id.imageloader_uri, item.getTitle()); - judgeRepo(item, viewHolder); - - if (Utils.isViewableImage(item.getTitle())) { - String url = dataManager.getImageThumbnailLink(((SeafStarredFile) item).getRepoName(), ((SeafStarredFile) item).getRepoID(), - ((SeafStarredFile) item).getPath(), WidgetUtils.getThumbnailWidth()); - if (url == null) { - judgeRepo(item, viewHolder); - } else { - GlideApp.with(viewHolder.icon) - .load(GlideLoadConfig.getGlideUrl(url)) - .apply(GlideLoadConfig.getOptions()) - .into(viewHolder.icon); - } - } else { - judgeRepo(item, viewHolder); - } - - if (actionModeOn) { - viewHolder.multiSelect.setVisibility(View.VISIBLE); - if (mSelectedItemsIds.get(position)) { - viewHolder.multiSelect.setImageResource(R.drawable.multi_select_item_checked); - } else - viewHolder.multiSelect.setImageResource(R.drawable.multi_select_item_unchecked); - - viewHolder.multiSelect.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (!mSelectedItemsIds.get(position)) { - viewHolder.multiSelect.setImageResource(R.drawable.multi_select_item_checked); - mSelectedItemsIds.put(position, true); - mSelectedItemsPositions.add(position); - mSelectedItemsValues.add((SeafStarredFile) item); - } else { - viewHolder.multiSelect.setImageResource(R.drawable.multi_select_item_unchecked); - mSelectedItemsIds.delete(position); - mSelectedItemsPositions.remove(Integer.valueOf(position)); - mSelectedItemsValues.remove(item); - } - - mActivity.onItemSelected(); - } - }); - } else - viewHolder.multiSelect.setVisibility(View.GONE); - - return view; - } - - private void judgeRepo(SeafItem item, ViewHolder viewHolder) { - if (((SeafStarredFile) item).isRepoEncrypted() && ((SeafStarredFile) item).isDir() && ((SeafStarredFile) item).getPath().equals("/")) { - viewHolder.icon.setImageResource(R.drawable.repo_encrypted); - - } else { - if (((SeafStarredFile) item).isDir() && ((SeafStarredFile) item).getPath().equals("/")) { - viewHolder.icon.setImageResource(R.drawable.repo); - } else { - viewHolder.icon.setImageResource(item.getIcon()); - } - } - } - - private static class ViewHolder { - TextView title, subtitle; - ImageView multiSelect, icon, action; - - public ViewHolder(TextView title, TextView subtitle, ImageView multiSelect, ImageView icon, ImageView action) { - super(); - this.multiSelect = multiSelect; - this.icon = icon; - this.action = action; - this.title = title; - this.subtitle = subtitle; - } - } - - -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/star/StarredQuickFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/star/StarredQuickFragment.java new file mode 100644 index 000000000..a3d753591 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/star/StarredQuickFragment.java @@ -0,0 +1,192 @@ +package com.seafile.seadroid2.ui.star; + +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; + +import com.blankj.utilcode.util.ToastUtils; +import com.chad.library.adapter4.BaseQuickAdapter; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.SeafException; +import com.seafile.seadroid2.ui.base.fragment.BaseFragmentWithVM; +import com.seafile.seadroid2.bottomsheetmenu.BottomSheetHelper; +import com.seafile.seadroid2.databinding.LayoutFrameSwipeRvBinding; +import com.seafile.seadroid2.data.model.ResultModel; +import com.seafile.seadroid2.data.model.star.StarredModel; +import com.seafile.seadroid2.ui.main.MainActivity; +import com.seafile.seadroid2.ui.main.MainViewModel; +import com.seafile.seadroid2.view.TipsViews; + +import java.util.List; + +import kotlin.Pair; + +public class StarredQuickFragment extends BaseFragmentWithVM { + private MainViewModel mainViewModel; + private LayoutFrameSwipeRvBinding binding; + private StarredAdapter adapter; + + public static StarredQuickFragment newInstance() { + Bundle args = new Bundle(); + StarredQuickFragment fragment = new StarredQuickFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (getViewModel() != null) { + getViewModel().disposeAll(); + } + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mainViewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = LayoutFrameSwipeRvBinding.inflate(inflater, container, false); + binding.swipeRefreshLayout.setOnRefreshListener(this::reload); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + initAdapter(); + + initViewModel(); + } + + private boolean isFirstLoadData = true; + + @Override + public void onResume() { + super.onResume(); + d("load data:onResume"); + if (isFirstLoadData) { + isFirstLoadData = false; + d("load data:isFirstLoadData"); + reload(); + } + } + + private void initAdapter() { + adapter = new StarredAdapter(); + TextView tipView = TipsViews.getTipTextView(requireContext()); + tipView.setText(R.string.no_starred_file); + tipView.setOnClickListener(v -> reload()); + adapter.setStateView(tipView); + adapter.setStateViewEnable(false); + + adapter.setOnItemClickListener((baseQuickAdapter, view, i) -> { + + StarredModel starredModel = adapter.getItems().get(i); + if (!starredModel.deleted) { + navTo(starredModel); + } else { + if (starredModel.isRepo()) { + ToastUtils.showLong(getString(R.string.library_not_found)); + } else if (starredModel.is_dir) { + ToastUtils.showLong(getString(R.string.op_exception_folder_deleted, starredModel.obj_name)); + } else { + ToastUtils.showLong(getString(R.string.file_not_found, starredModel.obj_name)); + } + } + + }); + + adapter.addOnItemChildClickListener(R.id.item_action, new BaseQuickAdapter.OnItemChildClickListener() { + @Override + public void onItemClick(@NonNull BaseQuickAdapter baseQuickAdapter, @NonNull View view, int i) { + showBottomSheet(adapter.getItems().get(i)); + } + }); + + binding.rv.setAdapter(createAdapterHelper(adapter).getAdapter()); + } + + private void showErrorTip() { + adapter.submitList(null); + TextView tipView = TipsViews.getTipTextView(requireContext()); + tipView.setText(R.string.error_when_load_starred); + tipView.setOnClickListener(v -> reload()); + adapter.setStateView(tipView); + adapter.setStateViewEnable(true); + } + + private void initViewModel() { + getViewModel().getRefreshLiveData().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + binding.swipeRefreshLayout.setRefreshing(aBoolean); + } + }); + + getViewModel().getExceptionLiveData().observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(Pair exceptionPair) { + showErrorTip(); + } + }); + + getViewModel().getListLiveData().observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(List starredModels) { + adapter.setStateViewEnable(true); + + adapter.notifyDataChanged(starredModels); + } + }); + + getViewModel().getUnStarredResultLiveData().observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(Pair pair) { + if (pair.getSecond().success) { + ToastUtils.showLong(R.string.success); + + mainViewModel.getOnForceRefreshRepoListLiveData().setValue(true); + + reload(); + } + } + }); + } + + private void reload() { + adapter.setStateViewEnable(false); + getViewModel().loadData(); + } + + private void showBottomSheet(StarredModel model) { + int rid = R.menu.bottom_sheet_unstarred; + BottomSheetHelper.showSheet(getActivity(), rid, menuItem -> { + if (menuItem.getItemId() == R.id.unstar) { + getViewModel().unStarItem(model.repo_id, model.path); + } + }); + } + + private void navTo(StarredModel model) { + Intent intent = new Intent(requireContext(), MainActivity.class); + intent.putExtra("repoID", model.repo_id); + intent.putExtra("repoName", model.repo_name); + intent.putExtra("path", model.path); + startActivity(intent); + } +} + diff --git a/app/src/main/java/com/seafile/seadroid2/ui/star/StarredViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/star/StarredViewHolder.java new file mode 100644 index 000000000..24033025b --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/star/StarredViewHolder.java @@ -0,0 +1,15 @@ +package com.seafile.seadroid2.ui.star; + +import androidx.annotation.NonNull; + +import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; +import com.seafile.seadroid2.databinding.ItemStarredBinding; + +public class StarredViewHolder extends BaseViewHolder { + public ItemStarredBinding binding; + + public StarredViewHolder(@NonNull ItemStarredBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/star/StarredViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/star/StarredViewModel.java new file mode 100644 index 000000000..f116e92d4 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/star/StarredViewModel.java @@ -0,0 +1,67 @@ +package com.seafile.seadroid2.ui.star; + +import androidx.lifecycle.MutableLiveData; + +import com.blankj.utilcode.util.ToastUtils; +import com.seafile.seadroid2.SeafException; +import com.seafile.seadroid2.data.remote.api.StarredService; +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.io.http.IO; +import com.seafile.seadroid2.data.model.ResultModel; +import com.seafile.seadroid2.data.model.star.StarredModel; +import com.seafile.seadroid2.data.model.star.StarredWrapperModel; + +import java.util.List; + +import io.reactivex.Single; +import io.reactivex.functions.Consumer; +import kotlin.Pair; + +public class StarredViewModel extends BaseViewModel { + private final MutableLiveData> listLiveData = new MutableLiveData<>(); + private final MutableLiveData> UnStarredResultLiveData = new MutableLiveData<>(); + + public MutableLiveData> getListLiveData() { + return listLiveData; + } + + public MutableLiveData> getUnStarredResultLiveData() { + return UnStarredResultLiveData; + } + + public void loadData() { + getRefreshLiveData().setValue(true); + Single flowable = IO.getSingleton().execute(StarredService.class).getStarItems(); + addSingleDisposable(flowable, new Consumer() { + @Override + public void accept(StarredWrapperModel starredWrapperModel) throws Exception { + getRefreshLiveData().setValue(false); + + if (starredWrapperModel == null) { + return; + } + + getListLiveData().setValue(starredWrapperModel.starred_item_list); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getRefreshLiveData().setValue(false); + getExceptionLiveData().setValue(new Pair<>(400, SeafException.networkException)); + String msg = getErrorMsgByThrowable(throwable); + ToastUtils.showLong(msg); + } + }); + } + + public void unStarItem(String repoId, String path) { + Single flowable = IO.getSingleton().execute(StarredService.class).unStarItem(repoId, path); + addSingleDisposable(flowable, new Consumer() { + @Override + public void accept(ResultModel resultModel) throws Exception { + getUnStarredResultLiveData().setValue(new Pair<>(path, resultModel)); + } + }); + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/transfer/TransferActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/transfer/TransferActivity.java index 99f4e96eb..5ee0573a8 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/transfer/TransferActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/transfer/TransferActivity.java @@ -2,93 +2,98 @@ import android.content.Intent; import android.os.Bundle; -import com.google.android.material.tabs.TabLayout; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; -import androidx.viewpager.widget.ViewPager; -import androidx.appcompat.view.ActionMode; -import androidx.appcompat.widget.Toolbar; +import android.text.TextUtils; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import androidx.annotation.NonNull; +import androidx.appcompat.view.ActionMode; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.Fragment; +import androidx.viewpager.widget.ViewPager; + +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; +import com.google.android.material.textfield.TextInputLayout; import com.seafile.seadroid2.R; +import com.seafile.seadroid2.databinding.TransferListLayoutBinding; import com.seafile.seadroid2.notification.BaseNotificationProvider; import com.seafile.seadroid2.notification.DownloadNotificationProvider; import com.seafile.seadroid2.ui.BaseActivity; +import com.seafile.seadroid2.ui.adapter.ViewPager2Adapter; + +import java.util.ArrayList; +import java.util.List; public class TransferActivity extends BaseActivity implements Toolbar.OnMenuItemClickListener { - private static final String DEBUG_TAG = "TransferActivity"; + private TransferListLayoutBinding binding; + private final List fragments = new ArrayList<>(); private TransferTaskAdapter.TaskType whichTab = TransferTaskAdapter.TaskType.DOWNLOAD_TASK; - private TransferTabsAdapter tabsAdapter; - private ViewPager pager; - private TabLayout mTabLayout; - private Menu overFlowMenu = null; @Override protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); Bundle extras = intent.getExtras(); - if (extras != null) { - if (extras.containsKey(BaseNotificationProvider.NOTIFICATION_MESSAGE_KEY)) { - // extract the extra-data in the Notification - String msg = extras.getString(BaseNotificationProvider.NOTIFICATION_MESSAGE_KEY); - if (msg.equals(DownloadNotificationProvider.NOTIFICATION_OPEN_DOWNLOAD_TAB)) { - whichTab = TransferTaskAdapter.TaskType.DOWNLOAD_TASK; - pager.setCurrentItem(0); - } else if (msg.equals(BaseNotificationProvider.NOTIFICATION_OPEN_UPLOAD_TAB)) { - whichTab = TransferTaskAdapter.TaskType.UPLOAD_TASK; - pager.setCurrentItem(1); - } - } + if (extras == null) { + return; + } + + if (!extras.containsKey(BaseNotificationProvider.NOTIFICATION_MESSAGE_KEY)) { + return; + } + + // extract the extra-data in the Notification + String msg = extras.getString(BaseNotificationProvider.NOTIFICATION_MESSAGE_KEY); + if (TextUtils.equals(msg, BaseNotificationProvider.NOTIFICATION_OPEN_DOWNLOAD_TAB)) { + whichTab = TransferTaskAdapter.TaskType.DOWNLOAD_TASK; + binding.pager.setCurrentItem(0); + } else if (TextUtils.equals(msg, BaseNotificationProvider.NOTIFICATION_OPEN_UPLOAD_TAB)) { + whichTab = TransferTaskAdapter.TaskType.UPLOAD_TASK; + binding.pager.setCurrentItem(1); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.transfer_list_layout); + + binding = TransferListLayoutBinding.inflate(getLayoutInflater()); + + setContentView(binding.getRoot()); findViewById(R.id.view_toolbar_bottom_line).setVisibility(View.GONE); - tabsAdapter = new TransferTabsAdapter(getSupportFragmentManager()); - pager = (ViewPager) findViewById(R.id.transfer_list_pager); - pager.setAdapter(tabsAdapter); - mTabLayout = (TabLayout) findViewById(R.id.sliding_tabs); - mTabLayout.setTabsFromPagerAdapter(tabsAdapter); - mTabLayout.setupWithViewPager(pager); - mTabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { - @Override - public void onTabSelected(TabLayout.Tab tab) { - // Log.d(DEBUG_TAG, "current tab index " + position); - whichTab = (tab.getPosition() == 0 - ? TransferTaskAdapter.TaskType.DOWNLOAD_TASK - : TransferTaskAdapter.TaskType.UPLOAD_TASK); - - ActionMode mode = null; - if (whichTab == TransferTaskAdapter.TaskType.DOWNLOAD_TASK - && getUploadTaskFragment() != null) { - // slide from Upload tab to Download tab, - // so hide the CAB of UploadTaskFragment - mode = getUploadTaskFragment().getActionMode(); - getUploadTaskFragment().deselectItems(); - } else if (whichTab == TransferTaskAdapter.TaskType.UPLOAD_TASK - && getDownloadTaskFragment() != null) { - // slide from Download tab to Upload tab, - // so hide the CAB of DownloadTaskFragment - mode = getDownloadTaskFragment().getActionMode(); - getDownloadTaskFragment().deselectItems(); - } + initTabLayout(); + initViewPager(); + + Toolbar toolbar = getActionBarToolbar(); + setSupportActionBar(toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setTitle(R.string.transfer_tasks); + } + + /** this is hacky to explicitly call onNewIntent() + * because it was never called when start the TransferActivity + * by notification bar */ + onNewIntent(getIntent()); + } - if (mode != null) - mode.finish(); + private void initTabLayout() { + binding.slidingTabs.setTabIndicatorAnimationMode(TabLayout.INDICATOR_ANIMATION_MODE_ELASTIC); + binding.slidingTabs.setSelectedTabIndicator(R.drawable.cat_tabs_rounded_line_indicator); + binding.slidingTabs.setTabIndicatorFullWidth(false); + binding.slidingTabs.setTabGravity(TabLayout.GRAVITY_CENTER); - supportInvalidateOptionsMenu(); - pager.setCurrentItem(tab.getPosition()); + binding.slidingTabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { + @Override + public void onTabSelected(TabLayout.Tab tab) { + onTabLayoutSelected(); } @Override @@ -101,25 +106,65 @@ public void onTabReselected(TabLayout.Tab tab) { } }); + } - Toolbar toolbar = getActionBarToolbar(); - setSupportActionBar(toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle(R.string.transfer_tasks); + private void onTabLayoutSelected() { + whichTab = (binding.slidingTabs.getSelectedTabPosition() == 0 + ? TransferTaskAdapter.TaskType.DOWNLOAD_TASK + : TransferTaskAdapter.TaskType.UPLOAD_TASK); - /** this is hacky to explicitly call onNewIntent() - * because it was never called when start the TransferActivity - * by notification bar */ - onNewIntent(getIntent()); + ActionMode mode = null; + if (whichTab == TransferTaskAdapter.TaskType.DOWNLOAD_TASK + && getUploadTaskFragment() != null) { + // slide from Upload tab to Download tab, + // so hide the CAB of UploadTaskFragment + mode = getUploadTaskFragment().getActionMode(); + getUploadTaskFragment().deselectItems(); + } else if (whichTab == TransferTaskAdapter.TaskType.UPLOAD_TASK + && getDownloadTaskFragment() != null) { + // slide from Download tab to Upload tab, + // so hide the CAB of DownloadTaskFragment + mode = getDownloadTaskFragment().getActionMode(); + getDownloadTaskFragment().deselectItems(); + } + + if (mode != null) + mode.finish(); + + supportInvalidateOptionsMenu(); + } + + private void initViewPager() { + fragments.clear(); + fragments.add(new DownloadTaskFragment()); + fragments.add(new UploadTaskFragment()); + + ViewPager2Adapter viewPager2Adapter = new ViewPager2Adapter(this); + viewPager2Adapter.addFragments(fragments); + binding.pager.setAdapter(viewPager2Adapter); + + String downloadTabTitle = getString(R.string.transfer_tabs_downloads); + String uploadTabTitle = getString(R.string.transfer_tabs_uploads); + + String[] tabArray = new String[]{ + downloadTabTitle, uploadTabTitle + }; + + TabLayoutMediator mediator = new TabLayoutMediator(binding.slidingTabs, binding.pager, new TabLayoutMediator.TabConfigurationStrategy() { + @Override + public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) { + tab.setText(tabArray[position]); + } + }); + + mediator.attach(); } public void onItemSelected() { // update CAB title - if (whichTab == TransferTaskAdapter.TaskType.DOWNLOAD_TASK - && getDownloadTaskFragment() != null) { + if (whichTab == TransferTaskAdapter.TaskType.DOWNLOAD_TASK && getDownloadTaskFragment() != null) { getDownloadTaskFragment().updateContextualActionBar(); - } else if (whichTab == TransferTaskAdapter.TaskType.UPLOAD_TASK - && getUploadTaskFragment() != null) { + } else if (whichTab == TransferTaskAdapter.TaskType.UPLOAD_TASK && getUploadTaskFragment() != null) { getUploadTaskFragment().updateContextualActionBar(); } } @@ -127,18 +172,21 @@ && getUploadTaskFragment() != null) { @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { - case KeyEvent.KEYCODE_MENU: - if (overFlowMenu != null) { - overFlowMenu.performIdentifierAction(R.id.transfer_overflow_menu, 0); - } + case KeyEvent.KEYCODE_MENU: + if (overFlowMenu != null) { + overFlowMenu.performIdentifierAction(R.id.transfer_overflow_menu, 0); + } } return super.onKeyUp(keyCode, event); } @Override public boolean onCreateOptionsMenu(Menu menu) { - getActionBarToolbar().inflateMenu(R.menu.transfer_list_menu); - getActionBarToolbar().setOnMenuItemClickListener(this); + + Toolbar toolbar = getActionBarToolbar(); + toolbar.inflateMenu(R.menu.transfer_list_menu); + toolbar.setOnMenuItemClickListener(this); + return true; } @@ -180,74 +228,14 @@ public boolean onMenuItemClick(MenuItem item) { } public DownloadTaskFragment getDownloadTaskFragment() { - return (DownloadTaskFragment)getFragment(0); + return (DownloadTaskFragment) getFragment(0); } public UploadTaskFragment getUploadTaskFragment() { - return (UploadTaskFragment)getFragment(1); + return (UploadTaskFragment) getFragment(1); } public Fragment getFragment(int index) { - return getSupportFragmentManager().findFragmentByTag(makeFragmentName(index)); - } - - private String makeFragmentName(int index) { - return "android:switcher:" + R.id.transfer_list_pager + ":" + index; - } - - /** - * Adapter for {@link ViewPager} to bind DownloadTaskFragment and UploadTaskFragment - */ - public class TransferTabsAdapter extends FragmentPagerAdapter { - - private String downloadTabTitle; - private String uploadTabTitle; - - public TransferTabsAdapter(FragmentManager fm) { - super(fm); - downloadTabTitle = getString(R.string.transfer_tabs_downloads); - uploadTabTitle = getString(R.string.transfer_tabs_uploads); - } - - private DownloadTaskFragment downloadsFragment = null; - private UploadTaskFragment uploadsFragment = null; - - @Override - public Fragment getItem(int position) { - switch (position) { - case 0: - if (downloadsFragment == null) { - downloadsFragment = new DownloadTaskFragment(); - } - return downloadsFragment; - case 1: - if (uploadsFragment == null) { - uploadsFragment = new UploadTaskFragment(); - } - return uploadsFragment; - default: - return new Fragment(); - } - } - - @Override - public CharSequence getPageTitle(int position) { - switch (position) { - case 0: - return downloadTabTitle; - case 1: - return uploadTabTitle; - - default: - return null; - } - } - - @Override - public int getCount() { - return 2; - } - + return fragments.get(index); } - } \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/ui/transfer/TransferTaskFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/transfer/TransferTaskFragment.java index 65f13886f..c8d70d64a 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/transfer/TransferTaskFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/transfer/TransferTaskFragment.java @@ -22,6 +22,7 @@ import android.widget.ListView; import android.widget.TextView; +import com.blankj.utilcode.util.ToastUtils; import com.google.common.collect.Lists; import com.seafile.seadroid2.R; import com.seafile.seadroid2.transfer.TransferService; @@ -275,7 +276,7 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { final List selectedIds = adapter.getSelectedIds(); if (selectedIds.isEmpty()) { if (item.getItemId() != R.id.action_mode_select_all) { - mActivity.showShortToast(mActivity, R.string.action_mode_no_items_selected); + ToastUtils.showLong(R.string.action_mode_no_items_selected); return true; } } @@ -285,7 +286,7 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { List ids = adapter.getSelectedIds(); if (ids != null) { if (ids.size() == 0) { - mActivity.showShortToast(mActivity, R.string.action_mode_no_items_selected); + ToastUtils.showLong(R.string.action_mode_no_items_selected); return true; } @@ -297,7 +298,7 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { List restartIds = adapter.getSelectedIds(); if (restartIds != null) { if (restartIds.size() == 0) { - mActivity.showShortToast(mActivity, R.string.action_mode_no_items_selected); + ToastUtils.showLong(R.string.action_mode_no_items_selected); return true; } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/viewholder/GroupItemViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/viewholder/GroupItemViewHolder.java new file mode 100644 index 000000000..9b0695621 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/viewholder/GroupItemViewHolder.java @@ -0,0 +1,15 @@ +package com.seafile.seadroid2.ui.viewholder; + +import androidx.annotation.NonNull; + +import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; +import com.seafile.seadroid2.databinding.ItemGroupItemBinding; + +public class GroupItemViewHolder extends BaseViewHolder { + public ItemGroupItemBinding binding; + + public GroupItemViewHolder(@NonNull ItemGroupItemBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/webview/SeaWebViewActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/webview/SeaWebViewActivity.java index f6f41c0ac..b844e5f44 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/webview/SeaWebViewActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/webview/SeaWebViewActivity.java @@ -19,7 +19,6 @@ import androidx.annotation.NonNull; import androidx.appcompat.widget.Toolbar; -import androidx.core.content.ContextCompat; import com.blankj.utilcode.util.ActivityUtils; import com.seafile.seadroid2.R; @@ -53,12 +52,19 @@ public static void openSdoc(Context context, String repoName, String repoID, Str ActivityUtils.startActivity(intent); } - public static void openThis(Context context, String url) { + public static void openUrl(Context context, String url) { Intent intent = new Intent(context, SeaWebViewActivity.class); intent.putExtra("targetUrl", url); ActivityUtils.startActivity(intent); } + public static void openUrlDirectly(Context context, String url) { + Intent intent = new Intent(context, SeaWebViewActivity.class); + intent.putExtra("targetUrl", url); + intent.putExtra("isDirect", true); + ActivityUtils.startActivity(intent); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -78,17 +84,26 @@ protected void onCreate(Bundle savedInstanceState) { throw new IllegalArgumentException("repoId is null"); } - targetUrl = buildSdocUrl(); + Account account = SupportAccountManager.getInstance().getCurrentAccount(); + if (account != null) { + targetUrl = buildSdocUrl(account.server); + } else { + throw new IllegalArgumentException("no login"); + } } initUI(); //let's go - mWebView.load(targetUrl); + if (intent.hasExtra("isDirect") && intent.getBooleanExtra("isDirect", false)) { + mWebView.loadDirectly(targetUrl); + } else { + mWebView.load(targetUrl); + } } - private String buildSdocUrl() { - return SupportAccountManager.getInstance().getCurrentAccount().server + "lib/" + mRepoID + "/file" + mFilePath; + private String buildSdocUrl(String server) { + return server + "lib/" + mRepoID + "/file" + mFilePath; } private void initUI() { @@ -97,9 +112,7 @@ private void initUI() { toolbar = findViewById(R.id.toolbar_actionbar); toolbar.setTitle(""); - toolbar.setBackgroundColor(ContextCompat.getColor(this, R.color.white)); setSupportActionBar(toolbar); - toolbar.setNavigationIcon(R.drawable.baseline_arrow_back_24); toolbar.setNavigationOnClickListener(v -> { finish(); }); @@ -155,19 +168,19 @@ public void onBackPressed() { } } - @Override - public boolean onCreateOptionsMenu(@NonNull Menu menu) { - getMenuInflater().inflate(R.menu.menu_sea_webview, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - if (item.getItemId() == R.id.download) { - download(); - } - return super.onOptionsItemSelected(item); - } +// @Override +// public boolean onCreateOptionsMenu(@NonNull Menu menu) { +// getMenuInflater().inflate(R.menu.menu_sea_webview, menu); +// return true; +// } +// +// @Override +// public boolean onOptionsItemSelected(@NonNull MenuItem item) { +// if (item.getItemId() == R.id.download) { +// download(); +// } +// return super.onOptionsItemSelected(item); +// } private void download() { if (txService == null) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/windowpreferences/WindowPreferencesManager.java b/app/src/main/java/com/seafile/seadroid2/ui/windowpreferences/WindowPreferencesManager.java new file mode 100644 index 000000000..c479d1f07 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/windowpreferences/WindowPreferencesManager.java @@ -0,0 +1,91 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.seafile.seadroid2.ui.windowpreferences; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.view.Window; +import android.view.WindowInsets; + +import androidx.core.view.OnApplyWindowInsetsListener; +import androidx.core.view.ViewCompat; + +import com.google.android.material.internal.EdgeToEdgeUtils; + +/** Helper that saves the current window preferences for the Catalog. */ +public class WindowPreferencesManager { + + private static final String PREFERENCES_NAME = "window_preferences"; + private static final String KEY_EDGE_TO_EDGE_ENABLED = "edge_to_edge_enabled"; + + private final Context context; + private final OnApplyWindowInsetsListener listener; + + @SuppressLint("WrongConstant") + public WindowPreferencesManager(Context context) { + this.context = context; + this.listener = + (v, insets) -> { + if (v.getResources().getConfiguration().orientation + != Configuration.ORIENTATION_LANDSCAPE) { + return insets; + } + if (VERSION.SDK_INT >= VERSION_CODES.R) { + v.setPadding( + insets.getInsets(WindowInsets.Type.systemBars()).left, + 0, + insets.getInsets(WindowInsets.Type.systemBars()).right, + insets.getInsets(WindowInsets.Type.systemBars()).bottom); + } else { + v.setPadding( + insets.getStableInsetLeft(), + 0, + insets.getStableInsetRight(), + insets.getStableInsetBottom()); + } + return insets; + }; + } + + @SuppressWarnings("ApplySharedPref") + public void toggleEdgeToEdgeEnabled() { + getSharedPreferences() + .edit() + .putBoolean(KEY_EDGE_TO_EDGE_ENABLED, !isEdgeToEdgeEnabled()) + .commit(); + } + + @SuppressWarnings("RestrictTo") + public void applyEdgeToEdgePreference(Window window) { + EdgeToEdgeUtils.applyEdgeToEdge(window, isEdgeToEdgeEnabled()); + ViewCompat.setOnApplyWindowInsetsListener( + window.getDecorView(), isEdgeToEdgeEnabled() ? listener : null); + } + + public boolean isEdgeToEdgeEnabled() { + return getSharedPreferences() + .getBoolean(KEY_EDGE_TO_EDGE_ENABLED, VERSION.SDK_INT >= VERSION_CODES.Q); + } + + private SharedPreferences getSharedPreferences() { + return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/util/ContactsDialog.java b/app/src/main/java/com/seafile/seadroid2/util/ContactsDialog.java index fb7a26ec8..741bba321 100755 --- a/app/src/main/java/com/seafile/seadroid2/util/ContactsDialog.java +++ b/app/src/main/java/com/seafile/seadroid2/util/ContactsDialog.java @@ -18,7 +18,7 @@ //import com.seafile.seadroid2.R; //import com.seafile.seadroid2.SeadroidApplication; //import com.seafile.seadroid2.SeafException; -//import com.seafile.seadroid2.SettingsManager; +//import com.seafile.seadroid2.util.sp.SettingsManager; //import com.seafile.seadroid2.account.Account; //import com.seafile.seadroid2.account.AccountManager; //import com.seafile.seadroid2.data.DataManager; diff --git a/app/src/main/java/com/seafile/seadroid2/util/FileExports.java b/app/src/main/java/com/seafile/seadroid2/util/FileExports.java index e955cb6fe..30a77d736 100644 --- a/app/src/main/java/com/seafile/seadroid2/util/FileExports.java +++ b/app/src/main/java/com/seafile/seadroid2/util/FileExports.java @@ -7,6 +7,7 @@ import android.os.Environment; import android.os.ParcelFileDescriptor; import android.provider.MediaStore; + import androidx.annotation.RequiresApi; import com.blankj.utilcode.util.FileUtils; @@ -27,12 +28,30 @@ public static void exportFileAndroid10AndAbove(String fileName, String mimeType, Uri uri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, cv); if (uri != null) { - ParcelFileDescriptor descriptor = contentResolver.openFileDescriptor(uri, "w"); - if (descriptor != null) { - FileOutputStream fos = new FileOutputStream(descriptor.getFileDescriptor()); - FileInputStream fis = new FileInputStream(file); - copyStream(fis, fos); + ParcelFileDescriptor descriptor = null; + FileOutputStream fos = null; + FileInputStream fis = null; + //TODO + try { + descriptor = contentResolver.openFileDescriptor(uri, "w"); + if (descriptor != null) { + fos = new FileOutputStream(descriptor.getFileDescriptor()); + fis = new FileInputStream(file); + copyStream(fis, fos); + } + + } finally { + if (fos != null) { + fos.close(); + } + if (fis != null) { + fis.close(); + } + if (descriptor != null) { + descriptor.close(); + } } + } else { File downloadFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); // @@ -41,6 +60,7 @@ public static void exportFileAndroid10AndAbove(String fileName, String mimeType, FileUtils.copy(file, dest); } } + } private static void copyStream(InputStream inputStream, FileOutputStream outputStream) throws IOException { diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileTools.java b/app/src/main/java/com/seafile/seadroid2/util/FileTools.java similarity index 93% rename from app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileTools.java rename to app/src/main/java/com/seafile/seadroid2/util/FileTools.java index 478d08766..db2128b6e 100644 --- a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileTools.java +++ b/app/src/main/java/com/seafile/seadroid2/util/FileTools.java @@ -1,9 +1,10 @@ -package com.seafile.seadroid2.folderbackup.selectfolder; +package com.seafile.seadroid2.util; import android.content.Context; import android.os.storage.StorageManager; +import android.text.TextUtils; -import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.ui.selector.folder_selector.StringTools; import java.io.File; import java.lang.reflect.Method; @@ -13,7 +14,7 @@ public class FileTools { public static File getFileByPath(String path) { - return StringTools.isEmpty(path) ? null : new File(path); + return TextUtils.isEmpty(path) ? null : new File(path); } public static boolean rename(String filePath, String newName) { @@ -25,7 +26,7 @@ public static boolean rename(File file, String newName) { return false; } else if (!file.exists()) { return false; - } else if (StringTools.isEmpty(newName)) { + } else if (TextUtils.isEmpty(newName)) { return false; } else if (newName.equals(file.getName())) { return true; @@ -71,7 +72,7 @@ public static boolean isFile(File file) { } public static String getDirName(String filePath) { - if (StringTools.isEmpty(filePath)) { + if (TextUtils.isEmpty(filePath)) { return ""; } else { int lastSep1 = filePath.lastIndexOf(File.separator); @@ -89,7 +90,7 @@ public static String getParentPath(File file) { } public static String getParentPath(String filePath) { - if (StringTools.isEmpty(filePath)) { + if (TextUtils.isEmpty(filePath)) { return ""; } else { int lastSep = filePath.lastIndexOf(File.separator); @@ -98,7 +99,7 @@ public static String getParentPath(String filePath) { } public static String getFileName(String filePath) { - if (StringTools.isEmpty(filePath)) { + if (TextUtils.isEmpty(filePath)) { return ""; } else { int lastSep = filePath.lastIndexOf(File.separator); @@ -107,7 +108,7 @@ public static String getFileName(String filePath) { } public static String getFileNameNoExtension(String filePath) { - if (StringTools.isEmpty(filePath)) { + if (TextUtils.isEmpty(filePath)) { return ""; } else { int lastPoi = filePath.lastIndexOf(46); @@ -121,7 +122,7 @@ public static String getFileNameNoExtension(String filePath) { } public static String getFileExtension(String filePath) { - if (StringTools.isEmpty(filePath)) { + if (TextUtils.isEmpty(filePath)) { return ""; } else { int lastPoi = filePath.lastIndexOf(46); diff --git a/app/src/main/java/com/seafile/seadroid2/util/SystemSwitchUtils.java b/app/src/main/java/com/seafile/seadroid2/util/SystemSwitchUtils.java index 75f45b9c4..bd37e9411 100644 --- a/app/src/main/java/com/seafile/seadroid2/util/SystemSwitchUtils.java +++ b/app/src/main/java/com/seafile/seadroid2/util/SystemSwitchUtils.java @@ -16,7 +16,7 @@ public class SystemSwitchUtils { private ConnectivityManager connManager; private static SystemSwitchUtils util; - public static SystemSwitchUtils getInstance(Context context){ + public static SystemSwitchUtils getInstance(Context context) { if (util == null) { util = new SystemSwitchUtils(context); } @@ -32,6 +32,7 @@ private SystemSwitchUtils(Context context) { /** * Open Sync + * * @return */ @SuppressWarnings("deprecation") @@ -98,6 +99,8 @@ public static String obj_type(Context ct, String obj_type, String op_type) { return ct.getString(R.string.recover_library); } else if (op_type.equals("edit")) { return ct.getString(R.string.edit); + } else if (op_type.equals("recover")) { + return ct.getString(R.string.recover_library); } else { return ""; } @@ -114,6 +117,8 @@ public static String obj_type(Context ct, String obj_type, String op_type) { return ct.getString(R.string.move_folder); } else if (op_type.equals("edit")) { return ct.getString(R.string.edit); + } else if (op_type.equals("recover")) { + return ct.getString(R.string.recover_folder); } else { return ""; } @@ -132,6 +137,8 @@ public static String obj_type(Context ct, String obj_type, String op_type) { return ct.getString(R.string.update_file); } else if (op_type.equals("edit")) { return ct.getString(R.string.edit); + } else if (op_type.equals("recover")) { + return ct.getString(R.string.recover_file); } else { return ""; } diff --git a/app/src/main/java/com/seafile/seadroid2/util/TUtil.java b/app/src/main/java/com/seafile/seadroid2/util/TUtil.java new file mode 100644 index 000000000..29f789b04 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/util/TUtil.java @@ -0,0 +1,33 @@ +package com.seafile.seadroid2.util; + +import java.lang.reflect.ParameterizedType; + +public class TUtil { + public static T getT(Object o, int i) { + try { + return ( + (Class) + ( + (ParameterizedType) + ( + //get parent class + o.getClass().getGenericSuperclass() + ) + ).getActualTypeArguments()[i] + ) + .newInstance(); + } catch (InstantiationException | ClassCastException | IllegalAccessException e) { + SLogs.e(e); + } + return null; + } + + public static Class forName(String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/util/TakeCameras.java b/app/src/main/java/com/seafile/seadroid2/util/TakeCameras.java new file mode 100644 index 000000000..e66bbb882 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/util/TakeCameras.java @@ -0,0 +1,76 @@ +package com.seafile.seadroid2.util; + +import android.content.Context; +import android.net.Uri; +import android.os.Environment; + +import androidx.core.content.FileProvider; + +import com.seafile.seadroid2.BuildConfig; + +import java.io.File; + +public class TakeCameras { + + public static String getStoragePictureFolder(Context context) { + return Environment.DIRECTORY_PICTURES; + } + + public static String getStorageDownloadFolder(Context context) { + return Environment.DIRECTORY_DOWNLOADS; + } + + // -> /storage/emulated/0/Android/data/package/files/Pictures + public static File getStoragePath(Context context) { + return context.getExternalFilesDir(getStoragePictureFolder(context)); + } + + public static Uri buildTakePhotoUri(Context context) { + File parentFolder = getStoragePath(context); + File file = new File(parentFolder, "sd_photo_" + System.currentTimeMillis() + ".jpg"); + return FileProvider.getUriForFile(context, BuildConfig.FILE_PROVIDER_AUTHORITIES, file); + } + + public static Uri buildTakePhotoUriAfterCleanOldCacheFiles(Context context) { + File parentFolder = getStoragePath(context); + File[] fs = parentFolder.listFiles(); + if (fs != null) { + for (File f : fs) { + boolean s = f.delete(); + SLogs.d(s + "->" + f.getAbsolutePath()); + } + } + + File file = new File(parentFolder, "sd_photo_" + System.currentTimeMillis() + ".jpg"); + return FileProvider.getUriForFile(context, BuildConfig.FILE_PROVIDER_AUTHORITIES, file); + } + + public static Uri buildTakeVideoUriAfterCleanOldFiles(Context context) { + File parentFolder = context.getExternalFilesDir(Environment.DIRECTORY_DCIM); + if (parentFolder == null) { + return null; + } + + File[] fs = parentFolder.listFiles(); + if (fs != null) { + for (File f : fs) { + boolean s = f.delete(); + SLogs.d(s + "->" + f.getAbsolutePath()); + } + } + + File file = new File(parentFolder, "st_video_" + System.currentTimeMillis() + ".mp4"); + return FileProvider.getUriForFile(context, BuildConfig.FILE_PROVIDER_AUTHORITIES, file); + } + + + /** + * for take photo + * {@link #buildTakePhotoUriAfterCleanOldCacheFiles(Context)} + */ + public static File uri2File(Context context, Uri u) { + File parentFile = TakeCameras.getStoragePath(context); + String p = u.getPath().replace("/external_files_path/" + getStoragePictureFolder(context), ""); + return new File(parentFile.getPath() + p); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/util/Times.java b/app/src/main/java/com/seafile/seadroid2/util/Times.java new file mode 100644 index 000000000..6ad683330 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/util/Times.java @@ -0,0 +1,13 @@ +package com.seafile.seadroid2.util; + +import com.blankj.utilcode.util.TimeUtils; +import com.seafile.seadroid2.config.DateFormatType; + +import java.util.Date; + +public class Times { + public static long convertMtime2Long(String mtime) { + Date date = TimeUtils.string2Date(mtime, DateFormatType.DATE_XXX); + return TimeUtils.date2Millis(date); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/util/Utils.java b/app/src/main/java/com/seafile/seadroid2/util/Utils.java index 4ae42eaee..b410ffad6 100644 --- a/app/src/main/java/com/seafile/seadroid2/util/Utils.java +++ b/app/src/main/java/com/seafile/seadroid2/util/Utils.java @@ -25,7 +25,6 @@ import android.provider.OpenableColumns; import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; import android.text.TextUtils; import android.text.format.DateFormat; @@ -34,17 +33,14 @@ import android.view.inputmethod.InputMethodManager; import android.webkit.MimeTypeMap; -import com.blankj.utilcode.util.StringUtils; -import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.gson.JsonObject; import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeadroidApplication; -import com.seafile.seadroid2.SettingsManager; -import com.seafile.seadroid2.cameraupload.MediaSchedulerService; +import com.seafile.seadroid2.config.Constants; +import com.seafile.seadroid2.util.sp.SettingsManager; +import com.seafile.seadroid2.ui.camera_upload.MediaSchedulerService; import com.seafile.seadroid2.data.SeafRepo; -import com.seafile.seadroid2.fileschooser.SelectableFile; import org.json.JSONArray; import org.json.JSONException; @@ -79,7 +75,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; -import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.Iterator; @@ -179,7 +174,7 @@ public static String getParentPath(String path) { } if (path.endsWith("/")) { - path = path.substring(0,path.lastIndexOf("/")); + path = path.substring(0, path.lastIndexOf("/")); } String parent = path.substring(0, path.lastIndexOf("/")); @@ -525,15 +520,6 @@ public static void copyFile(File src, File dst) throws IOException { out.close(); } - /************ MutiFileChooser ************/ - private static Comparator mComparator = new Comparator() { - public int compare(SelectableFile f1, SelectableFile f2) { - // Sort alphabetically by lower case, which is much cleaner - return f1.getName().toLowerCase().compareTo( - f2.getName().toLowerCase()); - } - }; - private static FileFilter mFileFilter = new FileFilter() { public boolean accept(File file) { final String fileName = file.getName(); @@ -550,40 +536,6 @@ public boolean accept(File file) { } }; - public static List getFileList(String path, List selectedFile) { - ArrayList list = Lists.newArrayList(); - - // Current directory File instance - final SelectableFile pathDir = new SelectableFile(path); - - // List file in this directory with the directory filter - final SelectableFile[] dirs = pathDir.listFiles(mDirFilter); - if (dirs != null) { - // Sort the folders alphabetically - Arrays.sort(dirs, mComparator); - // Add each folder to the File list for the list adapter - for (SelectableFile dir : dirs) list.add(dir); - } - - // List file in this directory with the file filter - final SelectableFile[] files = pathDir.listFiles(mFileFilter); - if (files != null) { - // Sort the files alphabetically - Arrays.sort(files, mComparator); - // Add each file to the File list for the list adapter - for (SelectableFile file : files) { - if (selectedFile != null) { - if (selectedFile.contains(file.getFile())) { - file.setSelected(true); - } - } - list.add(file); - } - } - - return list; - } - public static Intent createGetContentIntent() { // Implicitly allow the user to select a particular kind of data final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); @@ -694,9 +646,18 @@ public static String assembleUserName(String name, String email, String server) if (TextUtils.isEmpty(name) || TextUtils.isEmpty(email) || TextUtils.isEmpty(server)) return ""; + if (server.startsWith(Constants.Protocol.HTTPS)) { + server = server.substring(8); + } + + if (server.startsWith(Constants.Protocol.HTTP)) { + server = server.substring(7); + } + // strip port, like :8000 in 192.168.1.116:8000 - if (server.indexOf(":") != -1) + if (server.contains(":")) { server = server.substring(0, server.indexOf(':')); + } // String info = String.format("%s (%s)", email, server);//settingFragmeng set account name String info = String.format("%s (%s)", name, server); info = info.replaceAll("[^\\w\\d\\.@\\(\\) ]", "_"); @@ -959,7 +920,7 @@ public static String getUploadStateShow(Context context) { results = context.getString(R.string.is_uploading) + " " + (totalNumber - waitingNumber) + " / " + totalNumber; break; case CameraSyncStatus.SCAN_END: - results = context.getString(R.string.Upload_completed) + " " + SettingsManager.instance().getUploadCompletedTime(); + results = context.getString(R.string.Upload_completed) + " " + SettingsManager.getInstance().getUploadCompletedTime(); break; default: results = context.getString(R.string.folder_backup_waiting_state); diff --git a/app/src/main/java/com/seafile/seadroid2/util/sp/AppSPs.java b/app/src/main/java/com/seafile/seadroid2/util/sp/AppSPs.java new file mode 100644 index 000000000..e7938b4d5 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/util/sp/AppSPs.java @@ -0,0 +1,15 @@ +package com.seafile.seadroid2.util.sp; + +import com.seafile.seadroid2.config.Constants; + +public class AppSPs { + + public static boolean isMigratedWhenV300() { + return SPs.getBoolean(Constants.App.DATA_IS_MIGRATION, false); + } + + public static void setMigratedWhenV300() { + SPs.put(Constants.App.DATA_IS_MIGRATION, true); + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/util/sp/FolderBackupConfigSPs.java b/app/src/main/java/com/seafile/seadroid2/util/sp/FolderBackupConfigSPs.java new file mode 100644 index 000000000..64de0dbca --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/util/sp/FolderBackupConfigSPs.java @@ -0,0 +1,77 @@ +package com.seafile.seadroid2.util.sp; + +import android.text.TextUtils; + +import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.GsonUtils; +import com.google.gson.reflect.TypeToken; +import com.seafile.seadroid2.ui.folder_backup.RepoConfig; + +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class FolderBackupConfigSPs { + + private static final String FOLDER_BACKUP_REPO_CONFIG = "sp_folder_backup_config_list"; + private static final String FOLDER_BACKUP_PATHS = "folder_backup_paths"; + private static final String FOLDER_BACKUP_ACCOUNT_EMAIL = "folder_backup_account_email"; + + public static void saveBackupPaths(String path) { + SPs.put(FOLDER_BACKUP_PATHS, path); + } + + public static String getBackupPaths() { + return SPs.getString(FOLDER_BACKUP_PATHS, null); + } + + public static List getBackupPathList() { + String json = getBackupPaths(); + if (TextUtils.isEmpty(json) || "[]".equals(json)) { + return Collections.emptyList(); + } + Type listType = new TypeToken>() { + }.getType(); + return GsonUtils.fromJson(json, listType); + } + + public static void saveBackupEmail(String path) { + SPs.put(FOLDER_BACKUP_ACCOUNT_EMAIL, path); + } + + public static String getBackupEmail() { + return SPs.getString(FOLDER_BACKUP_ACCOUNT_EMAIL, null); + } + + public static RepoConfig getBackupConfigByAccount(String account) { + + String s = SPs.getString(FOLDER_BACKUP_REPO_CONFIG, null); + if (TextUtils.isEmpty(s)) { + return null; + } + Type listType = new TypeToken>() { + }.getType(); + + List list = GsonUtils.fromJson(s, listType); + Optional optional = list.stream().filter(f -> TextUtils.equals(account, f.getEmail())).findFirst(); + return optional.orElse(null); + } + + public static void setBackupRepoConfigByAccount(RepoConfig repoConfig) { + String s = SPs.getString(FOLDER_BACKUP_REPO_CONFIG, null); + List list; + if (TextUtils.isEmpty(s)) { + list = CollectionUtils.newArrayList(repoConfig); + } else { + Type listType = new TypeToken>() { + }.getType(); + + list = GsonUtils.fromJson(s, listType); + list.removeIf(f -> TextUtils.equals(f.getEmail(), repoConfig.getEmail())); + list.add(repoConfig); + } + + SPs.put(FOLDER_BACKUP_REPO_CONFIG, GsonUtils.toJson(list)); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/util/sp/SPs.java b/app/src/main/java/com/seafile/seadroid2/util/sp/SPs.java new file mode 100644 index 000000000..1e8f6e24d --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/util/sp/SPs.java @@ -0,0 +1,66 @@ +package com.seafile.seadroid2.util.sp; + +import android.content.Context; +import android.content.SharedPreferences; + +import androidx.annotation.NonNull; + +import com.blankj.utilcode.util.SPUtils; +import com.blankj.utilcode.util.Utils; +import com.seafile.seadroid2.config.Constants; + +public class SPs { + private static final String SP_NAME = "seadroid_sp"; + + public static boolean containsKey(String key) { + return SPUtils.getInstance(SP_NAME).contains(key); + } + + ////////////////////////put//////////////// + public static void put(@NonNull final String key, final String value) { + SPUtils.getInstance(SP_NAME).put(key, value); + } + + public static void put(@NonNull final String key, final int value) { + SPUtils.getInstance(SP_NAME).put(key, value); + } + + public static void put(@NonNull final String key, final boolean value) { + SPUtils.getInstance(SP_NAME).put(key, value); + } + + ////////////////////////get//////////////// + public static String getString(@NonNull final String key) { + return SPUtils.getInstance(SP_NAME).getString(key); + } + + public static String getString(@NonNull final String key, String defaultValue) { + return SPUtils.getInstance(SP_NAME).getString(key, defaultValue); + } + + public static int getInt(@NonNull final String key) { + return SPUtils.getInstance(SP_NAME).getInt(key); + } + + public static int getInt(@NonNull final String key, final int defaultValue) { + return SPUtils.getInstance(SP_NAME).getInt(key, defaultValue); + } + + public static boolean getBoolean(@NonNull final String key) { + return SPUtils.getInstance(SP_NAME).getBoolean(key); + } + + public static boolean getBoolean(@NonNull final String key, final boolean defaultValue) { + return SPUtils.getInstance(SP_NAME).getBoolean(key, defaultValue); + } + + public static void registerOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener) { + SharedPreferences sp = Utils.getApp().getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); + sp.registerOnSharedPreferenceChangeListener(listener); + } + + public static void unregisterOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener) { + SharedPreferences sp = Utils.getApp().getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); + sp.unregisterOnSharedPreferenceChangeListener(listener); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/SettingsManager.java b/app/src/main/java/com/seafile/seadroid2/util/sp/SettingsManager.java similarity index 68% rename from app/src/main/java/com/seafile/seadroid2/SettingsManager.java rename to app/src/main/java/com/seafile/seadroid2/util/sp/SettingsManager.java index b1483e6ed..98323ab45 100644 --- a/app/src/main/java/com/seafile/seadroid2/SettingsManager.java +++ b/app/src/main/java/com/seafile/seadroid2/util/sp/SettingsManager.java @@ -1,12 +1,13 @@ -package com.seafile.seadroid2; +package com.seafile.seadroid2.util.sp; -import android.content.Context; import android.content.SharedPreferences; -import android.preference.PreferenceManager; import android.text.TextUtils; +import androidx.preference.PreferenceManager; + import com.blankj.utilcode.util.NetworkUtils; -import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.SeadroidApplication; +import com.seafile.seadroid2.config.Constants; import com.seafile.seadroid2.gesturelock.LockPatternUtils; import com.seafile.seadroid2.util.Utils; @@ -17,20 +18,6 @@ * Access the app settings */ public final class SettingsManager { - private static final String DEBUG_TAG = "SettingsManager"; - - private static SettingsManager instance; - private static SharedPreferences settingsSharedPref; - private static SharedPreferences sharedPref; - private static SharedPreferences.Editor editor; - - private SettingsManager() { - if (SeadroidApplication.getAppContext() != null) { - settingsSharedPref = PreferenceManager.getDefaultSharedPreferences(SeadroidApplication.getAppContext()); - sharedPref = SeadroidApplication.getAppContext().getSharedPreferences(SupportAccountManager.SHARED_PREF_NAME, Context.MODE_PRIVATE); - editor = sharedPref.edit(); - } - } // Account @@ -53,9 +40,6 @@ private SettingsManager() { // Camera upload public static final String PKG = "com.seafile.seadroid2"; - public static final String SHARED_PREF_CONTACTS_UPLOAD_REPO_ID = PKG + ".contacts.repoid"; - public static final String SHARED_PREF_CONTACTS_UPLOAD_REPO_NAME = PKG + ".contacts.repoName"; - public static final String SHARED_PREF_STORAGE_DIR = PKG + ".storageId"; public static final String SHARED_PREF_CAMERA_UPLOAD_REPO_ID = PKG + ".camera.repoid"; @@ -96,10 +80,6 @@ private SettingsManager() { public static final String SETTINGS_CACHE_DIR_KEY = "settings_cache_location_key"; public static final String CAMERA_UPLOAD_STATE = "camera_upload_state"; - // Sort files - public static final String SORT_FILES_TYPE = "sort_files_type"; - public static final String SORT_FILES_ORDER = "sort_files_order"; - //CameraSyncStatus public static final String WAITING_UPLOAD_NUMBER = "waiting_upload_number"; public static final String TOTAL_UPLOAD_NUMBER = "total_upload_number"; @@ -110,13 +90,11 @@ private SettingsManager() { public static final String FOLDER_BACKUP_SWITCH_KEY = "folder_backup_switch_key"; public static final String FOLDER_BACKUP_ALLOW_DATA_PLAN_SWITCH_KEY = "folder_backup_allow_data_plan_switch_key"; public static final String FOLDER_AUTOMATIC_BACKUP_SWITCH_KEY = "folder_automatic_backup_switch_key"; - public static final String FOLDER_BACKUP_ACCOUNT_EMAIL = "folder_backup_account_email"; public static final String FOLDER_BACKUP_CATEGORY_KEY = "folder_backup_category_key"; public static final String FOLDER_BACKUP_MODE = "folder_backup_mode"; public static final String FOLDER_BACKUP_LIBRARY_KEY = "folder_backup_library_key"; public static final String SELECTED_BACKUP_FOLDERS_KEY = "selected_backup_folders_key"; public static final String FOLDER_BACKUP_STATE = "folder_backup_state"; - public static final String FOLDER_BACKUP_PATHS = "folder_backup_paths"; /** * Is it necessary to filter hidden files when the folder backup service is turned on @@ -128,41 +106,39 @@ private SettingsManager() { public static final String PRIVACY_POLICY_CONFIRMED = "privacy_policy_confirmed"; - public static SettingsManager instance() { - if (instance == null) { - synchronized (SettingsManager.class) { - if (instance == null) { - instance = new SettingsManager(); - } - } - } + private static SharedPreferences settingsSharedPref; + + private SettingsManager() { if (settingsSharedPref == null) { settingsSharedPref = PreferenceManager.getDefaultSharedPreferences(SeadroidApplication.getAppContext()); } - if (sharedPref == null) { - sharedPref = SeadroidApplication.getAppContext().getSharedPreferences(SupportAccountManager.SHARED_PREF_NAME, Context.MODE_PRIVATE); - editor = sharedPref.edit(); - } - return instance; + } + + private static class SingletonHolder { + private static final SettingsManager instance = new SettingsManager(); + } + + public static SettingsManager getInstance() { + return SingletonHolder.instance; } public void registerSharedPreferencesListener(SharedPreferences.OnSharedPreferenceChangeListener listener) { settingsSharedPref.registerOnSharedPreferenceChangeListener(listener); - sharedPref.registerOnSharedPreferenceChangeListener(listener); + SPs.registerOnSharedPreferenceChangeListener(listener); } public void unregisterSharedPreferencesListener(SharedPreferences.OnSharedPreferenceChangeListener listener) { settingsSharedPref.unregisterOnSharedPreferenceChangeListener(listener); - sharedPref.unregisterOnSharedPreferenceChangeListener(listener); + SPs.unregisterOnSharedPreferenceChangeListener(listener); } /** * Client side encryption only support for encrypted library */ public void setupEncrypt(boolean enable) { - settingsSharedPref.edit().putBoolean(CLIENT_ENC_SWITCH_KEY, enable).commit(); + settingsSharedPref.edit().putBoolean(CLIENT_ENC_SWITCH_KEY, enable).apply(); } /** @@ -173,7 +149,7 @@ public boolean isEncryptEnabled() { } public void setupGestureLock() { - settingsSharedPref.edit().putBoolean(GESTURE_LOCK_SWITCH_KEY, true).commit(); + settingsSharedPref.edit().putBoolean(GESTURE_LOCK_SWITCH_KEY, true).apply(); saveGestureLockTimeStamp(); } @@ -211,17 +187,12 @@ public void saveGestureLockTimeStamp() { } public String getCameraUploadRepoName() { - return sharedPref.getString(SHARED_PREF_CAMERA_UPLOAD_REPO_NAME, null); - } - - public String getContactsUploadRepoName() { - return sharedPref.getString(SHARED_PREF_CONTACTS_UPLOAD_REPO_NAME, null); + return SPs.getString(SHARED_PREF_CAMERA_UPLOAD_REPO_NAME, null); } public void saveCameraUploadRepoInfo(String repoId, String repoName) { - editor.putString(SHARED_PREF_CAMERA_UPLOAD_REPO_ID, repoId); - editor.putString(SHARED_PREF_CAMERA_UPLOAD_REPO_NAME, repoName); - editor.commit(); + SPs.put(SHARED_PREF_CAMERA_UPLOAD_REPO_ID, repoId); + SPs.put(SHARED_PREF_CAMERA_UPLOAD_REPO_NAME, repoName); } public boolean checkCameraUploadNetworkAvailable() { @@ -249,15 +220,15 @@ public boolean isVideosUploadAllowed() { } public void saveDataPlanAllowed(boolean isAllowed) { - settingsSharedPref.edit().putBoolean(CAMERA_UPLOAD_ALLOW_DATA_PLAN_SWITCH_KEY, isAllowed).commit(); + settingsSharedPref.edit().putBoolean(CAMERA_UPLOAD_ALLOW_DATA_PLAN_SWITCH_KEY, isAllowed).apply(); } public void saveFolderBackupDataPlanAllowed(boolean isAllowed) { - settingsSharedPref.edit().putBoolean(FOLDER_BACKUP_ALLOW_DATA_PLAN_SWITCH_KEY, isAllowed).commit(); + settingsSharedPref.edit().putBoolean(FOLDER_BACKUP_ALLOW_DATA_PLAN_SWITCH_KEY, isAllowed).apply(); } public void saveFolderAutomaticBackup(boolean isAllowed) { - settingsSharedPref.edit().putBoolean(FOLDER_AUTOMATIC_BACKUP_SWITCH_KEY, isAllowed).commit(); + settingsSharedPref.edit().putBoolean(FOLDER_AUTOMATIC_BACKUP_SWITCH_KEY, isAllowed).apply(); } public boolean isFolderAutomaticBackup() { @@ -265,106 +236,65 @@ public boolean isFolderAutomaticBackup() { } public void saveVideosAllowed(boolean isVideosUploadAllowed) { - settingsSharedPref.edit().putBoolean(CAMERA_UPLOAD_ALLOW_VIDEOS_SWITCH_KEY, isVideosUploadAllowed).commit(); - } - - public void saveSortFilesPref(int type, int order) { - editor.putInt(SORT_FILES_TYPE, type).commit(); - editor.putInt(SORT_FILES_ORDER, order).commit(); + settingsSharedPref.edit().putBoolean(CAMERA_UPLOAD_ALLOW_VIDEOS_SWITCH_KEY, isVideosUploadAllowed).apply(); } public void setCameraUploadBucketList(List list) { String s = TextUtils.join(",", list); - sharedPref.edit().putString(SHARED_PREF_CAMERA_UPLOAD_BUCKETS, s).commit(); + SPs.put(SHARED_PREF_CAMERA_UPLOAD_BUCKETS, s); } /** * @return list of bucket IDs that have been selected for upload. Empty list means "all buckets" */ public List getCameraUploadBucketList() { - String s = sharedPref.getString(SHARED_PREF_CAMERA_UPLOAD_BUCKETS, ""); + String s = SPs.getString(SHARED_PREF_CAMERA_UPLOAD_BUCKETS, ""); return Arrays.asList(TextUtils.split(s, ",")); } - public int getSortFilesTypePref() { - return sharedPref.getInt(SORT_FILES_TYPE, 0); - } - - public int getSortFilesOrderPref() { - return sharedPref.getInt(SORT_FILES_ORDER, 0); - } - public String getCameraUploadRepoId() { - return sharedPref.getString(SettingsManager.SHARED_PREF_CAMERA_UPLOAD_REPO_ID, null); - } - - public String getContactsUploadRepoId() { - return sharedPref.getString(SettingsManager.SHARED_PREF_CONTACTS_UPLOAD_REPO_ID, null); + return SPs.getString(SettingsManager.SHARED_PREF_CAMERA_UPLOAD_REPO_ID, null); } public int getStorageDir() { - return sharedPref.getInt(SHARED_PREF_STORAGE_DIR, Integer.MIN_VALUE); + return SPs.getInt(SHARED_PREF_STORAGE_DIR, Integer.MIN_VALUE); } public void setStorageDir(int dir) { - editor.putInt(SHARED_PREF_STORAGE_DIR, dir).commit(); - } - - public void saveContactsUploadRepoInfo(String repoId, String repoName) { - editor.putString(SHARED_PREF_CONTACTS_UPLOAD_REPO_ID, repoId); - editor.putString(SHARED_PREF_CONTACTS_UPLOAD_REPO_NAME, repoName); - editor.commit(); + SPs.put(SHARED_PREF_STORAGE_DIR, dir); } public void saveUploadCompletedTime(String completedTime) { - editor.putString(UPLOAD_COMPLETED_TIME, completedTime); - editor.commit(); + SPs.put(UPLOAD_COMPLETED_TIME, completedTime); } public String getUploadCompletedTime() { - return sharedPref.getString(SettingsManager.UPLOAD_COMPLETED_TIME, null); + return SPs.getString(SettingsManager.UPLOAD_COMPLETED_TIME); } public void savePrivacyPolicyConfirmed(int type) { - editor.putInt(PRIVACY_POLICY_CONFIRMED, type).commit(); + SPs.put(PRIVACY_POLICY_CONFIRMED, type); } public int getPrivacyPolicyConfirmed() { - return sharedPref.getInt(PRIVACY_POLICY_CONFIRMED, 0); + return SPs.getInt(PRIVACY_POLICY_CONFIRMED, 0); } - public void saveBackupPaths(String path) { - editor.putString(FOLDER_BACKUP_PATHS, path); - editor.commit(); - } - public String getBackupPaths() { - return sharedPref.getString(SettingsManager.FOLDER_BACKUP_PATHS, null); - } - - public void saveBackupEmail(String path) { - editor.putString(FOLDER_BACKUP_ACCOUNT_EMAIL, path); - editor.commit(); - } - - public String getBackupEmail() { - return sharedPref.getString(SettingsManager.FOLDER_BACKUP_ACCOUNT_EMAIL, null); - } public void setFolderBackupJumpHiddenFiles(boolean isJump) { - editor.putBoolean(FOLDER_BACKUP_JUMP_HIDDEN_FILES, isJump); - editor.commit(); + SPs.put(FOLDER_BACKUP_JUMP_HIDDEN_FILES, isJump); } /** * Is it necessary to filter hidden files when the folder backup service is turned on */ public boolean isFolderBackupJumpHiddenFiles() { - if (!sharedPref.contains(FOLDER_BACKUP_JUMP_HIDDEN_FILES)) { + if (!SPs.containsKey(FOLDER_BACKUP_JUMP_HIDDEN_FILES)) { setFolderBackupJumpHiddenFiles(true); return true; } - return sharedPref.getBoolean(SettingsManager.FOLDER_BACKUP_JUMP_HIDDEN_FILES, true); + return SPs.getBoolean(SettingsManager.FOLDER_BACKUP_JUMP_HIDDEN_FILES, true); } } diff --git a/app/src/main/java/com/seafile/seadroid2/util/sp/Sorts.java b/app/src/main/java/com/seafile/seadroid2/util/sp/Sorts.java new file mode 100644 index 000000000..3b9c0e7f2 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/util/sp/Sorts.java @@ -0,0 +1,91 @@ +package com.seafile.seadroid2.util.sp; + +import android.text.TextUtils; + +public class Sorts { + /** + * sort files type + */ + public static final String SORT_KEY = "sort_by_name"; + /** + * sort files type + */ + public static final String SORT_ORDER = "sort_by_modified_time"; + + /** + * sort files type + */ + public static final String BY_NAME = "sort_by_name";//sort_by_name/sort_by_modified_time + /** + * sort files type + */ + public static final String BY_MODIFIED_TIME = "sort_by_modified_time"; + /** + * sort files order + */ + public static final String ASCENDING = "ascending"; + /** + * sort files order + */ + public static final String DESCENDING = "descending"; + + public static final int SORT_BY_NAME_ASC = 0; + public static final int SORT_BY_NAME_DESC = 1; + public static final int SORT_BY_MODIFIED_TIME_ASC = 2; + public static final int SORT_BY_MODIFIED_TIME_DESC = 3; + + public static void init() { + String k = SPs.getString(SORT_KEY); + if (TextUtils.isEmpty(k)) { + SPs.put(SORT_KEY, BY_NAME); + } + + String o = SPs.getString(SORT_ORDER); + if (TextUtils.isEmpty(o)) { + SPs.put(SORT_ORDER, DESCENDING); + } + } + + public static String getSortKey() { + return SPs.getString(SORT_KEY); + } + + public static String getSortOrder() { + return SPs.getString(SORT_ORDER); + } + + public static void setSortType(int type) { + if (type == SORT_BY_NAME_ASC) { + SPs.put(SORT_KEY, BY_NAME); + SPs.put(SORT_ORDER, ASCENDING); + } else if (type == SORT_BY_NAME_DESC) { + SPs.put(SORT_KEY, BY_NAME); + SPs.put(SORT_ORDER, DESCENDING); + } else if (type == SORT_BY_MODIFIED_TIME_ASC) { + SPs.put(SORT_KEY, BY_MODIFIED_TIME); + SPs.put(SORT_ORDER, ASCENDING); + } else if (type == SORT_BY_MODIFIED_TIME_DESC) { + SPs.put(SORT_KEY, BY_MODIFIED_TIME); + SPs.put(SORT_ORDER, DESCENDING); + } + } + + public static int getSortType() { + String order = getSortOrder(); + switch (getSortKey()) { + case BY_NAME: + if (order.equals(ASCENDING)) + return SORT_BY_NAME_ASC; + else if (order.equals(DESCENDING)) + return SORT_BY_NAME_DESC; + break; + case BY_MODIFIED_TIME: + if (order.equals(ASCENDING)) + return SORT_BY_MODIFIED_TIME_ASC; + else if (order.equals(DESCENDING)) + return SORT_BY_MODIFIED_TIME_DESC; + break; + } + return SORT_BY_NAME_ASC; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/CustomClearableEditText.java b/app/src/main/java/com/seafile/seadroid2/view/CustomClearableEditText.java deleted file mode 100644 index d233723ff..000000000 --- a/app/src/main/java/com/seafile/seadroid2/view/CustomClearableEditText.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.seafile.seadroid2.view; - -import android.content.Context; -import android.text.Editable; -import android.text.InputType; -import android.text.TextWatcher; -import android.text.method.PasswordTransformationMethod; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.AutoCompleteTextView; -import android.widget.RelativeLayout; -import com.seafile.seadroid2.R; - -/** - * clearable EditText, also supports auto complete text typing.
- * if want to use auto complete feature, should set data source to it. - * - */ -public class CustomClearableEditText extends RelativeLayout { - - public static final String INPUT_TYPE_PASSWORD = "password"; - public static final String INPUT_TYPE_EMAIL = "email"; - - LayoutInflater inflater = null; - AutoCompleteTextView edit_text; - Button btn_clear; - - public CustomClearableEditText(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - initViews(); - } - - public CustomClearableEditText(Context context, AttributeSet attrs) { - super(context, attrs); - initViews(); - - } - - public CustomClearableEditText(Context context) { - super(context); - initViews(); - } - - void initViews() { - inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(R.layout.clearable_edit_text, this, true); - edit_text = (AutoCompleteTextView) findViewById(R.id.clearable_edit); - btn_clear = (Button) findViewById(R.id.clearable_button_clear); - btn_clear.setVisibility(RelativeLayout.INVISIBLE); - clearText(); - showHideClearButton(); - } - - void clearText() { - btn_clear.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - // TODO Auto-generated method stub - edit_text.setText(""); - } - }); - } - - void showHideClearButton() { - edit_text.addTextChangedListener(new TextWatcher() { - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - if (s.length() > 0) - btn_clear.setVisibility(RelativeLayout.VISIBLE); - else - btn_clear.setVisibility(RelativeLayout.INVISIBLE); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // TODO Auto-generated method stub - - } - - @Override - public void afterTextChanged(Editable s) { - // TODO Auto-generated method stub - - } - }); - } - - public Editable getText() { - Editable text = edit_text.getText(); - return text; - } - - public void setEmailAddressAutoCompleteAdapter(ArrayAdapter adapter) { - if (adapter != null) - edit_text.setAdapter(adapter); - } - - public void setText(String text) { - edit_text.setText(text); - } - - public void setError(String errorMessage) { - edit_text.setError(errorMessage); - } - - public void setDisplayHintText(String text) { - edit_text.setHint(text); - } - public void setInputType(String type) { - if (type.equals(INPUT_TYPE_EMAIL)) { - edit_text.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); - edit_text.setHint(R.string.email_hint); - } else if (type.equals(INPUT_TYPE_PASSWORD)) { - edit_text.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); - edit_text.setTransformationMethod(PasswordTransformationMethod.getInstance()); - edit_text.setHint(R.string.passwd_hint); - } - } - - public int getSelectionStart() { - return edit_text.getSelectionStart(); - } - - public void setSelection(int offset, int offset1) { - edit_text.setSelection(offset, offset1); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/view/LinearRecyclerView.java b/app/src/main/java/com/seafile/seadroid2/view/LinearRecyclerView.java new file mode 100644 index 000000000..5370a1aa4 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/LinearRecyclerView.java @@ -0,0 +1,31 @@ +package com.seafile.seadroid2.view; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +public class LinearRecyclerView extends RecyclerView { + + public LinearRecyclerView(@NonNull Context context) { + super(context); + init(); + } + + public LinearRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public LinearRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + setLayoutManager(new LinearLayoutManager(getContext())); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/ListPreferenceCompat.java b/app/src/main/java/com/seafile/seadroid2/view/ListPreferenceCompat.java new file mode 100644 index 000000000..907796c49 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/ListPreferenceCompat.java @@ -0,0 +1,61 @@ +package com.seafile.seadroid2.view; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.ListPreferenceDialogFragmentCompat; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class ListPreferenceCompat extends ListPreferenceDialogFragmentCompat { + private int mWhichButtonClicked = 0; + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE; + + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getContext()); + builder.setTitle(getPreference().getTitle()); + builder.setIcon(getPreference().getIcon()); + builder.setPositiveButton(getPreference().getPositiveButtonText(), this); + builder.setNegativeButton(getPreference().getNegativeButtonText(), this); + View v = onCreateDialogView(getContext()); + if (v != null) { + onBindDialogView(v); + builder.setView(v); + } else { + builder.setMessage(getPreference().getDialogMessage()); + } + onPrepareDialogBuilder(builder); + return builder.create(); + } + + @Override + public void onClick(@NonNull DialogInterface dialog, int which) { + super.onClick(dialog, which); + mWhichButtonClicked = which; + } + + private boolean onDialogClosedWasCalledFromOnDismiss = false; + + @Override + public void onDismiss(@NonNull DialogInterface dialog) { + onDialogClosedWasCalledFromOnDismiss = true; + super.onDismiss(dialog); + } + + @Override + public void onDialogClosed(boolean positiveResult) { + if (onDialogClosedWasCalledFromOnDismiss) { + onDialogClosedWasCalledFromOnDismiss = false; + super.onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE); + } else { + super.onDialogClosed(positiveResult); + } + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/TipsViews.java b/app/src/main/java/com/seafile/seadroid2/view/TipsViews.java new file mode 100644 index 000000000..d1ec59867 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/TipsViews.java @@ -0,0 +1,15 @@ +package com.seafile.seadroid2.view; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import com.seafile.seadroid2.R; + +public class TipsViews { + public static TextView getTipTextView(Context context) { + TextView v = (TextView) LayoutInflater.from(context).inflate(R.layout.view_tip_textview, null); + return v; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebView.java b/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebView.java index bb80fec58..9072c6281 100644 --- a/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebView.java +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebView.java @@ -42,6 +42,10 @@ public SeaWebView(@NonNull Context context, @Nullable AttributeSet attrs, int de @SuppressLint("SetJavaScriptEnabled") private void init() { Account account = SupportAccountManager.getInstance().getCurrentAccount(); + if (account == null) { + return; + } + URL_LOGIN = account.server + PATH_ACCOUNT_LOGIN; WebSettings webSettings = this.getSettings(); @@ -73,8 +77,10 @@ private void init() { } public void load(String targetUrl) { - if (mWebViewClient != null) { - mWebViewClient.go(targetUrl, this); - } + mWebViewClient.go(targetUrl, this); + } + + public void loadDirectly(String targetUrl) { + mWebViewClient.goDirectly(targetUrl, this); } } diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java b/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java index ec03c53f8..316588665 100644 --- a/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java @@ -10,7 +10,9 @@ import android.webkit.WebView; import android.webkit.WebViewClient; +import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.util.SLogs; import com.seafile.seadroid2.util.Token2SessionConverts; import java.util.HashMap; @@ -66,26 +68,34 @@ public void go(String targetUrl, WebView wb) { goWithToken(targetUrl, wb, true); } + public void goDirectly(String targetUrl, WebView wb) { + goWithToken(targetUrl, wb, false); + } + private void goWithToken(String targetUrl, WebView wb, boolean isRedirect) { - Log.d(getClass().getSimpleName(), "targetUrl: " + targetUrl); + SLogs.d("targetUrl: " + targetUrl); mOriginTargetUrl = targetUrl; - Map map = new HashMap<>(); - map.put("Authorization", "Token " + SupportAccountManager.getInstance().getCurrentAccount().token); - wb.loadUrl(buildUrl(mOriginTargetUrl, isRedirect), map); + + if (isRedirect) { + Map map = new HashMap<>(); + + Account account = SupportAccountManager.getInstance().getCurrentAccount(); + if (account != null) { + map.put("Authorization", "Token " + account.token); + } + + wb.loadUrl(buildUrl(mOriginTargetUrl), map); + } else { + wb.loadUrl(mOriginTargetUrl); + } } - private String buildUrl(String url, boolean isRedirect) { + private String buildUrl(String url) { if (!url.startsWith("http")) { return url; } - if (isRedirect) { - String rUrl = Token2SessionConverts.buildUrl(url); - Log.d(getClass().getSimpleName(), "link redirect to -> " + rUrl); - return rUrl; - } - // Optimise the code here: // The expiry time of each cookie field is not consistent, // and it is possible to be redirected to the login page when opening the link diff --git a/app/src/main/res/anim/bs_list_layout_fade_in.xml b/app/src/main/res/anim/bs_list_layout_fade_in.xml deleted file mode 100644 index 1a3391c18..000000000 --- a/app/src/main/res/anim/bs_list_layout_fade_in.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/anim/pull_to_refresh_header_loading.xml b/app/src/main/res/anim/pull_to_refresh_header_loading.xml deleted file mode 100644 index 6562deff5..000000000 --- a/app/src/main/res/anim/pull_to_refresh_header_loading.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - | - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/action_more.png b/app/src/main/res/drawable-hdpi/action_more.png deleted file mode 100644 index 3a30c94bc2e58dcc30f8decd8373b2f7aa32db21..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243 zcmV^q5cBy@AO!k2vfxo_u@<-vfGRb_N^Y1q+egIuH0d30JU1 zn9GVcH$n@Qzi1(&cb6@^7qkBPszB`yhj;LzJSL60a)MR84(f%Z3dz#CD=-cAjJ tsO$DGXg@{m7rFg5ZTX`KA%qa3giqH#2zgLh(47DP002ovPDHLkV1l^lWi0>z diff --git a/app/src/main/res/drawable-hdpi/action_remove_cache.png b/app/src/main/res/drawable-hdpi/action_remove_cache.png deleted file mode 100644 index 29900825875cf1c4cdac6d038e28209e79cd7177..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 617 zcmV-v0+#)WP)2_!_r_vN^`DhnPmj4Q z+akhioGglxm-a{m7(`2JI0C#ReY~0h>v(hWFPeoYQ3yp4nms7Os@ZWi~i% zOqdQ!=v1BT;~~E*-I7*YKC_k!ysJQ$*R0bP8t&}kZiP!OUU_#-xI22B$pugq2?^tl zgM33#zMI>;sIr63nw}ilZXTCsOp1ibRl!fyU~F>Q$9W;cA*0S}h6Ca6DRRYz9rp#|3o?o~vdM-(f?|Nc4Q3g$6)FEpOiDk> z0eh*nSfU{9%rKY&UAY4mCz5z#%+tM*9Edh=q_}s^Izqo(+4mtR-^q00000NkvXXu0mjf D7dk1* diff --git a/app/src/main/res/drawable-hdpi/action_share_password.png b/app/src/main/res/drawable-hdpi/action_share_password.png deleted file mode 100644 index 0c75886ca8e519da3bb4879beac6bff87d81ed62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1540 zcmV+f2K)JmP)Px)zDYzuRA>e5SzBxyMHHP~$Fbc;pafAwlr|4iRaI$9fr?P^lPZWu`KYJ}J^Ns`XD zwY9ldn_^uM7#tjYS`dT}0Jc01@M#d~Zf|e@BTm0m%{zhN;o(1Iv)OES>h=1rnVFex%yU zdcung6AB|EBZrZ{g$_owQ&jH41RVfFm(h}7weP@ccTpICcJdx4F~-N!)6?UKd88%7 zNP*&@>gwty*c^mv8?0tgH0 zO=u1y4H9q!Wxp?*uWd<_0Iqpjk&z^)Bah-l+Kc2S(D3s%Y%hvqvMQQq03fU>%v=Q9 zyRl^sxZUnC-Zn{LtdvZ-1h9w6D{M_^B*x|u_R?X@sctX4+-Vc6Y{f0ENq{<23BRoG{%89~8d06cc-Kw~#tOV`Jm>va+&T)L%q8i==k;pzHwHT6`^d4e1zMFOj6J zbRHqaYT*Y=V+G)kM1l7K@E9dn`N)#m$R@T|8eb*scDqq)oAmVb81VA_E{It$(QyQ0 zT?odAY(nvx@7*jg(G4RVAi0<*)|)=Rc$GT&p5I;0B+h z%k>zFhmqb41OngT6XF(Xix){)`Z_Vs^)!p#N4b}xY?WcMMu0}As1!ur!Bg0aYANzb zJPmqj;fgGX1t56{p?o`%;zqfKQOc|mQ0KwR&ab#r?nC_q(w6{v3?H^OsO_HHOO@a; zq|0D$iIgj5}#KpzM z{FN85xTcM&nUc$XI3Q_77^Jl2jQ|eyrWlk|5k+ROI+#$lHKs|P`I7~#4O|jAKLb(7 zln+}`b;b-PCX~`&WU->?(8d$~W8_(8LFY5qAcBY$-TnKRxa~ovdzhjT1V2BIGwJd< z7QV3Jf@UVWQ&@KdR60i6q*C;EYF`6HAfH=E#hOu`w?n)dYX6~!Rx?rB*DV1SOzzF} qrr$z&B>)#h!CiVH%k<81^UMD`eDd`}Lxn#80000QdU;^Ex%O#RzI%sB4K~GxWswRbB0r$ zz0M49su?32lXy@~N2+cyc4TEdPw32> zrj4wuYKYDZw@GB>Y~Sfj)%B5$vAwP<-ZxETVMBwp^phX7G{#<$g-g{mH`*mm)l*Bi zDMwnS@T}&#+g~BV242$KI~K_D2MyKKBbIOSgk9$=jlQ&Zq~#nxN{n(&^Jz7a1N4-r zxiRIqna1N1AK0O-a%*``;#~_y8a6NxaGzy5_*XCY1Ng!ok%m+JgfE=X@ggv>ef0y^ zr+6mK=xMxbQ{zM@l&#`Pd}4{{D%-2N-vSEJC1!Sos^g=ptZOgRrd;gX+15Cb-v53E X)Inj?EXL4K00000NkvXXu0mjf9|}GI diff --git a/app/src/main/res/drawable-hdpi/actionbar_background.9.png b/app/src/main/res/drawable-hdpi/actionbar_background.9.png deleted file mode 100755 index 948fa3258a77d2be33e0fed7067c41105deba930..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 198 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P1|(P5zFY^SdOcknLn7SY-Z12AP!MprxHPo- z>%aEk>IGL7CeHFSxcMl!U%JL;>%n95Zl5{SG4cI`8AXyWp6PrJ`QO)Fx{otyZh&r^ zMAE8;S<90r-6DwW89hZHCr@XD^W?{=L2-8yvR(uc`mf(769e$18V6-FbUE oO1wN~&E_26GiPFKQzH%R?aobN=DaS$4|EuVr>mdKI;Vst0QtL9(f|Me diff --git a/app/src/main/res/drawable-hdpi/activity_normal.png b/app/src/main/res/drawable-hdpi/activity_normal.png deleted file mode 100644 index a8dfff3ba0ee3d19e9ff0ecc774bb6e0ec7781df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1017 zcmVB_Tvm)Dj`I5=0GRsi4W|{NI07)f#DeC=woQ zL279wZG#9wY%LW@?OR)QZfAPu%*?sBx9wxylbPh+bN=t&m(!%l|EIA(2U_z=VM^#hekCRw%k1L3?Ep99F)y?eYZ zC2=y(a>dXjEEEpEfDZv@&zVM6b_+R-g3GcV$q9u+i65?~6xyMs|ty;{mCN>fz~ zhT*+&BtNwS4Zm5WzMLAqJ# zBTEM9-BDshQh`c?Bm=&jmeiervoi_}gqJGvjmGvbIXLYyINT9=212n)gSEzX1uav$ zak|Hd8R}ZV4-JIpD*56iAM8{?XgzSRdiRE_oq|D6DNXOFB#8B<3THcll}74aZH97{ z30*PXDC83=`TF9yktk1GQ6>dCitg&8K%fqC9Z(i+M^m~arOHy%9uS3GA;Kd6UM_i{ zg2$S9gmeZwPUiTTOFJ8%fs@h-5vVvDsoeX?3=pZ3;wUS}KLQ+ye} zZNoo6Y(1_aG+XFVQ7d>c4y1Rl=QT>)78WB%*xwAi5GgdIfp;~n4UG_&!7Mx#!~GZW n)Bj6_ZYhYd zbr_3gQeST5Re2)c8?wDSQ3?2-TXlMx$cs}eugR>uJTvO-)QaO$BllaTX>(eefDpM6 zFJwlJ^Y|gvw9Vw_YC4{IMw7=|tlWcnqE1;dKwD9#(9*O?X2ngJ5kHp%`gl;&mQ-MT zoYrHj%)+yPF#-$1k|P7O#s4e35r^`IuRDZz6N>$#fF_Y!`2?OtjKA2b@29rs1Zp>M zGJNWH@+M2&n~Rs(0vk|)A2s!|dp(b5HO6nbQPYO*p$*$}>WMgp7ky{SO|5)p{85rr zAe%uO!^ex~e?q8Yj72E&+<%IVyuWX|EKzs!Ldz;QOXMR|0#2`plRHwY&TLt6Y?|~O z&n|Mo>flzd@VvBI3Gq@aTfu)-mNZV{1wM&5t@|SP5MVJV$a5D=wKl)^6l9`M@M+%5 z6IaFgtx9TJ+NwPv5I2$oF;>SKyw<443VGHq52R2GY1*3o z-|(V~aROBeFlAm%Z2+%z1%lqQ&ukDyWFX6fdE&w<1&}U~17IVp(KO<)v|k)iN;kYn z6E)!3XE?Xw=;(9-=Bs8QbuKx8yoas0s90l|$HP>CrPFCMj>EY=wqti&KgOi@2gJY_ zk~XIW6Ls3Z2w^q_pqZ#M_}$+fdwtWija|Y{U(-gc@w={@l3C|f{`_u&CBX6IKtabV z%9Ka}_;waGyqBkKoE*wV2l0dtn1;X7|-0E>x6ShF2 z&f*Cx*9cfEpPWR0TJ8^pA{2-@z){!j9C*RVD_s=Ifq5G_&~61BnaLn-t+hXSC)%OdieQ_F71 ze*Jh7rs zoLVF2U>bYPlapLZK%^4rJ``?nVO0(8@&8qMB94lv4aCi!8u+u~@`?hoQSdnG4J2*k z0crzR{fw?fRGr+^y*r}`6>_CV2euVA_$o9s$~=4OIvuzNeC-cpn^XhHC{ahFaj-@L zX~c`3{T8&0+Pg+4PVMDP>WWJRY(X`ERwNjz+{wT_;V+uhT~rrHEBvLU8f4R+gLWbT zH7zpHXZ4Kv*)-ag>H?iMziEpaA(0PpuD*M0dnGsOJdnZ^YyawRH^v9wyo%~f|c1uD^Rd(ImV_Y-%hIW*i?H(&|<3Fz#IM1ZsFNwK1Uu#odC*CSR=L- z>7!&`YbGi&>Lif2q()v;u+;K+!4Xqx27ewvYZLf^qt!1D!B0g9Ui5H^*8B)QOMf?E mH3*HH9PCD2bkW7Si@yOkP-Qf{FU_?80000-4wZlV>7iVE#>VbR#aZjPqLZBI2e_X} zJmi+Q@VGsZf3n+N4xNHive7z*UX4tPbv7QAU4DgQqQCIYm`+(CGtK{vLK>Sr56L~2 zJSH6Ru*>Rl{dLx5KhKD+E_`2mCoDW+qRsq_W&U!uvvMXccT?P5TQ`C0`jz?nuU~e5 z6%(9ju=|IMv(1H9JFY)!T)1PWvfjS4yv!oj??fdZw3U^8+-@S7X7{C>!*0FAvXi-G zAHT~?`q1W8AyuxuNA+RHZ=LozLc3LRY+iqsR(WGM@$CfR6@QH%>K^l}xO?*3`t6%r~)kKeO`Q`3N#X6%rT!`LC^K aWbhQ7wJkioF%p66R1KbQSB)||PSnD(&6qb`}_HOQ7$10W? zz^nMS&HSL1sMB<_fW+pLEguyFk~=Rf<_=q#8vQ#*J3giLfbz9w@v;`n7R90(`Ea%?Xu>(@mz~N`tEl- w`@hNa_n$p$_rLy-xedv|0u9V!nfc5NXY_pgVki3a0i&3~)78&qol`;+0BNrIv;Y7A diff --git a/app/src/main/res/drawable-hdpi/btn_search_icon.png b/app/src/main/res/drawable-hdpi/btn_search_icon.png deleted file mode 100644 index 182187f274d74572bf3f25308c7f82aa6e0b6e89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15537 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 diff --git a/app/src/main/res/drawable-hdpi/custom_tab_indicator_divider.9.png b/app/src/main/res/drawable-hdpi/custom_tab_indicator_divider.9.png deleted file mode 100644 index 0279e17a123f8cbb3c7e3a9ce5c5af8e693b6977..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~!k#XUAsp9}6B-)+^LX%RmN2q0 Ycy4A9FVZ~13zTN?boFyt=akR{01+Y(GXMYp diff --git a/app/src/main/res/drawable-hdpi/custom_tab_indicator_selected.9.png b/app/src/main/res/drawable-hdpi/custom_tab_indicator_selected.9.png deleted file mode 100644 index 2e5c725deb688a1468e2731db11746e24da0d792..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1i!3HFsuehcLq!c|}978H@B_}jA{P&R1YvB1e oS?$D!b%%f0^YB|beqmr_5D$%6@_fP^PM~52Pgg&ebxsLQ0PO@C761SM diff --git a/app/src/main/res/drawable-hdpi/custom_tab_indicator_selected_focused.9.png b/app/src/main/res/drawable-hdpi/custom_tab_indicator_selected_focused.9.png deleted file mode 100644 index d3db97abd90bbb2badb719d926140f1bceff0214..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 194 zcmeAS@N?(olHy`uVBq!ia0vp^EI`b~!3HEJ|NhShq!^2X+?^QKos)S9`sfT^>PZCKcx(hXp_(Yfl%)5RU7~ zKmPx>XJ%$TT(5HGK*O1ak257UFsy01aiHFfjg76XT=rN&ajVw3M;G$;eAVol+_T*- bm4QK3S$OKRMQ<&D#xQug`njxgN@xNA1ME7c diff --git a/app/src/main/res/drawable-hdpi/custom_tab_indicator_unselected_focused.9.png b/app/src/main/res/drawable-hdpi/custom_tab_indicator_unselected_focused.9.png deleted file mode 100644 index a01a84e1a9b0800e200fc5fab8b1a37253d56345..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 230 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1c!3HD^Kbl$tDaPU;cPEB*=VV?2Ic!PZ?k)`f zL2$v|<&%LToCO|{#S9GGogmC;e)9BMpdfpRr>`sfT^>OuE55!h@w;En4TCwj?v;_%SgY@lYxX{j={2 P&_)JNS3j3^P6`sfT^>PZNhxjCX%m4$ww^AIAsp9} z4etG#(%9Je(4I#+zxLFD69*1__<#SmERVqfl@|vO9BE*EAd=!Do|2f5knqD`^Q;YJ dY&m{R3}01*9X2RTod7h5!PC{xWt~$(69CCdJOls$ diff --git a/app/src/main/res/drawable-hdpi/dialog_textfield_activated_holo_light.9.png b/app/src/main/res/drawable-hdpi/dialog_textfield_activated_holo_light.9.png deleted file mode 100755 index 506be530a9f831d52858b412958d4f7aae47bc7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196 zcmeAS@N?(olHy`uVBq!ia0vp^nm}yC!3HD`7Jga>q`Ey_978f1-`;fOYBAt(xLESx zpZJ+XqiFM}^dC{o|JTNd{1s{Pa^H|uZu_8mf4J+F^wJ}uZb3a$w)Ex8^h^{{)&z2v zNO&4fN>VxM;WhijtaVXq&iZjZ(0}{aXtUAhi6`gVu0HA~<(VnC^Q@SSsZDy^{>fJ# v+&+|SJ!Rq)!9p9y$KRX3?&p7cUTR+rr=nC(eD6h|vlu*G{an^LB{Ts5F9S}3 diff --git a/app/src/main/res/drawable-hdpi/dialog_textfield_default_holo_light.9.png b/app/src/main/res/drawable-hdpi/dialog_textfield_default_holo_light.9.png deleted file mode 100755 index 5fa1d0315a012bf2a90a30cf5294a2b0b93b7276..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 227 zcmeAS@N?(olHy`uVBq!ia0vp^nm}yC!3HD`7Jga>q*i&lIEG|2zP%a9)odW*a?$+K z)1Cj{`q;N9u_m`hpKZ2`Zwl0JT;L*ldUtZ5d1&a=x0?&kPuyd3_d~Z!kdWs~ft7p{ zdDAZC2}JUy1yw$75c2d?QJpviBnM^Oa`D@>gTe~DWM4f&v;)F diff --git a/app/src/main/res/drawable-hdpi/dialog_textfield_disabled_focused_holo_light.9.png b/app/src/main/res/drawable-hdpi/dialog_textfield_disabled_focused_holo_light.9.png deleted file mode 100755 index b70db4e101a8f7e0a53a10d0b3dc0feda850ccf2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1208 zcmbVMTWB0r7~UizNwg`HmTKt3anc%!-I+NvyV=<>(sldL#t*labZ51q`M-3-ZG zGIP?MELws>RInOFs0x-sLF0pcSSUUivBeUIeQ3Ra*nr@bD&lHTABwj#$!_!^^ud9d zbN+Mw@B8ob?C8kQj?V7RL?W>xKdctWdXT(a`|ad=X=eRAS?A8ebzeb~_HZ>oW7UzOS)DSo7Iow>=#NA~;2>;($eDISF_NeaUXkqMV}=47 zDtJnwHk}&RM}gu62&8E?X|jDR=z}!mQ<+TW5a3wIGAzeHE(tRt&xtGx8V^OR1y)Hc zs9M7oktAvoV_#&LN~J;%*PvsO|gH3GS4YA>xsA<a`T7@Z=YGAGZlQV`m!^F+;pJr(ukq(TvTdhxot>Hl~nV5i##+C2h zw8&W9$g6|J==zVZu5~>H?p!!HbNK1{-_Kl;-(I<+cA?hY_1Yiraq5lo3-h~w=qz6? zPJQ&mJ*BR9_E67%+0r^Z(7G<4{NSb8>uo*v-k08o_YJh}TAaUaKWOP7ce@tp;e`W_ zDj()gRR>=Fb?NjHSH5|3et-S(?u&bVTN-)?&RR$Kex^)dghaP6UQrO!V1^o3*dEs6D=%Fj#fwR7>mB%d2m&&$W3 F{0G5%lraDR diff --git a/app/src/main/res/drawable-hdpi/dialog_textfield_disabled_holo_light.9.png b/app/src/main/res/drawable-hdpi/dialog_textfield_disabled_holo_light.9.png deleted file mode 100755 index a77d66d990371079ac3626ee54891cf0fe6e35b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1116 zcmbVLO=#p)9FJJU7K?Tfp^Gdr-5yqUlJ}CCNhWqIGs$E-(9}Ae#py*%lh;n@Bri=~ zJL!nHlp+c(dg(z53JN_q(4!(AbXkxU^son^dhi2EQFqsZ3x&Px?#pzhdMG^@NZv>O zzu)iw`QG%@mBE4Y0}R6qmM8QIT`$pBeDVqUy}tC%E?tI6sYYh-JZV}XVhRqPLu}cz z7ElFQ&g#;4XpCVVpLeS@QZpx28+*K!*zmCzP&C7gjmLpyFCxOup#|5^bBAAj&9SbN z=U&U0zzj50cPCmQnrTf{?bf25bGY%B*|Auq1Rf$58+#2uQsX?=;Z^BAxfVFKV?q}5 z+_6(NbDGs~h}aAdQZ~o}HVb(urDa*Z%!&X40f+(=Q&3hVQ3ZhQJ{(mGoq4sQ8(l4W z%5!x>0#y*2%_iTJcpNSWFqg|E9HN+_7O7~}Csv&Dqmdqijv_mB1L9(zO&F~?yiD>O z_4GIdFEGs$Vn6C8il$76tw4Z05Iir5t79FJ3i{WLQ>~-wYJh|aituu1(|XL0^uRQC zXA32U)Eo7c(4|GO8alR@J>-+Jp6BQv-fUD|rFv&zRV_po!C@8S zMy~^=>zLrEjssSk$*`tnyMA&`%5xm8jzV_@IYx**wv%7gJw;zB$wo%hB@rIST{IL} zltrUh1Q{Ks0oTPk|0ibxbw)^<<3G*PyP_SKY)@LB9!`b_`E+7JIvT&-Y~QA1wNcg! z)%fQ-KNSY9fv5Ca8}W08Z{HZYyR$v;PF>Ip% diff --git a/app/src/main/res/drawable-hdpi/dialog_textfield_focused_holo_light.9.png b/app/src/main/res/drawable-hdpi/dialog_textfield_focused_holo_light.9.png deleted file mode 100755 index 464a2ceb77b7dd00013956b7b4f8350e0b233610..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 290 zcmeAS@N?(olHy`uVBq!ia0vp^nm}yC!3HD`7Jga>q+WWuIEG|2zMbjF*JL2#viw-9 zqTXI7{uiD$3FY$+3WQ&2b@Tku+Qhu{^l!8VC_NL5v6FMU+t?2uV`ebJgbq3yh<`)?=_I~#-t4Q;c@u<_Y zkxsUovBq0hIy-MHE&u@OWFU{>f(3#qT(1){&R(X&jk*p>P|FVHRGg~O7XPc z+jg-Q$XjQ6mi;*UL98k4-}DgIHFrJKjw;xvN8jNpj_wiNvvehc*HOlorRATwUw3zZ aO6JpVcyIphNHYt_(+r-jelF{r5}E*I9CbVZ diff --git a/app/src/main/res/drawable-hdpi/et_normal.9.png b/app/src/main/res/drawable-hdpi/et_normal.9.png deleted file mode 100644 index a9a9274aea44b64cc4b50919d1e3ab29f3b480f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 425 zcmV;a0apHrP)P001-y1^@s6f7_Fb0004SNklS^9(S=O7b>#ERBC>Np$%3HsR{J#KA(>@S79*^X@E_t3uzVA~I1TTf(;e#JJTJITb}QD)`_>4tey5zReS)eMNM=UZwedzf)CJRM#~%O*1O^ z;71O5^caRgj^n&PAOi4Z+qN{6WjU$%m5@gd`q0~l?C@T3CvyOu(1Sko_94~nc9W01 z1Oz?k6TM|0B0t~~fKKQ`FGCozK7<&;5dNPqgdq%J2tydc5QeM^sc_(55|U+^oI{?D z*ijTM2Ecd_hT-6dt)5P&MUU9%L+=BEc>*&NrEbzgP9P+(|NcnSw!(t?iTX83Qpm7L^XWxp1+zWF zWs{c`HkY0jEj01fJYTHUl@tPz6s%os?eV+oMTwPXapbg+>!z{m!?R|kL1hhjnr+Tk z?{{Q*aJn=$bH%ExppV9i{c>LxZs))GVDBTpIK~nu{$Cevw_ov`bi#cebCcYc&U>r) zC(0M_IjDbGI9-t^QqGAdk;C~%eybvgmN+EPro+9m@WFiHNvSjEIKS(=@%(e88N;z= zjV?C#oYL*L&sH6AldsAbI3qkm^O40o)+XaW3Ewi<=S)qFy!1hf!S<1+9lLhmc9DmC z-+u2k@xL4SpjqDXNj3MxPp=u;k9tU4f8@8#>FAL_-hH}7$&00Q6)J0)f0voew2tPm Q0!9IYr>mdKI;Vst0L_!SWdHyG diff --git a/app/src/main/res/drawable-hdpi/fancy_orange_text_select_handle_left.png b/app/src/main/res/drawable-hdpi/fancy_orange_text_select_handle_left.png deleted file mode 100755 index 4321d54bcb476869889ff38f908c905322f3d016..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1244 zcmV<21S9*2P)KgJN8wXW^iroGGa^Tj1JlyI%>bICRRec-qx zkmr8+-0%64eTekxU!Jb+rjcD8!4}N~>dIu4W%*&U2+fShfV{HIxuuM5#pOf0Q zj{TvLfwKnzJP#nUsCN;Sb`T4nPkZFqmo^j8dk*=MzjKXw8Ye)mI1m4**DFvH9X6o(QfrLTK$hN9(T+ z_V)oe2@iRqqD+e$>|mXI%{n>lsS=HTfI!wDk#dWrc`dYL1HPc$bwLzgdn zwhwl`)D@$Sg($e3wYsjgb@$d^{P>+-0Eb}z2J$2Yl~Wa7uRna`ShWAuqsIUwu#z)D zC3Vr};47aFCWz=OBAQl|FDpB9gwH2h9_y+e893VoJ74PIk*^R7;i{anK)FBO-4nU^ z(c3)$4#UIwk`LG9RM&3MclIQs=Uz)D0d!ym=8G$G>bAy$T`!)9pMH8zAAnZ3j^j4U zi>_^n2fi8l;)!roMIV3$6d_+EsToJPoU+C(fpAsD5dfQ1kG)vZitMO9tJ>t9asSM$ zS+snanohw=OC@8e>>L3=n;!j3pPZZ&qOwxq(2K_~3|MJ3ougciWsK{oR7&{$#aR`Z znVA7ek9LlnP3clfAr%$)e<31}qCi%jIY?V2vqr7*LkIu@Qm)AFH2F||h6###+5?nC z`@03pae(DGz;Ya5IS#NK2Uv~+EXM(s;{eNXfaN&AavWee4zL^tSdIfM#{rh(fO~F|D$KW}#?Wsg?s|*UaanWS_f<#9Rki zInS4c8*;LUd3(2!^Y#qGFbu;m48t%C!!QiPFbu;KgZ}{Usm47{fTi&O0000u5F5{Ds4x)qyjZ;rzp9qa45ODbaja5zdc)> zf3Hga(ZQ&yEWS%-vmz#!OwQT9`}5Zg##U2*ojUjCozJEJ{Qus}KD_7WGxPU*Z^p_g za2#TDca=TRFI&pcZ~k3ALvDu6R_*OOcI^35@$USJ&qvzc>~_{wIX`jF{tw4beOwz` zJ!{c95!-1?H!-tEd(=685llJ^-eX@N0_qX|-R{3`~=y;d%@ot|q$FSq$EAvV7 z_kKM0;Fx%d=F(mF3=)0CR4c|!u>ts-bv0pqmjCv-*MZuRqtP%G@EoiOXHaSi&OV5_8Xt-+o=$( z{$*e1%d$!FR~>ueFI?UCQ8@S1wT~hxuUp>Dx4G;0sY>=}#n&lMw$=(Szm+q$W4eO% z#0&ZUb6+Y{Ha`iPd-Mlu?ECpvm*u1#ttMXid;FZ6?y=RXlgxiDKUa36v!(A=?<<+v z?-VMH*^>S=<(2jMmMc8EpY-e8UG~R@LZ5vu%-zj@jIl+^Ht~!WgVye)nz2PUB-C8C zYFg%;$; z&tKnrk~VToIhw;3;9!}3wCRwK_FJ}&{;BmsR`EAX-I;HQEb5Qi#QC(Q@x9@T&U;(9 zNfdKJNJNdGGtVSC`Ib_`~&}{7Ph>Q>349gGocr@0%^3=FQL$t73oJ>vfvpS%8=^ zdxBu9rRaLcL^HX{#7_)Gc^vFd?}h}PJh@m${rNU-j*SNG6N5|i=Nul)rv`7-gM@fLh8TlY;^F@4MH-p`Y^DR4BQqOXtg{|4Q^#HJ%P16ay1 Nc)I$ztaD0e0szesFA)F$ diff --git a/app/src/main/res/drawable-hdpi/fancy_orange_text_select_handle_right.png b/app/src/main/res/drawable-hdpi/fancy_orange_text_select_handle_right.png deleted file mode 100755 index e95219b9e197c6e66dbbf36fe6f0557919bd5606..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1221 zcmV;$1UmbPP)Wh{>r64vH(a$y;Z^1{SzS~nk1qZ z`W_yQpbBTvYSCh2N1BM}XCnHWh>qQNXd>!g)97=$V@{(1sNcM2UxbK$A)*`+EfCS< z$kF%i4nV}GUq(*O%%OJ&_aENm_4{i|kPaXe?(E%SAxGxW zsfS)W*1xuCea+;M4j>%5J=RY|7cAt+96EjchqJ@&J$*HiLj^g}?z{enoZ=ih&@(*J zgeuP=Q%=`PlLvasVI*U~l|`H(wndKYGXYP3tQvhwNNd#U)Kd-@f?qmlc&m<&q|PsK6NF@kpuyZ#ovEp;@*Q3 zox$b}uFauxF018o=$iu{og8TCjJYz0bO7PtZH~y1IduN=?A)H_?mp#L_DYsjUXW8; zoEm_eZ(198%HQ7MPB}(iU|}JpqN-Jq&Ro?K|IGQI_=;XhWk#NmOeW#=dR0`nViJjj zmY(@bM`1j|EG45v4hey5Hj8vRt)jXWlgVT>BRMDK7;u#wGjN1Z(x=s+iEKv7QFeM# ziRU@Xp&@V7LviL*iyS}*Ics?4EX9tKiK>xf2Q0?|mSX|Sv4G`Rz;Y~LITo-S3s{Z? zEXM+tV*$&tfaO@gax7pu7O)%(SdIm1

hKJ9c*+fB_qB?kkZalCuVYDdE8OgW2Y^ zNX;p4Tg`Ux{7h!YljC2dQmN@&E@ucKP>zzz<&0D+H5J|U#MdG*n^7^AV(Lo#ybd4$ zAYAA1ws~7SV!y{vL_L}og8g|W@*`^etY%3U0 zGXN<^iV-N!%*kT$3rU)NaFrZ6b5bZ|j4)p--?zR-yBQS3B+YgWm5r2|Ie_`!aukI4 zuqdV+%SsB$!iJfnLJqLFM=hl&-Lt+1@;MwSN!fEb5>;{}+LEK)h$TmcVHk#C7=~dO jhG7_nVHk#C7#H&|G>YMUV^XG*00000NkvXXu0mjfKQTwT diff --git a/app/src/main/res/drawable-hdpi/gallery_loading_failed.png b/app/src/main/res/drawable-hdpi/gallery_loading_failed.png deleted file mode 100644 index c59e10d8a99bcf8778fe36fe8ff972515c52e6ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7071 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 diff --git a/app/src/main/res/drawable-hdpi/indicator_code_lock_point_area_red_holo.png b/app/src/main/res/drawable-hdpi/indicator_code_lock_point_area_red_holo.png deleted file mode 100644 index 5cf908658cc239653ffe46b0d1b90a357b3c482c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7343 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 a7#F|cu~j4unZ02E00006oo->bX1&~ zPEwT~U5SXq`!URRQl0K}XHgdp-HX(%oYdo12_HUu_-KD*N}e+@%^6L-CJA0D7wgr zesIEaB1H5CqcE(%(xe2i^9?0nV>z8``Gk{wV&=!p0&{PuDO8qX6B6VqJc=(dPVzWB zh8;=N39Ni1z#{Ic&cVLw?wFz<>s@y1O1KkgDMHg&Bmv9T~ zD|x00JdYO|sM6mTn{nnx+p7Gi#JG=GO_y;ps9wOD-ZG1>=XOr~)oA$`<=#~QFJmKm ziMGh$$Reh7wLrbaJC7;kJjzLM1$W;%i`hXSSgs*3i@7?;2;i&MxZu2QO5kG_1y?Z& zSUVemgQAJ#HJr8DB=DChn3TyYNSRgUU&8{;;|08c7w`gJzzd9AAlGDDD-hE=m(%D| qJjl$o39IP?|K1foeE9Ius`w8xad)^k8@w3+0000kdg0003{NklG83tNk<+4UKy4Us(X~ZR z#Aq*A{0FK96aHCLKv?^K{1C4Zi<}U)<8g%(&Y%PN9S=OUXzilEps>IZoQo3hI(;q! zBT&tH5PM-fRs$CA#G`#l0~1gQURWS5y0=ISVgw|p7I-6q7ZHf~qw#njCp>l7 zFaCxRtUC!Oq}2?JU@?`Y;S nLgOM05u6Fw3RM~v>!SbwTjgN!FZZEaloaenILZob0`9Im1px(@vR|L()akhjxP zRkq$zUcZ|6A%8c+M`Z^m`OBqCx1FeXP%~A!SeIRmp)z@useFXw4K}kC3F$?Q3mS{p zZV8*}`Zu&ekWpa$slDAx4)^b@ICi4vpiaVh`Bn|T{Iqk158kN#G7-G^NMd%k{2cYKC$`CTdfuPzab=?Ylxxhj_g(M5Wv)_f>0hn& z&St~z(~sM}hsd7)nYL10rb5pwaTlNP>&k}hF~83He`k7{%<*8}rd=k$U}Ere^>bP0 Hl+XkK)i9<= diff --git a/app/src/main/res/drawable-hdpi/loading_1.png b/app/src/main/res/drawable-hdpi/loading_1.png deleted file mode 100644 index 02b78223eb8b98958650fca1a9fd603de6e586b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1252 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 diff --git a/app/src/main/res/drawable-hdpi/loading_3.png b/app/src/main/res/drawable-hdpi/loading_3.png deleted file mode 100644 index 7f7bf216a2d498c9dc0164c1ccea2f890092e602..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1461 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 PbPR*1tDnm{r-UW|_zr91 diff --git a/app/src/main/res/drawable-hdpi/operation_button_delete.png b/app/src/main/res/drawable-hdpi/operation_button_delete.png deleted file mode 100755 index 6197b4c4c3d09e92b3558e7f6a1e8833c45e51b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3214 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 diff --git a/app/src/main/res/drawable-hdpi/select_all.png b/app/src/main/res/drawable-hdpi/select_all.png deleted file mode 100644 index c6044016083919eacea57f383e76f409074cea07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 661 zcmV;G0&4wm4mc!@miA7@}r>VFRm0ogjr1dkh*fM8y`i!8^P@?o|H> z|6JT{{>3ON6>IUFSmqA%ri26MVIq|~5S`mdgL^Q5$`F2%6y`DR!af`;uKaVcjEWh_ zmr0)R6Bn2ZQm(!eTd9b1-`1lzj>>@rt+ID`%51LVdMX;qLE;LvsGOa6qSIV2!3rum zympAV@~`Gz?((k|2f^8xPi0^K2yuswRO34><;j>v#V8yr?)=lRk2_*dcNoaA98Kla zgp(y4{D{h~4g)!Y{i&Rncvi}%F=*ed!hu024Cn}fo;*@(@U?1)k zHyBSfH``5og34XN6Oz?IXiVk0r+Gvy{7N#B~%OGhdlypBNTk@O~ok zOx->WD^0Xd%$yAzvCpLJbB%;2c;6uV*pq!G;KdRwC_4gIY)7%3J`?B$%GzbZ1jb2l zUn+6K0RLMb(Y6Ezm-K zM}9{7JbIiKJrc+sZ}HeD=qu|e70JQlLDe?!mi`!_@Yy*H zsH1aX+Fcb;zEG)&Ma%Y~F`o^urXn-+sFoQIE zZj{~UvkWZMgu=mXM@FeNY)gBynf75)!7|l8OlzWy_UX;Fz)C!)Z1bOI+n#H0DU*G= z$a|rL6;%8J*FO2@rXrP&xCljoS2cAt@&Ch+0QEL>7@?gR>4Bf^#zYcP&DEs_OeEch};BBhTgZ5MXEJc?BqsTrX^1v_`zO9{0e+J_ncb8w*o^lA_#Cr&>j}8)%zTVeoi%IL3}WWH z$$(9VyOh(bq>wpHL_H`K2?WeIW|J=gH`+?9_5=Xz0JvK!amiIF#fCLH8{lebn{pfA z^o<)gj_?HBbpUGsZWoCw_Bnl1Wms1N+$_OA+q`-6XtI1RAkSR}m{w`0hxWMx$xjjq zlK{9&0=i+dI)H>P1Oxz!m`%M1Ken$K=2zzTdm9GeGep#rgzpK6#7ux|MZ72+4v(e( z4S=1@=C!TDK@xseKoT$kFeNhN$9)=JGmnH{5)fSG0dq>Kms)&ZAf-_O6;J^cPyrQC z0TobzKp-##;0||k*Xfs06h(ATgR;3#;6_F3fry-YCW~x&U=3+)`2yL z6?jK(e~d*`em9S(RC&r? zszkar;l~KN%sQ%6$$1{2j&wjCvy&>(&Q`2n;6t`jnd^NRm>d9dPui)>aCTyS%oFLM zO1$q5VCG2_!_r_vN^`DhnPmj4Q z+akhioGglxm-a{m7(`2JI0C#ReY~0h>v(hWFPeoYQ3yp4nms7Os@ZWi~i% zOqdQ!=v1BT;~~E*-I7*YKC_k!ysJQ$*R0bP8t&}kZiP!OUU_#-xI22B$pugq2?^tl zgM33#zMI>;sIr63nw}ilZXTCsOp1ibRl!fyU~F>Q$9W;cA*0S}h6Ca6DRRYz9rp#|3o?o~vdM-(f?|Nc4Q3g$6)FEpOiDk> z0eh*nSfU{9%rKY&UAY4mCz5z#%+tM*9Edh=q_}s^Izqo(+4mtR-^q00000NkvXXu0mjf D7dk1* diff --git a/app/src/main/res/drawable-ldpi/action_share_password.png b/app/src/main/res/drawable-ldpi/action_share_password.png deleted file mode 100644 index 4f6ab1f1a7c5b67437eeb91a11e53fe19b9a7c4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 830 zcmV-E1Ht@>P)Px%_(?=TR7efARZVCUK@@(on>JO8id8DP2#Q1xRY5)Url_b#L8bOD4N}vCjW{!a2L(x_Hvv)KoNlyiOx(gS%DkH?!O z--;3lB0((4in>lwlOV^>!@J3Aj%Lg=1jCjhYB4LbZa4!gvhJ-2sbjiKKrPA4pY z(Ieec)4MkMc3Sv$7h#w7&6hANIbX`e2v@1p=-M-ow{T(~ga}*21SZzl2~eWD@PF$X zB>q|`6yBFhu=u&@7!ljgiEgcqjqMR4Y!O?+90|I*x+c&u$3gV5NQSvIg3dXws_GMT z(NT92&e(1dQ%Q|VOxhB2-T}3>wT)t;KbcJW@DAqDhB>hitvHHS96*vIxm@l_I2>NI z2pM@%5;8frPj1=86*TT=2sMP!W3c{bQ6lrm@QG9@sMMd_D@t7 Vu@56Y`kep(002ovPDHLkV1k7(ikSca diff --git a/app/src/main/res/drawable-ldpi/activity_normal.png b/app/src/main/res/drawable-ldpi/activity_normal.png deleted file mode 100644 index 799d6d608982f5dc3fd16b5696732d7aac49579f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 564 zcmV-40?Yl0P)5<>z4y{yT!+b-J(%5Cj87>vyFyRlS2{GEmqFG&| zTHIGFXdremRzzK#0?9k$eH2wa`V;I@hF7lj_;W?~+YG^lJf&T_EZy%iOw>xFwYA`v zGDly6pQvrWUfp}~#0t8@9m%w-grf#3`G-WyC^oWSO#5aNOJae1b*xF2dC?^nk0kR} zti>7e;_Q%9bFTd7oC36oS^k5(wS#S9?lcCup648QB}q%8U5^X^0000Z*SiX(D7&?CkLo;(n^ssWui($%@0mcKkktMt5~Fe**NmPE2gji}lrel!D|Jn6 z=(ut@+XlVGf!Db%baV;w=S}*KJ{0u|Wd#Tvb&?7ed+Jfeh)FFgHpLmCr_QEqejtGz zmf~Ppj1OJ4n>}sLNIm#y#8~P?mqcg9D^3rMp51>;6cXFWhz{RTCsKCL0@FPuCmX#G zLO)Z*{r5(6Q;u^2?tRpuZZ<*crtEfF0Wg01{wT=nXwPY!*N~y6{6kqx#M{G$+^8kC zz1ousaIs0q-{+LX!r{ctUI5t_`arP9O+q-4T&g(0 zH%!QS%K<(+u3#wJA`+(!6~9f!I~$+_j~8O$Pb|+?YtjyiF{tu(oZ&L-552-nE@$kc z--ZTP_ar^!jOXr)tLH)f6qTH}{=m~4*k0Ph*DcGKZAli*_tX=9svy~p=0N98&5>*J zJ3!V>E(XP)7{zTkDq2w8==oT%T?x>^;kFbC)Z-zr$#?V?ZetPz_j2X17@eofWos;% zr#C~lk{Zz?Eis7(%)EKo^i8y7BU?s!^5KMP$a^`Osm;G@K}%j{G4KtiF$VmSQ$%?M5R22v2@9kn{>a-e+4@tW zY{Htv1|V+BtQTF diff --git a/app/src/main/res/drawable-ldpi/loading_1.png b/app/src/main/res/drawable-ldpi/loading_1.png deleted file mode 100644 index a5a304babec1a0d4e9331a1ad69cfd70f1474a53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 612 zcmV-q0-ODbP)Px%9!W$&R7ef&ls!mWQ547Xl9=*_2nsq0CA834#JO`F#6c_w`FId`kRi6Ey0tv) zR4k2V2&M*JKBiWec5rX^;?%)Gq|l+X&;~Pv#(elYB{#fW6CD-t!pZ-Hr2i_yU-~SH#6v+?3fX4Cl zDk(>yP`Dckg$^ljhih=0_Wgmi2jTiWO z$#vej0ubiAq@r!xr+R?r+o1bxzAcaK2C99RT&Yw#`X0IH9WL6&e$J=39Mx;?Vh!#Q z-22KdIOq<%e)qEEGF${Cx{NKoGJRUFWKK0$V1O2HLQn6T^ y;k~g?A0iRtlwwEVeyvu^q*5t){BGAZK7lhtd&}V#2i<=F0000Px%bV)=(R7ef&luK(9VHAct854~tL8K^JC=y(V`UhNSwJR5b?gY&Rl8A~#K?NxY z0YSQG!KMo@Ngx*&uIxfkHySVCA5d`>P^qgTF1&;!Ch>X8Oqe7y?XoU<;Bd~I^PcPX zeKRt$PHVjZW3>iKrP6l8FfPCo-EMc>!hc4-YBrk>lgZ?lm2#|DfaEtxd;{L>k)(-z zu2!o}Wipw+ZW!O7v0N@6D;A6UWhe0h;y0Zy!PA{iCxJYJ?Y?Zt0DL_h4nL=W^dah- zfW(ugX+9zN7s-ys$3>4r19P$+Em`~44S;r^E@zNaXaCno{K>Liar&hD zv;q>TgSPCuy@Bjb+ diff --git a/app/src/main/res/drawable-ldpi/loading_3.png b/app/src/main/res/drawable-ldpi/loading_3.png deleted file mode 100644 index 6e168514ec5294b659685ba32302203992678d41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 723 zcmV;^0xbQBP)Px%jY&j7R7ef&ls{)p?0vtJa)X8ksBWo2d0ah!D)9oX_dpYIg&dMFh7P_0(a**FTb3hy;SBgA z!4X=k)oSN8&e-(`Ks-Z1!e!*s>GV#k)w)F77+2wBGMNrSml1g&xZm%;Td7oz=$sL5 z0xX_!u{SL6Qv7DK*~ibBT#38g?i%I?bB1lbBA7lr9UyR42z=RdohXnIkHTCo_YFr* zoNU$j)Qyt-m;yc)o^z8Q6={;C=Do`~VRB6Y`O3bKD2kfo-^FmH3mMbD%>J(_AZ_kQ z8hdh|_Gb_7oXhZZ?GfN>Uby57+=qzkPcm^KAoR*4dIUJ%cOc0nIvR;YDb$ zje82@CNIK0EituKg)3`)i@2Ff6m1EKj&5-e*5#;5ECL93aW`4;Hb?U{{w(uxGMT)H zJuo~SNVeT>Kg5*9M`$%3kALC<7A4lkGS9?2Vp)8*)9GB(a%0yjP%IV)e9aQ9Wjg2~ za-4Z%T;)1o@_ak}et#jK&wn-Z5?cin5s@v5=ZQC&y9zgKkWN+;iA2D_N1^pUun`re z{RavfSvuWjYm;>E=!w4;kYW+h7g4@E<@nYoMREQ;`x7?o=JPxImI(j=002ovPDHLk FV1hBOOI-i} diff --git a/app/src/main/res/drawable-ldpi/loading_4.png b/app/src/main/res/drawable-ldpi/loading_4.png deleted file mode 100644 index 30a62dbe57b60ceafd59746fce20a968c045a3f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 756 zcmVPx%u1Q2eR7ef2R!wVDK@@G0=Ee9ya3P8oiwQ17e}M~W?Lr0JbfsWEAc^9_AlQ|w zfFLfqFe&(z1d`ywl?Z~a(u#shSBjt{sHh-8BovG`zQl9VnJ}F<&pwL-FZa&5=bk$= zZ)RS`#x~GOrP74gG{0A($_s@;H>=g^jEcGW8!7-XD-Z}g@>0a-R@sEtYPCH+pKm*2 zcVG|0c5T}}l}e==CO)xs3)JiNL*QM4;0f5>Oww9+Fc_RnBod1nKJ1R0Kw|lP-ik(} z^ANtMacuOtw8Ozce7WE6f7LjnuJIi}d^{G5J(l7Sd4nedp^11r&Otq-0~YA&}y~jQ1rKwYYXvdisNM;#9(HV$>ddqGjtvApj<9bVjI3i)Pl%; ztnC>+2`_sAvD*~qnE?MKI9q`2MUihR-?2oNWt|n`?Ov~UP9;mf)9K8~W$AXiZQ_{0 zW;_=pM_NY#1g01S`9V6JUZ$_NTrT%ZV#2SmkDMuqOKU2yk3mfLnIx@e!F?xkCUvf< zz(9~U>14M)M*h<+W-9QJWF+l(OVV+0j|l9&9y1kK5~OsoSPaPo)t4A;rBd03C7Ko3 zQ(;HOQ2^vycz%!(4u^Npa-T;ck$L)h3kHLCF$3~e(C+DRM*;3*6u6EPI-SjCKk+W| z`~6FJcB#oc?T8g1ep=v#OeXU|<c#yGy=EGZ{CN%8&X7RxNd*zKLlQtBTBBN-# zk7AbcobfP3AwX#@n2&zfk@Eg27~#gl6}MF)jtHs0QaVRI$_%t&#qZNn zbm0=8F-MWj_+Pc{q!eWaPvItNE%mt_r`J?#KaJmvLlmvVfbBuH*mxPe$0x_R!aoDp zD={!0MtxW(%zZNL_?--&AA@GLNqJjepwf7RJj+}W$Eg61A`odeLuLv_)QMS~Dj1Jo zUg*bDTH-p=ZU#=HfQnrfXjngNhA*|@JM~d4yT+gKfBXa#vUw2O0L_a40000V6avQ6o9V!tX}hRkb90P z=IUk74hh(%I7*y6Kh0V|eoq2G-pa7{L7Z#*aN6Jb+- Qs{jB107*qoM6N<$f==zxf&c&j diff --git a/app/src/main/res/drawable-ldpi/upload.png b/app/src/main/res/drawable-ldpi/upload.png deleted file mode 100644 index 6fd1da616397d7f4c103bb76c9424b117bec8acc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 232 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gj>pfi@Ln;`PB_a=;*AJ3u$xN!q z$XHS3;_~EiM@NUdqod=e5K|i+oikDyKTd!6(824lhc_j#pkM>r)EGHICX)}Q7lfv7 z*wDep(bvNm^~J`<=1zyw;YPL$Cf+I`8JU&rj}GfJochW9H16lGUqyjJCX5F@Ol$lg zAf`|w{*tAL<>^ fQduX?jgdiWUv0*b19AO8r!#oE`njxgN@xNAK?qgu diff --git a/app/src/main/res/drawable-ldpi/upload_disabled.png b/app/src/main/res/drawable-ldpi/upload_disabled.png deleted file mode 100644 index c9265077259b410d9448473c6d8a9619bf760f74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 185 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_+i22U5qkP60R2@M0snkEa)Q&*SU z&-<3qWi#=VdbEGFW$EE9xxw=O*A$$7UYU@&e)&$H*9?bdtUSM5LNI8Z<=c}cwrp$@ z*x1@M{{_dJ7CgLapqns>X980;gZ25$Rcemz2TcW-7E47f-)h98lkwn%@F9Nnjf;2f j%)3?3w4pEY2@`|P=}5h=^3j$+w=j6R`njxgN@xNA?WIC7 diff --git a/app/src/main/res/drawable-mdpi/account.png b/app/src/main/res/drawable-mdpi/account.png deleted file mode 100644 index 087fee4a74722735fdbc5ea2b28ddf70af80e4a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1400 zcmV-;1&8{HP)3-iE`ruTb>4qQ zg0)ITyK|vXN9fV!=f3mcfBFxSq(r=0P*CfnV}BcfC00BlY0{_n+yO{J5*IIC4D$K>Vqgo<1I%(% z3&3Z<7pqsVzSY^;X-c-weFY>n180GEf%FXO5%4XrPSS7H#!gjgxOeZ~Qu3>S`@ot6 z$zyfa0Qar!6JO7K1thHlzVd9Bq*MStkaYH$t-$f)$4kk#1N}gQN9{-8NDu^@f*{xg z9I-r?8i0Q5>xu7j=%sQE^7(w}@iZ{U-QPhFbfr?MZ}#usUx@L)K5*c`$HijtJ!8yZ zY^w#<$7x`B%y<1(U|?VXNqc~0j%Wf{6o%miV@$!dGsYCcFuVXPvbu3vCTY*Wz(9Qg z7-Puga!tSvk9e!3F-c@HnWu!AOr}(F$AGOK`yIJluE`ivKL9}x04xRC9nob;=Vz+x zCAp;Yz-33=4lK1X>bHf8#o`8s^%&Thw7bT1c3Ru%eAlH9yzl%!+Su4QQmzwgGdhFs z+5#9H983Xi4*PCzZ|`5_2DUbL9d?_IQ6B);u3cN;VPCDH7hX*mqh^PAp@%(O1%Tm% zF=|#uAA158D!#{fY<)3mxdJ1efJ-U`;Is9`s0o1P=4Qe$eB=?mRmB#1$~FwcN6pR6 zwFmIv!GqG{9Y?fE(wquzvU7k{4*QN1t9Afl9kSmMz2PaXNvO@*#;M!acUS6Ki`Q-gFmJzLUt;5N|YQT;t3uL-yckx#9gUNdlf#~g1V9n^ z88|4ZH=F%;%V)D$B=rIZtxnNnzie`Ha=4?Tqp_S$932!Kl6h)T-U#a*W_z)PC z^l%1WF9UDdUb5Vy@?AQeekY2eLXvZlN(vx~qGMAee^}BAN!Njg3EVxDbY0R4;IOY! zM^SVv>7h=#0-pkVJX>ayq%&1qr;)V5PUcdcALgT~S0J0sE}UYJm6LR)n)$wMaA$y= zZ_s69R|cSzm@(!MFxRc9lCss*@3^h?<<`%+HukhJf*As|d2XKf_~+BnM6Es(SwF7< zBfxz3eM8buQ`ycmoj&R*r$@suoS=qe0Ze4G*`vmoPdquSFKY!P5d^_}W6X7+B_>jeUEs}cZRz?Byu`76^M;=chpGX=73ivA)10000k4UJ)SO(Ar*|t3$!Fy7bl4PN&g@J z<^RS1{Qt%OhBtI){)zwm|M7q6|H1!^4;~e1(6VC)75kg+ZmRIxo+sz`d3G_ufAu_C zpPtw2gow%qh_P)7if1N|5#Ey=v1JD0+OyCn+=_*wD vA)ZBNTA#|dnMvXSGHfBet$_wyZj20j8h-@$8GYRhbQgoCtDnm{r-UW|-Wp8B diff --git a/app/src/main/res/drawable-mdpi/action_remove_cache.png b/app/src/main/res/drawable-mdpi/action_remove_cache.png deleted file mode 100644 index 50b5485099415172323f740aa4fd8736484ccabe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 490 zcmVkdg00058NklAhDP%BqT&?kS^-o@2&FEq!K?$ zNR7qFV$eY}h-f?b5v2OuUT@H6=zG`T-t~UI_uTKf=Nyyie^$s&=P)DJ2;xB-PM|;` z0U=zrAYR1TjtSt3nGzL$lsW|ju&Cnq)GdNfs`w3>1#mK=;#agmCkiT3o$u#M{mj$z5oCK07*qoM6N<$f||+R00000 diff --git a/app/src/main/res/drawable-mdpi/action_share_password.png b/app/src/main/res/drawable-mdpi/action_share_password.png deleted file mode 100644 index 1b2783d715d1dc97fb677839904489de4e31a591..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1006 zcmVPx&s7XXYR9Fe+R$WL_VHiH=%-Zs&ZX`%e%}G(D6p2t5{U}j{1^qyRKe2%tQeH50+841O7b35njdFT1o&gR&ro^JZ!-TQv; z-}CPM-tU|v2><&M^=v?AXXhHmSfwOMX7EX1-LI*sc@d7OTLE2NT@?Uz0xUfY5Gr<| zwzjsJ>vSo=>2$6Q1OjgXlFET1_%U?i9_geA!;)jT}gq0z^^F=KwKy z9@pzq0FwXY05SL{u212@VtIFWx54Z69zdRY&^C}CvU=(*6|H{*eOpdW&f$rPiM@!wi+u+71!tgU+yWQZMco1b zuS8%w9FB4)m45-R#`He1*=$llLBW39?|S%_0N4(?T~$>TYAwY$9F9y`mMb9^!ak`e ziVNVQWaDeT0(y{iA9sl0GuYn*9Rhtu((4|N=Rre51GRfWOH%;;eL27%#>w;!_6l_3 zV;sMP%$i$(7VSwODs!p82}@G|TDm-e0*ndxB!7H z%GRN|JXP-Fl$f(SpdF*SJR^VE_OC diff --git a/app/src/main/res/drawable-mdpi/action_star.png b/app/src/main/res/drawable-mdpi/action_star.png deleted file mode 100644 index f3669a9c0b9154b42e6bafe26c2e8f62e1f3ad1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 399 zcmV;A0dW3_P)kdg00042Nkl7Wi0W|>=1$P!z)HPY?F;PT4T8tmRV??Yu-6$vKeNXZN3E-zPz=^sPKxMhDg>^ zL*)){_(@&oB<{4DehMFw%Ut)xch5vwLL|P^!Bw$5bw;xgi*h!*E3zAADG=iEhtZxy z%-%mFMq3Yk>WOY45q>sSWHbC3;#0{fkzG+Q#HWMz&Gy(k-4hw%cmP+ml}2+%0W6Rq z#HNb#fHi7_my|Xca7*0~o7RB7@BcRc=?Ulp@Uc}M~F^McPvrF tHz5MqO+@1moyG?FCFURLt7-Uuc?J(KU5mT(Izj*d002ovPDHLkV1lniwNn59 diff --git a/app/src/main/res/drawable-mdpi/actionbar_background.9.png b/app/src/main/res/drawable-mdpi/actionbar_background.9.png deleted file mode 100755 index f82ecff4a2d855ff57a8df52e33dbe03fb8ea4a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^QXtI11|(N{`J4k%sh%#5As*hzDGL05(i0LAR9v+q zK2BeM-$5#qtzyScg>_O}JFXbW@md8r^Q@~gdEwTaXJR;EE}NUfg}?tleU*Otu=#k) zMWv2650=(nu&w_%Y1vu@PtHOQ?T$P#Lj!{gSKJpIQ)74*oy5F;SH(}Dg$$mqelF{r G5}E*lb~;M{ diff --git a/app/src/main/res/drawable-mdpi/activity_normal.png b/app/src/main/res/drawable-mdpi/activity_normal.png deleted file mode 100644 index d97b7c635e57b6a0202828b9feaa8f5a5bbce569..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 720 zcmV;>0x$iEP)kdg0007)NklR9;kRAR4o)EL<)i;3KgnQ^g+S)CANq~MD50%Z`LC9 zz=QQc5m8hSA0&_#>Wipg!J~aqqxHb5jdh*f{j;6fWX0!ZV43{${ojAiKcUcQ{B=wR z=dJcmU(DN}qn)>6w^4O*{C}yl&HM>0wAgzb5WC!Zj|ZsNCjL`dFJr)O2i|D^=Z7T% zcct_Vf$FHk^Bj=vawMno2rPVK`wQ8wU>C@?y@nBYJcywYwQP&{p2zDE64qpeRoe}C zt62HPYWyaQSvZQXbUuU~DnTkqPe<5gMna)vsTK_;z%nX$S{Bxd+6y7S zVT@701qehz-9)~UQy6mza3xWsq_$0v4(Lmjip@lyTS^&NjjZFry`1ypLA{V>v0mxg zxzte4i^n>m6 zQ*0~fm%Xx;N-nz)ZI3n&jE z#kTv#NZ{jA&i3$2VoHA$A!WpykVcZnSjnh-N&f=))H-pYfPyLj0000_gb}`;}ns4{a+3(DZ$)Bw~_0~6Mp7Xx%d2i1l-K$Hicm)~a zIqZxtxeE4N!KS(nuW}_j07knM6_%eC^`-DD^LzM+!_4uE4mN&EE3?KB@|uq&&FNJ%R#~GP6{2i zsoepG9mBl>moP$O^!JG0wL_)Hc{}ujl~)C386N&5hFe1$T}^ zm%^EM**fS4jIzD(B%cVtE4ExhyUo(^btg#VqaXvDE6IS=4N}s{i71@|=i<5^Y}uTt zfL2>)aiBRa*_4p9y#aX|lYCNWY9-rlM4{1Dkujmh(K7(tHVwB&K=5&fvxkEEh)X&* zys$|hY)4%uI-aZd8?-+5vG6>}9r#@vQ>7yf2V@5O8Pj7QW^|8{h6|Sdr}F^tLjzZ* z1mS5VqSsK&c`*+7Q8qKHwH@|66o@^I{jTNfR_ZKaCeH4Fy*2e>1Z;gM5Qj^a1BzXR z2=ufmMl#A^lc02l4VR3Tj=7ZOee+XmdQ$Ufxwcw;cV z3J;awlZ|2Y7ISY?jJ*$O5J9wT{f#3$yR-P+P=$?0Ng3R#MgzCGimx?sX6xcxAe}lz z7g}b)`D|X`l_W=4j~H{{u~~@rbNpVtAf@^~K2Si1r|E0KRNECr*>odce|$;z=5qjx z6&X(_EUX_WxI4y$o&+x@`!rPSoN?$oFxkceoZy~{j19hB$xe2rWnND5ttL@691GAm z+=7gm6r_K?^XLRb?Pds{Tr){47dn+2nxP^2K%C~}C&b*ovn4(OYT&xeQeB=~-C6Vsc71m!bWU~cW S7V1<00000+hF}sMkl$S==gm{g(2Vmae>pe;3b)EZBQH=(+lx-^+hj73xbcDgHD#v~C&D OS_V&7KbLh*2~7Z~KRWD4k+-jqq&tI5sY0aNb&|zTG2X#ViNUAB!JcJ^ag7bnZSc hqnP5W<>61OnQS_w)%9*=Zw5Mq!PC{xWt~$(69E4gMe6_n diff --git a/app/src/main/res/drawable-mdpi/dialog_textfield_disabled_focused_holo_light.9.png b/app/src/main/res/drawable-mdpi/dialog_textfield_disabled_focused_holo_light.9.png deleted file mode 100755 index 0d5ea839d159c8a4c24584813d0113aef9bac9c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1133 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 T;*W0_00000NkvXXu0mjf8klh; diff --git a/app/src/main/res/drawable-mdpi/library_selected.png b/app/src/main/res/drawable-mdpi/library_selected.png deleted file mode 100644 index 4a9ec7768fa2ae37b209f34e7e7e98b40112c381..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 342 zcmV-c0jd6pP)8f!-7SSq{W30o04KkL6x=$yC3i03HL)&&nd3P#_>>bMJ`3z$ zj~skmQC)}pvB0-5$_Zfan{L#K4{M z!CEjK12!E~@ehzK3k@;N-;P!Jv`OrXrUU8i7$F1nDyH%|42(ddHh{d37Gev-v6#QO zfeEO*6T({8G=0AR-I=7XOATM|c`DSQlNG$^+y%fx{o6 zVLQHvJHNPh(HGR@G2eiaq(M0Gk(9g_8j_ZQh5`TpQfOSv$?cbr00000NkvXXu0mjf D!I6Mw diff --git a/app/src/main/res/drawable-mdpi/loading_1.png b/app/src/main/res/drawable-mdpi/loading_1.png deleted file mode 100644 index 0f8083d8c78e555b879c96c4219c9e6e1f4c6a77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 893 zcmV-@1A_dCP)Px&H%UZ6R9Fe^mP<%fQ5b-mSCcW*izX;~Xy7)`jfB zFtlh91S42FGmb@25J80ELLqPwm~Lui%w$DFNzV~%-hBt|kvsPuz2ho^IdJ&jkNcnh zKj&`QQeD*w{NoDPHXDC%aByo~UEK-0-F_U~{p@YUV(~rnVkVQB2nK`SH{-M71cru& zc6&UYD>j?$G68m~f|N?74D#o>T<%tPclW$eXKZY&p{uKFLC>!!0Sue@!iN}&FRo-2 zNG6<4rw97``&WbkK8}&xbZ2MhUcCd$1Y)t+S%N>n*se4D48A~+MfNhXgV2q@CY_Ez z=cz_3_WRf~$bB6h9c3|{27@w^;4Kb^BMwsrUm{>=b#*n?+uNHfQ-+8{B1fH0=Kz5M zkRni{%jKE{?qK`ard5`WUOF-|vd8Uqzkw&q{yLk@Huv=Or1g3#TsyL7$VA|>Wvqbw zvN9z>5PSe2gD+Cbwn~BvTN-VqP$*mwxYbf75RFE6VR%s*GiKN5>Sak=LF{We^V?Q> zLat1Jb8kiF(Hx|b$>a+Sw*>UTA?&B=gTt1}C@7ndM%#|;iEscu#pRP1xYa_>P7_{2 zZw|XTEB&fmRKPTb@p%042BmqINF;7h>f#1EYa+%mNH4Sq21hcjzMOu|h}>Blk$X`Lv13wYa#}-L8un$N*WSAGv6a10nsi0{JF#N_oh2dLa5Dte= zdcEEdLHZ#bf(I_%{U)FwRV#&$bNHB13H3aa2hQ5`+awHVEb+Eq$MI+AcWPx&SV=@dR9Fe^mOW@wQ546UyrvSRjp(L}AB$E*>>wzmB1owcb*i|uX>4O1B%@RZ z2L~$-4#A~0)g(nJWU*9QsDfCEf`TG~bP*h?peQsIlbSS%zf<1}H|c%(m~_a4%Q^Ra z{@*z#=cS>c{?se*k1OD)HGe1++Tihc+8hqY0erV|jC?-7n9JopOixe0j>qHKT4GiW zAQFk}b~>Gx34WI3n{ASOMgAa}Ob!nY4t}?>DH2x&faINqVNBuKROD@cS*9XEpU)Sy zH~Fh^+8IQn(G%#_0QU9uJ%NuYw(lwXEs|V>UP2y)E-CsJsuhXFV#A6|%W5lt;}B&Q z=WtI?&jo0n;}{h|BGEpTqACx)w!$o*4I)dl9=1fRfxYR6$rW zTWZcq93Tfxqtxo^YMbBhU(h)fwLp_xd+EaPD?(SdN=cPRc9-* zLQtFJs$Wf&V1^De2QMT5U^1MY4CQAvpzv!Is3N)8)>H|Sy(Fm&Zv*Xc0p_&urH_;3 zn=n{lxXy6AO!i3}e~O_XM|PCcj{FGcF7_KsgOs`7vvkC2w|JE{_V)JvLhwix((?rO zW`H?$6R+Bq<1?AeTvJnFby!1LP^rKSvd5fB0PW#>k<F(|}yk74i zO5TcmJ4ZI%4-&siBoc3e!QgMzzRuSx@ZVS94`OO}TcV#F&Hw-a07*qoM6N<$f^S8q A?EnA( diff --git a/app/src/main/res/drawable-mdpi/loading_3.png b/app/src/main/res/drawable-mdpi/loading_3.png deleted file mode 100644 index 9dd93395a0d44e287f26ad839a8755529d154107..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 996 zcmVPx&o=HSOR9Fe^mRm?vQ51$}bac`(ii#+S63cq9hk`CdH?ST=$^v~z&>2&ZAbjYp zs1OpO9s@x!@fH|JSr|l7VL?Gb5J5zt5ZWCw3leYR^sV9S!**ux(+u=h8}_WV*ZTi; z{d%|+LB8;oMuCYLZN5m4j^KJ88pm* z;&yd)g%Mneov&iaTB`slz#Tq~Mx*<1zk3x#+~Ksev>VtFM4W4DYs>M%>p4#W4saL| z-*l3PzZemxGcqz>B4nor&P{m=kUOE^ZRT2+VQ>(5Ul$2}4X}HNIADO!33&?OPR@hg z>@a@L&dyeHq0e(Fu;8dJjg5`vMk0|4^23rxL=bZg5!+;K8&(1B?d@A29dn4$nVFeV z9?UD#)6;v2eRSfksjsh}42Q!#2-v}VB?&}iV6HYbH7#&~agJ3$dV2aP3dHghyj)#f zJs{kgnwmG5J;X+29X}BHVQOk>H~H93$#LZ5ZC;;Lo7vb}{$DzfNb#pb~JI!}pqX4o`Tm+4% zp`pQs(_o1%N4ZHwX<|dl*R8Ga19o9tfE3Bg&(A-ilMKE;K1Lf~Pb}6&sWmYaF)(!JVU{BcXv0owze);c(Iw8nJ3knT77kObwAWvw?+X1;G!3p z>OC$91Oj&vu}CLteqk^e9H4=%kXT1YM-e9E7+K>iT#{Ph@vA8K5pC%FAU#g(-R5CDDs76s~% z<{Cs4u;^o|&p2sf)|M?qPXPj8Ct49u#*7_@#JEr%vsirF0RNVM&SH6DFMRf96xh6@_n$Hc@!Z7SnFViRw3XaAFFaK){*cq zS&<$I9=w~R4_Y9f5L<-!*QR{H6ihWXK0aPXqp|Mh=;)~DUw5k4`;Y&tJMbIal1mQU SN@IHf0000Px&k4Z#9R9FeUS50UWQ4r21jrkExQSo3s=|vQgDhh%W4_c_Sc#5dlrh#A&N{+>P z@FLjaG3X&SHBF09%t?hPdJseq6k0?SFNy~}2r7+YgDFY;ChRP;FK?So32x~WZWwR7j?yn24)@Q^&0Uf@w|qwcR4SFKVHlI`fanMSR9m5n+X2xL z07zWrmbVJRU`MxfdU|?$Fc=)F)oMrJ(~E=~z(0OjEqc4osM@6Ae+tZ^7(w1 z5#%(I`E|imUA0<05sSqhs(LARNx_MqpP!Fo?Ixa(#-@U5=@6=*GD!YNV{cP6Jp(w| zlkm8W$4ZWbPw<>6m&-lF!^2&u^q4KG$CH8nJajch-yp8pfm|*ZLuJ^tklqQ_^P-QZiO#qz7x-~^tR#xbRru{hrpvi}6HY6sTOilx;!51&D!+}6x5!Lr{ zHWZS?9QnqwO64_C*E|5hZ!ueP)3DxwnVFewe!ssDN$H!RUj_llDZIRLQm686zKC?m zsROG>N?p)xMG5)~=w4P$sLD+NpsfSg(}L=#lL-{`3)s4A@p$~b&PLTW1wan;y^m^b z2MNi3L02RSlO^!avETR6@-J$VwPaTSD1ZjU09xGin{Tb;wAOHvogC;B8`aWBO8tj6 z>-L_PT~pTpD53)5{{xU9@U7Ppc-Jj)YxpmJ$z_he%voB7YTZ!*00000NkvXXu0mjf DPfosU diff --git a/app/src/main/res/drawable-mdpi/operation_button_cancel.png b/app/src/main/res/drawable-mdpi/operation_button_cancel.png deleted file mode 100644 index ee41c630ca7db61924dd6cf7986e882e8249efcb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 226 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaN3?zjj6;1;w^#Gp`S0Mc*Qs-Nu{@|yh4-wu?B z^>lFzsbEZ2U^LK@<~(piC-LdimAnDx))Xm3a3&T{Yd*+gaGd*4B-67NXN#o?i!Uk8 zF`Cfm^E6-q!xMw)?-?~MV)Gi#Ichi?GbEa9TB>a@r@27m5I3L3JY_ishVYLj|D(Fs Rd;(g;;OXk;vd$@?2>_8+PtO1V diff --git a/app/src/main/res/drawable-mdpi/pulltorefresh_arrow.png b/app/src/main/res/drawable-mdpi/pulltorefresh_arrow.png deleted file mode 100644 index bb3d235418002c1ee083bfad9293e416c20f7bea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 241 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UJ3L(+Ln;`P5Ae?W-+uV%j!@n# z=6PQaCh{C^Xqf%gph9`F4s+h$_QR*0uQUkP7*tHGPDn~TUoUZ|wz5NN!)|^)Z^<^b zh(Gdd;<0jz*mPbsA8ch7;_ludZgwD1P?GJ8m^)Xq`WLf=iSG}6IGV}9EVe&dVxK|H zME#3wI$z@d2(J=ltk?UZev&uC?GMW>f2oKEdtb2I@YLyiP*au=uVU(D$c}&EnOF8H o;)^WDT}=-^?m6O?55pN5(n~p+xSs4a0s4W#)78&qol`;+01=i~r2qf` diff --git a/app/src/main/res/drawable-mdpi/select_all.png b/app/src/main/res/drawable-mdpi/select_all.png deleted file mode 100644 index 564133386d5a75d90a5a4c6f900ed1c916836d6f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 482 zcmV<80UiE{P)kdg00050Nkl2_$ivJ*wr$(C>#kp;_?@18`0w{+GIB82^SmDO z5;t;`l^LGv8izi*&ug4+_h&2i)?{AfBZhj##cN)%O*y!UOwKjD%RyE=eB>3^?!nud z2q1>$>Q2`>lFQ_S+Q_ZoUk^KOi~ z0B3xp)_5x-CHL@fR64w7mF7LpMhv`WH_;2P*l@xt?1}fH(&-5IM5HuDbc4%2RAO5` zfHQ7aBtDAHs?*xWi(2ayAMeSRhvAIDI`ZY=(J`(IPJ)lLgM>4?q+Z%}@foj}_>5gq zFRco2!-YJ^#P}|+_}al0aK|jVtff}G@BJm3uNzh_{0PhHmNtD|(9WjSylbk$6DB|R z=?SNrIwqr6+{g^9sfm2V!&WhPszoez;TFBn%Xb&I+r7HR!Ox0H$FioODHoXm|NZ7( Y0kZYRm)im>ivR!s07*qoM6N<$f*U{Xf&c&j diff --git a/app/src/main/res/drawable-mdpi/star_selected.png b/app/src/main/res/drawable-mdpi/star_selected.png deleted file mode 100644 index 74e751e81da4d0d405d5a473c7dc26891829520d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 563 zcmV-30?hr1P)rmTngC0uiwTWx7fpbv z(JNDv!cm2k`%a$Jq&5h61wJg5_7)@h1%Ri)R*E6u9N;!WoRbpY$kBMsAboKNc#CUN zhkpWeI-4eTK&Ny5*-xXVKL|MTsry+I2%iD|1*xPV*X7exm+*81*9$~*B7WPE9sqkH zLQ|!?;(vf=z%kz0N@#2i_kn~;cf{4+e<-fNjX3vvT@_LZJ1yDX<^{qVu>Po!s^2m( z2q97L$)yKiIJ+iOSR*?9%p>x5AQ;!n8b~gSD}Sv3*LbP%aCi*&)Kq=as?>^W_!Q*>~b9Zi1{${)!UXXjo?|#DFS;Z zyh#6sGv#rWb!U9npe@4x4gvdlk%uRUjDv3Nl<%2iq&geNSQ=XuB@vS_uQK3(KKs*a zI@fUu+m2#iDla!665Y~Sm+J{?&{oked6olc6ONhfdNN~$=yIs{QAVfoUfJ?{neoL2$j0hoRc#E|?a07}|DlC|P} z5a1cZNzy*L&=&bmeSLk!;(Za|k!+Lb`8vQ$n}84y0zyEk4U8$DedLn;{Go>6sjb7XLR*zJ8J zQC8%R5^p-I%a+8;g=z16*ED>&l7*7JF5U5ws5 z+j(urN@r7rQkjcupNSN^p0*0s4obZAwBj)v!_h8ZtLY^t<64Y<_LQXEZhX$jP%z8< zjk82)(d?@Sjx#elF{r5}E*&zGC2_!_r_vN^`DhnPmj4Q z+akhioGglxm-a{m7(`2JI0C#ReY~0h>v(hWFPeoYQ3yp4nms7Os@ZWi~i% zOqdQ!=v1BT;~~E*-I7*YKC_k!ysJQ$*R0bP8t&}kZiP!OUU_#-xI22B$pugq2?^tl zgM33#zMI>;sIr63nw}ilZXTCsOp1ibRl!fyU~F>Q$9W;cA*0S}h6Ca6DRRYz9rp#|3o?o~vdM-(f?|Nc4Q3g$6)FEpOiDk> z0eh*nSfU{9%rKY&UAY4mCz5z#%+tM*9Edh=q_}s^Izqo(+4mtR-^q00000NkvXXu0mjf D7dk1* diff --git a/app/src/main/res/drawable-nodpi/action_share_password.png b/app/src/main/res/drawable-nodpi/action_share_password.png deleted file mode 100644 index 0c75886ca8e519da3bb4879beac6bff87d81ed62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1540 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 D+5yL@ diff --git a/app/src/main/res/drawable-xhdpi/action_star.png b/app/src/main/res/drawable-xhdpi/action_star.png deleted file mode 100644 index 54d4e7b6fb27cf3e5114b2e43082e037f024342c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 746 zcmVMM=zga)>ypu9O>EI+e&f0g(Dm0aGhj+wU3D+J0^6XKaJGqmWr&I z$p=R2GmAvl?5W1c9T8b`oh0ryPGrfP-ju|*R*EdyO`~KEi|qKH^CfkQNh2%f^{%9< ztQc9brNP|o2K5HEcVxrZ&M=T#XBaQSo<1>Px# diff --git a/app/src/main/res/drawable-xhdpi/actionbar_background.9.png b/app/src/main/res/drawable-xhdpi/actionbar_background.9.png deleted file mode 100755 index e3d8ebef4e99c2d671a445a9d28cd2e294d4a7e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 266 zcmV+l0rmcgP);ZP7PiLy?%^4W>Z(X{mWZgFqxSM~nzupOp89?} zC~=XCIK;?BM4X?25sa7!@U%=|GCETbBPOtNEk>N}B58g{e@;1#L`2q4Yg=kt0sBM311I-| QPyhe`07*qoM6N<$f|gQlCIA2c diff --git a/app/src/main/res/drawable-xhdpi/activity_normal.png b/app/src/main/res/drawable-xhdpi/activity_normal.png deleted file mode 100644 index be90f19f8dc7baba5b317ebfb2c4e057e5c35ab9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1299 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 V2ece9%CZ0e002ovPDHLkV1jvG{gnU! diff --git a/app/src/main/res/drawable-xhdpi/btn_radio_off_holo_light.png b/app/src/main/res/drawable-xhdpi/btn_radio_off_holo_light.png deleted file mode 100755 index 08b65d235eda6d42feda99a72bb9116aaeb80a8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1043 zcmV+u1nm2XP)Hi=Ec4#v6PDLyNZbLM*kL(=%S18Unn*0!<$VA zBH;(xl%}%NA&!S>h|^&h&dhYNR(Kdjvv=+Dn$3P-Gcf0zwb%RGYk#fvLZMJ76bgkx zp-?Ck)*jE)*;xo-9WVxL0Ghx$`>1Pe7HG#9=j-xmpaO&tKp%1wFa>M}M$v~Kwg#|Z zt%Hhi8Mpu>U_Qn;FdqjdKnS4$ybSCI_5nTgfvy2J(FdKgudhdinFL+|HUbUcd*EAO zI>y)=grC_3uu&fa-UZ$S=7BGPOZ>x#u85c&z&mJ^oCQt-t=!R&Re%t}5U?5e$o_r@ zoB{3vy~lOlglGU0zyaV5G-^Hr?!*|Ea`~K10DJfyz+wB>=fFj8=-O180zN?3iBEy+ zxkPw2hY=gGEx=K9t{(%g#26R7wo^(=B61InfY(qlu8YX;DJ8G@JOYTw7;pr54mcKL zoK7h%=dcZsQd&+a-4~JDKmfJ^S5r!VdTkGR4Ooc2hb~+vfS*0&2+ z1X$>AL+9sN;8c#Ve;6sHWf7SHo=3OWA4R0)nF`(rXrin19B_J2dv!n7#~E~ceVqrV z$x3(y*n}Q(z5-fKTmOa`>ocH-rpkB)*n=KlFQKb{UG#t-&?uPl7^*Bl2%!n=L{E2V zkmvQ4SYOH7&Je=zqkr*mJP|O8-s*m;WA3lQ0&v587_Dl=E5HbF%VDE376rG=hYeNj zcmxGjhmooVJOVWA_Z^1~FCFvaiL3$t3p@h!fF&H^mw~4Fu~5~3M}Q8x z505!)co{Vxx>XH$1eird*zB-T8M}u}nh))&c02;KP!V3taVx99Fz|x;P`>~%#x8Ib zOOJ13**PBfxHSJF1HYunRo{T<{q33UCKF4;-Z4#cIa)Mku9(h_ulR%|T#BMDC@OvZby2vCQ*c;CNVtX;PujpA_Vxbq_;#M= z@_0WsqD^!}*VYRXtFg)}GEMua{}q{5ov+9g3WY+UP$(1%g+gI1@)sS`QjFzPcewxn N002ovPDHLkV1hyz*PH+V diff --git a/app/src/main/res/drawable-xhdpi/btn_radio_off_pressed_holo_light.png b/app/src/main/res/drawable-xhdpi/btn_radio_off_pressed_holo_light.png deleted file mode 100755 index eed0810d0957eefa6957553797fd7b05eb6dd664..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3302 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 diff --git a/app/src/main/res/drawable-xhdpi/btn_radio_on_disabled_focused_holo_light.png b/app/src/main/res/drawable-xhdpi/btn_radio_on_disabled_focused_holo_light.png deleted file mode 100755 index 03afc89b6992ad87d4117bff21aa82f1ea34374b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3408 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 mWm=|XTBc=Mre*qtrT+n43t-C&OcN6T0000s_yF z(*09!(sYxwRD}zX0Jq7ZoZ!};Do`(6kU#`gReL}}po*%hl!`Maq)L^5Udn|YNX4oG z(gT0m5VpxGiT7W+-QBU*9?y(t-g_ME-87Kc?ry>bf9GJ0#xuW=vLB@T95)gnV08w*e*M1+g#e*jPgn;a#IIHZtBK=%+lw(v<7?;k)_Qec`g8lSps z%Fyb7a&$)LnIMN`lXV)4`aS?u!DWtVlvquB)2*MY2S0Q520ylLjzyDZ4#^rTgv0tU zBtTU##tFv6Mtc2g?H9ExZoAt61m;!wr))H1j?m;|T8rkM0aOK(oIs?mKE3^tM)Z!` z0ZjLJMgCzbK8|4tZx{q?DD(3?ig7PL?|ly>5{ZSx!s?yLeIW6qq5ZGjh)7>@hT-!~8lB*IwNt-qx%FS*+ujr1^0Jxwt@}p#oyz*LU|vs711n>kmf@>+J zX;i}6VE`&NM{(RMzwC71^^$YGc=2T6!EgG*IKp-wU*UC;gu{+#(U>iClcDj76e9`I73$Gi|AWi1r0BLO5qB~oN_j2Ks z$acitS6xLcL^hmyFSj>O$`&^1g9D_nMF3p~)?AercK=0RA#9ajb9*x*BEZ5vIDkZ0 zNe_U)HjbA4=M{sN#tH16Ygh@ALyUntmiRnZ2d_r`0W?P0drxu`(J=#C4WmWJP4;T* z6q>&G^aqGh#_a)^nLsvGG6+B=vNaQ8x&S7FB0e}kM6Abpzs8*L-R-13=p7_&ck8KE zuR}nOk#LBaFeEaC7ev>AuXK)X#+$mY2Rey2k8YmsbRBq6m~kTdF3EiX78M@34|9>h zpy$=I+u4<1=kDXc=wQcPd1hx`p)ry9dyX7l|8{-QzhDbHES?t_j8NZt?#wHBt0|of zA0yz>Vle3G!vH{@FM@mdJG;-(%LH;i8^B&B(B02B`PF#vD29({4$enIixp{*4Zoa?NW3C!xFp0#Ixa}S)K*P#Le{D-J{uS^8Ic!Yw-I?}p52qWvY^~>_qM5~JjTM4>98P->d+}V+rY?77)Qjh7JGoeqZAG zz7KmF6lv`J4GK*{#Lj)+p!f{P-WH7wB!}6e8EK@EMjC0Pk^T+!A(8RWOu6^~001R) zMObuXVRU6WV{&C-bY%cCFflPLF)%GMI8-q+Ix{pnH8d?SH##sd==8PG0000bbVXQn zWMOn=I&E)cX=ZrXPw diff --git a/app/src/main/res/drawable-xhdpi/btn_radio_on_focused_holo_light.png b/app/src/main/res/drawable-xhdpi/btn_radio_on_focused_holo_light.png deleted file mode 100755 index 5c53e94ec19b9c91934b7c556b4596c0580fc20f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3527 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 diff --git a/app/src/main/res/drawable-xhdpi/btn_radio_on_holo_light.png b/app/src/main/res/drawable-xhdpi/btn_radio_on_holo_light.png deleted file mode 100755 index 8fe5955f6f37c5ee0d84f60598433821d8563018..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2426 zcmV-=35E8FP)FGFon5%PK%P9JT{XfQPL5 zAHe?SIu0k6N*NF4&=;&?w+fg76hz1a`3X(TKi#+7e<-!!HsI@+qh=2&GNtjoJHB(Q~18GdCrifN1$80CtaK*7lvn(Xr!GHK$^# zdo|`_+7pRHCd%P=Sw?_0=YNh#cGv#($`Iyu=fWiE0X(^LqG!+4oz4$pRgLL%x<=D9 zH-OXWoRMXD>ej|w?4g=Yt;(hsw#6*w8j=OnCys}jx+dx$0!D#d8usMa3sZeP`#uK5A*8t_u7yMu7$SFVTmu=Da3FO5=0fDbdOJ9)b=Q8?W>0p~Nxt*&G; z*&GN2nwF#wHdSZOd}`y>*)x14^L|qx5NJv!lg-a0w-%ojgheh@3%F;7EgyXIB~hvX z5->nf&j3Qt=xn3I;Rpn4jb0n)N@xOtwMMVQ;Ry7M&Ndde0oeUjxv1rXcfR&Z5kSHu zK8o2Zg`=&IPN!=HLGW&u_AM*HJ^VfpZk>w;v)&%A$CyiOgZ#pnCG7_n;uOEE8{?96NiIV(?>T-^2&c&7w*IoccQL?}>py|mIp~5uC z18S8`&#r3ro>W!!<2@%AHe=1%PYGks$%W0Ts`i9}4X3Oefm&$iV4}Wtc*gAox)nwF zOx6G}Q`b@?TfJgJ6N}SX*vuKAPIw%%HY^d`HjPcbXy#q5Jrj$?a$RxP;x}_^ z#P^Jw`)+haGqOGF1&Gf2`kxx#7I`VUOz1qR&P+~DeiR4K-r`;WgduznJz$H>*NkiejDJZg&9~ zhN0@Zo|>JV9jdRdzidkE#WTSTe~&eOH>ErGQHGRQxssxdz)t~xdu{6Z->y6sVJD2ffR5SSTi)^S$#NFmssl^kGmX3@gYgKN-?<^%pNY(=jS^xGpP&4-l6L^ z{A=9zy}pc`1arm$@37(gk~G zeBqLT>FUjgQVT*U-PyzE`D&~f)|zlP=H+O1nUsE~y)Mz%8Sn+24I}d zRJ#tP>Xr@7cvhTD%j*WS)f!)v%h6&KU60=h+zM!3F_#E3M#0ef(k0A;LC{r0<0GNGwSIy`~Uy|07*qoM6N<$f;PXdumAu6 diff --git a/app/src/main/res/drawable-xhdpi/btn_radio_on_pressed_holo_light.png b/app/src/main/res/drawable-xhdpi/btn_radio_on_pressed_holo_light.png deleted file mode 100755 index 586165654e1bf56b913544bc865e1827d53d57f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3690 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 IM6N<$f?z}{y#N3J diff --git a/app/src/main/res/drawable-xhdpi/dialog_textfield_activated_holo_light.9.png b/app/src/main/res/drawable-xhdpi/dialog_textfield_activated_holo_light.9.png deleted file mode 100755 index 1218745f13b1083138fb314a83de553803b66411..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 231 zcmeAS@N?(olHy`uVBq!ia0vp^WrqYIJ_mx;Ud8D4uH^ZxHO)+z4CgshX>E=ss< zma+W#dEJ+ijmBbGI+ZBxvXlxZ3Yd6R^a%=!5$kknnQSk zq02+94^Uf8AJ9JFL`w#FtTsCjyJsV|Zs9 zOmp{UL$M|tX{f$^f4UkE2 zP{DY--2nrmnBZ^}`>Z;cW=p2!c(ELpXC+z<1IEUiZO9_3ZiEN4o3> zJ-b13b#^}adU5L58ta<5v7=)dU%D`T`PQ9h)vNa|Pu#h2>%{fn?tXJsnZ5L#fA5O8 z=fH+Xf7P!&?dYCaoLib({Nl;r#dF?5*Q5Khjqb+jn={d5b?M}p3rCbsmiw-}G{r{! z@m$ZJ>z8I9P900Hdw183zhC7W)7wvVUwpCSN9Ok}2j|73cX!19ilW}H%@htDe+@ob Bj_d#c diff --git a/app/src/main/res/drawable-xhdpi/dialog_textfield_disabled_holo_light.9.png b/app/src/main/res/drawable-xhdpi/dialog_textfield_disabled_holo_light.9.png deleted file mode 100755 index 4ffdd869e10d4a2d36b9248894b2f39b3851cfb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1116 zcmbVLOK8+U7>)|16ezXKvYR>G{a^xQ;U%Ih@>>IvqiXPTty%%?*0xp^Xq*6BOgLK9<`(X~6cKO&-*g;V% z3QoQk_h$Dg7IIlL!muURCv1x9*j4gPYY<|(5B57=lKDI_$ z0J}#z^VZ0qm9UvzJL!&+LKL_Vn{>$?@JV3JvIsyEw3t0;hU zJIlu`KF-r|zyh&NlBDglzyqG+1r7)?ASt4t@H|~x46zp21tq5$HCseVG6NX<3da?T zMYbriDCp-vB9Vx62tte?Vqw|CW+~={t#u6=46VTNv4cE1(rEUfVVq=$rwbvtem1+P z*b8fkA}Qlarq2PE=Ug|6tBMYB4*qsyNpzSm`;g1Q5DfCL}an2BN6fbu7h|44L3{ zNmf&70CXVBvXRyV-iT|0DjJ~9&3GX;Jqyc^`8~-O)Q3GgV6yy;as&_!w0KzC7 zKt8Q@x6@hEa=b{6%Ciuy1_S3fw2c6{bTz+*(D#o>V-nV5i##?+Tj_sLj|XS7tlH1py0^QPVW z>e06?_oI)=(Ax z-gZ2k9hCS6 zSzE*(Igfwyq{sv_18xCLrMEUFd_J>QU;KCP^v?tBv$j~y_w;V|Y%G1T_;J$GvfPWz zwL42YmM)lKrm>8-OVEi!@pp6n_g7GUW^>XlEX>1OBKGV!!m_OTj7r+kBS)j|{T6ay zUU29Ga|y#chP-n-%jQtTo)(5S9}MI0|rl5KbLh* G2~7Y1U%L+g diff --git a/app/src/main/res/drawable-xhdpi/fancy_orange_text_select_handle_left.png b/app/src/main/res/drawable-xhdpi/fancy_orange_text_select_handle_left.png deleted file mode 100755 index e6557d8febcb9fcf610e3d25c5906268df9d4a9f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1734 zcmbtV`#;kQAOFr}IxaI2j#ISUG8{W6_xoJ3B@`+5Ga@{5ea6fsmS#Z*YE^`Jb$3#!%2o%13oj1ZN$D8v6Isj!D=txuXKrJh${$nvF1K z-cxo$TJ*3m+;;8h4d)q}&6%0?ifR#+w!J)Yke!L=gP}4=sLZVkzEK(r=Dv5$Q?vbt z&O01czkk1%vXb#YdhpKLCdV@8LcPcKB0Z0qH6`mM7m)Bh}JA1Uu>a|SxCeBwDQB{>ByR5!l^}^oY znACMxEr#vyA(z`Gn8s4SsAw z&uLh_sL5G+%L)Y`03rKV1(m)at2gBYs)h0J^N3vrk8xAi6tUBt__av2fG)V9S48;u zrn37*g*Yc9o8H=f>=j(o5Bd7GB9F7}Z*hacPBd1K{_cthDq0GDIKyi89G~Ftb2;57 zoOOxzUCy=(VZy*$9&-<7r`Y~QwL@6B4XKdPpn)X=VGT=Q3C`|L!YdHf0o;P`XQC&wDSG$% zyD#(bt`uH?D)9=YrzFk7+egu}YEj5}dfeo%6y78Zuq%@p1w|W%nVJNzIT0RT{mia_ zLqMm=9ReEEdlventKK%LL;TeM7y{%M_oDfvr0Ic2KhCAB-<#bnO!yX-0NQ{NPg%FI zjh23Ao=LohANWFRb?4hTPAk-@JLj#`xAYnDrp8zb`>nP3^3KG-83@Lv+OJCAI(+GU zx-osd^Xi}bJQ=sWEFrfY|zv%V>$_2m9mZBn=8y(;fj(OP&w*hugc`J z5nS{5PGacp=Xh%FHS3-yN#U!GSL`zFHwS}nvwZ&n@;&7MnqfWxOjIg$yR?`~Q_m>Z zJ(~KGuu1gMPsu@zJg!q}uGZ?sKrcEh@=q1_GPH+*)4Kl=b1;Y_?#bT-v42vmRNKBF zy_E9%<02!Oc^vN=l%7;Va%rb=hCD#dchzRwSa(`_`H^u6!sN(tu|$HSsJW%OAGsx} z?YD>E+5b{g;m~He@l1B0l62#KPMkWD=Y=Bap?dybtQ<3wA+e%^^rT=iWp2?V=TG5~ zlaYK|!vcd-x>W5zY_sH(Q<@8qJ(gOe>{u(+@MN|SA_~@mX0nt;SXNbl8GL4Lm zSLb)(bBI{+dhX^+5SJAov<)FFW;@#U0WJ$!Wy;D3M|)_lu(&K;x?D1y7fyk|s7and z^&DP0_aBdZ*gQ(*r+r#r{_Iz;KdyypMT`z*EOo-dCo2hl5=6wr&kGIncJ6ygT#C>2 zzINBFtZ7%uY+64zz^`ZUvT7{>M?zR`{lHpdzMe-9##sr)ys}Ed8O>{<=v#!7RQQ?j zWd&^3bW}m9&tEnJVhJf?E9KXk$=HhaHu(?inDYZZfVD|EWYSn$vnmdpj@I$X`nug! tYucM26#h-!M;sSl1RwtU{|(a2wsP87;LJC!rs!~=0JOWe8^Z;Y`(KMuJnaAg diff --git a/app/src/main/res/drawable-xhdpi/fancy_orange_text_select_handle_middle.png b/app/src/main/res/drawable-xhdpi/fancy_orange_text_select_handle_middle.png deleted file mode 100755 index 531287c075a0a6a2c68a77d195605e5be3311001..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1563 zcmcIk`!^E`9G|+HN63{?QjvFFAk#nv<5gXlm*0SciIVd{r3q(n#A6f}}cA zv7T?T17WI}uhFk1Rk98^LtMOwvyaa;`~c?LtnEkON5WiQd;|#*RGX%zCO+nr&1xd6 zX2jgiW^xniGf|@R#|)o6mR#9X6p$XSN(fi2P^HPDm9q};R+5vVzp}Pz!7$7@Nk`gM zOmLnaj4j(krTULk z4ow0wweeEc%*yia)0D)s`GV>#8O5ocltt=o%+6*UZ;X6^@b;FnIXLn9t#Wrpa{$WZ z+A-E}e<;u_P%jd3^peJtw$pjjhQjwvvcB(-GwqKlqDHCkKD+D)0X|!!QR6QoNusg+FTB-Fj2~ov(!mUeLgTZ6Tf2EeYtM*pjH35=!o7gk^Ml^QU*kiV|hbIr3 z!#CM#{>7O+0oIhG>Iqd4@Nb2Da3WHi2n6-T>2atbK^P066_8xjNXRF7)7v&b z71{G*_G&p{OhH6AB9@m3a%^O)#gosPrc|0ZL1T18zoo4wKGvXWx#J=Qa7zk47F6%6{ zT;!yB;flwHv5NTUf_vUham-+tnb9CVN~g-sva8YX%%GE zUW~Y``7<}FW`d3|6i0a;$EPzjIqBQoo&cQ-h7{uGl!4_BdlWMa3Z+Gj%uHutoB$22 zr+hoA^n_kmI(*V)^s=UfS54c1>}XbXLr?ee>H4$?WeRvviHqo`k16(sl6keo=x(JJ zmx=2?TAG`;MlK??a2p}{b+xyC(Ut`@NdIpjp}VQ?9d4DPK}uwF&BW1kv!3$;H@}8g zUZWm{!#8_1>EEwS?~9#5O?BHZg*5EqR>qGd9pN)h{Op-$c8(`djRuQRM;M;|Jd#xI zJi748ko?14cG5~ugnsTORKM80GY!!KxWTzAt{)6eTBgf)b33Y?@z-Up zZS@;V#Et~rX7URR)@S8FSkBKq1902i)$HkpQ8v*3(iRHXG70Ysq>;cENy*PcFtLXE zbAw!^&|bv)fSQj90}M`tx8H=ertD56l(nH-A_AS`@2m419iQjFWmw@kku0m#7`dpb@c()JBQ)_ E0h5>biU0rr diff --git a/app/src/main/res/drawable-xhdpi/fancy_orange_text_select_handle_right.png b/app/src/main/res/drawable-xhdpi/fancy_orange_text_select_handle_right.png deleted file mode 100755 index 318c6be3c551a1d874025c7c6ece598c512d8fe7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1769 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 diff --git a/app/src/main/res/drawable-xhdpi/library_normal.png b/app/src/main/res/drawable-xhdpi/library_normal.png deleted file mode 100644 index 18caa1aaa6fc38899d4ffecd413d9cafc4a5ff8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 494 zcmVS8~!IN-U&Ad<=y6 z8)ywe1QCW_V-3Cc^!DD!;PaGLdMF^lD z&oDjVcB{DvfoMCe!e9~Ca4fRMA_UB8?$b5ybBnG;>MO5+G>1KGd&&-tJ1O9?NA>#y zTXQ0C;JpA=oCxeo5Lk91a3(=OcObx~5Qgq4VT@JA229nGU)OYv`*h7qxKG!}Pkvq_ zIst}|V7o}jfRcz(hp2v&66%-Z0|O#~AbunYJfUCo2`2EZ1eC!47La@nyvGMc6Bxk_ zZgC@pJEUTr0s-DY0Sd%hq|g>=<#pwJ6Cy~{2w6kZG>cyQr#u_RyxkfIu^BdMCGY|h zSsP54v}Avo!w{^Ig^6=3flL8Cp0{mK4aEys7^#A|> diff --git a/app/src/main/res/drawable-xhdpi/library_selected.png b/app/src/main/res/drawable-xhdpi/library_selected.png deleted file mode 100644 index 5e1803f627b0e1a485c0b8c3bbea93d4b38543d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 659 zcmV;E0&M+>P)nx1RVMClh zcnqft>ytH$MUjsCjS%8@?1K`|p3XnT|*<}Db t&4jOV!WXH_gs<7*MNkw)Q4~e_hd->(Swn`&)~x^l002ovPDHLkV1hLYBPRd= diff --git a/app/src/main/res/drawable-xhdpi/list_item_download_waiting.png b/app/src/main/res/drawable-xhdpi/list_item_download_waiting.png deleted file mode 100644 index 83e37d11c37a4d4549c1dbe24e09d2c6bbe7bcc4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 554 zcmV+_0@eMAP)4}LcHX>i9!8Z4 zDW6awp&WWarlYt|U+F9E(^1Iig>tTd``XeL{G?zq<0ozLwRHq8H-bR{3JN#?LvCbu z?H>9>K?Z;Fd2GOAJp2^oG5i>B8J>dH*pdmK2Y$;2{5}u+YQtx5kv}o78-^8Fyr+AS zXM^YW0SoZ!=9iHq3Z=hNUU8RV(@+aN%ASQw7GvYiKR#l>k1RD)`9`eqZPx*Z%IT!RCodHnh$7IRUF6n=Z&^q1GAbNoi&9R-kH zfEg+;FZW7hwU8qUKxb#?<3#y7?I9=80>)qH?FESX($dlssy13%TjS9aHl)9Z03Z@4 z_-*v|15(fGjL(=N8_UYdezv39(9m#Ia&mG{QBjc?dD5R&0P5=M64KJr-p5;o)MI#z zOH7TL{{H^@?(XgxnW?6xrnrQJgne|t(}jhFmN~}AjHuFET3YT*O-&s}FH*^ikQPup zcjn~e9BXT9TV@txZEdZGKWzV)&w1iV<{x`&lb{_=~4~1PEJ%oJc-5H@B19byj*tp5^>73LBrPvP

zE=WpB`VNOnj85mDZ51qu?qxDPEJoHFv`r)!cbQ@HCKLc3n|6Z45)%`5LX;(cj(7L! zl9G~BL5hk5rpTYDY^BJyvzQV?JX6txrMROSK(B}u-HT?H8SX#Q9@LS1pncx|rza*R zR$=?=Kzg|M$^eLBgVP2x#{?&OE010~YsL?kR#jE?hu7&wdL1W1|T~-`#E|) z-ESc93IH#W{11UN-S?H1m6x!2T$f#C0Agcfw@RFUSzKKFp@gT6%rg6JjBVCsPzeBD zdn<8lT7-BN)#?gLW`{M0@bQ|A?}V=O7NGZY&73gwzoqvNn(=heD7@87y&7Hi{C1Q^ z07_5z6%E&YhG(sh^!%$N|CUyx^TuqC0D`!gATlID^hxMplj(V}5_~UgLlOhF0bq{L z5XgF?9frS;;UAI8FE%u=e-58PevNUs&n^Nk0xkkh5D58qm@1s_#IVqzfXz`n`&IS< zRDAJfT1#OLD&+hVJ&ey$JGO$9jY>G^GSU>DH$^IWI_3^yGhKhMU3QDz7*!WScI`%q zT^jxv?Xs<)C`j_MRp}{S%fHw)7ZCtRzM3F**bTyo9JA|LiQaw*-IN>&0BT8oyQw!# zMCSe~Mu6Ci4f-+vdxMA;1l<5^XAf?hg+DDU=6J2d&~a+cAqmfvtP_AblCL)PqLE1N zzks=i%}mn!@0!uAXgUGNq%vEc4Oj(Yhc{pOKQNP}CY%`<82HqTXGPNqz)BqL=TI`q z%Ar#p$&0gr9_GFb=eHeDp46f{;vsV0#@sE_QB;) zGYiFhqld+rT*%4`5VOdMJ;N^UPZRV{LqkL7{c~1)SHQ;%x6#x0BextmvBtmFAEen8 zJ6_sjtT~3w_;zVZ0N}8@udnZUf8fFiAQW(ECg6CxPUq!(*t@`elBzCx;s9nf^ZtmU z&meKzH2|kQBO@aPfyoPN1o1IT-xcu1L8;2mL*myvNd6=vC`(83B3N4hkX%3Wcs*ZV zn10B>Ip8ry#xj5q#YmjoMC3;tyT`}Jv&1=}E(al}07HCea&j_{bGg6mIOP?1E2p^i z$gZ^`Z%2m2zhR`*u+se8j=YLYNB~4dcJiMdy`>bjjRdnnb(jYvaG3iyBayh@M@Rtt zf=;jEt5ASg!)xssY}^MJOSsDXUZl4Vz#rt38S#d6AJtFJ7_r!NbcBIy8< z0x*dbwBIo@O8pp(^jD$E!MX}A0xkkB0xkkB0xkkB0xkmoVFdmGC2(!`b*>Ku00000 LNkvXXu0mjf2Yo@) diff --git a/app/src/main/res/drawable-xhdpi/loading_2.png b/app/src/main/res/drawable-xhdpi/loading_2.png deleted file mode 100644 index 5c8f57a5a5ae5ff48a739ecebc2fe57d30264170..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1699 zcmV;U23+}xP)Px*T}ebiRCodHnplAOe(jX|(p5UmMVU!qpR3Zc-1R?|?1R?|?1R?|?1R@0fmk7l25%u8U;EnNk zd?&WpwY4R3frP13fj*3mj&6jhlX{TA%K`=T7=8eZLk$fL zZ|Sk?bA<^&Y;0_-9!H-e@Ei19FyfqAUS58(xw-kgk(3W}fzO=LRCjlGoZ#zn7`*ti zqo}Cp+{nnt0VC&pm@{1gBA0aP1%%?Ns;V~up~g999|KeZo*$w8M%~D`obeX=awzJS zp8ERwE|^_@=qeCCV*uFWPa${z^z`(j1ia)l<^#fc4zzIsJvA{g@%{Mt_=;Ut&JO0c z7Zemc2u}-hSKH@ReSjP0TxeF!>C%horh9v*&-tuX`;B`RI`aEV$C*ng6H5WwlT>(MC+m+{OCG6&Gn82>e?}hpK z`J1>g{%-dxsPDM~OoA69d0Gi)09p=4RVghiD{EIx_|cRAmg;PQ}=~O;(AMt{SI4KGv8UR16jkWBcf* z0l+wc>`{y7GPIvy(**zz>R#wt0IY8T4d-3F7&7^K(=cJoGU*{9+d!+hg7T7LScNQp$^tcS5J9&{I38vvg6uP$l6-${6GR?zc8YH4Yi#n7_T7!T`OC(UcD4FC!* zs+6o{dsS2UqPdc#JgiN+#!Ub?=b=;%oXb{t+O8FVGxA2#D%yt_h9*JQr4;%u7cnFP zxxd@7DF7F)Re502w_54D&LMoN;H~vCiI_q@qf@-lESXfx+>3l=e&>z|jUGj^zVf|& zm2)2D+j$*+?xbfs_DQd0r5b-b@b@KAB53FqxqBTwp?O2={5NS{-Q*EkFiRKbfq zBZoUWIu?+5Mdc{l;2~0DU8=b-XxV_^6}SC zzu?6$V*n)QhSEWgpkn0gPLS8n+1Po?c{)_{4G*9c+r zFioTM14`=b;0pRafqlkJ>~A}NU0q#aZEfvV0@w?v`>A)@=XF4U|3mABXP5QsXAJ<# zW2}Uu8Y{u>$1(w3MCLDu0>m|pZ)WVe*V~2sK!8tTk=v?K2?KzXLHW}HPj-lbQC{B2 zTbKhtFjZ&%8uYS2;`1bbUy)SQlowC7bb|kxRWi=u=gI+Osynn5V0VBmu@6V(0K3Ib z&$LhA^T-~j9&?@@nd-FUasq%T=ePZMNlD3Gw#5O!co>rD)*$ZW$H+9Q8US3`U?Jnw8>a7G;MSyY>mE|m19SQIo=<*M+uO#6~&hIDsQHc}G002ovPDHLkV1jMJG7JC! diff --git a/app/src/main/res/drawable-xhdpi/loading_3.png b/app/src/main/res/drawable-xhdpi/loading_3.png deleted file mode 100644 index 793da2fd6a47e0c8a8528956bdfc894169c86d9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1937 zcmV;C2X6R@P)Px+OG!jQRCodHnrmznRTRhBcH5L%Y80)Hpdf_AAP^scC~6Ews`4_TjS-;~Thd5P zi4Bhr3TcDUNCGCtR+~a!NK_=gYJ?_=iUA+dU=%TGD6vLVtW+tqwv?{FOLm%J_uSok zXJ_z(dy~n`{Li`PocrH%pR*%Iq)VECGy`b{(hQ^-NHg$%WgukR@Y>qiy!`z9H>o$x zwyvXp`t<1qB_$>AJMaJHJ;T;{MMcGl-rnB(sCSSn-^-PekrB4_8N%h?jf~hw*QlwkuJ&~R z^@RWeTTR+N9Z=&PvtfL!n=oO*r)0x82Tb*zG6D3_MFjRts)1P4zacX-a}Oj;w5X$1 zO^E;+Kb^*J)>^j^baZqyu|T?ov19|)eoRyqpI0an8XFrYCDJpIS+4{gqxJRmStCb| z+)psqIGFoRLq6dB2=a&AxOzr*7c)qrK{M3c++4%}xK3w3YUe1nu3xfb$uF@!Oy50c zKyGesl`c4f+D8EM_*IK}ZZ-|Zr59nTyq)?}!B%k%x@5zT_%GnvR#a4!ISg7pc*+1Z z2$6!{;yhRI_hol?_wALHmB*v95QAhry2~70gA8y~&)ms|o5_Y9*tqm8LG)#4XsCuP zsFq~OFi#o4l=Qr&@dE_2(A9Y5KSH{f)94qH1*4Ob4SN|M3bFB*q(dxg8IP`$WXUjV z0{HcB#ONH!Fad7hm-s=DDH4_=e@>7YL98{tfrR-e$8`q?AVD&!jSR&oSrb6&uZXd6 z=9R`B?&|7#N|HGkB&r5Hm}|SGwjux@_ehSxcG36$)X02?Ig4nev12Q+w)uv%C&Xo9xWSa~a!3)Cr53N&S_y*x>DnCkfnB~MivJf6Ts9mz^^T?;aP)=3#2%2J_7t0S4-x= z532D&l1Z2`0je^o^{!+Dr$sh%uVljmR-~!(nFbeOD+0&>-STrB$JK?RhX6nZz^jO5 zhGd!_<82AoVO9jNl|6ui?<8a3t0mkNQ-`dky7+ofqkDoZeNQ73VJiZn7M_$W@r%sl z6IR>_u%s(t45SM=Z+JkGxiPifWz4B?W6m?gycGRPaA)&y`;`wCXSmn;*&riO-w zF_J7927zH3wha;%CDLW|uR*RhX0as#V149fO#p-45RTTzdROl>qJUqry{@iKw{MWD zxv@-aw9v;(+@SLeIa85WI`8f~* zFiVmfV3-KiQ~wdAtNxe7S12_FU#qG6I7el_YGesv&jhGb87wd4iaVxO9%(=Uw|0KX z9UP@kQJ7^3pX4|Iy&5GTWB_YxPe2< zBQ*9lm(24cWYTu;jaQCSCO{d11l2$1K*AsDJxNhnmy<*%sTf~tIEhaAFxD3WRJ4$= zo7-eAtEH%_Yg=giO8lw+5u{vvBS1w$P)C^})wcsRLzrZPrv_mAVow!?O1=`{*oTBB zz7vim&_}rrJGftW#*#HmksJN67YG3=F!h~~jHqpEYn#YmrM9kW5pzVzd@5B8rq)Z} zSl=m6gRusay%Y3g&KJ94r$Ud@p!y#8B1A98ui6V(mvc^lE<&#aMD1;6F;pJir|4XW zcwk*X^cEag#(_+ums6>@Pu;=AnAkF#nN~h_7=L0f$q Xnhuq&D-ZHE00000NkvXXu0mjf9;S%$ diff --git a/app/src/main/res/drawable-xhdpi/loading_4.png b/app/src/main/res/drawable-xhdpi/loading_4.png deleted file mode 100644 index 010b2dad08acfb075767011167d9db227f4b2d91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1863 zcmV-N2e|l&P)Px+0ZBwbRCodHn_FldRTPGEax<|_i`aO z1!)j((3-R&QX~~I4?%KigNU?MY+8ywXlZO!5VYWn3aLfJOA1mObIh&BZ-vZZ&YZR9 z%$_qd9;q9Kv-g_4*INI-*S=0tR`!1?9R`MmhE^r*O?lD={p^*Omp{L6-@a{rb)tS( zQUm~QE|p64BuPT45pY3zI7t#pjet2P#FHYSqzC}?d;}y(LP-$-@N5JmMM6mtaDn6i zcmKvY97>7+mchITaEOSDgp^|-q2Cls+$z8{V($)~<(W+8O3I7LUm(Afe4U>!)ah(# zY1!jf$L3w^1oZUu)YjM6H{;X>9szxsje}aIHolMFgY(KZwEBAVvXj z3X+adca;1nGy3mNJ64DtK zc;=J)skfH-;~+%;Woa+m5Wv_&7+R+p!vvYpnMcIlKE7nQadle(Qw7Rmrf&u^2{ z+S>Xl^^ZGk+`qK9o(!#)kKW$i#Y>hfk&R2<~mht$7 zB9B!xGBWZ#U}qE~H!3ot$cg}1Z}4iui8En#U|v18<^B8j%V)G7m3CHp0V@Ikn^zwv z&MQtNCCVP9T?VbT_j!O^inOd2a@PBAO>s!sBBfoGA~T9?36R?xZ@R)ONUBUk=7l>E znLSSg@xzJ$l>g~fiWApv+_MpE0k@uWUsC%Ja_kI8oKq)TAzt zeuj&E$I^QnI<)crosDiQ0s#AsrW|4()Z}r>SbFc#^y5o>1Yq!#W)xzqPhpy#pfYZw zYOvX6sQ;Bgu8OT9fP=tk=$0*8CbV}ZnH2$|1mnX7W$TH8 zyA1Ej1iV~e(tB2?HLpItH||DuwO1}>CdT(Pxu49Mfa&S!128@5Rf_EJ>C>lAc;!-d zz(bxKr$aB9e0qBv9ZzXCko_<W2w>f6au_74;%qRPdPijZsv^<+AWpFN=kDLzg3Y35``i_H1nxz*dpxR z&IGav@YO37PD z0GB_hF#=yux{Lck>zUu}P%#9!^~~o3fR}nX1Plxe)HA*t0W8boojifAv>1Lv48@m4 zVKJ!?!+~`N1TT}OT%OJVSjd;NyLBg^;}yVf5A2*z#YhMU3f{+$K(7G)D`UQcuTO^p zH3(2C(1cV#PfB)p^r&od;!5%-G`&2lqXaL`T8E#4P!30*@I+1kfX5-WcXD#Fmrc%D z#dgu6NQQuT!R);aXM2kFsI2S{be!ZlG(J8)+}YWgjzFhTMPvksg&-u*tLGu=9M#Lo zPmuqO{288;Y-m2?3zOVBNmM10A|*gPE<^&L-(&`VCJ}T2j6^XB=9mH89zb^H2wer< zMQ+oj-Z~qLGkzox@;MtJYmXyt5DNj~qlp9vh|OjgZen%DK*Gfl0ZwmtNw_HH00+GD z>n7o%nFAc~&aXp4sYQU}Ut+)i#y?0W2JXLT=pV&O-fL5-q7(oC002ovPDHLkV1ln) BZFm3x diff --git a/app/src/main/res/drawable-xhdpi/operation_button_cancel.png b/app/src/main/res/drawable-xhdpi/operation_button_cancel.png deleted file mode 100644 index 80ea31dc8d9a891fc6f99933cbd86b8c510e2540..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 343 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-s_5nU2u0Z-ltlqZ-{qG5S-xKw} zCjpV(*Es!eKz5=Yh#RH(4M-;HeNEB@azX4QJ&4G+XubN%_;)}fL`#DFf*H7j=Ip=u z*>K(W|ME-zd44GVyMCL@inKFgKn0sTT^vIy7~fuXopwNhf%$@iZRyVcp~rdaC8NLQ z7jp0TE1bM~SLW?y>()3%Ug!T~@J061vCL)3^W0K&w3%NwEy+zkvQPE3vEs#~n=?1) zcSwY*tyOjsHdbTVHB+*f{ra2K}_2bPRfgOH!*$=XQ zzHvHoG=1$IhxwECWz>g6`g?T1$^zyd7b zq(<9*0b;{!U1{4Kg;y-V0xV#$M%!u;Ol-_vsT+j_Sbzn1WxPh)WD$(qC@jDN zEWj(t8f~c}n3!<-L{h4jXy+BH)0H@K&}#4j%5gpQ?xVQuW-sz29RU{J}ce!3HEZNY`WoDQ`~~$B>F!$q5Ho{u{cyySKMn zbg}jZ_D7Q){>XdXUaVcgH?`&A^Mcn4xc)7Ethn^S{|z5lFLKRWpjpu*cUR6MkZ+N# hM1jio|0hoJG3>fros%xRK?Z0ngQu&X%Q~loCIAHGFtGpt diff --git a/app/src/main/res/drawable-xhdpi/select_all.png b/app/src/main/res/drawable-xhdpi/select_all.png deleted file mode 100644 index 65eb90e8d1abf42368d942662132f2502c58cd38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 988 zcmV<210(#2P)+WDjv`3;Zq=(Yu<58iw{kT>IJ+hSPDBGL!Wg?e+O z6oR)h-_r4Q;974{NdOD771{}WZZ;PSX60B0?DXanSIjo}PTfxQ))=tu5**Ed-*>n4 zX5iCmJI;OOSA@#*uCkeB5F9-n8qGGM>^BYWRomBf$y{ii6R+tAT4X9o%T&BhZ9gze zh%0zNUEJ#+FKE-ze}R?66>h8YFJe{)`Aby(d#)qSOvMS>f%P0V?#U|u3%3w=xSz`3 z=`i|(!>b8@U^giQJj#H%wiQy3eh>T+o_rionEb5xl>vsNw%aPSV_;4w^yc43SMl`d zDIY+7J^Y~Z7g{e1^FV(GaeQ6w$8m+z1Hd|&4DkR2>NG|pldXLRE%k=B># z=X1Gq;C%)dBQcmmGy}?EZ&Avo$KxvVLR82u!EY{+L@nF50Q+@0uE|g8p&lup1yy3bvOhElDB>@Z%L$aO>7oe-l zJtYs`>}AvDpF=z(u9#KO!2mD278)3;9oS0T=}Ibjv|AmewvS06v@fpr;ZNRLspXf_ zxY(KAGIk&Hy)<==UdB3#(IOSq~1{Z zH#nCR2Qv?JMEnI6`6~o(2ApZ@$jL?ghlSRASJ_+dQyd>ZGR$fY!MnEAy?Xf+7aonz zt@}UtBu@0V_E+>~Z=A&}?{CSY`5=C^-+pJ#((qe+yxCc;DOvK*@9>-El~RHQ%?Wr4 ze?+Cx!4Wvap$c|`bD3RvEGlr7xdv4@-t3|E|G$%rjEszoj6VTo#Fj_2 diff --git a/app/src/main/res/drawable-xhdpi/star_selected.png b/app/src/main/res/drawable-xhdpi/star_selected.png deleted file mode 100644 index 2c30be4420d69b64f7993610fcdb1abf5a159845..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1013 zcmV)HKc<|ze2qG$?B7z6?B3|4OL=i-h+H$dDl% z3<ev<)`u30MeegXh5lG=2nGaSCiFqz%Oa+eP0#a>CZxO-LKi@NP05 z=^?+T(E&xIC+QXo9V^ph{B|jSPqJp2>^!6ej?TK2{3UhvI!y|&rVLlC4BmjnkaP?h zd@~w5_74Y)J32oKNe4Z7XzU+lxT0lfPg5X&-cQChVY0JANAL|K|Ct;>2~u($VDRf` zhQ|H!;eeWDunS3){}-g^ zi#mG)NkU0hXpJnqBB+ui2NF2QSgnHU|WephPIp5pQN5UIJLLI zPOCGivw~lc@jeeqMpY$i@{$`P%FiQ2_4fOxT+^Ml)=#-s-_JL-%j#VZofF7$dir2K zVGFiR*7o%^W(r1A=EN5lr5x%GIs zUSu_3$v=v0_qewUa#TMA^h%7{X6_Kg0qBIs0XU#1PPM-sL~2{=gr%{X`?Z;_eCwkP zJmj4KE6AQWC}2n(I8|Yw=6*pwgA@j_nEM4qM`y31Jog)Dv4|Oy-Qr5@!=NPirWWL1 zMSdDI^J_f;TqRHb!ytZhKSeV1J{jJu2N}ySz^d?vAIX!U&;+CiQU$PY9VY)6SLe^l-I3xn{--ep^7ciY*MC(zuH9381JP-AZ;d1_h&+;yV;s^N!$ z8hh7~6_n}w`S`u>HvxHSi^*4_9%>jkhIU0tvC9*n&2QboPmu#c4FgJQEK@rWW+N8& jzX};LWXO;q!++r~v|-5Ra4@H#00000NkvXXu0mjfKty8!#p86ARyq>QlXhMXNEH5sr)^?f_X>Mg$0gJgH&v!nf@KP{bAYTYX#~d zWhZ^&=H+vK@Dcf;eRrM0!e!oWPL=OjKTPASzS9$LcJ!gggn;|1!VGOpH91attOl>| zmP`nC`7|N2t|w)&%Fa*wI39HKKl~H(JjRpZvfY`(TYx}n$^AT)Im{RC9G$u%llf0g z@oal_%iDXO1~cki*kAwOtKojmww+SnE?+#_tHE*7RPDs!6=%yE^Eo>@W;stfeMYue z-Q$Je;?#J|IZtmh>8N^~ zGyYx0)X*uj&*-H0vsG`;ojkenHIu#e0j3|8+vFGxE~;v&xbM~7zS_vhNNd-D_Z$Yl z=l)%&npn8V{sYf8ee?aA2i{Ci_Rur?-DiGZY(kAk0}FQKkhoBu^Mm6R*T;SwiY)@2 zzaDR{dvWXNn$_?Bi8RE!{F?sp)eeKDtS-<0u**kVYTinmv+Kp2xd#=1Q3F!{Nc7g@ Wdv$q>Q+@&?ox#)9&t;ucLK6TJE%=iF diff --git a/app/src/main/res/drawable-xhdpi/upload_disabled.png b/app/src/main/res/drawable-xhdpi/upload_disabled.png deleted file mode 100644 index 8e26905abeb2a1e46f837e9a1123f2a31a5682b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 419 zcmV;U0bKrxP)hCQ5{fu2BH9uFXLX$l1HiiB=51(kli#C{I%&t7nvlxA2&d@{ zcu*(nxEVno0)QPYCHw*t0TQXCp?(XOe{4hYdE$?3?EAc;Z zk@$JW+Z_05fZ}F-pZM^9xrzEu=QjcXw}eN-`7i3*fM~G*3$OqSumB6N01JqEfLq9N zmb-;WFvb{m<&ZFab;a+HBjOLw7Y|1{K#u%gz$!Tsrms~n#u#H_PXI{tB02)o%2)sZ N002ovPDHLkV1m}E$M*mL diff --git a/app/src/main/res/drawable-xxhdpi/action_more.png b/app/src/main/res/drawable-xxhdpi/action_more.png deleted file mode 100644 index 5e160ecc5df4da70937ad7075fc0afb470eca2cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 505 zcmVzxkOC^1~Tpj4{R-V~jDz z5SkpcX9kNHM=cV{M-YKR^kNPR7)&{0$VU;Ld~{vbJk0Bzn@NnVAHV{_=@E_gKO!Kao)_P4?(J&LuJ&H*H!m%CAdD4jkQCW`W zF6l+XX>@HR0GT<9u9P^Cnxp8-i-vpX`Uft8Vq8a8M;yq>1$0eC!)0{c2og}{<|4X^ zo`*k)u7+q>iRKYiB>*uwfaV@K#DUmsMwdf0v_x~5)DnQ8OhmJkh~hvnhN4+c4AGE) zUHEG!E@b2aejZUvJV?t4{MDC_p*Yv@nn7gopb_`*8q9yKsKo^W-J=6xML`Wt5$FNkh#;RvTw1Y~Yux7)Qz%RbQIV3a?BF(cIm|G!@t=GI vDQM4DZoRHYk&EE+S24yIV~jDz7-P%};bBt*YY*4N00000NkvXXu0mjf{Sn;i diff --git a/app/src/main/res/drawable-xxhdpi/action_remove_cache.png b/app/src/main/res/drawable-xxhdpi/action_remove_cache.png deleted file mode 100644 index 357288c0297e0fae385d153aee2d6deda9b9e493..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 924 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 LNkvXXu0mjfG`3?T diff --git a/app/src/main/res/drawable-xxhdpi/actionbar_background.9.png b/app/src/main/res/drawable-xxhdpi/actionbar_background.9.png deleted file mode 100755 index 380e53fa7132703fc946d8098b46a3ff3426f14f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 358 zcmeAS@N?(olHy`uVBq!ia0vp^ULefD1|(%J94cgBVAS<=aSW-5dwa(*uPK1R_2T(0 zrSjW;$M=-VzX(b8ZNG4uL%9EO`VY&PLp_;$p1t2zuqJc;<9~4#`(mc^ZM(iR?$xgL z^ZkrkcZ32MUm7y9YH=NzxyZq_(IQx6g@!S; z2nbX-Cqj3{(hKuGKb89bY2N1jiyZzI|93pf&H{1fEe=yZ`}BB~Xvg3GtnBuCuw~im q27J883~}N%r(VN7U$0$UX1(s1lF;j4?x%p^$KdJe=d#Wzp$Py9%7`5R diff --git a/app/src/main/res/drawable-xxhdpi/activity_normal.png b/app/src/main/res/drawable-xxhdpi/activity_normal.png deleted file mode 100644 index 78c6b0a38b75788b7e3d73c43d9776998b98a1a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1271 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 diff --git a/app/src/main/res/drawable-xxhdpi/default_avatar.png b/app/src/main/res/drawable-xxhdpi/default_avatar.png index 4b6f92d3bd30e7cedc7e0c183b89b4ca25337ce6..59f27fd6f6173052b4f56d98765b25813386c324 100644 GIT binary patch delta 1283 zcmV+e1^oKjJB$jD8Gi!+003*OSX2N205ecbR7KX-*8l(i+S=Ov{r&p-`rqH*-YEf^Yiod_4V!T?eFjJ@$vE8+}!2m<)`+r-T(julu1NERCwC#-O-|(APffJ z`~(nD)c1edo$c&w>w0Q!OIt$n-SoOqLqLdRj4{R-V~jDz7=L4oF~%5Uj4{R-W1hps zv`n7yf&Elcbficu9%p=sq(WR67w{6OcZ0_+KTyvG##P*J2%^U!zRzUfG7eih91|kr z2Hf3EFylsvdg3PBEK@(6#~sFQ5`pu$i|-@?&I76=;BmK+I*{>zg~UrZp>8ycc#!b< z;ok(PG!Yg&tbdST0UtC!1S9@XITmiHVk4+V}sg*!RB~srLc~l~G81Se? zXf23}M2>?N01H4};$9C}04x9&01JQxzye?aumD&9EPntN01JQxzye?aumC*5R)j17 z0t3LPib)#vFGeO*BXg%*K{Yb>j3-nhbCXa+H8OY0I#eUF5G3-t2LYl*cMhz$SzhXy z38y>-RNE#cbBokZo9-vz1M8g$Rop8nbC=|&6_unJ!s81AoqFsKZOZbO!7Jf+$Ln~ zP|#nW$R&?&`6PbsdsG-E#tEK=o*(`1gD_{$0Dt>D4;t?LZ#n1G-1f#8V~jECxe$jT zl_gK(IQkDCKT}>x8ip9UE(^n!%H(l{pGw+#n;N*7vd2w)?&TYdVOTuw$`6-)XeX_y&Lz(VeWZN$@z&A%~oGvldvZh3GF3n~%$?jU5m zNPliI1ZzemBHtgnf)^`I<${6nT5Wxxc^y%ayloVTQJtWj6mG&BO>fTxk?}qae4;wx z?GoLgfql?DoU3&cn((ef;6^lJyfFf;d{4C=e;@+=UjN(ff%l(+_?O^DO9hQ7bWA%3 zB2NiBU>O+A;Xv0l`6an?%^xvppa8zJD1UC}O!zrL=gb#0YN1_?^@aGCtY#z4l>k?Z z0Ua4&FAMZu10AS_dRqj!XK|#(CUIXRwVjJ-TBL7ir~EC3b&3&0=kbw;@aipXvJ@7LOz%A|J~?*ITms;Z)>3jiS8E(m}U z;5KI7B@O^^N6}e9L0eTp0j}-s>EP^U4*)8p?=t-LsVC_$ivwF`$@{?=3Z>l!5wSfZQKF!EJJ8E?a`uGl z;#zZo#b4$q z>iLSDg_C(eVB<5zWi)wJ1zU2GgTA?3=JQKx=1hn62 zB|%1ih>&tHDBdufnGbB1LRJ$_|2JaQij*77VMRPeR@PymO~{C#k|db3NP)Ccvd(ZO0L7oSV-WkK#;;1X7MNxPU6oGPB6~d83 zGv(+MqQCqEG4HLj=!p|}IRR0Yt{%SLitcoqGT|7Z&shQkegi@dlt&b5JuX&*Uj(KQ zZcGnJbCUTx4Q;ln`KO4da;9{)U75%*-x-#2Uw|3|%m_RfDybXpP9{8w-@+vR`LrL< z!ni=P7fs&fvrmbn2<5tg-jIX>vTiuvXD%lRtQRV(i2ngJ z97V76mm(APGuE%0%!b7)T~a~!KEszWK+g>YQ7t@A@e0K!M`l z;*jac*HY@CYC0dXbf?l4nIDxsywqARZpnF}KP53k;XpND_^qWfNHDYCPmGo4Li zSV>r2_<6Pd!Ix$G9~C)toMMY~*;7+Ie?Os_N1Ep5KRK{lCk8KmIIUsWJL;lGe${DStS$ zU*91}xHijRKwzyfDOwYir0>|SSv8bU`DPb6|FvnbiTqe4h_6+@^-im1t7?e*wf6NY zHWBMj8cVVNj`lkBR(U)xQkemr@Hv5kcFF>E3ymX*eTK{ zHtp^`Nd3@HNJHYY@Q!H2Ak!ekp?MHHSiLs1CVymlBu5+BpWg99<&a-kS4d)G6KHsaTt=OvAo93Q|Ojmr}{BkirF_z)_+%~u?$z|QKp}%r|H>x#T zdNDNl+MN)sP|`&ZqZvbIwtC6b_Vl ztv>x1Uqk!uS*q6Z{d-!yoET0P&Cp*&D`VcDbisNri`2cR=kCopN@-c(d*<%-3a%#? zH5FwP%c%OO&1O8z+kIn^EyHR>T!t?w0v0)SPBXCBBMj2mi6(CFg(_4c2FtBR}0 z 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 HQ$iB})K^}) diff --git a/app/src/main/res/drawable-xxhdpi/dialog_textfield_disabled_focused_holo_light.9.png b/app/src/main/res/drawable-xxhdpi/dialog_textfield_disabled_focused_holo_light.9.png deleted file mode 100755 index d157d7d60a5a315c0768e6974691821c77df98c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 464 zcmeAS@N?(olHy`uVBq!ia0vp^0YIF@!3HFcFAm`bQfx`y?k)`fL2$v|<&%LToCO|{ z#S9GGLLkg|>2BR0pdfpRr>`sfU2Ya}CX1P~bN(?fFvfbiIEGZ*dVBY7UXy`D!^8fh zO?MCYmH#i_q2u_PQ|=8zrlw!rJaa|0*~Q;HeHB-fY&`qv$obtD3Qq}ICT4IeI5aRY za)C%y&q*qtR*rsJTiFCad{z+2!~rB3SvUk1Ffg%j1TZi%v1kA_Fp0pmePuUDHed#e z=dW0O6KyFVvf_WYfM->m+d?i)OK_1Lr5xFlcu_p8SEnV z{^|A?`b-YQeqZ%>o4+yGH7mr|AG*(8_`{Idye#L(3*{INE%zhuFG*GyA794nrRP=p z^!uF!DQtY|$E2UdwXXc&*f#mm*}mOhw>%RR?|F22)=lZnv$oc9u64EqdTZ6kh#hH` l@BXEWKg$)*ShsZtv(*`D9y2k(j diff --git a/app/src/main/res/drawable-xxhdpi/dialog_textfield_disabled_holo_light.9.png b/app/src/main/res/drawable-xxhdpi/dialog_textfield_disabled_holo_light.9.png deleted file mode 100755 index c91f7da91e4028574774f32553c040a1b058aad8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 315 zcmeAS@N?(olHy`uVBq!ia0vp^0YIF@!3HFcFAm`bQfx`y?k)`fL2$v|<&%LToCO|{ z#S9GGLLkg|>2BR0pdfpRr>`sfU2Ya}CUud?i6EUPJY5_^DsH{KYsl50AklE~{tL0U zcmEfi-FD@|g@mTCef&{O?Q&f%Et>Zt_wAhT^k38c38!-`10xd)M*xV_@SLRLd5PiY ziV$gs2B3flh~!cLlB@zil8FOIGO}<8EMS1^04ck|YLINe4AS55E2hqDOAC;>;+yVq z(?6eQG)^;sX|K8vV_EmN^KcrQ*WsrDTD##AOgEO7?3*OQlTqkVw4HgDw{BVEKYmsA TOP>UQeq!)+^>bP0l+XkKEaktG3V`FTff5wG7cZ> zS8w0r^qvXY2f+s^~|1s-JMd{n&A@)qYh^QuX{|{t{NF zouU=}|CQ}eozgCpuQuM!lrPF&6w=J=CFfPT)@FvXRIY*4hWinf&pK|NUHZm%36OcL z!*zB_N%ZH7KDVyD`Eyw3n7VXW0?f0%N4DH9+D_qwQ5~kZBL%(FL-`=emLj+a9-zi&ilO1dA&XfW{>naLBD_i0N^x0A}km& zg<(}}R~VkdJyjC`Kw<`n2UekZ8~L;}h`z|{==qIM&Dxnk6%oPBUYFZ=fkg3G>j)jN z6mz^PD;E;S2GxS<=)k#{WnN3O^6L(Qbiuvg1fFeHe|S8H)Q2rrojO(gy59R(S8UJP z=91H>H}&a={bIoLmWJrG;Z}#vDkFnZD*kvWAp7XcdJmD zz~5m3+ZoU_xJDe?$L8Yh|Ech*DE8z$%tmapB{BM@{TsqC#)}U78!YxaqvVwtK{rQciJn>s@%)Jy?uxgd~F&8%icOa z(f-+bx;@Ny*>4bD%|{ z!-i-CU>Rhq7*Z>0{L}L}$HDM|i8*bI653vRS41h9LSKzmyb`j%`2A*&FVOl=9qmjZWTE-LW#w%FBN@IG4d%)(# z$+k#p=>fa3K7oGXIs70&JM1Vak!_NJWyffaZ9N_{9SD7@+56Vb?#)v-d+B7@dF+h# zvqw9GHT4MoZzq`k`Ru4KG$h{C`h}O?n?6S2S7Sz!rg26tvEUSWK*!ba+BDgDl@+k| zqrUv+&FB@wc|NVnC5bniq&3#0Q~ZLgIzz7-7i$Zj!|vK_|Eg5K6R+}t85c)oo~65f z8ou_0YWO*(EZf6goOb`HFUqU>NF&C}O&}0u`x@z@Cqz(MiYaT$~*B8UJBcbDzg+WV`44|6QMPUk8@eR3&&=YpxA12%R5*|{ZekP5EJi6h;YBRcW$nWdAq1^8$=FXyjS`f%29oj)Wzl!+7P8oK?|B<`-*BF# zOL2DsQhbS`P{9Xe>lJM~lPS7x>V_^t=vPDlZJ?$ zFuX&x4VH?~!A$)qBv#7;w|Gq*M1q^A$8?Ee*hlzLM800GZ)1{qi$mb{-(;8-cuR76L z9o_J9SdAi874K-=_b~S6%Bq25!~Q6b7_vP1C&MttD7~BzXaW4|8yJQnjEuC_X(y(k z6V4%t5-DGW?lgvZOpCr%K5E^BT;Nu~RR1z(yfcHD=ccSiNRYA9a!+9SQ!#35@p1hs&JuebTPl&^}rY{uTkuhN^@Bdv#&l^U(1; z&{sb)vS_JE>DX$hZPm)At^m!y^=bO4KG3=2 z2SoEufd{zi`y3AvVPS)MglnUr#;eTk7kWGzhPp!O=aP>{j=H6QOUzGXD4b!1VIO{+rU-Ox6LBEZ%jOs?CIiVNGVM58>$n`_|w~B zSi|Y|#F&;>l1kM_4;1-sh5GS1hX4{zHYYmT>hf5M)+;EQ=SZej1U_6C{63`Zc*}w@ z-SycFxrPB9)oq$#OtC?cL-T?%%a^rn`=ljy;yc_Z!5XZe|DG9(0me=69goLR;2z0j z6VafHxSN`E={@()QXNQYpv8x(-}}RUWYu4E#LK5BB%M!$WY)T7h@y?*;;B9@RPM<& zczgh1vdenBfojBi0}VxH3HS_C836;`M~Eu8(~Ext40ZEo diff --git a/app/src/main/res/drawable-xxhdpi/fancy_orange_text_select_handle_middle.png b/app/src/main/res/drawable-xxhdpi/fancy_orange_text_select_handle_middle.png deleted file mode 100755 index 0768f14e71eeb05e8e44c7d6b7745b699b157f4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2611 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 diff --git a/app/src/main/res/drawable-xxhdpi/home_up_btn.png b/app/src/main/res/drawable-xxhdpi/home_up_btn.png deleted file mode 100644 index bc854540b850331e6037e50927f744b7f6a557d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1968 zcmZ{lc|6qX7stP{Mz&Ei#gN;NxyTsHxHOtD4KtK&lI50VEMsgl!_0_~G?cXP%UTg` zi4u**SYwo=NS3k`Ng?~SyNtPYzxwa@`u%?A{d&&lb)M%r=RAL%=d!aSL0(o>761Tw zJ0hM0Dsg)$Nr7H6o-P53M3A+EH2_qm$%)94U@XQY5pY1oTlh2>X!;XL4ghc#0RZQd z06++Cou2{#wjlt__yPbX7XV<4yn2_z;DMCiF#;Y`&^C`YB!IG$LG)yT`m-ls`#SUp zc#C9*w|0&GI6v$|JVDTe8q7QQ3!1+<&E@59#u=2L zCgZ>5OvZP`n2E{!lB9(DFNdF4?GuK?v#F&tH?y+apQ@2P=~N+IX>meiL-Y?Dk(eejj^9zH&Zo&w-`O; zXJv6AZV-*84LPQvG&3UN%`^BL7nh>dxV{6#>7Al*5%#-A0?J}2W6o|}>vLm=0v(0&yOwKvk;+UUS`i0`+BPuQK4%FYp7If$Pu(9qKTZU zC&sdGXGoopZ}JLr7e8^g-sb;Mhx2@)Zw4xKy$FR%TMgXS(MOloxFbUm{pBl%xXP+U zl~t}p$c?0X5A}*?Wn9lSBt@L6D4&EA*ym;XW{sqzeu`ve z;Y(WzXy1=AqDK=6c$Hh-uA~F1cn-DpoYyp_UsmuzeCgK>718DAxRhj%l=3nTl$r?b z>{VZOXZY?w_)Ic&8X&OQ;QSoL)jp2T5rYjZD*?GIS6%FBKi z`O&}4!{{KTwT8C2>$Zb~ zXf00pe~gOMN2JT9aIpH+D--nC(Tp4IjkC>5?xKUBjkGka+YPn#nTFP@W>bziy95G(E+Qgcs%GG(8f_$j5<8XBdi<; zdBjr*^A#VuD1StnmWbpQ7g3SH%<$Z*PC;wXk|&=pXkQ#MX|yWY@Elxywas`ymBH_@@>pSJD=!*KM?bfS zmR`W&vf)!ZV70rX=C4NCTBxX5Ox%fds(J3hX@vyvWyuN`SdR+@Ma_DnEtek{%Oncc( z>wTGQcH++CzOr&FrV5=@+m-ntzgxM8`+N4-J?^E%@;5sn)tR~jnW62bhaQx_>kpn4 z3h!xmP?K^NA>Ia!Rh0()t@RK{mBU9w_=&izhYA*~1n;IycpZpid-Ium^d#GsDoTYii9e~*T#B*)= zlW&=m85v)8|9k89`O)Yc|r z`~4gHW=x1=B`LDg9htvzg6@Ls`>UeAy_)sO$Nud&FD>nDd?)#p+AqFGZyP804^KjKp#a;y&WKZiIt*uSqSsu_+XM-TX^ zfnSX_%f^EhKxPGE{38QF0gMfe%#nuXNMlo1BNL1%8e?pF(9j%XXgF<9YVaQk;q-uD zO3eRH$YmEF;@W=0 diff --git a/app/src/main/res/drawable-xxhdpi/library_normal.png b/app/src/main/res/drawable-xxhdpi/library_normal.png deleted file mode 100644 index 299f5f3aa2cef52a4c542de5eaa3027422daa424..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 478 zcmV<40U`d0P)&@Xb5_Rnj(szf=YxCpYL-BE>W};MTpqT zpAZx@*qhc=fud$>!f`%#-1BJo`+n#9@j33E=k9ShC=?2%Y7=eXtN2FR_Gb8)CD2YI z3!*5aY{DW4Ye?b6=Ds4$#w~$X>cgFE5dF4yDAYy^!uDVpCpePYL-@>qVsh43Nw>^^ z&ZKjGuwe#tDjih7`hNzoTlw!0-!}o`(=3R9%(9@#FIurq?#3c}zO00r!aW$~1FDJ< z-em}iIB|(AGRm6e+lCL1bs(W&kN(>|Lr}ztxMI*Qfe1t(0+nuz{7Nw>UBtA;Zv%LS=L8qJnM%;KNk30`LZQ8dC(&88{E$3#U}Dac=bS^$Rj7vJRUWdzzuqk z6P_5;8}Kf?G9DsM0g&6oTD#C2kQToD-geNr0+80L4s;i4Ee{HJs{=j53pYH5b;GX? zRKOdOcrt8~eW(N3Ujh+`Km;NXfe2KSpj5SizA6Zc>w%7uK)fQo!6qHlDU_PcFMghT U*16K%mH+?%07*qoM6N<$f-8*KE&u=k diff --git a/app/src/main/res/drawable-xxhdpi/library_selected.png b/app/src/main/res/drawable-xxhdpi/library_selected.png deleted file mode 100644 index 55e2915cd06a7b2657fa7f1a974a9c7f0dc54aa5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 561 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 diff --git a/app/src/main/res/drawable-xxhdpi/loading_2.png b/app/src/main/res/drawable-xxhdpi/loading_2.png deleted file mode 100644 index dcdbbdacecbbe73be3b0b28aed7b1df6063b73db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1969 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 diff --git a/app/src/main/res/drawable-xxhdpi/pulltorefresh_arrow.png b/app/src/main/res/drawable-xxhdpi/pulltorefresh_arrow.png deleted file mode 100644 index 1219011e891631a628423145f708071b5af33bec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 401 zcmV;C0dD?@P)X97TuTyV(&&w@JvV7K|SefkwSz_QXgjJrz!=nYvMs>gla_kZlF z>L0YOs#WiKtK2~cP!I)C5CwG~A+f!h=N8l=Bn<|1G+`_u_C)~ zaz)t((8VOoH5*+OmFwaJ@|>+E3U8jM(kOff&NPD(tJOD z=l1#+ezlkK+<%RjIdB#K(X;d)7|Du9)(euiNBXnH_=$+iQNLfN>Wcp z6H_QSUqTI%&G!OpX-9v(FeQajn$P8iwY=pl$KQyY5s3^GTra`yCr`}6_)HRd*Umwq zNcZ`rtGdim>%NuBQG!`kCNT_nwOzu;A=$CNnXF53IpWVKk^!+ zL=$6P@+TS>UrPU3gvyAwv6nWxiJ3|8tQwI`wLE6W6Z|v38q$I0lnmdaGB7EA;C6g# zS7?yR_;^t5$u7OmqqN4Kqw&cXtSmV`Lly84S?mk5M7_z!H|v-1**twnJ0#pNBta47FGSnxnLB8-|l98b*%o6VD!n=hz>kQ&{o2&Zdc6ombe?ccm z=(DK?W<`7}pN@)WE?e{z-JMISC!f2K@iycO_LR_<)tgXiXNP2`B?SkoYr1*6ai|J@ zS_G})FKB%c^Z?a?H<3Qxj#ti}LY}#u-B|<=)~+NTEn@voBk^JqejwGwZqL6tTalTB zH&Ki6Ts>caAG$tQ2kF;qMbJ-}l7nQa{?WeN|4W@37Z`tF%b7O8l3rA@O#%}3-*i{ zBa)7#K==Yze-sM#liTsR6t*{Z{xBn4WWJ@ zY^{UzyO!<4SUAE>I(%)Gr{uv}{J9Q@`{Tu^3A_eg(*8Y0O>6RCUTFbRbGQtvMq}48 z_#5I*mEMl0%yjhUQJ50vsmTye%@<)8UZXT$W|Fg~z(RH%-V4{_>z>cQ$C=`c{#@b( z`3`PxcHwtiBIfq5!LGy>@75JFoX<83(_hY5SkAWLT0G7p@EyL!P&~|6;zTS&|0YsW fQc_YFqP8c9Sx zC<*mo=0gM#5p+Q?D6>3g_S(m5&Y8>EZvTJW>$q{onKQ%8oV91x2OnpKvp4%+-~PXK z`PUk7aBy&NaB%1p0-t?2;tf3n4z(Ed-~p}pb?vil;7|(`=N%FVc^Vw*3iKl~u8OoB z7J@^Ky`n^*apc?JP-8C=CazNQQ{Yh7UWS$P9?S*@02E@Lu~&+P&=+gL0U(h4O%end z-^S0t0U(t8dlIA$FUKC-1%r&s9?UiNs>9NlV#}Sq%;;6iik9LdXD<|^0na*nH5Mq! zdqWQoYWBQt?A3rp?eq5sb#izT8Bc*f!Jy&7F1QaHJ~2gsqKh8fKWO%P)l3eXldcml zIeRrEJLc@wn1%Qt@WHT`60aD0wZLL1*(w+W1WJ6z|#ZV$Xz%FQp{}GH!zbq!51rGih`qQptpN zLx_Q&do=L)lf-bCKV_bumu$^sTuQunD`vjF+8)W@nqmhkj^81`31lf@8CgVLOIS$$ zUL_`<|4)4{q^>EpDNv=^z3KSUv8CfOpHuOalY4)IsUlIel%pA2Mm!M=WEz4(r`;FLVN0=AKJ@Tgkees2wzzK&PR- z*^2_nf1r*s!}#F}+c75DY7F)wvfRVr`ve`KfvjL;2h2~ZhNW&>iq?&gMbMl>EB0@f z1sXrI7joGwt$MQ_q!LND1)^n8Mho-ogICvHxz%rU~lUZL(LCDxm)}s>I@6Z(^cIM^hq=)9E*x z>=jjm^ZDnaAH&@+O`xnbmZKla*kZ4rNgP``M#^5wVq|26&FCPc_HvJbMWESq=(8== i8j*v8gM)*^K;i~k@(F6Q9ZE9*00000yZSc}C< z)(U3aSFf)TQTND;>C>lANdOCjaVi^MDA2QIu1c8M{8JoNSjaT}ny*B|(BQrloX2l|5nRVNn`yS^@5) zmSZjEt0rF+B&5^nCd@dZ^5@KMV(8^qYkL9ix<2~=MoIP~f`r0CR|mkpplCNI%#{OX zJk*0MksBh}&j=Fo^75)NW6MY5*n$wsanq$61lKa?B-yVB5(W(#R6z!JY4=h4;vpK8 zk&?yUyDb9|HCFL22@(>CL>U0fJ{t7LhHU)7RKRUYCX-d=_3soU5K##L*|#TQiM-AU z8|p{@RzZql)?z#nm5#yo<7NAHhyV}(L{V_B=^oo@POS5@3NQoIeF@kHLLh+z5=bC{ z1QG~=1QO^R0tqCLKmrLQkU#JDZQIz?#;W$Fwl=kG+e~s!v6^+YtGTL;O>O&`gPZ$a?cSs~-zd&68vF}M zk|a@XnnDBDP0uXqMw}poTwlP z3X`ISA3D|GPc&1Xm3y>R4eg>>_4~qV2(O!$nKo4aeCXyyFz7Gnx|^k_C7l1lX0`r;d`SuQ!}3blc_70_ffc0Zuu( zrp8lg1We~N;g|aO2Lhi50qcs<1sBcJ&2oBCyuNAM0bd3IT!4>h(h75&ah&S@(^z$Y zuY-V$|ePEbwg*U}d8nnluDva>me+wfTYX0|DJLae#xot$QYz zN^kl*K)MV(Y+hCv@B2aDk$?mwAOQ(TKmrnwfCMBUTFNmTLrWn^lJu{(How)-{A+D~ zd~Y CY}Fb7 diff --git a/app/src/main/res/drawable-xxxhdpi/default_account.png b/app/src/main/res/drawable-xxxhdpi/default_account.png deleted file mode 100644 index d4efd33ff368367ba4f6bee57cefbcb441a2d28a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13822 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�+L%q?(JwB=m;m4|s=OXx31p#e7lTXW~Y0ZMn2Lca@Vi?Xu0pp(7u$6?l6Q!$`oZI>e zQ;X7F84>Lk)lVB;1yNW&ae5w^{v6KM-!kJHn6E!8FL(a9QXZ+gJn!SRA1E4T0o)%2$T$zKToc9=ez5}U&&lK8{C!j_En$>JzD&Fnd`z+X zO@TyGKq)DHYfiDz=wRAgtd>5f1j6l00j2d?lnzRCG)>=IBwL*x7bUVK&p(v?BMpuG z4F}#L>6h_#iKqg7`yH8jG_;Oi@a+_pf5mIDO3Cb2Ix^`fAhD3_+5)yHYH&?e5x1h- z@!H>?Uhuv<4IwK(PNgrYw10chBT!$qVi$C0CoDk1wIG3nal-aN@_ zUSNG|N&y3=884ZxQQhndISUM!k~#wG*9sy!WzyeJZvP|24$nUxf{lflsohjDQYJ0A z;c3Jfk{t1ymp#X<(lY-!qv?08k~%&EKDWE2b(bF@QB`$fU(#ZIQ;TgE-HN~1DqTFb z+R=8+>&Ln`*GEZSqG)Y=*kD;CB9m51#(5F^=~`=6jT0S~oiajw!gRoa>^LsZXkj6! ztS-Xd#dMzBv_s-l&MIv^eu-~L848!aiC#W)Y_Iu?2!nDQ&ZFyqy4HGZ5gR~ z{gzU&YeK-)U&6gAJHGWFd6VUE=0Cv`UUX{`QtWZO+%U2>_C0pz4dN{W@Md~CW@PYC z$mK52nlELJr*QApyBQG)O{|gpczFj|?<`4#kgJgd<=q^eUi z=LERpnjmhZJ6{FcohSQbZlQTHD2Lu zN7r!9w7i2-O&b8cf`Z69HU>1R-8K0i48()3s3$%OimaSXyqtWm|60=lfNs?wDD+EF z6*^fZkEiDnqJoeEDUfI$4}4pFl8lckU)^VL0I;4U=;$k7xm89ty+OqVH#do7As&eE z+tVk$#pUgI`x57YTv=~RbQoy~BkJ3kjvIY9|G0G;bd)zxlQfFfR(IThnfHMe#nI^k z$k0KLgE_a%C9J0;%w=toNI4=1y37ixH{D%_+mrhC%HDn(=NB;Carb9_*VM^VQapX2 zc%&mfis!_B*WS;ZClQ4bYXzYRFf8uxbJKx^_riI!V7qOmAcMIbS)k8(`pF%y-JGlH zdeS5le25gNp6FXa6XAk`R|#&Wc-DxeRRxgd7KEs}q^~Y#cUT_tR+5|Ez3##T!!efr ztC|`Hi!#z4%OIqQAVeUX$2*b>v1X_BzJs{07uA|zcy{@GXaFj}V%a#Xm@l&5r^moA1nvGsOJib;t@T{rDr?_AB6NL|WD5Q9x112j)g-iz0U8d>o>*Gbg!>l%aD zYPpN0qIdO1GY{Y5;{9~%Eu4ATRG!-nM5rW>3799tQ|`Yd>C)(OK^B<117v!iRJf*8 zKUA6mGXLI+s9&S`y}klltg9-nkq6_Pd}kII{kL53gJ!vc@!DTqlblP}mIy}gz8^;} zWK<+p?AatLf9e0a#yawsRO1Qu-x5`4Kk=rs&f5QVXnfG(b9tr_dCLvLp21N!uzR6; zCI0*%n!*SWd|&DBbMhdM0Z6;%uJI4Yz0OvH zlI$-kwBvO?Pm-ZeMOUX!)&!!zc&yP;rwG9&*qmm4M++@yzfdvBJj0_}w4?3>iG!b8 zMwI?v%gA@pmWQ|7&y6n*vi%hDiZs{?y0~RdNxKmQeJJaiX4|i8*ln|njG$w-|vpu^Zr@v(*D+ZR6V@>Abf{@BC~g=xJ@7u`k4hBZ#g2GX>DF1M+5aXVb82RmUU@>k YEq=ke3rBW&Zs-E!rB$S=Bus+-4{UEx{Qv*} diff --git a/app/src/main/res/drawable-xxxhdpi/home_up_btn.png b/app/src/main/res/drawable-xxxhdpi/home_up_btn.png deleted file mode 100644 index 722fecf014665af9d779bf3d8340aa7d5a1f2d15..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2411 zcmaLYc{r3^8vyVJ*+w6B()|(P# zCVO^9h&P0pEX81~$&B@T^xyZ#_nzN%p8L9=bMEsz*SW59oy5yF<_93M5C8xUT(U4l zgVBAjiwc0xw?Z$8VAzK>LKy)-dAi7!2S1qe;L+yBK-ni43rrNfEYK(bh*SlD*gF8Q z0ltcz0f1ZD05Izg0Eip_kPgZHV5bib2zXkVn}QK+T$5@N7=%MCobX_@_}wpd1Rw!G z=-?$&Bm3~L>`{`%6|9Wl(zL;#$9Q^b)`xC4qfg%r%=6xn3$253abMpPx?YK0Rmxy1 z7#SbQFuqSVmE^yBxd}EKxPMn|M7{5dmHJlP7_p!3@?%sWmEWJRNZ*`KuaKDXyY;#% z55Kq_vr|`B14Z^kFxGR|VMoEA_}8W_pUIFAlKe5bM_5=#&nvWberqCmrg;w(c|Rk+ zKCBip+(bWLD)2;AGif$}-%Za^2(o4lO2S_mI&SCtyX6*m*}=3oXIWTU?^X*Qr9*9`m;|D84i1VVb^iyhr7)56788BHFoyvV)c)I2??OSBLj&FIlP4cI?G<=%5cqa-uR~wY~}kTUOPod zjqMhzbPwvu{70?%kE{W3H;`K~!UdJ;uPg)X#9sgSr{)8tWvhB`VDCXYx$>(wu^Jeqvm^0wUOhZtye9*KM-*W%Q#%JojAjrz-yw#%fQOMQ~4K8Kgv zIUq-D5u^XJjB5EK1jVK#IZ>>_(}8~W?9pYuW@XgpsV?}c1EbT~`C8>}#qW8I)VvXz zfySKwGijb(2eKDQu*X^y|*;f-v ziYAv1G8*-Q#vmtzLFJD3njgrBR|T=;6lS`#iLu9TJo-;3%*Vt8*6IQK^+~TX-k~>% zNm?hs%CF8#5wkUkLqFfJJTSJlh$1`!?pzoaMTR$p#c~I-zhcm)nSSD^v9UA8$Ih&D zYRE(FEh+X09VMD__S9R)G6$De)N9MDJ&|pKyYPAqAoX+6&lAxa4wnu7!9?3aAv`tX zRs7@#d!ZuMnY@Jshi8iQ8>MZ0&qd=in9{MW*t>6%9QsN}s#;=YMEMRScl%Zh2To3c ztM!;SNXDwJxz5%O5QA$P1-Xg}^DHB!29hwiXZq@QlA|8NL&GrqS1z{%ypGTpg(#5^ z@0=~J3Vit9!g*?K?kL7PiGFLYa-COSzWCWjiJ?;5)fG$E;r?UH$H84y+3-O<E&lY9f8+pxkjv0NGsQ80*9>517q*h6*V6dx)d0AE!i2IJT3O|I z!W`}CoS?wYnu&;0`zeF&Fs>F;^Tc7{BcM(8x!P&yH_UKu_b^O6Kc;n?T?O81_PG%e z9h5y!>zkqfdfkG9qBT1L6TC{CAM4-iz*L1`=1B1NAow7>Zu)=$IHj#~UQ_$L<|$o! zozn>2QwZJD8rtU(+S;iqQ)J~)ch*6@9mV-s+I&Ew!^zl&t3Ij`}#ws($u+2-o3qc_p4uNxQx^M{G!I0;(@7W zU$g8BP`;aauZiP_KBJNYO9zAC1-Xzz^8UG3`3|_M{`q;U`JQf_Pr2D0!-}H?a`|gs zJw96?w`cLJ#o6AB*RRS{bWb{}eff2~R^*3y|9E0&aP4?;o`L_DaYb6i-_1o*KLY$N zuT=ayd7;RMB#qPu%d7q@InV2JY2v!C{Dz$V1wIxRuG|k7Iv}pv_`9C%P0_XbMb>`1 z_pjT3SC;)>$??M9#~apW@%(W#b&I}#?S8`2^>Z7f=YP5WYw-j9UyFBqTNXcE`GNft qj%)Loru}@Ct$yag5}~&Hf_IoR@^{1tZV=!H1(v6)pUXO@geCw5vd72( diff --git a/app/src/main/res/drawable-xxxhdpi/select_all.png b/app/src/main/res/drawable-xxxhdpi/select_all.png deleted file mode 100644 index dc2778b8435019da60ef30a3c8bc74025c27bacf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1302 zcmV+x1?l>UP)jCL9SGxdZTs1_ZQHhO^V)aOKAa|_Is?DkxEjo_C;t%qCNkc@meht?L9iI# z9W|nPN@Fly39qPmEasT`NkJ!SHJi5M5sfae$29eRZ#*sXYor4Xj(PT-r(n@rl|KR~ z#)NLTU*w-lZFJmb~$`6OX3=&3GWsL%l_6!yAZx|o${OAjCA4KoT&@$Nt&?AHhFgL6rUdwLXZF23U!l?k z_sH14pgHr!y6#Q)kY8}0Gq_6P)dQp4EHx025gtn@uivf17!Xk} zP8gU~X~2atcq-M80(T>@TnU@{V16>e5N$v$#frr6Qc^o*j|ZueB-WnC!g?7 zjn@wCn33aNDouDG**uRG`7fn?C%oh85!3v44UmK|8r`_PWk!X=)d2RCq#jbMY;6AFWB8$+#Z;kPM`Yqeahd7KY6D$}W^S$z5zZ zw7s$$&4^l`lC<;mM&O4v_-7~o`|P@Obp~;xnhRMrwLwvsU;`qvG=C>$#I^smWFrfi zekhSu#CX_Ne; za#TI05l=Ns0TLQs7+;B?iLI(myrr2^zLr0wG>h=d|75$zQZA%2I=(nbeVghGl`eU| zm~2+fr&?CQD>`*!z9ro2DN|ufuq(@a*{DRNMWGEIaq^#+Iaa3Pu02ZJ?OG(1ro7H; zOQ?4x38mTKFmuRy@sUDlU3-gZVt)z!eh!u;1#~z0=ECdZeRd}WRJU2NMl9GkffP{L z=$72;K>jtgLL?OQsDWLTpNw@ukP)O8@34oN7dONYdt~GZMLTZ8Gx0&V2fxLh_`gl0002U95f8y`!>=0kpKVy M07*qoM6N<$f*xa%Z~y=R diff --git a/app/src/main/res/drawable-xxxhdpi/upload.png b/app/src/main/res/drawable-xxxhdpi/upload.png deleted file mode 100644 index f00e2a5eb70e581b327991ffb6cf4cd3cee8d1ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 725 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>V4CCU;uuoF_~sgO&mBh*wh!UA zx8>e8`**YQK1-?ewJxcU!dGs*T|2f(UkQuQUXgot&c}26YWH8R%HN;wqUGwPxpYbp zm{=IF!o1<`nHxvX-1SVAPd1n=xu4;;{;TK94X*`U3uXxWaiMj-{TZ9)kZHP>t}{3K z^4WhoF0p%p(cHH9K1ub~14rf4c26>DpJh>)l$%y<-Fl%_XhUNMSHQ283LA1~evIMx zzEI$o`oqH8zrMeZpK-}gGMSC}TeH9`OLJ{2wS9AsGk)9p;m2NOw;l~QE!~H=Pd5D3 zY}t9b_r)Ve0TZ8!`TNhl71d?jBQUT0(%tY`LCY9)n)hs6r@E-=iAt!$mA1PK8~BCx zv+4ConXY0wIVFDe%#E&A_nBw-2L@>=WIbNT_+VYYqulU-(k}m>PZB3vqWbWMn?3#V_xB}buVMDUd#Vm5|_JKJjE15PUrY@tJLjfc&K%knMc zjCGCcIA%ZjXPA6!ii-MTRt_$M+rCZ@<6}9M4{Ow%Njw&>*l{*5OhifjGM4~DgzlM^ z`Dc0zqNr*5nyf*cSE1W-|`l(R7`A{b3uh-olrk^}{j>(2$bIprFwnL2j!&|Nf zJxOP_ycyKcFjH{np9ekmx)V>`1FHCYyiYdzS-o|6UW)?{YhXa9AcJ7fiTiBav#ax~ z`NejvR%5jNy*kH9s6~AYTU+)K|K8Ef(-7=3NpEqB{ vq**NPuAR@o#Ia%3l5h4`Z*BDf$pgUwW`8fXEQNb-c7k}Gu6{1-oD!M<|LrgX diff --git a/app/src/main/res/drawable-xxxhdpi/upload_disabled.png b/app/src/main/res/drawable-xxxhdpi/upload_disabled.png deleted file mode 100644 index 854dad811b18e235430000dd19d78dee8c03f4fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 561 zcmV-10?z%3P)@Q zh=_=!f-I56UKvD_bd^W??8VnZ*9y_2BlX$aFL~m+AWDYC()l+2+lao1igAf$)BB@a z!YD+6mqCu$&>tuG_y^&ph4j1)e9~^+vRm_q#Xxvz4Y^@Ud%G+4_V&!ia@ihV=EX)h zx%T?d##}rsj%Ksvp3TcA7woxBk^tc&gWucMrw#iTcs~H}v+4{b8WaM z(i;Fi4gjX6m1Fj*Ba#MTAqixyje5^-4tzcUn37EP*ki}1W;BEYyd`cStHi-TbOM1b+Dj86ze zHsB)+U;qOczyJm?fB_6(00S7n00uCC0SsUO0~jD_^orb&8+ujJfQX2QC~~}te%;`| zyo&f)`Yaq@kquyg+zP|^avMZML_|bHMD&U0YPpZSb0p(Y00000NkvXXu0mjf*{=xb diff --git a/app/src/main/res/drawable/ab_upload.xml b/app/src/main/res/drawable/ab_upload.xml deleted file mode 100644 index 4c07b1a6d..000000000 --- a/app/src/main/res/drawable/ab_upload.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/actionbar_space_between_icon_and_title.xml b/app/src/main/res/drawable/actionbar_space_between_icon_and_title.xml deleted file mode 100644 index bcf44e8d9..000000000 --- a/app/src/main/res/drawable/actionbar_space_between_icon_and_title.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/activities_text_bg.xml b/app/src/main/res/drawable/activities_text_bg.xml deleted file mode 100644 index 8d1ffaa9a..000000000 --- a/app/src/main/res/drawable/activities_text_bg.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/baseline_star_24.xml b/app/src/main/res/drawable/baseline_star_24.xml new file mode 100644 index 000000000..12a4a5056 --- /dev/null +++ b/app/src/main/res/drawable/baseline_star_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/bottom_line.xml b/app/src/main/res/drawable/bottom_line.xml deleted file mode 100644 index a0e88ca37..000000000 --- a/app/src/main/res/drawable/bottom_line.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/btn_bg_selector_holo_dark.xml b/app/src/main/res/drawable/btn_bg_selector_holo_dark.xml deleted file mode 100644 index bee344532..000000000 --- a/app/src/main/res/drawable/btn_bg_selector_holo_dark.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/btn_radio_holo_light.xml b/app/src/main/res/drawable/btn_radio_holo_light.xml deleted file mode 100755 index 65e939bfb..000000000 --- a/app/src/main/res/drawable/btn_radio_holo_light.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/btn_search_selector.xml b/app/src/main/res/drawable/btn_search_selector.xml deleted file mode 100644 index 9034751bc..000000000 --- a/app/src/main/res/drawable/btn_search_selector.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/camera_bucket_selected.xml b/app/src/main/res/drawable/camera_bucket_selected.xml deleted file mode 100644 index fc6884f07..000000000 --- a/app/src/main/res/drawable/camera_bucket_selected.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/progressloading.xml b/app/src/main/res/drawable/cat_tabs_rounded_line_indicator.xml similarity index 53% rename from app/src/main/res/drawable/progressloading.xml rename to app/src/main/res/drawable/cat_tabs_rounded_line_indicator.xml index 8216dc121..63ff6bb89 100644 --- a/app/src/main/res/drawable/progressloading.xml +++ b/app/src/main/res/drawable/cat_tabs_rounded_line_indicator.xml @@ -1,33 +1,34 @@ - + --> - - - - - - - \ No newline at end of file + + + + + + + + + diff --git a/app/src/main/res/drawable/custom_tab_indicator.xml b/app/src/main/res/drawable/custom_tab_indicator.xml deleted file mode 100644 index e7f4cf37a..000000000 --- a/app/src/main/res/drawable/custom_tab_indicator.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/edit_text_holo_light.xml b/app/src/main/res/drawable/edit_text_holo_light.xml deleted file mode 100644 index 9608e49da..000000000 --- a/app/src/main/res/drawable/edit_text_holo_light.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/et_selector.xml b/app/src/main/res/drawable/et_selector.xml deleted file mode 100644 index b8b164d7f..000000000 --- a/app/src/main/res/drawable/et_selector.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/indicator_selector.xml b/app/src/main/res/drawable/indicator_selector.xml new file mode 100644 index 000000000..e0b5bfb8c --- /dev/null +++ b/app/src/main/res/drawable/indicator_selector.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/list_item_background.xml b/app/src/main/res/drawable/list_item_background.xml deleted file mode 100644 index eaea82f0f..000000000 --- a/app/src/main/res/drawable/list_item_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/list_selector.xml b/app/src/main/res/drawable/list_selector.xml deleted file mode 100644 index 0600a267d..000000000 --- a/app/src/main/res/drawable/list_selector.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/lv_expandable_divider.xml b/app/src/main/res/drawable/lv_expandable_divider.xml deleted file mode 100644 index cd6828f87..000000000 --- a/app/src/main/res/drawable/lv_expandable_divider.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/policy_dialog_style.xml b/app/src/main/res/drawable/policy_dialog_style.xml deleted file mode 100644 index c6e5cce78..000000000 --- a/app/src/main/res/drawable/policy_dialog_style.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_divider.xml b/app/src/main/res/drawable/shape_divider.xml deleted file mode 100644 index 24dd70f0b..000000000 --- a/app/src/main/res/drawable/shape_divider.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_radius_solid_orange_dark.xml b/app/src/main/res/drawable/shape_radius_solid_orange_dark.xml deleted file mode 100644 index 6cb9a4e36..000000000 --- a/app/src/main/res/drawable/shape_radius_solid_orange_dark.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_activity.xml b/app/src/main/res/drawable/tab_activity.xml deleted file mode 100644 index 080fe6741..000000000 --- a/app/src/main/res/drawable/tab_activity.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/tab_background_drawable.xml b/app/src/main/res/drawable/tab_background_drawable.xml new file mode 100644 index 000000000..a42e28152 --- /dev/null +++ b/app/src/main/res/drawable/tab_background_drawable.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_bg_selector.xml b/app/src/main/res/drawable/tab_bg_selector.xml deleted file mode 100644 index 7fc23e845..000000000 --- a/app/src/main/res/drawable/tab_bg_selector.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_library.xml b/app/src/main/res/drawable/tab_library.xml deleted file mode 100644 index 0e17cc0ca..000000000 --- a/app/src/main/res/drawable/tab_library.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/tab_starred.xml b/app/src/main/res/drawable/tab_starred.xml deleted file mode 100644 index 09f5dbd90..000000000 --- a/app/src/main/res/drawable/tab_starred.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/layout-sw320dp/list_item_account_entry.xml b/app/src/main/res/layout-sw320dp/list_item_account_entry.xml index e76866b14..91a3118fe 100644 --- a/app/src/main/res/layout-sw320dp/list_item_account_entry.xml +++ b/app/src/main/res/layout-sw320dp/list_item_account_entry.xml @@ -1,34 +1,41 @@ + android:gravity="center_vertical" + android:paddingHorizontal="16dp"> - + - + @@ -36,11 +43,20 @@ android:id="@+id/list_item_account_subtitle" android:layout_width="wrap_content" android:layout_height="@dimen/lv_act_subtitle_height" + android:layout_below="@+id/list_item_account_title" android:layout_marginTop="@dimen/lv_act_subtitle_margin_top" android:layout_marginBottom="@dimen/lv_act_subtitle_margin_bottom" - android:lines="1" - android:text="Subtitle" + android:layout_toEndOf="@+id/list_item_account_icon" + android:maxLines="1" android:textColor="@color/light_grey" android:textSize="@dimen/lv_act_subtitle_txt_size" /> - + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp/gesturepassword_unlock.xml b/app/src/main/res/layout-sw600dp/gesturepassword_unlock.xml index 0562a3c4a..067af8cbd 100644 --- a/app/src/main/res/layout-sw600dp/gesturepassword_unlock.xml +++ b/app/src/main/res/layout-sw600dp/gesturepassword_unlock.xml @@ -6,7 +6,6 @@ android:orientation="vertical" > @@ -22,7 +21,6 @@ android:src="@drawable/icon" /> @@ -40,7 +38,6 @@ android:textSize="@dimen/tv_subtitle_txt_size" /> @@ -61,7 +58,6 @@ android:visibility="invisible" /> @@ -74,7 +70,6 @@ android:layout_marginTop="0.0dip" /> @@ -90,7 +85,6 @@ android:textSize="18.0sp" /> diff --git a/app/src/main/res/layout-sw600dp/list_item_account_entry.xml b/app/src/main/res/layout-sw600dp/list_item_account_entry.xml index 8eff27c63..91a3118fe 100644 --- a/app/src/main/res/layout-sw600dp/list_item_account_entry.xml +++ b/app/src/main/res/layout-sw600dp/list_item_account_entry.xml @@ -1,47 +1,62 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:selectableItemBackground" + android:orientation="horizontal" + android:gravity="center_vertical" + android:paddingHorizontal="16dp"> - + + - - + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:layout_marginEnd="8dp" + android:src="@drawable/default_avatar" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:shapeAppearance="@style/ShapeCircleStyle" /> + android:id="@+id/list_item_account_title" + android:layout_width="wrap_content" + android:layout_height="@dimen/lv_act_title_height" + android:layout_alignParentTop="true" + android:layout_marginTop="@dimen/lv_act_title_margin_top" + android:layout_toEndOf="@+id/list_item_account_icon" + android:maxLines="1" + android:textColor="@color/light_black" + android:textSize="@dimen/lv_act_title_txt_size" /> - + android:id="@+id/list_item_account_subtitle" + android:layout_width="wrap_content" + android:layout_height="@dimen/lv_act_subtitle_height" + android:layout_below="@+id/list_item_account_title" + android:layout_marginTop="@dimen/lv_act_subtitle_margin_top" + android:layout_marginBottom="@dimen/lv_act_subtitle_margin_bottom" + android:layout_toEndOf="@+id/list_item_account_icon" + android:maxLines="1" + android:textColor="@color/light_grey" + android:textSize="@dimen/lv_act_subtitle_txt_size" /> + + + \ No newline at end of file diff --git a/app/src/main/res/layout-xlarge/list_item_account_entry.xml b/app/src/main/res/layout-xlarge/list_item_account_entry.xml index d3f082c6d..91a3118fe 100644 --- a/app/src/main/res/layout-xlarge/list_item_account_entry.xml +++ b/app/src/main/res/layout-xlarge/list_item_account_entry.xml @@ -1,47 +1,62 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:selectableItemBackground" + android:orientation="horizontal" + android:gravity="center_vertical" + android:paddingHorizontal="16dp"> - + - + - + - - + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-xlarge/start.xml b/app/src/main/res/layout-xlarge/start.xml index ae8e0596a..2bd2fe6dc 100644 --- a/app/src/main/res/layout-xlarge/start.xml +++ b/app/src/main/res/layout-xlarge/start.xml @@ -1,34 +1,34 @@ + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical"> + + android:id="@+id/welcome_view" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/tv_title_margin_top" + android:gravity="center_horizontal" + android:text="@string/welcome_to_seafile" + android:textColor="@color/fancy_orange" + android:textSize="@dimen/tv_title_txt_size" /> + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/tv_subtitle_margin_top" + android:gravity="center_horizontal" + android:text="@string/select_account" + android:textColor="@color/fancy_gray" + android:textSize="@dimen/tv_subtitle_txt_size" /> + android:id="@+id/account_list_view" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_marginTop="@dimen/lv_margin_top" + android:divider="@null" /> \ No newline at end of file diff --git a/app/src/main/res/layout/account_detail.xml b/app/src/main/res/layout/account_detail.xml index 2f3135b15..b11ac48c3 100644 --- a/app/src/main/res/layout/account_detail.xml +++ b/app/src/main/res/layout/account_detail.xml @@ -1,17 +1,19 @@ + @@ -21,6 +23,7 @@ android:layout_height="wrap_content" android:gravity="center" android:orientation="vertical" + android:paddingHorizontal="@dimen/app_padding_horizontal" android:paddingTop="@dimen/form_padding_top" android:paddingBottom="@dimen/form_padding_bottom"> @@ -34,8 +37,6 @@ android:id="@+id/server_url" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginLeft="@dimen/et_margin_left" - android:layout_marginRight="@dimen/et_margin_right" android:layout_marginBottom="@dimen/et_margin_bottom" android:importantForAutofill="auto" android:inputType="textUri" @@ -52,16 +53,15 @@ android:layout_height="wrap_content" android:hint="@string/email_hint"> - + android:inputType="textEmailAddress" + android:maxLines="1" + android:textSize="@dimen/et_txt_size" /> - + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:orientation="horizontal"> + android:layout_weight="1" + android:hint="@string/passwd_hint" + android:orientation="horizontal" + app:passwordToggleDrawable="@drawable/icon_eye_close" + app:passwordToggleEnabled="true"> + + android:visibility="gone" /> - - - - - + - - -