From ae12e44e99ff77a5f5188e3f01c22bbc4e7709d3 Mon Sep 17 00:00:00 2001 From: zhwanng <48609908+zhwanng@users.noreply.github.com> Date: Tue, 29 Oct 2024 18:15:03 +0800 Subject: [PATCH 1/4] fix bugs - [FEAT] UI and ICON - [REV/REF] SSL verify - [FIX] Bugs --- app/build.gradle | 11 +- app/src/main/AndroidManifest.xml | 2 + .../seafile/seadroid2/account/Account.java | 45 +- .../seafile/seadroid2/config/ColumnType.java | 59 ++ .../seadroid2/framework/data/SeafRepo.java | 200 ----- .../framework/data/SeafRepoEncrypt.java | 56 -- .../framework/data/db/AppDatabase.java | 2 + .../framework/data/db/dao/CertCacheDAO.java | 1 + .../data/db/entities/CertEntity.java | 1 + .../data/db/entities/RepoAttributeEntity.java | 37 - .../framework/data/db/entities/RepoModel.java | 23 +- .../EncryptFieldJsonAdapter.java | 2 +- .../MetadataConfigDataJsonAdapter.java | 87 +++ .../model/adapter/OffsetDateTimeAdapter.java | 25 + .../adapter/RecordResultDeserializer.java | 38 + .../model/sdoc/MetadataConfigDataModel.java | 66 ++ .../data/model/sdoc/MetadataConfigModel.java | 5 + .../data/model/sdoc/MetadataModel.java | 60 ++ .../data/model/sdoc/OptionsTagModel.java | 79 ++ .../data/model/sdoc/RecordResultModel.java | 122 +++ .../model/sdoc/RecordResultWrapperModel.java | 10 + .../data/model/sdoc/SDocCommentModel.java | 35 + .../model/sdoc/SDocCommentReplyModel.java | 31 + .../model/sdoc/SDocCommentWrapperModel.java | 7 + .../data/model/sdoc/SDocDataModel.java | 6 + .../data/model/sdoc/SDocDetailModel.java | 217 ++++++ .../framework/data/model/sdoc/SDocModel.java | 14 + .../data/model/sdoc/SDocPageOptionsModel.java | 248 ++++++ .../model/sdoc/SDocProfileConfigModel.java | 33 + .../model/sdoc/SDocRecordWrapperModel.java | 42 + .../data/model/sdoc/SDocWrapperModel.java | 10 + .../framework/data/model/user/UserModel.java | 78 ++ .../data/model/user/UserWrapperModel.java | 7 + .../framework/datastore/DataStoreKeys.java | 1 + .../ClientEncryptSharePreferenceHelper.java | 11 +- .../framework/http/SafeOkHttpClient.java | 68 +- .../framework/http/UnsafeOkHttpClient.java | 93 --- .../framework/monitor/SeafileObserver.java | 7 +- .../seadroid2/framework/util/GlideCache.java | 2 + .../seadroid2/framework/util/Icons.java | 32 +- .../seadroid2/preferences/Settings.java | 18 + .../seafile/seadroid2/ssl/CertsHelper.java | 61 +- .../seafile/seadroid2/ssl/CertsManager.java | 34 +- .../ssl/SSLSeafileSocketFactory.java | 25 +- .../seadroid2/ssl/SSLTrustManager.java | 75 +- .../ui/account/AccountDetailActivity.java | 342 ++++---- .../account/SeafileAuthenticatorActivity.java | 13 +- .../SingleSignOnAuthorizeActivity.java | 71 +- .../ui/activities/AllActivitiesFragment.java | 4 +- .../ui/camera_upload/CameraSyncAdapter.java | 731 ------------------ .../CameraUploadConfigActivity.java | 16 +- .../data_migrate/DataMigrationActivity.java | 9 +- .../seadroid2/ui/dialog/SslConfirmDialog.java | 79 +- .../GetShareLinkPasswordDialogFragment.java | 14 +- .../seadroid2/ui/main/MainActivity.java | 20 +- .../seadroid2/ui/repo/RepoQuickFragment.java | 6 +- .../seadroid2/ui/sdoc/SDocService.java | 34 + .../seadroid2/ui/sdoc/SDocViewModel.java | 223 ++++++ .../ui/sdoc/SDocWebViewActivity.java | 339 ++++++++ .../ui/sdoc/comments/SDocCommentAdapter.java | 198 +++++ .../sdoc/comments/SDocCommentUserAdapter.java | 54 ++ .../comments/SDocCommentUserViewHolder.java | 17 + .../sdoc/comments/SDocCommentViewHolder.java | 17 + .../sdoc/comments/SDocCommentsActivity.java | 184 +++++ .../sdoc/directory/SDocDirectoryAdapter.java | 59 ++ .../sdoc/directory/SDocDirectoryDialog.java | 137 ++++ .../ui/sdoc/profile/SDocProfileDialog.java | 715 +++++++++++++++++ .../seadroid2/ui/search/Search2Activity.java | 5 +- .../ui/settings/TabSettingsFragment.java | 33 +- .../ui/star/StarredQuickFragment.java | 14 +- .../ui/webview/SeaWebViewActivity.java | 19 +- .../seadroid2/view/HideBottomBehavior.java | 43 ++ .../com/seafile/seadroid2/view/KeyViews.java | 220 ++++++ .../seadroid2/view/MaxHeightScrollView.java | 57 ++ .../seafile/seadroid2/view/NestedWebView.java | 545 +++++++++++++ .../view/rich_edittext/DeletableEditText.java | 80 ++ .../rich_edittext/DeleteInputConnection.java | 117 +++ .../view/rich_edittext/OnRichAtListener.java | 23 + .../OnRichImageClickListener.java | 23 + .../view/rich_edittext/RichEditText.java | 371 +++++++++ .../view/webview/PreloadWebView.java | 5 +- .../seadroid2/view/webview/SeaWebView.java | 11 +- .../widget/prefs/ButtonPreference.java | 57 ++ .../widget/prefs/CardPreferenceCategory.java | 49 ++ .../widget/{ => prefs}/TipTextPreference.java | 4 +- .../main/res/drawable-xhdpi/tip_no_items.png | Bin 0 -> 4221 bytes .../main/res/drawable/baseline_delete_20.xml | 36 + .../res/drawable/ic_calendar_alt_solid.xml | 9 + .../res/drawable/ic_check_square_solid.xml | 9 + app/src/main/res/drawable/ic_duration.xml | 9 + app/src/main/res/drawable/ic_email.xml | 9 + .../main/res/drawable/ic_file_alt_solid.xml | 9 + app/src/main/res/drawable/ic_links.xml | 12 + app/src/main/res/drawable/ic_location.xml | 9 + app/src/main/res/drawable/ic_long_text.xml | 9 + .../res/drawable/ic_multiple_selection.xml | 9 + app/src/main/res/drawable/ic_number.xml | 9 + app/src/main/res/drawable/ic_picture.xml | 9 + .../main/res/drawable/ic_single_election.xml | 9 + .../main/res/drawable/ic_single_line_text.xml | 9 + app/src/main/res/drawable/ic_star2.xml | 9 + app/src/main/res/drawable/ic_url.xml | 9 + .../res/drawable/ic_user_collaborator.xml | 12 + .../res/drawable/indicator_drawable_2.xml | 7 - .../res/drawable/selector_password_toggle.xml | 5 + ...awable.xml => shape_solid_ff_radius_8.xml} | 5 +- .../drawable/shape_solid_grey100_radius_8.xml | 5 + .../shape_stroke1_radius8_solid_grey.xml | 8 + .../shape_stroke1_radius8_solid_white.xml | 8 + app/src/main/res/layout/account_detail.xml | 120 +-- .../main/res/layout/account_list_footer.xml | 1 + .../main/res/layout/activity_sdoc_comment.xml | 108 +++ .../main/res/layout/activity_sea_webview.xml | 41 +- .../res/layout/activity_sea_webview_pro.xml | 75 +- .../main/res/layout/dialog_sdoc_directory.xml | 24 + .../main/res/layout/dialog_sdoc_profile.xml | 35 + .../main/res/layout/dialog_ssl_confirm.xml | 155 ++-- app/src/main/res/layout/item_dirent.xml | 9 +- app/src/main/res/layout/item_repo.xml | 9 + app/src/main/res/layout/item_sdoc_comment.xml | 107 +++ .../main/res/layout/item_sdoc_directory.xml | 15 + app/src/main/res/layout/item_starred.xml | 2 +- app/src/main/res/layout/item_user_avatar.xml | 20 + .../layout/layout_avatar_username_round.xml | 50 ++ .../res/layout/layout_detail_text_round.xml | 40 + .../layout_details_keyview_valuecontainer.xml | 45 ++ app/src/main/res/layout/layout_empty.xml | 29 + app/src/main/res/layout/layout_image.xml | 13 + .../main/res/layout/layout_logout_view.xml | 13 + app/src/main/res/layout/layout_textview.xml | 10 + .../main/res/layout/layout_upload_file.xml | 35 + .../main/res/layout/preference_cardbox.xml | 29 + .../res/layout/view_dialog_share_password.xml | 4 +- app/src/main/res/menu/bottom_sdoc_menu.xml | 15 +- app/src/main/res/menu/browser_menu.xml | 10 +- app/src/main/res/values-ko/strings.xml | 3 +- app/src/main/res/values-night/styles.xml | 37 +- app/src/main/res/values-ru/strings.xml | 15 +- app/src/main/res/values-zh-rCN/strings.xml | 20 +- app/src/main/res/values-zh-rTW/strings.xml | 4 +- app/src/main/res/values/attrs.xml | 124 +-- app/src/main/res/values/colors_material.xml | 282 +++++++ app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/ids.xml | 1 + app/src/main/res/values/strings.xml | 19 +- app/src/main/res/values/styles.xml | 29 +- app/src/main/res/values/theme.xml | 1 + app/src/main/res/xml/prefs_settings.xml | 7 +- 148 files changed, 6800 insertions(+), 2025 deletions(-) create mode 100644 app/src/main/java/com/seafile/seadroid2/config/ColumnType.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/SeafRepo.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/SeafRepoEncrypt.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/RepoAttributeEntity.java rename app/src/main/java/com/seafile/seadroid2/framework/data/model/{repo/deserializer => adapter}/EncryptFieldJsonAdapter.java (95%) create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/adapter/MetadataConfigDataJsonAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/adapter/OffsetDateTimeAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/adapter/RecordResultDeserializer.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/MetadataConfigDataModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/MetadataConfigModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/MetadataModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/OptionsTagModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/RecordResultModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/RecordResultWrapperModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocCommentModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocCommentReplyModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocCommentWrapperModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocDataModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocDetailModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocPageOptionsModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocProfileConfigModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocRecordWrapperModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocWrapperModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/user/UserModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/user/UserWrapperModel.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/framework/http/UnsafeOkHttpClient.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraSyncAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocService.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocViewModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocWebViewActivity.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserViewHolder.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentViewHolder.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentsActivity.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/directory/SDocDirectoryAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/directory/SDocDirectoryDialog.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/profile/SDocProfileDialog.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/HideBottomBehavior.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/KeyViews.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/MaxHeightScrollView.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/NestedWebView.java create mode 100755 app/src/main/java/com/seafile/seadroid2/view/rich_edittext/DeletableEditText.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/rich_edittext/DeleteInputConnection.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/rich_edittext/OnRichAtListener.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/rich_edittext/OnRichImageClickListener.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/rich_edittext/RichEditText.java create mode 100644 app/src/main/java/com/seafile/seadroid2/widget/prefs/ButtonPreference.java create mode 100644 app/src/main/java/com/seafile/seadroid2/widget/prefs/CardPreferenceCategory.java rename app/src/main/java/com/seafile/seadroid2/widget/{ => prefs}/TipTextPreference.java (93%) create mode 100644 app/src/main/res/drawable-xhdpi/tip_no_items.png create mode 100644 app/src/main/res/drawable/baseline_delete_20.xml create mode 100644 app/src/main/res/drawable/ic_calendar_alt_solid.xml create mode 100644 app/src/main/res/drawable/ic_check_square_solid.xml create mode 100644 app/src/main/res/drawable/ic_duration.xml create mode 100644 app/src/main/res/drawable/ic_email.xml create mode 100644 app/src/main/res/drawable/ic_file_alt_solid.xml create mode 100644 app/src/main/res/drawable/ic_links.xml create mode 100644 app/src/main/res/drawable/ic_location.xml create mode 100644 app/src/main/res/drawable/ic_long_text.xml create mode 100644 app/src/main/res/drawable/ic_multiple_selection.xml create mode 100644 app/src/main/res/drawable/ic_number.xml create mode 100644 app/src/main/res/drawable/ic_picture.xml create mode 100644 app/src/main/res/drawable/ic_single_election.xml create mode 100644 app/src/main/res/drawable/ic_single_line_text.xml create mode 100644 app/src/main/res/drawable/ic_star2.xml create mode 100644 app/src/main/res/drawable/ic_url.xml create mode 100644 app/src/main/res/drawable/ic_user_collaborator.xml delete mode 100644 app/src/main/res/drawable/indicator_drawable_2.xml create mode 100644 app/src/main/res/drawable/selector_password_toggle.xml rename app/src/main/res/drawable/{indicator_drawable.xml => shape_solid_ff_radius_8.xml} (64%) create mode 100644 app/src/main/res/drawable/shape_solid_grey100_radius_8.xml create mode 100644 app/src/main/res/drawable/shape_stroke1_radius8_solid_grey.xml create mode 100644 app/src/main/res/drawable/shape_stroke1_radius8_solid_white.xml create mode 100644 app/src/main/res/layout/activity_sdoc_comment.xml create mode 100644 app/src/main/res/layout/dialog_sdoc_directory.xml create mode 100644 app/src/main/res/layout/dialog_sdoc_profile.xml create mode 100644 app/src/main/res/layout/item_sdoc_comment.xml create mode 100644 app/src/main/res/layout/item_sdoc_directory.xml create mode 100644 app/src/main/res/layout/item_user_avatar.xml create mode 100644 app/src/main/res/layout/layout_avatar_username_round.xml create mode 100644 app/src/main/res/layout/layout_detail_text_round.xml create mode 100644 app/src/main/res/layout/layout_details_keyview_valuecontainer.xml create mode 100644 app/src/main/res/layout/layout_empty.xml create mode 100644 app/src/main/res/layout/layout_image.xml create mode 100644 app/src/main/res/layout/layout_logout_view.xml create mode 100644 app/src/main/res/layout/layout_textview.xml create mode 100644 app/src/main/res/layout/layout_upload_file.xml create mode 100644 app/src/main/res/layout/preference_cardbox.xml create mode 100644 app/src/main/res/values/colors_material.xml diff --git a/app/build.gradle b/app/build.gradle index a36e69838..65365a20c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,8 +14,8 @@ android { targetSdkVersion rootProject.ext.targetSdkVersion compileSdk rootProject.ext.compileSdkVersion - versionCode 154 - versionName "3.0.4" + versionCode 155 + versionName "3.0.5" multiDexEnabled true resValue "string", "authorities", defaultConfig.applicationId + '.debug.cameraupload.provider' @@ -199,14 +199,15 @@ android { implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.core:core-splashscreen:1.0.1' implementation "androidx.appcompat:appcompat:1.7.0" - implementation "androidx.activity:activity:1.9.1" + implementation "androidx.activity:activity:1.9.3" implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation "androidx.preference:preference:1.2.1" // implementation "androidx.datastore:datastore-preferences:1.0.0" // implementation "androidx.datastore:datastore-preferences-rxjava2:1.0.0" implementation "androidx.core:core-splashscreen:1.0.1" - implementation "androidx.webkit:webkit:1.11.0" + implementation "androidx.webkit:webkit:1.12.1" + implementation 'com.google.android.flexbox:flexbox:3.0.0' //https://github.com/material-components/material-components-android implementation "com.google.android.material:material:1.12.0" @@ -310,8 +311,6 @@ android { implementation 'com.madgag.spongycastle:core:1.54.0.0' implementation 'com.madgag.spongycastle:prov:1.54.0.0' -// implementation 'org.greenrobot:eventbus:3.3.1' - def glide_version = "4.16.0" implementation "com.github.bumptech.glide:glide:$glide_version" implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2eb65d447..2846f793c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -209,6 +209,8 @@ + + https://dev.xxx.com + * @return DOMAIN/IP_ADDRESS(:PORT),
like www.goo.gle, like 192.168.0.1:8000 + */ + public String getServerHost() { + String s = server.substring(server.indexOf("://") + 3); + return s.substring(0, s.indexOf('/')); + } + + /** + * @return DOMAIN/IP_ADDRESS,
like www.goo.gle, like 192.168.0.1 + */ + public String getServerDomainName() { + String dn = getServerHost(); + // strip port, like :8000 in 192.168.1.116:8000 + if (dn.contains(":")) + dn = dn.substring(0, dn.indexOf(':')); + return dn; + } + + /** + * @return https://dev.xxx.com/dev/ => https://dev.xxx.com */ public String getProtocolHost() { return URLs.getProtocolHost(server); } /** - * https://dev.xxx.com/dev/ => dev.xxx.com/dev + * @return https://dev.xxx.com/dev/ => dev.xxx.com/dev */ public String getServerNoProtocol() { String result = server.substring(server.indexOf("://") + 3); diff --git a/app/src/main/java/com/seafile/seadroid2/config/ColumnType.java b/app/src/main/java/com/seafile/seadroid2/config/ColumnType.java new file mode 100644 index 000000000..6fa176fba --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/config/ColumnType.java @@ -0,0 +1,59 @@ +package com.seafile.seadroid2.config; + +import com.blankj.utilcode.util.CollectionUtils; + +import java.util.List; + +public class ColumnType { + public static final String INIT_FIELD = "0000"; + + + + public static final String TEXT = "text"; + public static final String COLLABORATOR = "collaborator"; + public static final String DATE = "date"; + public static final String LONG_TEXT = "long-text"; + public static final String NUMBER = "number"; + public static final String DURATION = "duration"; + public static final String SINGLE_SELECT = "single-select"; + public static final String MULTIPLE_SELECT = "multiple-select"; + public static final String IMAGE = "image"; + public static final String FILE = "file"; + public static final String EMAIL = "email"; + public static final String URL = "url"; + public static final String CHECKBOX = "checkbox"; + public static final String RATE = "rate"; + + //Advanced + public static final String GEOLOCATION = "geolocation"; + public static final String LINK = "link"; + public static final String DIGITAL_SIGN = "digital-sign"; + public static final String FORMULA = "formula"; + public static final String LINK_FORMULA = "link-formula"; + public static final String AUTO_NUMBER = "auto-number"; + public static final String CTIME = "ctime"; + public static final String MTIME = "mtime"; + public static final String BUTTON = "button"; + public static final String CREATOR = "creator"; + public static final String LAST_MODIFIER = "last-modifier"; + + + public final static List ENABLE_TYPE_LIST = CollectionUtils.newArrayList( + TEXT, + COLLABORATOR, + DATE, + LONG_TEXT, + NUMBER, + DURATION, + SINGLE_SELECT, + MULTIPLE_SELECT, + IMAGE, + FILE, + EMAIL, + URL, + CHECKBOX, + RATE, + GEOLOCATION, + LINK, + DIGITAL_SIGN); +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/SeafRepo.java b/app/src/main/java/com/seafile/seadroid2/framework/data/SeafRepo.java deleted file mode 100644 index 839408ac0..000000000 --- a/app/src/main/java/com/seafile/seadroid2/framework/data/SeafRepo.java +++ /dev/null @@ -1,200 +0,0 @@ -package com.seafile.seadroid2.framework.data; - -import android.text.TextUtils; - -import com.blankj.utilcode.util.TimeUtils; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.SeadroidApplication; -import com.seafile.seadroid2.config.DateFormatType; -import com.seafile.seadroid2.framework.datastore.sp_livedata.ClientEncryptSharePreferenceHelper; -import com.seafile.seadroid2.framework.util.PinyinUtils; -import com.seafile.seadroid2.framework.util.Utils; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.Comparator; -import java.util.Date; - -/** - * SeafRepo: A Seafile library - * - * @author plt - */ -public class SeafRepo implements SeafItem { - public String type; //mine\group\shared - public long group_id; - public String group_name; - - public String repo_id; // repo id - public String repo_name; //repo_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 last_modified; - - public boolean encrypted; //last_modified - public long size; - public boolean starred; - - public String permission; - public boolean monitored; - public boolean is_admin; - public String salt; - public String status; - - - //local used -// public String root; // the id of root directory - - public long mtime; - public boolean isGroupRepo; - public boolean isPersonalRepo; - public boolean isSharedRepo; - - static SeafRepo fromJson(JSONObject obj) throws JSONException { - SeafRepo repo = new SeafRepo(); - repo.type = obj.optString("type"); - - //group - repo.group_id = obj.optLong("group_id"); - repo.group_name = obj.optString("group_name"); - - //repo - repo.repo_id = obj.optString("repo_id"); - repo.repo_name = obj.optString("repo_name"); - - //owner - repo.owner_name = obj.optString("owner_name"); - repo.owner_email = obj.optString("owner_email"); - repo.owner_contact_email = obj.optString("owner_contact_email"); - - //modifier - repo.modifier_email = obj.getString("modifier_email"); - repo.modifier_name = obj.getString("modifier_name"); - repo.modifier_contact_email = obj.getString("modifier_contact_email"); - - repo.is_admin = obj.optBoolean("is_admin"); - - repo.size = obj.optLong("size"); - repo.encrypted = obj.optBoolean("encrypted"); - repo.permission = obj.optString("permission"); - repo.starred = obj.optBoolean("starred"); - repo.monitored = obj.optBoolean("monitored"); - repo.status = obj.optString("status"); - repo.salt = obj.optString("salt"); - - repo.last_modified = obj.optString("last_modified"); - -// repo.root = obj.optString("root"); - - repo.mtime = convertLastModified2Mtime(repo.last_modified); - - repo.isGroupRepo = TextUtils.equals(repo.type, "group"); - repo.isPersonalRepo = TextUtils.equals(repo.type, "mine"); - repo.isSharedRepo = TextUtils.equals(repo.type, "shared"); - - return repo; - } - - public String getRepoId() { - return repo_id; - } - - public String getRepoName() { - return repo_name; - } - -// public String getRootDirID() { -// return root; -// } - - @Override - public String getTitle() { - return repo_name; - } - - @Override - public String getSubtitle() { - return Utils.readableFileSize(size) + " " + Utils.translateCommitTime(mtime); - } - - private static long convertLastModified2Mtime(String last_modified) { - Date date = TimeUtils.string2Date(last_modified, DateFormatType.DATE_XXX); - return TimeUtils.date2Millis(date); - } - - @Override - public int getIcon() { - if (encrypted) - return R.drawable.repo_encrypted; - if (!hasWritePermission()) - return R.drawable.repo_readonly; - - return R.drawable.repo; - } - - public boolean isStarred() { - return starred; - } - - public void setStarred(boolean starred) { - this.starred = starred; - } - - public boolean canLocalDecrypt() { - return encrypted && ClientEncryptSharePreferenceHelper.isEncryptEnabled(); - } - - public boolean hasWritePermission() { - return permission.indexOf('w') != -1; - } - - /** - * Repository last modified time comparator class - */ - public static class RepoLastMTimeComparator implements Comparator { - - @Override - public int compare(SeafRepo itemA, SeafRepo itemB) { - return (int) (itemA.mtime - itemB.mtime); - } - } - - /** - * Repository name comparator class - */ - public static class RepoNameComparator implements Comparator { - - @Override - public int compare(SeafRepo itemA, SeafRepo itemB) { - // get the first character unicode from each file name - int unicodeA = itemA.repo_name.codePointAt(0); - int unicodeB = itemB.repo_name.codePointAt(0); - - String strA, strB; - - // both are Chinese words - if ((19968 < unicodeA && unicodeA < 40869) && (19968 < unicodeB && unicodeB < 40869)) { - strA = PinyinUtils.toPinyin(SeadroidApplication.getAppContext(), itemA.repo_name).toLowerCase(); - strB = PinyinUtils.toPinyin(SeadroidApplication.getAppContext(), itemB.repo_name).toLowerCase(); - } else if ((19968 < unicodeA && unicodeA < 40869) && !(19968 < unicodeB && unicodeB < 40869)) { - // itemA is Chinese and itemB is English - return 1; - } else if (!(19968 < unicodeA && unicodeA < 40869) && (19968 < unicodeB && unicodeB < 40869)) { - // itemA is English and itemB is Chinese - return -1; - } else { - // both are English words - strA = itemA.repo_name.toLowerCase(); - strB = itemB.repo_name.toLowerCase(); - } - - return strA.compareTo(strB); - } - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/SeafRepoEncrypt.java b/app/src/main/java/com/seafile/seadroid2/framework/data/SeafRepoEncrypt.java deleted file mode 100644 index 823ded26c..000000000 --- a/app/src/main/java/com/seafile/seadroid2/framework/data/SeafRepoEncrypt.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.seafile.seadroid2.framework.data; - -import android.text.TextUtils; - -import com.seafile.seadroid2.framework.datastore.sp_livedata.ClientEncryptSharePreferenceHelper; - -import org.json.JSONException; -import org.json.JSONObject; - - -public class SeafRepoEncrypt { - public String id; // repo id - public String name; - public String owner; - public long mtime; // the last modification time - public boolean isGroupRepo; - public boolean isPersonalRepo; - public boolean isSharedRepo; - public boolean encrypted; - public String permission; - public String magic; - public String encKey; - public int encVersion; - public long size; - public String root; // the id of root directory - - static SeafRepoEncrypt fromJson(JSONObject obj) throws JSONException { - SeafRepoEncrypt repo = new SeafRepoEncrypt(); - repo.magic = obj.optString("magic"); - repo.permission = obj.getString("permission"); - repo.encrypted = obj.getBoolean("encrypted"); - repo.encVersion = obj.optInt("enc_version"); - repo.mtime = obj.getLong("mtime"); - repo.owner = obj.getString("owner"); - repo.id = obj.getString("id"); - repo.size = obj.getLong("size"); - repo.name = obj.getString("name"); - repo.root = obj.getString("root"); - repo.encKey = obj.optString("random_key"); - repo.isGroupRepo = obj.getString("type").equals("grepo"); - repo.isPersonalRepo = obj.getString("type").equals("repo"); - repo.isSharedRepo = obj.getString("type").equals("srepo"); - return repo; - } - - public SeafRepoEncrypt() { - } - - public boolean canLocalDecrypt() { - return encrypted - && encVersion == 2 - && !TextUtils.isEmpty(magic) - && ClientEncryptSharePreferenceHelper.isEncryptEnabled(); - } - -} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/db/AppDatabase.java b/app/src/main/java/com/seafile/seadroid2/framework/data/db/AppDatabase.java index 1a3286025..db8a5fa0c 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/data/db/AppDatabase.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/db/AppDatabase.java @@ -1,6 +1,7 @@ package com.seafile.seadroid2.framework.data.db; import androidx.annotation.NonNull; +import androidx.room.Dao; import androidx.room.Database; import androidx.room.Room; import androidx.room.RoomDatabase; @@ -70,6 +71,7 @@ public void migrate(@NonNull SupportSQLiteDatabase database) { public abstract EncKeyCacheDAO encKeyCacheDAO(); + @Deprecated public abstract CertCacheDAO certDAO(); public abstract FolderBackupMonitorDAO folderBackupMonitorDAO(); diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/db/dao/CertCacheDAO.java b/app/src/main/java/com/seafile/seadroid2/framework/data/db/dao/CertCacheDAO.java index 402a47878..e7e8fe1ae 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/data/db/dao/CertCacheDAO.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/db/dao/CertCacheDAO.java @@ -11,6 +11,7 @@ import io.reactivex.Completable; +@Deprecated @Dao public interface CertCacheDAO { @Insert(onConflict = OnConflictStrategy.REPLACE) diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/CertEntity.java b/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/CertEntity.java index b9d3cc652..92177c948 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/CertEntity.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/CertEntity.java @@ -3,6 +3,7 @@ import androidx.room.Entity; import androidx.room.PrimaryKey; +@Deprecated @Entity(tableName = "cert_cache") public class CertEntity { @PrimaryKey(autoGenerate = true) diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/RepoAttributeEntity.java b/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/RepoAttributeEntity.java deleted file mode 100644 index 038a6889c..000000000 --- a/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/RepoAttributeEntity.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.seafile.seadroid2.framework.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.framework.data.model.BaseModel; -import com.seafile.seadroid2.framework.data.model.repo.deserializer.EncryptFieldJsonAdapter; -import com.seafile.seadroid2.framework.datastore.sp_livedata.ClientEncryptSharePreferenceHelper; - -@Entity(tableName = "repo_attributes") -public class RepoAttributeEntity extends BaseModel { - @PrimaryKey - @NonNull - public String repo_id = ""; - public String repo_name; //repo_name - - @JsonAdapter(EncryptFieldJsonAdapter.class) - public boolean encrypted; - - public String root; - public String magic; - - public String random_key; - public int enc_version; - public int file_count; - - public boolean canLocalDecrypt() { - return encrypted - && enc_version == 2 - && !TextUtils.isEmpty(magic) - && ClientEncryptSharePreferenceHelper.isEncryptEnabled(); - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/RepoModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/RepoModel.java index 55dbba5aa..5b73beed7 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/RepoModel.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/RepoModel.java @@ -8,9 +8,7 @@ import com.google.gson.annotations.JsonAdapter; import com.seafile.seadroid2.R; import com.seafile.seadroid2.framework.data.model.BaseModel; -import com.seafile.seadroid2.framework.data.model.repo.deserializer.EncryptFieldJsonAdapter; -import com.seafile.seadroid2.framework.datastore.sp.SettingsManager; -import com.seafile.seadroid2.framework.datastore.sp_livedata.ClientEncryptSharePreferenceHelper; +import com.seafile.seadroid2.framework.data.model.adapter.EncryptFieldJsonAdapter; import com.seafile.seadroid2.framework.util.Utils; @Entity(tableName = "repos", primaryKeys = {"repo_id", "group_id"}) @@ -105,14 +103,21 @@ public boolean hasWritePermission() { } +// /** +// * If the result is true, and the decryption was successful, +// * there is definitely one row of data in the {@link EncKeyCacheEntity} +// */ +// public boolean canLocalDecrypt() { +// return encrypted +// && enc_version == SettingsManager.REPO_ENC_VERSION +// && !TextUtils.isEmpty(magic) +// && ClientEncryptSharePreferenceHelper.isEncryptEnabled(); +// } + /** - * If the result is true, and the decryption was successful, - * there is definitely one row of data in the {@link EncKeyCacheEntity} + * new feature at 2024/10/22 with v3.0.5 */ public boolean canLocalDecrypt() { - return encrypted - && enc_version == SettingsManager.REPO_ENC_VERSION - && !TextUtils.isEmpty(magic) - && ClientEncryptSharePreferenceHelper.isEncryptEnabled(); + return false; } } diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/repo/deserializer/EncryptFieldJsonAdapter.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/adapter/EncryptFieldJsonAdapter.java similarity index 95% rename from app/src/main/java/com/seafile/seadroid2/framework/data/model/repo/deserializer/EncryptFieldJsonAdapter.java rename to app/src/main/java/com/seafile/seadroid2/framework/data/model/adapter/EncryptFieldJsonAdapter.java index bba672bca..30206c37d 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/data/model/repo/deserializer/EncryptFieldJsonAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/adapter/EncryptFieldJsonAdapter.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.framework.data.model.repo.deserializer; +package com.seafile.seadroid2.framework.data.model.adapter; import android.text.TextUtils; diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/adapter/MetadataConfigDataJsonAdapter.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/adapter/MetadataConfigDataJsonAdapter.java new file mode 100644 index 000000000..84306d5b3 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/adapter/MetadataConfigDataJsonAdapter.java @@ -0,0 +1,87 @@ +package com.seafile.seadroid2.framework.data.model.adapter; + +import android.text.TextUtils; + +import com.blankj.utilcode.util.CollectionUtils; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.reflect.TypeToken; +import com.seafile.seadroid2.framework.data.model.sdoc.MetadataConfigDataModel; +import com.seafile.seadroid2.framework.data.model.sdoc.OptionsTagModel; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +public class MetadataConfigDataJsonAdapter implements JsonDeserializer>, JsonSerializer> { + @Override + public JsonElement serialize(List list, Type typeOfSrc, JsonSerializationContext context) { + + if (CollectionUtils.isEmpty(list)) { + return null; + } + + JsonArray jsonList = new JsonArray(); + + for (MetadataConfigDataModel src : list) { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("format", src.format); + jsonObject.addProperty("geo_format", src.geo_format); + + if (!CollectionUtils.isEmpty(src.options)) { + JsonArray optionsJsonArray = new JsonArray(); + for (OptionsTagModel option : src.options) { + JsonObject jsonObject1 = new JsonObject(); + jsonObject1.addProperty("borderColor", option.borderColor); + jsonObject1.addProperty("color", option.color); + jsonObject1.addProperty("id", option.id); + jsonObject1.addProperty("name", option.name); + jsonObject1.addProperty("textColor", option.textColor); + optionsJsonArray.add(jsonObject1); + } + + jsonObject.add("options", optionsJsonArray); + } + + + jsonList.add(jsonObject); + } + + return jsonList; + } + + @Override + public List deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + List values = new ArrayList<>(); + + if (json == null || json.isJsonNull()) { + return values; + } + + String valuesString = json.toString(); + if (TextUtils.isEmpty(valuesString)) { + return values; + } + + if ("{}".equals(valuesString) || "[]".equals(valuesString)) { + return values; + } + + if (json.isJsonObject()) { + MetadataConfigDataModel valuesTemp = new Gson().fromJson(valuesString, MetadataConfigDataModel.class); + values.add(valuesTemp); + } else if (json.isJsonArray()) { + List valuesTemp = new Gson().fromJson(valuesString, new TypeToken>() { + }.getType()); + values.addAll(valuesTemp); + } + return values; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/adapter/OffsetDateTimeAdapter.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/adapter/OffsetDateTimeAdapter.java new file mode 100644 index 000000000..3067cf39b --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/adapter/OffsetDateTimeAdapter.java @@ -0,0 +1,25 @@ +package com.seafile.seadroid2.framework.data.model.adapter; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import java.lang.reflect.Type; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; + +public class OffsetDateTimeAdapter implements JsonSerializer, JsonDeserializer { + @Override + public JsonElement serialize(OffsetDateTime src, Type typeOfSrc, JsonSerializationContext context) { + return context.serialize(src.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + + @Override + public OffsetDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + return OffsetDateTime.parse(json.getAsString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/adapter/RecordResultDeserializer.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/adapter/RecordResultDeserializer.java new file mode 100644 index 000000000..d9fcfef08 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/adapter/RecordResultDeserializer.java @@ -0,0 +1,38 @@ +package com.seafile.seadroid2.framework.data.model.adapter; + +import com.google.gson.Gson; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.seafile.seadroid2.framework.data.model.sdoc.RecordResultModel; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +public class RecordResultDeserializer implements JsonDeserializer { + @Override + public RecordResultModel deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + RecordResultModel result = new Gson().fromJson(json, RecordResultModel.class); + JsonObject jsonObject = json.getAsJsonObject(); + + Map dynamicFields = new HashMap<>(); + + for (Map.Entry entry : jsonObject.entrySet()) { + String key = entry.getKey(); + + if (!isFixedField(key)) { + dynamicFields.put(key, context.deserialize(entry.getValue(), Object.class)); + } + } + + result.dynamicFields = dynamicFields; + return result; + } + + private boolean isFixedField(String key) { + return key.startsWith("_"); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/MetadataConfigDataModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/MetadataConfigDataModel.java new file mode 100644 index 000000000..64d6008a4 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/MetadataConfigDataModel.java @@ -0,0 +1,66 @@ +package com.seafile.seadroid2.framework.data.model.sdoc; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +public class MetadataConfigDataModel implements Parcelable { + + public String format; + + public String geo_format; + public List options; + + public boolean enable_precision; + public int precision; + public String currency_symbol; + public String currency_symbol_position; + + @SerializedName("max") + public int rate_max_number; + @SerializedName("color") + public String rate_style_color; + +// @SerializedName("type") +// public String rate_style_type; + + public MetadataConfigDataModel() { + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.format); + dest.writeString(this.geo_format); + dest.writeTypedList(this.options); + dest.writeInt(this.rate_max_number); + dest.writeString(this.rate_style_color); + } + + protected MetadataConfigDataModel(Parcel in) { + this.format = in.readString(); + this.geo_format = in.readString(); + this.options = in.createTypedArrayList(OptionsTagModel.CREATOR); + this.rate_max_number = in.readInt(); + this.rate_style_color = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public MetadataConfigDataModel createFromParcel(Parcel source) { + return new MetadataConfigDataModel(source); + } + + @Override + public MetadataConfigDataModel[] newArray(int size) { + return new MetadataConfigDataModel[size]; + } + }; +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/MetadataConfigModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/MetadataConfigModel.java new file mode 100644 index 000000000..78fbf7894 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/MetadataConfigModel.java @@ -0,0 +1,5 @@ +package com.seafile.seadroid2.framework.data.model.sdoc; + +public class MetadataConfigModel { + public boolean enabled; +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/MetadataModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/MetadataModel.java new file mode 100644 index 000000000..2af936b2e --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/MetadataModel.java @@ -0,0 +1,60 @@ +package com.seafile.seadroid2.framework.data.model.sdoc; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.seafile.seadroid2.framework.data.model.adapter.MetadataConfigDataJsonAdapter; + +import java.util.List; + +public class MetadataModel implements Parcelable { + public String key; + public String name; + public String type; + + @SerializedName("data") + @JsonAdapter(MetadataConfigDataJsonAdapter.class) + public List configData; + + //note this + public Object value; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.key); + dest.writeString(this.name); + dest.writeString(this.type); + dest.writeTypedList(this.configData); + dest.writeValue(this.value); + } + + public MetadataModel() { + } + + protected MetadataModel(Parcel in) { + this.key = in.readString(); + this.name = in.readString(); + this.type = in.readString(); + this.configData = in.createTypedArrayList(MetadataConfigDataModel.CREATOR); + this.value = in.readParcelable(Object.class.getClassLoader()); + } + + public static final Creator CREATOR = new Creator() { + @Override + public MetadataModel createFromParcel(Parcel source) { + return new MetadataModel(source); + } + + @Override + public MetadataModel[] newArray(int size) { + return new MetadataModel[size]; + } + }; +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/OptionsTagModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/OptionsTagModel.java new file mode 100644 index 000000000..c5b50fa19 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/OptionsTagModel.java @@ -0,0 +1,79 @@ +package com.seafile.seadroid2.framework.data.model.sdoc; + +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.room.Ignore; + +public class OptionsTagModel implements Parcelable { + public String borderColor; + + //default #EED5FF + public String color = "#EED5FF"; + public String id; + public String name; + public String textColor = "#202428"; + + @Ignore + public boolean isSelected; + + @Override + public String toString() { + return "ColumnDataOptionsModel{" + + "borderColor='" + borderColor + '\'' + + ", color='" + color + '\'' + + ", id='" + id + '\'' + + ", name='" + name + '\'' + + ", textColor='" + textColor + '\'' + + ", isSelected=" + isSelected + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.borderColor); + dest.writeString(this.color); + dest.writeString(this.id); + dest.writeString(this.name); + dest.writeString(this.textColor); + dest.writeByte(this.isSelected ? (byte) 1 : (byte) 0); + } + + public void readFromParcel(Parcel source) { + this.borderColor = source.readString(); + this.color = source.readString(); + this.id = source.readString(); + this.name = source.readString(); + this.textColor = source.readString(); + this.isSelected = source.readByte() != 0; + } + + public OptionsTagModel() { + } + + protected OptionsTagModel(Parcel in) { + this.borderColor = in.readString(); + this.color = in.readString(); + this.id = in.readString(); + this.name = in.readString(); + this.textColor = in.readString(); + this.isSelected = in.readByte() != 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public OptionsTagModel createFromParcel(Parcel source) { + return new OptionsTagModel(source); + } + + @Override + public OptionsTagModel[] newArray(int size) { + return new OptionsTagModel[size]; + } + }; +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/RecordResultModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/RecordResultModel.java new file mode 100644 index 000000000..11386d1ed --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/RecordResultModel.java @@ -0,0 +1,122 @@ +package com.seafile.seadroid2.framework.data.model.sdoc; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.gson.annotations.JsonAdapter; +import com.seafile.seadroid2.framework.data.model.adapter.OffsetDateTimeAdapter; +import com.seafile.seadroid2.framework.data.model.adapter.RecordResultDeserializer; + +import java.time.OffsetDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RecordResultModel implements Parcelable { + + public List _collaborators; + public String _creator; + public String _ctime; + public String _file_creator; + public String _file_ctime; + public String _file_modifier; + + @JsonAdapter(OffsetDateTimeAdapter.class) + public OffsetDateTime _file_mtime; + + public String _file_type; + public String _id; + public boolean _is_dir; + public String _last_modifier; + public String _mtime; + public String _name; + public String _obj_id; + public String _parent_dir; + public long _size; + public String _status; + public String _suffix; + public String _description; + public List _owner; + + @JsonAdapter(RecordResultDeserializer.class) + public Map dynamicFields; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStringList(this._collaborators); + dest.writeString(this._creator); + dest.writeString(this._ctime); + dest.writeString(this._file_creator); + dest.writeString(this._file_ctime); + dest.writeString(this._file_modifier); + dest.writeSerializable(this._file_mtime); + dest.writeString(this._file_type); + dest.writeString(this._id); + dest.writeByte(this._is_dir ? (byte) 1 : (byte) 0); + dest.writeString(this._last_modifier); + dest.writeString(this._mtime); + dest.writeString(this._name); + dest.writeString(this._obj_id); + dest.writeString(this._parent_dir); + dest.writeLong(this._size); + dest.writeString(this._status); + dest.writeString(this._suffix); + dest.writeString(this._description); + dest.writeStringList(this._owner); + dest.writeInt(this.dynamicFields.size()); + for (Map.Entry entry : this.dynamicFields.entrySet()) { + dest.writeString(entry.getKey()); + dest.writeValue(entry.getValue()); + } + } + + public RecordResultModel() { + } + + protected RecordResultModel(Parcel in) { + this._collaborators = in.createStringArrayList(); + this._creator = in.readString(); + this._ctime = in.readString(); + this._file_creator = in.readString(); + this._file_ctime = in.readString(); + this._file_modifier = in.readString(); + this._file_mtime = (OffsetDateTime) in.readSerializable(); + this._file_type = in.readString(); + this._id = in.readString(); + this._is_dir = in.readByte() != 0; + this._last_modifier = in.readString(); + this._mtime = in.readString(); + this._name = in.readString(); + this._obj_id = in.readString(); + this._parent_dir = in.readString(); + this._size = in.readLong(); + this._status = in.readString(); + this._suffix = in.readString(); + this._description = in.readString(); + this._owner = in.createStringArrayList(); + int dynamicFieldsSize = in.readInt(); + this.dynamicFields = new HashMap(dynamicFieldsSize); + for (int i = 0; i < dynamicFieldsSize; i++) { + String key = in.readString(); + Object value = in.readParcelable(Object.class.getClassLoader()); + this.dynamicFields.put(key, value); + } + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public RecordResultModel createFromParcel(Parcel source) { + return new RecordResultModel(source); + } + + @Override + public RecordResultModel[] newArray(int size) { + return new RecordResultModel[size]; + } + }; +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/RecordResultWrapperModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/RecordResultWrapperModel.java new file mode 100644 index 000000000..969e7bddf --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/RecordResultWrapperModel.java @@ -0,0 +1,10 @@ +package com.seafile.seadroid2.framework.data.model.sdoc; + +import java.util.List; +import java.util.Map; + +public class RecordResultWrapperModel { + public List> results; + + +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocCommentModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocCommentModel.java new file mode 100644 index 000000000..9260574cd --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocCommentModel.java @@ -0,0 +1,35 @@ +package com.seafile.seadroid2.framework.data.model.sdoc; + +import com.google.gson.annotations.JsonAdapter; +import com.seafile.seadroid2.framework.data.model.adapter.OffsetDateTimeAdapter; + +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +public class SDocCommentModel { + public int id; + public String item_name; + public String parent_path; + + public String avatar_url; + public List replies; + public String comment; + public String repo_id; + public String resolved; + public String user_contact_email; + public String user_email; + public String user_name; + + @JsonAdapter(OffsetDateTimeAdapter.class) + public OffsetDateTime created_at; + + @JsonAdapter(OffsetDateTimeAdapter.class) + public OffsetDateTime updated_at; + + public String detail; + + public String getCreatedAtFriendlyText() { + return created_at.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME).replace("T", " "); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocCommentReplyModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocCommentReplyModel.java new file mode 100644 index 000000000..cc04afbd1 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocCommentReplyModel.java @@ -0,0 +1,31 @@ +package com.seafile.seadroid2.framework.data.model.sdoc; + +import com.google.gson.annotations.JsonAdapter; +import com.seafile.seadroid2.framework.data.model.adapter.OffsetDateTimeAdapter; + +import java.time.OffsetDateTime; + +public class SDocCommentReplyModel { + public int id; + public int comment_id; + + public String doc_uuid; + public String reply;//"True" + + public String author; + public String avatar_url; + public String type;//"type" + + public String resolved; + public String user_contact_email; + public String user_email; + public String user_name; + + + @JsonAdapter(OffsetDateTimeAdapter.class) + public OffsetDateTime created_at; + + @JsonAdapter(OffsetDateTimeAdapter.class) + public OffsetDateTime updated_at; + +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocCommentWrapperModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocCommentWrapperModel.java new file mode 100644 index 000000000..796389d0c --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocCommentWrapperModel.java @@ -0,0 +1,7 @@ +package com.seafile.seadroid2.framework.data.model.sdoc; + +import java.util.List; + +public class SDocCommentWrapperModel { + public List comments; +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocDataModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocDataModel.java new file mode 100644 index 000000000..7e412a6ac --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocDataModel.java @@ -0,0 +1,6 @@ +package com.seafile.seadroid2.framework.data.model.sdoc; + +public class SDocDataModel { + public String src; + public int width; +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocDetailModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocDetailModel.java new file mode 100644 index 000000000..9f9aa26ba --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocDetailModel.java @@ -0,0 +1,217 @@ +// YApi QuickType插件生成,具体参考文档:https://plugins.jetbrains.com/plugin/18847-yapi-quicktype/documentation + +package com.seafile.seadroid2.framework.data.model.sdoc; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.seafile.seadroid2.framework.data.model.adapter.OffsetDateTimeAdapter; +import com.seafile.seadroid2.framework.util.Utils; + +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; + +public class SDocDetailModel implements Parcelable { + + private String type; + private String id; + private String name; + private String permission; + private long mtime; + + @JsonAdapter(OffsetDateTimeAdapter.class) + @SerializedName("last_modified") + private OffsetDateTime lastModified; + + @SerializedName("last_modifier_email") + private String lastModifierEmail; + @SerializedName("last_modifier_name") + private String lastModifierName; + @SerializedName("last_modifier_contact_email") + private String lastModifierContactEmail; + @SerializedName("last_modifier_avatar") + private String lastModifierAvatar; + private long size; + private boolean starred; + private long commentTotal; + private boolean canEdit; + + + public String getPermission() { + return permission; + } + + public void setPermission(String value) { + this.permission = value; + } + + public String getType() { + return type; + } + + public void setType(String value) { + this.type = value; + } + + public long getMtime() { + return mtime; + } + + public void setMtime(long value) { + this.mtime = value; + } + + + public long getSize() { + return size; + } + + public String getSizeText() { + return Utils.readableFileSize(size); + } + + public void setSize(long value) { + this.size = value; + } + + public boolean getStarred() { + return starred; + } + + public void setStarred(boolean value) { + this.starred = value; + } + + public String getName() { + return name; + } + + public void setName(String value) { + this.name = value; + } + + public long getCommentTotal() { + return commentTotal; + } + + public void setCommentTotal(long value) { + this.commentTotal = value; + } + + public String getId() { + return id; + } + + public void setId(String value) { + this.id = value; + } + + public OffsetDateTime getLastModified() { + return lastModified; + } + + public String getLastModifiedText(){ + return getLastModified().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME).replace("T"," "); + } + + public void setLastModified(OffsetDateTime value) { + this.lastModified = value; + } + + public String getLastModifierEmail() { + return lastModifierEmail; + } + + public void setLastModifierEmail(String value) { + this.lastModifierEmail = value; + } + + public String getLastModifierName() { + return lastModifierName; + } + + public void setLastModifierName(String value) { + this.lastModifierName = value; + } + + public String getLastModifierContactEmail() { + return lastModifierContactEmail; + } + + public void setLastModifierContactEmail(String value) { + this.lastModifierContactEmail = value; + } + + public String getLastModifierAvatar() { + return lastModifierAvatar; + } + + public void setLastModifierAvatar(String value) { + this.lastModifierAvatar = value; + } + + public boolean getCanEdit() { + return canEdit; + } + + public void setCanEdit(boolean value) { + this.canEdit = value; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.type); + dest.writeString(this.id); + dest.writeString(this.name); + dest.writeString(this.permission); + dest.writeLong(this.mtime); + dest.writeSerializable(this.lastModified); + dest.writeString(this.lastModifierEmail); + dest.writeString(this.lastModifierName); + dest.writeString(this.lastModifierContactEmail); + dest.writeString(this.lastModifierAvatar); + dest.writeLong(this.size); + dest.writeByte(this.starred ? (byte) 1 : (byte) 0); + dest.writeLong(this.commentTotal); + dest.writeByte(this.canEdit ? (byte) 1 : (byte) 0); + } + + public SDocDetailModel() { + } + + protected SDocDetailModel(Parcel in) { + this.type = in.readString(); + this.id = in.readString(); + this.name = in.readString(); + this.permission = in.readString(); + this.mtime = in.readLong(); + this.lastModified = (OffsetDateTime) in.readSerializable(); + this.lastModifierEmail = in.readString(); + this.lastModifierName = in.readString(); + this.lastModifierContactEmail = in.readString(); + this.lastModifierAvatar = in.readString(); + this.size = in.readLong(); + this.starred = in.readByte() != 0; + this.commentTotal = in.readLong(); + this.canEdit = in.readByte() != 0; + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public SDocDetailModel createFromParcel(Parcel source) { + return new SDocDetailModel(source); + } + + @Override + public SDocDetailModel[] newArray(int size) { + return new SDocDetailModel[size]; + } + }; +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocModel.java new file mode 100644 index 000000000..d8b316122 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocModel.java @@ -0,0 +1,14 @@ +package com.seafile.seadroid2.framework.data.model.sdoc; + +import com.seafile.seadroid2.framework.data.model.BaseModel; + +import java.util.List; + +public class SDocModel extends BaseModel { + public String id; + public String type; + public String text; + public boolean indent; + public List children; + public SDocDataModel data; +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocPageOptionsModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocPageOptionsModel.java new file mode 100644 index 000000000..74e1c0119 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocPageOptionsModel.java @@ -0,0 +1,248 @@ +package com.seafile.seadroid2.framework.data.model.sdoc; + +import android.os.Parcel; +import android.os.Parcelable; + +public class SDocPageOptionsModel implements Parcelable { + public String docName; + public String docUuid; + public String seadocServerUrl; + public String seadocAccessToken; + public String repoID; + public String repoName; + public boolean isLocked; + public boolean isStarred; + + @Override + public String toString() { + return "SDocPageOptionsModel{" + + "docName='" + docName + '\'' + + ", docUuid='" + docUuid + '\'' + + ", seadocServerUrl='" + seadocServerUrl + '\'' + + ", seadocAccessToken='" + seadocAccessToken + '\'' + + ", repoID='" + repoID + '\'' + + ", repoName='" + repoName + '\'' + + ", isLocked=" + isLocked + + ", isStarred=" + isStarred + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.docName); + dest.writeString(this.docUuid); + dest.writeString(this.seadocServerUrl); + dest.writeString(this.seadocAccessToken); + dest.writeString(this.repoID); + dest.writeString(this.repoName); + dest.writeByte(this.isLocked ? (byte) 1 : (byte) 0); + dest.writeByte(this.isStarred ? (byte) 1 : (byte) 0); + } + + public SDocPageOptionsModel() { + } + + protected SDocPageOptionsModel(Parcel in) { + this.docName = in.readString(); + this.docUuid = in.readString(); + this.seadocServerUrl = in.readString(); + this.seadocAccessToken = in.readString(); + this.repoID = in.readString(); + this.repoName = in.readString(); + this.isLocked = in.readByte() != 0; + this.isStarred = in.readByte() != 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public SDocPageOptionsModel createFromParcel(Parcel source) { + return new SDocPageOptionsModel(source); + } + + @Override + public SDocPageOptionsModel[] newArray(int size) { + return new SDocPageOptionsModel[size]; + } + }; +} + +// +// \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocProfileConfigModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocProfileConfigModel.java new file mode 100644 index 000000000..658ef12cd --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocProfileConfigModel.java @@ -0,0 +1,33 @@ +package com.seafile.seadroid2.framework.data.model.sdoc; + +import com.seafile.seadroid2.framework.data.model.user.UserWrapperModel; + +public class SDocProfileConfigModel { + public UserWrapperModel users; + public MetadataConfigModel metadata; + public SDocDetailModel detail; + + public UserWrapperModel getUsers() { + return users; + } + + public void setUsers(UserWrapperModel users) { + this.users = users; + } + + public MetadataConfigModel getMetadata() { + return metadata; + } + + public void setMetadata(MetadataConfigModel metadata) { + this.metadata = metadata; + } + + public SDocDetailModel getDetail() { + return detail; + } + + public void setDetail(SDocDetailModel detail) { + this.detail = detail; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocRecordWrapperModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocRecordWrapperModel.java new file mode 100644 index 000000000..4f1748c07 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocRecordWrapperModel.java @@ -0,0 +1,42 @@ +package com.seafile.seadroid2.framework.data.model.sdoc; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +public class SDocRecordWrapperModel implements Parcelable { + public List metadata; + public List results; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedList(this.metadata); + dest.writeTypedList(this.results); + } + + public SDocRecordWrapperModel() { + } + + protected SDocRecordWrapperModel(Parcel in) { + this.metadata = in.createTypedArrayList(MetadataModel.CREATOR); + this.results = in.createTypedArrayList(RecordResultModel.CREATOR); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public SDocRecordWrapperModel createFromParcel(Parcel source) { + return new SDocRecordWrapperModel(source); + } + + @Override + public SDocRecordWrapperModel[] newArray(int size) { + return new SDocRecordWrapperModel[size]; + } + }; +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocWrapperModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocWrapperModel.java new file mode 100644 index 000000000..03e7cfdea --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocWrapperModel.java @@ -0,0 +1,10 @@ +package com.seafile.seadroid2.framework.data.model.sdoc; + +import java.util.List; + +public class SDocWrapperModel { + public int version; + public int format_version; + public String last_modify_user; + public List elements; +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/user/UserModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/user/UserModel.java new file mode 100644 index 000000000..48febae54 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/user/UserModel.java @@ -0,0 +1,78 @@ +package com.seafile.seadroid2.framework.data.model.user; + +import android.os.Parcel; +import android.os.Parcelable; + +public class UserModel implements Parcelable { + private String email; + private String name; + private String contact_email; + private String avatar_url; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getContactEmail() { + return contact_email; + } + + public void setContactEmail(String contact_email) { + this.contact_email = contact_email; + } + + public String getAvatarUrl() { + return avatar_url; + } + + public void setAvatarUrl(String avatar_url) { + this.avatar_url = avatar_url; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.email); + dest.writeString(this.name); + dest.writeString(this.contact_email); + dest.writeString(this.avatar_url); + } + + public UserModel() { + } + + protected UserModel(Parcel in) { + this.email = in.readString(); + this.name = in.readString(); + this.contact_email = in.readString(); + this.avatar_url = in.readString(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public UserModel createFromParcel(Parcel source) { + return new UserModel(source); + } + + @Override + public UserModel[] newArray(int size) { + return new UserModel[size]; + } + }; +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/user/UserWrapperModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/user/UserWrapperModel.java new file mode 100644 index 000000000..c48570f43 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/user/UserWrapperModel.java @@ -0,0 +1,7 @@ +package com.seafile.seadroid2.framework.data.model.user; + +import java.util.List; + +public class UserWrapperModel { + public List user_list; +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/datastore/DataStoreKeys.java b/app/src/main/java/com/seafile/seadroid2/framework/datastore/DataStoreKeys.java index 8930c8690..243f74ebf 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/datastore/DataStoreKeys.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/datastore/DataStoreKeys.java @@ -35,4 +35,5 @@ public class DataStoreKeys { public static final String KEY_DARK_MODE = "key_dark_mode"; + public static final String KEY_SERVER_CERT_INFO = "key_server_cert_info"; } \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/framework/datastore/sp_livedata/ClientEncryptSharePreferenceHelper.java b/app/src/main/java/com/seafile/seadroid2/framework/datastore/sp_livedata/ClientEncryptSharePreferenceHelper.java index 7567a42c9..d984139f9 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/datastore/sp_livedata/ClientEncryptSharePreferenceHelper.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/datastore/sp_livedata/ClientEncryptSharePreferenceHelper.java @@ -3,6 +3,8 @@ import com.seafile.seadroid2.preferences.Settings; public class ClientEncryptSharePreferenceHelper { + + @Deprecated public static void writeClientEncSwitch(boolean isChecked) { if (Settings.CLIENT_ENCRYPT_SWITCH == null) { return; @@ -12,10 +14,11 @@ public static void writeClientEncSwitch(boolean isChecked) { } public static boolean isEncryptEnabled() { - if (Settings.CLIENT_ENCRYPT_SWITCH == null) { - return false; - } + return false; - return Settings.CLIENT_ENCRYPT_SWITCH.queryValue(); +// if (Settings.CLIENT_ENCRYPT_SWITCH == null) { +// return false; +// } +// return Settings.CLIENT_ENCRYPT_SWITCH.queryValue(); } } diff --git a/app/src/main/java/com/seafile/seadroid2/framework/http/SafeOkHttpClient.java b/app/src/main/java/com/seafile/seadroid2/framework/http/SafeOkHttpClient.java index f257a82b6..c8fe25103 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/http/SafeOkHttpClient.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/http/SafeOkHttpClient.java @@ -2,16 +2,24 @@ import com.blankj.utilcode.util.CollectionUtils; import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.ssl.SSLSeafileSocketFactory; +import com.seafile.seadroid2.ssl.SSLTrustManager; +import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; @@ -58,18 +66,58 @@ public SSLSocketFactory getTLSSocketFactory(TrustManager[] trustManagers) { public OkHttpClient getOkClient() { OkHttpClient.Builder builder = new OkHttpClient.Builder(); - TrustManager[] trustManagers = getTrustManagers(); - X509TrustManager trustManager = (X509TrustManager) trustManagers[0]; - SSLSocketFactory sslSocketFactory = getTLSSocketFactory(trustManagers); + //https + if (account.getServer().startsWith("https://")) { + //ssl + + SSLSocketFactory factory = SSLTrustManager.instance().getSSLSocketFactory(account); + TrustManager[] trustManagers = SSLTrustManager.instance().getTrustManagers(account); + X509TrustManager trustManager = (X509TrustManager) trustManagers[0]; + + builder.sslSocketFactory(factory, trustManager); + + builder.hostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + //check host + if (account.getServerDomainName().equals(hostname)) { + return true; + } + + //check by default verifier + HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier(); + return verifier.verify(hostname, session); + } + }); + } // Add an interceptor to set SSL only for HTTPS - builder.addInterceptor(chain -> { - Request request = chain.request(); - if (request.isHttps()) { - builder.sslSocketFactory(sslSocketFactory, trustManager); - } - return chain.proceed(request); - }); +// builder.addInterceptor(chain -> { +// Request request = chain.request(); +// if (request.isHttps()) { +// //ssl +// TrustManager[] trustManagers = getTrustManagers(); +// X509TrustManager trustManager = (X509TrustManager) trustManagers[0]; +// SSLSocketFactory sslSocketFactory = getTLSSocketFactory(trustManagers); +// +// builder.sslSocketFactory(sslSocketFactory, trustManager); +// +// builder.hostnameVerifier(new HostnameVerifier() { +// @Override +// public boolean verify(String hostname, SSLSession session) { +// //check host +// if (account.getServerDomainName().equals(hostname)) { +// return true; +// } +// +// //check by default verifier +// HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier(); +// return verifier.verify(hostname, session); +// } +// }); +// } +// return chain.proceed(request); +// }); builder.connectionSpecs(Arrays.asList( ConnectionSpec.MODERN_TLS, diff --git a/app/src/main/java/com/seafile/seadroid2/framework/http/UnsafeOkHttpClient.java b/app/src/main/java/com/seafile/seadroid2/framework/http/UnsafeOkHttpClient.java deleted file mode 100644 index 82997a0a6..000000000 --- a/app/src/main/java/com/seafile/seadroid2/framework/http/UnsafeOkHttpClient.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.seafile.seadroid2.framework.http; - -import com.blankj.utilcode.util.CollectionUtils; -import com.seafile.seadroid2.account.Account; - -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import okhttp3.ConnectionSpec; -import okhttp3.Interceptor; -import okhttp3.OkHttpClient; - -public class UnsafeOkHttpClient extends BaseOkHttpClient { - public UnsafeOkHttpClient(Account account) { - super(account); - } - - private final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[]{}; - } - } - }; - - @Override - public OkHttpClient getOkClient() { - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - - - try { - // Install the all-trusting trust manager - final SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); - // Create an ssl socket factory with our all-trusting manager - final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); - - builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]); - } catch (Exception e) { - e.printStackTrace(); - } - - builder.connectionSpecs(Arrays.asList( - ConnectionSpec.MODERN_TLS, - ConnectionSpec.COMPATIBLE_TLS, - ConnectionSpec.CLEARTEXT)); - builder.cache(cache); - builder.hostnameVerifier(new HostnameVerifier() { - @Override - public boolean verify(String hostname, SSLSession session) { - return true; - } - }); - //cache control - builder.interceptors().add(REWRITE_CACHE_CONTROL_INTERCEPTOR); - builder.networkInterceptors().add(REWRITE_CACHE_CONTROL_INTERCEPTOR); - - //add interceptors - List interceptors = getInterceptors(); - if (!CollectionUtils.isEmpty(interceptors)) { - 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); - - return builder.build(); - } - - -} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/monitor/SeafileObserver.java b/app/src/main/java/com/seafile/seadroid2/framework/monitor/SeafileObserver.java index e79851c6e..f54a056e0 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/monitor/SeafileObserver.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/monitor/SeafileObserver.java @@ -1,19 +1,14 @@ package com.seafile.seadroid2.framework.monitor; -import android.util.Log; - import com.google.common.collect.Maps; import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.framework.datastore.DataManager; import com.seafile.seadroid2.framework.data.SeafCachedFile; -import com.seafile.seadroid2.framework.data.SeafRepo; -import com.seafile.seadroid2.framework.util.Utils; +import com.seafile.seadroid2.framework.datastore.DataManager; import org.apache.commons.io.monitor.FileAlterationListener; import org.apache.commons.io.monitor.FileAlterationObserver; import java.io.File; -import java.util.List; import java.util.Map; public class SeafileObserver implements FileAlterationListener { diff --git a/app/src/main/java/com/seafile/seadroid2/framework/util/GlideCache.java b/app/src/main/java/com/seafile/seadroid2/framework/util/GlideCache.java index ac59f0f01..2a85c011a 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/util/GlideCache.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/util/GlideCache.java @@ -28,6 +28,8 @@ @GlideModule public class GlideCache extends AppGlideModule { + + @Override public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) { super.applyOptions(context, builder); diff --git a/app/src/main/java/com/seafile/seadroid2/framework/util/Icons.java b/app/src/main/java/com/seafile/seadroid2/framework/util/Icons.java index 7139551fb..7a3679bde 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/util/Icons.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/util/Icons.java @@ -23,22 +23,22 @@ public class Icons { "py", "cpp", "h"); public static int getFileIcon(String name) { - return getFileOldIcon(name); -// String suffix = FileUtils.getFileExtension(name); -// if (TextUtils.isEmpty(suffix)) { -// return R.drawable.icon_extended_file; -// } -// -// if (codes.contains(suffix)) { -// return R.drawable.icon_extended_css; -// } -// -// if (getSuffixIconMap().containsKey(suffix)) { -// return Objects.requireNonNull(getSuffixIconMap().get(suffix)); -// } -// -// String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(suffix); -// return getResIdForMimetype(mime); +// return getFileOldIcon(name); + String suffix = FileUtils.getFileExtension(name); + if (TextUtils.isEmpty(suffix)) { + return R.drawable.icon_extended_file; + } + + if (codes.contains(suffix)) { + return R.drawable.icon_extended_css; + } + + if (getSuffixIconMap().containsKey(suffix)) { + return Objects.requireNonNull(getSuffixIconMap().get(suffix)); + } + + String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(suffix); + return getResIdForMimetype(mime); } public static int getFileOldIcon(String name) { diff --git a/app/src/main/java/com/seafile/seadroid2/preferences/Settings.java b/app/src/main/java/com/seafile/seadroid2/preferences/Settings.java index afc6d123d..2519d4d0c 100644 --- a/app/src/main/java/com/seafile/seadroid2/preferences/Settings.java +++ b/app/src/main/java/com/seafile/seadroid2/preferences/Settings.java @@ -3,6 +3,7 @@ import android.content.SharedPreferences; import android.content.res.Resources; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.seafile.seadroid2.R; @@ -52,6 +53,7 @@ public Settings() { // public static SettingsLiveData USER_GESTURE_LOCK_TIMESTAMP; + @Deprecated public static SettingsLiveData CLIENT_ENCRYPT_SWITCH; //album backup @@ -75,6 +77,7 @@ public Settings() { //cache public static SettingsLiveData CACHE_SIZE; + private static final List> REGISTER_LIST = new ArrayList<>(); @Nullable @@ -88,6 +91,20 @@ public static SharedPreferences getUserSharedPreferences() { return sharedPreferences; } + @NonNull + public static SharedPreferences getSpecialUserSharedPreferences(Account specialAccount) { + return getSpecialUserSharedPreferences(specialAccount.getEncryptSignature()); + } + + @NonNull + public static SharedPreferences getSpecialUserSharedPreferences(String encryptSignature) { + return SharedPreferencesHelper.getSharedPreferences(encryptSignature); + } + + @NonNull + public static SharedPreferences getCommonPreferences() { + return SharedPreferencesHelper.getSharedPreferences(null); + } /** * Initialization is required before use @@ -147,6 +164,7 @@ public static void initUserSettings(Account account) { //cache CACHE_SIZE = new StringSettingLiveData(_account.getEncryptSignature(), R.string.pref_key_cache_info, R.string.settings_account_info_load_data); + REGISTER_LIST.add(USER_INFO); REGISTER_LIST.add(SPACE_INFO); REGISTER_LIST.add(NIGHT_MODE); diff --git a/app/src/main/java/com/seafile/seadroid2/ssl/CertsHelper.java b/app/src/main/java/com/seafile/seadroid2/ssl/CertsHelper.java index 2991825a2..7550358f2 100644 --- a/app/src/main/java/com/seafile/seadroid2/ssl/CertsHelper.java +++ b/app/src/main/java/com/seafile/seadroid2/ssl/CertsHelper.java @@ -1,52 +1,17 @@ package com.seafile.seadroid2.ssl; -import android.database.Cursor; import android.util.Base64; -import com.blankj.utilcode.util.CollectionUtils; -import com.seafile.seadroid2.framework.data.db.AppDatabase; -import com.seafile.seadroid2.framework.data.db.entities.CertEntity; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.security.cert.X509Certificate; -import java.util.List; public class CertsHelper { - public static X509Certificate getCertificate(String url) { - List list = AppDatabase.getInstance().certDAO().getListByUrl(url); - if (CollectionUtils.isEmpty(list)) { - return null; - } - - return convertToCert(list.get(0).cert); - -// String[] projection = {COLUMN_CERT}; -// -// Cursor c = database.query(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; -// } -// -// X509Certificate cert = cursorToCert(c); -// -// c.close(); -// return cert; - } - - private static X509Certificate convertToCert(String text) { + public static X509Certificate convertToCert(String text) { X509Certificate cert = null; ByteArrayInputStream bis = null; @@ -81,23 +46,18 @@ private static X509Certificate convertToCert(String text) { } } - private static X509Certificate cursorToCert(Cursor cursor) { - X509Certificate cert = null; - String text = cursor.getString(0); - return convertToCert(text); - } - public static void saveCertificate(String url, X509Certificate cert) { + public static String getCertBase64(X509Certificate cert) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = null; - String text = null; + String text; try { out = new ObjectOutputStream(bos); out.writeObject(cert); byte[] data = bos.toByteArray(); text = Base64.encodeToString(data, Base64.DEFAULT); } catch (IOException e) { - return; + return null; } finally { try { if (out != null) { @@ -113,17 +73,6 @@ public static void saveCertificate(String url, X509Certificate cert) { } } -// ContentValues values = new ContentValues(); -// values.put(COLUMN_URL, url); -// values.put(COLUMN_CERT, text); -// database.replace(TABLE_NAME, null, values); - - CertEntity certEntity = new CertEntity(); - certEntity.cert = text; - certEntity.url = url; - - AppDatabase.getInstance().certDAO().deleteByUrl(url); - AppDatabase.getInstance().certDAO().insert(certEntity); - + return text; } } diff --git a/app/src/main/java/com/seafile/seadroid2/ssl/CertsManager.java b/app/src/main/java/com/seafile/seadroid2/ssl/CertsManager.java index 458724162..5cc91bb45 100644 --- a/app/src/main/java/com/seafile/seadroid2/ssl/CertsManager.java +++ b/app/src/main/java/com/seafile/seadroid2/ssl/CertsManager.java @@ -4,21 +4,27 @@ import java.util.List; import java.util.Map; +import android.text.TextUtils; import android.util.Log; +import com.blankj.utilcode.util.EncryptUtils; import com.google.common.collect.Maps; +import com.seafile.seadroid2.R; import com.seafile.seadroid2.framework.data.db.AppDatabase; import com.seafile.seadroid2.framework.data.db.entities.CertEntity; +import com.seafile.seadroid2.framework.datastore.DataManager; +import com.seafile.seadroid2.framework.datastore.DataStoreKeys; +import com.seafile.seadroid2.framework.datastore.DataStoreManager; +import com.seafile.seadroid2.framework.datastore.sp.SettingsManager; import com.seafile.seadroid2.framework.util.ConcurrentAsyncTask; import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.preferences.Settings; +import com.seafile.seadroid2.preferences.SharedPreferencesHelper; /** * Save the ssl certificates the user has confirmed to trust */ public final class CertsManager { - private static final String DEBUG_TAG = "CertsManager"; - - private final CertsDBHelper db = CertsDBHelper.getDatabaseHelper(); private final Map cachedCerts = Maps.newConcurrentMap(); @@ -42,26 +48,26 @@ public void saveCertForAccount(final Account account, boolean rememberChoice) { cachedCerts.put(account, cert); if (rememberChoice) { - ConcurrentAsyncTask.submit(new Runnable() { - @Override - public void run() { - CertsHelper.saveCertificate(account.server,cert); -// db.saveCertificate(account.server, cert); - } - }); + // save cert info to shared preferences + String certBase64 = CertsHelper.getCertBase64(cert); + String keyPrefix = EncryptUtils.encryptMD5ToString(account.getServer()); + Settings.getCommonPreferences().edit().putString(DataStoreKeys.KEY_SERVER_CERT_INFO + "_" + keyPrefix, certBase64).apply(); } - - Log.d(DEBUG_TAG, "saved cert for account " + account); } + public X509Certificate getCertificate(Account account) { X509Certificate cert = cachedCerts.get(account); if (cert != null) { return cert; } + String keyPrefix = EncryptUtils.encryptMD5ToString(account.getServer()); + String certBase64 = Settings.getCommonPreferences().getString(DataStoreKeys.KEY_SERVER_CERT_INFO + "_" + keyPrefix, null); + if (TextUtils.isEmpty(certBase64)) { + return null; + } -// cert = db.getCertificate(account.server); - cert = CertsHelper.getCertificate(account.server); + cert = CertsHelper.convertToCert(certBase64); if (cert != null) { cachedCerts.put(account, cert); } diff --git a/app/src/main/java/com/seafile/seadroid2/ssl/SSLSeafileSocketFactory.java b/app/src/main/java/com/seafile/seadroid2/ssl/SSLSeafileSocketFactory.java index ef88d9e12..19470a888 100644 --- a/app/src/main/java/com/seafile/seadroid2/ssl/SSLSeafileSocketFactory.java +++ b/app/src/main/java/com/seafile/seadroid2/ssl/SSLSeafileSocketFactory.java @@ -24,18 +24,17 @@ * Used to manually select TLS protocol versions. * * based on: - * https://stackoverflow.com/questions/1037590/which-cipher-suites-to-enable-for-ssl-socket + * https://stackoverflow.com/questions/1037590/which-cipher-suites-to-enable-for-ssl-socket */ -@Deprecated public class SSLSeafileSocketFactory extends SSLSocketFactory { - private SSLContext context; - private String[] allowedCiphers; - private String[] allowedProtocols; + private final SSLContext sslContext; + private final String[] allowedCiphers; + private final String[] allowedProtocols; public SSLSeafileSocketFactory(KeyManager[] km, TrustManager[] tm, SecureRandom random) throws NoSuchAlgorithmException, KeyManagementException { - context = SSLContext.getInstance("TLS"); - context.init(km, tm, random); + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(km, tm, random); allowedProtocols = getProtocolList(); allowedCiphers = getCipherList(); @@ -52,7 +51,7 @@ public String[] getSupportedCipherSuites() } public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { - SSLSocketFactory factory = context.getSocketFactory(); + SSLSocketFactory factory = sslContext.getSocketFactory(); SSLSocket ss = (SSLSocket)factory.createSocket(s, host, port, autoClose); ss.setEnabledProtocols(allowedProtocols); @@ -62,7 +61,7 @@ public Socket createSocket(Socket s, String host, int port, boolean autoClose) t } public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { - SSLSocketFactory factory = context.getSocketFactory(); + SSLSocketFactory factory = sslContext.getSocketFactory(); SSLSocket ss = (SSLSocket)factory.createSocket(address, port, localAddress, localPort); ss.setEnabledProtocols(allowedProtocols); @@ -72,7 +71,7 @@ public Socket createSocket(InetAddress address, int port, InetAddress localAddre } public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { - SSLSocketFactory factory = context.getSocketFactory(); + SSLSocketFactory factory = sslContext.getSocketFactory(); SSLSocket ss = (SSLSocket)factory.createSocket(host, port, localHost, localPort); ss.setEnabledProtocols(allowedProtocols); @@ -82,7 +81,7 @@ public Socket createSocket(String host, int port, InetAddress localHost, int loc } public Socket createSocket(InetAddress host, int port) throws IOException { - SSLSocketFactory factory = context.getSocketFactory(); + SSLSocketFactory factory = sslContext.getSocketFactory(); SSLSocket ss = (SSLSocket)factory.createSocket(host, port); ss.setEnabledProtocols(allowedProtocols); @@ -92,7 +91,7 @@ public Socket createSocket(InetAddress host, int port) throws IOException { } public Socket createSocket(String host, int port) throws IOException { - SSLSocketFactory factory = context.getSocketFactory(); + SSLSocketFactory factory = sslContext.getSocketFactory(); SSLSocket ss = (SSLSocket)factory.createSocket(host, port); ss.setEnabledProtocols(allowedProtocols); @@ -157,7 +156,7 @@ protected String[] getCipherList() { } // now filter out any ciphers that aren't supported by this device - SSLSocketFactory factory = context.getSocketFactory(); + SSLSocketFactory factory = sslContext.getSocketFactory(); String[] availableCiphers = factory.getSupportedCipherSuites(); ArrayList available = new ArrayList(Arrays.asList(availableCiphers)); diff --git a/app/src/main/java/com/seafile/seadroid2/ssl/SSLTrustManager.java b/app/src/main/java/com/seafile/seadroid2/ssl/SSLTrustManager.java index 697cff9ca..fc0ea4f36 100644 --- a/app/src/main/java/com/seafile/seadroid2/ssl/SSLTrustManager.java +++ b/app/src/main/java/com/seafile/seadroid2/ssl/SSLTrustManager.java @@ -141,9 +141,9 @@ public SslFailureReason getFailureReason(Account account) { } /** - * Reorder the certificates chain, since it may not be in the right order when passed to us - * - * @see http://stackoverflow.com/questions/7822381/need-help-understanding-certificate-chains + * Reorder the certificates chain, since it may not be in the right order when passed to us. + *

+ * see http://stackoverflow.com/questions/7822381/need-help-understanding-certificate-chains */ public List orderCerts(X509Certificate[] certificates) { if (certificates == null || certificates.length == 0) { @@ -207,14 +207,12 @@ public SslFailureReason getReason() { } @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) - throws CertificateException { + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { defaultTrustManager.checkClientTrusted(chain, authType); } @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) - throws CertificateException { + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { if (chain == null || chain.length == 0) { defaultTrustManager.checkServerTrusted(chain, authType); return; @@ -231,19 +229,13 @@ public void checkServerTrusted(X509Certificate[] chain, String authType) } } -// public String getCeritificateInfo() throws CertificateParsingException { -// X509Certificate cert = CertsManager.instance().getCertificate(account); -// return "sigalgName:" + cert.getSigAlgName() + " Type: " -// + cert.getType() + " Version: " + cert.getVersion() -// + " IssuerAlternative: " + cert.getIssuerAlternativeNames() -// + " NotAfter: " + cert.getNotAfter(); -// } /** * Interface for checking if a hostname matches the names stored inside the server's X.509 certificate */ private void validateHostName(List chain) throws CertificateException { X509Certificate cert = chain.get(0); + // BrowserCompatHostnameVerifier can verify hostnames in the form of IP addresses (like a browser) // where as the DefaultHostnameVerifier will always try to lookup IP addresses via the DNS. X509HostnameVerifier mHostnameVerifier = new BrowserCompatHostnameVerifier(); @@ -254,42 +246,33 @@ private void validateHostName(List chain) throws CertificateExc } } - private void customCheck(List chain, String authType) - throws CertificateException { + private void customCheck(List chain, String authType) throws CertificateException { certsChain = ImmutableList.copyOf(chain); - - ConcurrentAsyncTask.submit(new Runnable() { - @Override - public void run() { - - try { - X509Certificate cert = chain.get(0); - - X509Certificate savedCert = CertsManager.instance().getCertificate(account); - if (savedCert == null) { - Log.d(DEBUG_TAG, "no saved cert for " + account.server); - reason = SslFailureReason.CERT_NOT_TRUSTED; - throw new CertificateException(); - } else if (savedCert.equals(cert)) { - // The user has confirmed to trust this certificate - Log.d(DEBUG_TAG, "the cert of " + account.server + " is trusted"); - return; - } else { - // The certificate is different from the one user confirmed to trust, - // This may be either: - // 1. The server admin has changed its cert - // 2. The user is under security attack - Log.d(DEBUG_TAG, "the cert of " + account.server + " has changed"); - reason = SslFailureReason.CERT_CHANGED; - throw new CertificateException(); - } - } catch (CertificateException e) { - throw new RuntimeException(e); - } + try { + X509Certificate cert = chain.get(0); + + X509Certificate savedCert = CertsManager.instance().getCertificate(account); + if (savedCert == null) { + Log.d(DEBUG_TAG, "no saved cert for " + account.server); + reason = SslFailureReason.CERT_NOT_TRUSTED; + throw new CertificateException(); + } else if (savedCert.equals(cert)) { + // The user has confirmed to trust this certificate + Log.d(DEBUG_TAG, "the cert of " + account.server + " is trusted"); + } else { + // The certificate is different from the one user confirmed to trust, + // This may be either: + // 1. The server admin has changed its cert + // 2. The user is under security attack + Log.d(DEBUG_TAG, "the cert of " + account.server + " has changed"); + reason = SslFailureReason.CERT_CHANGED; + throw new CertificateException(); } - }); + } catch (CertificateException e) { + throw new RuntimeException(e); + } } 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 5c18bd3be..c6595be0c 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 @@ -17,6 +17,7 @@ import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.CheckBox; +import android.widget.CompoundButton; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; @@ -39,6 +40,7 @@ import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.Authenticator; import com.seafile.seadroid2.config.Constants; +import com.seafile.seadroid2.databinding.AccountDetailBinding; import com.seafile.seadroid2.framework.util.Utils; import com.seafile.seadroid2.ssl.CertsManager; import com.seafile.seadroid2.ui.base.BaseActivityWithVM; @@ -52,19 +54,19 @@ public class AccountDetailActivity extends BaseActivityWithVM private final String TWO_FACTOR_AUTH = "two_factor_auth"; - private TextView mStatusTv; - private Button mLoginBtn; - private EditText mServerEt; - private TextInputEditText mEmailEt; - private EditText mPasswdEt; - private CheckBox mHttpsCheckBox; - private TextView mSeaHubUrlHintTv; - private ImageView mClearEmailIv; - private TextInputLayout mAuthTokenInputLayout; - private EditText mAuthTokenEt; + private AccountDetailBinding binding; + +// private TextView mStatusTv; +// private Button mLoginBtn; +// private EditText mServerEt; +// private TextInputEditText mEmailEt; +// private EditText mPasswdEt; +// private CheckBox mHttpsCheckBox; +// private TextView mSeaHubUrlHintTv; +// private TextInputLayout mAuthTokenInputLayout; +// private EditText mAuthTokenEt; private boolean serverTextHasFocus; - private CheckBox mRemDeviceCheckBox; private String mSessionKey; /** @@ -73,27 +75,22 @@ public class AccountDetailActivity extends BaseActivityWithVM @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.account_detail); + binding = AccountDetailBinding.inflate(getLayoutInflater()); + + setContentView(binding.getRoot()); - mStatusTv = findViewById(R.id.status_view); - mHttpsCheckBox = findViewById(R.id.https_checkbox); - mServerEt = findViewById(R.id.server_url); - mEmailEt = findViewById(R.id.email_address); - mPasswdEt = findViewById(R.id.password); - mSeaHubUrlHintTv = findViewById(R.id.seahub_url_hint); + initView(); - mClearEmailIv = findViewById(R.id.iv_delete_email); + initViewModel(); + } - mAuthTokenInputLayout = findViewById(R.id.auth_token_hint); - mAuthTokenEt = findViewById(R.id.auth_token); - mAuthTokenInputLayout.setVisibility(View.GONE); - mRemDeviceCheckBox = findViewById(R.id.remember_device); - mRemDeviceCheckBox.setVisibility(View.GONE); + private void initView() { - mLoginBtn = findViewById(R.id.login_button); - mLoginBtn.setOnClickListener(v -> login()); + binding.authTokenHint.setVisibility(View.GONE); + binding.rememberDevice.setVisibility(View.GONE); + binding.loginButton.setOnClickListener(v -> login()); setupServerText(); @@ -114,21 +111,23 @@ public void onCreate(Bundle savedInstanceState) { // isFromEdit = mAccountManager.getUserData(account, Authenticator.KEY_EMAIL); if (server.startsWith(Constants.Protocol.HTTPS)) - mHttpsCheckBox.setChecked(true); + binding.httpsCheckbox.setChecked(true); + + binding.serverUrl.setText(server); + binding.emailAddress.setText(email); + binding.emailAddress.requestFocus(); - mServerEt.setText(server); - mEmailEt.setText(email); - mEmailEt.requestFocus(); - mSeaHubUrlHintTv.setVisibility(View.GONE); + binding.seahubUrlHint.setVisibility(View.GONE); } else if (defaultServerUri != null) { if (defaultServerUri.startsWith(Constants.Protocol.HTTPS)) - mHttpsCheckBox.setChecked(true); - mServerEt.setText(defaultServerUri); - mEmailEt.requestFocus(); + binding.httpsCheckbox.setChecked(true); + binding.serverUrl.setText(defaultServerUri); + binding.emailAddress.requestFocus(); + } else { - mServerEt.setText(Constants.Protocol.HTTP); + binding.serverUrl.setText(Constants.Protocol.HTTP); int prefixLen = Constants.Protocol.HTTP.length(); - mServerEt.setSelection(prefixLen, prefixLen); + binding.serverUrl.setSelection(prefixLen, prefixLen); } @@ -138,66 +137,10 @@ public void onCreate(Bundle savedInstanceState) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setTitle(R.string.login); - initListener(); - - initViewModel(); - } - - private void initListener() { - mEmailEt.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus && mEmailEt.getText().toString().trim().length() > 0) { - mClearEmailIv.setVisibility(View.VISIBLE); - } else { - mClearEmailIv.setVisibility(View.INVISIBLE); - } - } - }); - - mEmailEt.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - + binding.httpsCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - if (mEmailEt.getText().toString().trim().length() > 0) { - mClearEmailIv.setVisibility(View.VISIBLE); - } else { - mClearEmailIv.setVisibility(View.INVISIBLE); - } - } - - @Override - public void afterTextChanged(Editable s) { - } - }); - - - mClearEmailIv.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mEmailEt.setText(""); - } - }); - - TextInputLayout passwordInputLayout = findViewById(R.id.password_hint); - passwordInputLayout.setEndIconOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mPasswdEt.getTransformationMethod() instanceof PasswordTransformationMethod) { - mPasswdEt.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); - passwordInputLayout.setEndIconDrawable(R.drawable.icon_eye_open); - } else { - passwordInputLayout.setEndIconDrawable(R.drawable.icon_eye_close); - mPasswdEt.setTransformationMethod(PasswordTransformationMethod.getInstance()); - } - - String input = mPasswdEt.getText().toString().trim(); - if (!TextUtils.isEmpty(input)) { - mPasswdEt.setSelection(input.length()); - } + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + refreshServerUrlPrefix(); } }); } @@ -229,60 +172,84 @@ public void onChanged(Account account) { }); } + private void refreshServerUrlPrefix() { + boolean isHttps = binding.httpsCheckbox.isChecked(); + String url = binding.serverUrl.getText().toString(); + String prefix = isHttps ? Constants.Protocol.HTTPS : Constants.Protocol.HTTP; + + String urlWithoutScheme = url.replace(Constants.Protocol.HTTPS, "").replace(Constants.Protocol.HTTP, ""); + + int oldOffset = binding.serverUrl.getSelectionStart(); + + // Change the text + binding.serverUrl.setText(String.format("%s%s", prefix, urlWithoutScheme)); + + if (serverTextHasFocus) { + // Change the cursor position since we changed the text + int offset; + if (isHttps) { + offset = oldOffset + 1; + } else { + offset = Math.max(0, oldOffset - 1); + } + binding.serverUrl.setSelection(offset, offset); + } + } + private void onLoginException(Account account, SeafException err) { if (err == SeafException.sslException) { - mAuthTokenInputLayout.setVisibility(View.GONE); - mRemDeviceCheckBox.setVisibility(View.GONE); - - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); - builder.setTitle(R.string.ssl_confirm_title); - builder.setMessage(getString(R.string.ssl_not_trusted,account.getServerHost())); - builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }); - builder.create().show(); - -// SslConfirmDialog sslConfirmDialog = new SslConfirmDialog(account, -// new SslConfirmDialog.Listener() { -// @Override -// public void onAccepted(boolean rememberChoice) { -// CertsManager.instance().saveCertForAccount(account, rememberChoice); -// login(); -// } -// -// @Override -// public void onRejected() { -// mStatusTv.setText(R.string.ssl_error); -// mLoginBtn.setEnabled(true); -// } -// }); -// sslConfirmDialog.show(getSupportFragmentManager(), SslConfirmDialog.FRAGMENT_TAG); + binding.authTokenHint.setVisibility(View.GONE); + binding.rememberDevice.setVisibility(View.GONE); + +// MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); +// builder.setTitle(R.string.ssl_confirm_title); +// builder.setMessage(getString(R.string.ssl_not_trusted,account.getServerHost())); +// builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { +// @Override +// public void onClick(DialogInterface dialog, int which) { +// dialog.dismiss(); +// } +// }); +// builder.create().show(); + + SslConfirmDialog sslConfirmDialog = new SslConfirmDialog(account, + new SslConfirmDialog.Listener() { + @Override + public void onAccepted(boolean rememberChoice) { + CertsManager.instance().saveCertForAccount(account, rememberChoice); + login(); + } + + @Override + public void onRejected() { + binding.statusView.setText(R.string.ssl_error); + binding.loginButton.setEnabled(true); + } + }); + sslConfirmDialog.show(getSupportFragmentManager(), SslConfirmDialog.FRAGMENT_TAG); } else if (err == SeafException.twoFactorAuthTokenMissing) { // show auth token input box - mAuthTokenInputLayout.setVisibility(View.VISIBLE); - mRemDeviceCheckBox.setVisibility(View.VISIBLE); - mRemDeviceCheckBox.setChecked(false); - mAuthTokenEt.setError(getString(R.string.two_factor_auth_error)); + binding.authTokenHint.setVisibility(View.VISIBLE); + binding.rememberDevice.setVisibility(View.VISIBLE); + binding.rememberDevice.setChecked(false); + binding.authToken.setError(getString(R.string.two_factor_auth_error)); } else if (err == SeafException.twoFactorAuthTokenInvalid) { // show auth token input box - mAuthTokenInputLayout.setVisibility(View.VISIBLE); - mRemDeviceCheckBox.setVisibility(View.VISIBLE); - mRemDeviceCheckBox.setChecked(false); - mAuthTokenEt.setError(getString(R.string.two_factor_auth_invalid)); + binding.authTokenHint.setVisibility(View.VISIBLE); + binding.rememberDevice.setVisibility(View.VISIBLE); + binding.rememberDevice.setChecked(false); + binding.authToken.setError(getString(R.string.two_factor_auth_invalid)); } else if (err.getCode() == HttpURLConnection.HTTP_NOT_FOUND) { - mStatusTv.setText(R.string.invalid_server_address); + binding.statusView.setText(R.string.invalid_server_address); } else if (err.getCode() == HttpURLConnection.HTTP_BAD_REQUEST) { - mStatusTv.setText(R.string.err_wrong_user_or_passwd); + binding.statusView.setText(R.string.err_wrong_user_or_passwd); } else { - mAuthTokenInputLayout.setVisibility(View.GONE); - mRemDeviceCheckBox.setVisibility(View.GONE); - mStatusTv.setText(err.getMessage()); + binding.authTokenHint.setVisibility(View.GONE); + binding.rememberDevice.setVisibility(View.GONE); + binding.statusView.setText(err.getMessage()); } - mLoginBtn.setEnabled(true); + binding.loginButton.setEnabled(true); } private void onLoggedIn(Account loginAccount) { @@ -300,7 +267,7 @@ private void onLoggedIn(Account loginAccount) { retData.putExtra(SeafileAuthenticatorActivity.ARG_NAME, loginAccount.getName()); retData.putExtra(SeafileAuthenticatorActivity.ARG_AUTH_SESSION_KEY, loginAccount.getSessionKey()); retData.putExtra(SeafileAuthenticatorActivity.ARG_SERVER_URI, loginAccount.getServer()); - retData.putExtra(TWO_FACTOR_AUTH, mRemDeviceCheckBox.isChecked()); + retData.putExtra(TWO_FACTOR_AUTH, binding.rememberDevice.isChecked()); setResult(RESULT_OK, retData); finish(); } @@ -313,9 +280,9 @@ protected void onDestroy() { @Override protected void onSaveInstanceState(Bundle savedInstanceState) { - savedInstanceState.putString("email", mEmailEt.getText().toString()); - savedInstanceState.putString("password", mPasswdEt.getText().toString()); - savedInstanceState.putBoolean("rememberDevice", mRemDeviceCheckBox.isChecked()); + savedInstanceState.putString("email", binding.emailAddress.getText().toString()); + savedInstanceState.putString("password", binding.password.getText().toString()); + savedInstanceState.putBoolean("rememberDevice", binding.rememberDevice.isChecked()); super.onSaveInstanceState(savedInstanceState); } @@ -323,9 +290,9 @@ protected void onSaveInstanceState(Bundle savedInstanceState) { protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); - mEmailEt.setText((String) savedInstanceState.get("email")); - mPasswdEt.setText((String) savedInstanceState.get("password")); - mRemDeviceCheckBox.setChecked((boolean) savedInstanceState.get("rememberDevice")); + binding.emailAddress.setText((String) savedInstanceState.get("email")); + binding.password.setText((String) savedInstanceState.get("password")); + binding.rememberDevice.setChecked((boolean) savedInstanceState.get("rememberDevice")); } @Override @@ -362,36 +329,8 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } - public void onHttpsCheckboxClicked(View view) { - refreshServerUrlPrefix(); - } - - private void refreshServerUrlPrefix() { - boolean isHttps = mHttpsCheckBox.isChecked(); - String url = mServerEt.getText().toString(); - String prefix = isHttps ? Constants.Protocol.HTTPS : Constants.Protocol.HTTP; - - String urlWithoutScheme = url.replace(Constants.Protocol.HTTPS, "").replace(Constants.Protocol.HTTP, ""); - - int oldOffset = mServerEt.getSelectionStart(); - - // Change the text - mServerEt.setText(String.format("%s%s", prefix, urlWithoutScheme)); - - if (serverTextHasFocus) { - // Change the cursor position since we changed the text - if (isHttps) { - int offset = oldOffset + 1; - mServerEt.setSelection(offset, offset); - } else { - int offset = Math.max(0, oldOffset - 1); - mServerEt.setSelection(offset, offset); - } - } - } - private void setupServerText() { - mServerEt.setOnFocusChangeListener(new View.OnFocusChangeListener() { + binding.serverUrl.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { Log.d(DEBUG_TAG, "serverText has focus: " + (hasFocus ? "yes" : "no")); @@ -399,7 +338,7 @@ public void onFocusChange(View v, boolean hasFocus) { } }); - mServerEt.addTextChangedListener(new TextWatcher() { + binding.serverUrl.addTextChangedListener(new TextWatcher() { private String old; @Override @@ -408,19 +347,19 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { - old = mServerEt.getText().toString(); + old = binding.serverUrl.getText().toString(); } @Override public void afterTextChanged(Editable s) { // Don't allow the user to edit the "https://" or "http://" part of the serverText - String url = mServerEt.getText().toString(); - boolean isHttps = mHttpsCheckBox.isChecked(); + String url = binding.serverUrl.getText().toString(); + boolean isHttps = binding.httpsCheckbox.isChecked(); String prefix = isHttps ? Constants.Protocol.HTTPS : Constants.Protocol.HTTP; if (!url.startsWith(prefix)) { - int oldOffset = Math.max(prefix.length(), mServerEt.getSelectionStart()); - mServerEt.setText(old); - mServerEt.setSelection(oldOffset, oldOffset); + int oldOffset = Math.max(prefix.length(), binding.serverUrl.getSelectionStart()); + binding.serverUrl.setText(old); + binding.serverUrl.setSelection(oldOffset, oldOffset); } } }); @@ -428,48 +367,61 @@ public void afterTextChanged(Editable s) { private void login() { - String serverURL = mServerEt.getText().toString().trim(); - String email = mEmailEt.getText().toString().trim(); - String passwd = mPasswdEt.getText().toString(); + String serverURL = binding.serverUrl.getText().toString().trim(); + String email = binding.emailAddress.getText().toString().trim(); + String passwd = binding.password.getText().toString(); if (!NetworkUtils.isConnected()) { - mStatusTv.setText(R.string.network_down); + binding.statusView.setText(R.string.network_down); return; } - if (serverURL.isEmpty()) { - mStatusTv.setText(R.string.err_server_andress_empty); + String urlWithoutScheme = serverURL.replace(Constants.Protocol.HTTPS, "").replace(Constants.Protocol.HTTP, ""); + if (TextUtils.isEmpty(urlWithoutScheme)) { + binding.serverHint.setErrorEnabled(true); + binding.serverHint.setError(getResources().getString(R.string.err_server_andress_empty)); return; + } else { + binding.serverHint.setError(null); + binding.serverHint.setErrorEnabled(false); } if (email.isEmpty()) { - mEmailEt.setError(getResources().getString(R.string.err_email_empty)); + binding.emailHint.setErrorEnabled(true); + binding.emailHint.setError(getResources().getString(R.string.err_email_empty)); return; + } else { + binding.serverHint.setError(null); + binding.serverHint.setErrorEnabled(false); } if (passwd.isEmpty()) { - mPasswdEt.setError(getResources().getString(R.string.err_passwd_empty)); + binding.passwordHint.setErrorEnabled(true); + binding.passwordHint.setError(getResources().getString(R.string.err_passwd_empty)); return; + } else { + binding.passwordHint.setError(null); + binding.passwordHint.setErrorEnabled(false); } String authToken = null; - if (mAuthTokenInputLayout.getVisibility() == View.VISIBLE) { - authToken = mAuthTokenEt.getText().toString().trim(); + if (binding.authTokenHint.getVisibility() == View.VISIBLE) { + authToken = binding.authToken.getText().toString().trim(); if (TextUtils.isEmpty(authToken)) { - mAuthTokenEt.setError(getResources().getString(R.string.two_factor_auth_token_empty)); + binding.authToken.setError(getResources().getString(R.string.two_factor_auth_token_empty)); return; } } boolean rememberDevice = false; - if (mRemDeviceCheckBox.getVisibility() == View.VISIBLE) { - rememberDevice = mRemDeviceCheckBox.isChecked(); + if (binding.rememberDevice.getVisibility() == View.VISIBLE) { + rememberDevice = binding.rememberDevice.isChecked(); } try { serverURL = Utils.cleanServerURL(serverURL); } catch (MalformedURLException e) { - mStatusTv.setText(R.string.invalid_server_address); + binding.statusView.setText(R.string.invalid_server_address); Log.d(DEBUG_TAG, "Invalid URL " + serverURL); return; } 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 9c5c25152..d4840bedd 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 @@ -18,30 +18,19 @@ 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.widget.Toolbar; import androidx.core.app.ComponentActivity; import androidx.core.app.NavUtils; import androidx.core.app.TaskStackBuilder; -import androidx.preference.Preference; -import com.blankj.utilcode.util.ToastUtils; import com.google.firebase.analytics.FirebaseAnalytics; import com.seafile.seadroid2.R; import com.seafile.seadroid2.account.Authenticator; import com.seafile.seadroid2.account.SupportAccountManager; -import com.seafile.seadroid2.framework.datastore.sp.SettingsManager; -import com.seafile.seadroid2.framework.util.Objs; +import com.seafile.seadroid2.config.Constants; import com.seafile.seadroid2.framework.util.SLogs; -import com.seafile.seadroid2.ui.WidgetUtils; import com.seafile.seadroid2.ui.camera_upload.CameraUploadManager; -import com.seafile.seadroid2.config.Constants; -import com.seafile.seadroid2.ui.markdown.MarkdownActivity; -import com.seafile.seadroid2.ui.repo.RepoQuickFragment; -import com.seafile.seadroid2.ui.webview.SeaWebViewActivity; -import java.io.File; import java.util.Locale; /** 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 a4e616ed8..4f764c1ec 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 @@ -238,50 +238,35 @@ public void onReceivedError(WebView view, int errorCode, String description, Str public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) { Log.d(DEBUG_TAG, "onReceivedSslError " + error.getCertificate().toString()); - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(SingleSignOnAuthorizeActivity.this); - builder.setTitle(R.string.ssl_confirm_title); - builder.setMessage(getString(R.string.ssl_not_trusted, serverUrl)); - builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - handler.cancel(); - displaySSLError(); - dialog.dismiss(); - } - }); - builder.create().show(); - -// final Account account = new Account(serverUrl, null, null, null, null, false); -// SslCertificate sslCert = error.getCertificate(); -// -// //todo notice main thread -// X509Certificate savedCert = CertsManager.instance().getCertificate(account); -// -// if (Utils.isSameCert(sslCert, savedCert)) { -// Log.d(DEBUG_TAG, "trust this cert"); -// handler.proceed(); -// } else { -// Log.d(DEBUG_TAG, "cert is not trusted"); -// SslConfirmDialog dialog = new SslConfirmDialog(account, -// Utils.getX509CertFromSslCertHack(sslCert), -// new SslConfirmDialog.Listener() { -// @Override -// public void onAccepted(boolean rememberChoice) { -// CertsManager.instance().saveCertForAccount(account, rememberChoice); -// // Ignore SSL certificate validate -// handler.proceed(); -// } -// -// @Override -// public void onRejected() { -// displaySSLError(); -// handler.cancel(); -// } -// }); -// dialog.show(getSupportFragmentManager(), SslConfirmDialog.FRAGMENT_TAG); -// } + final Account account = new Account(serverUrl, null, null, null, null, false); + SslCertificate sslCert = error.getCertificate(); + + X509Certificate savedCert = CertsManager.instance().getCertificate(account); + if (Utils.isSameCert(sslCert, savedCert)) { + Log.d(DEBUG_TAG, "trust this cert"); + handler.proceed(); + } else { + Log.d(DEBUG_TAG, "cert is not trusted"); + SslConfirmDialog dialog = new SslConfirmDialog(account, + Utils.getX509CertFromSslCertHack(sslCert), + new SslConfirmDialog.Listener() { + @Override + public void onAccepted(boolean rememberChoice) { + CertsManager.instance().saveCertForAccount(account, rememberChoice); + // Ignore SSL certificate validate + handler.proceed(); + } + + @Override + public void onRejected() { + displaySSLError(); + handler.cancel(); + } + }); + dialog.show(getSupportFragmentManager(), SslConfirmDialog.FRAGMENT_TAG); + } } - + @Override public void onPageFinished(WebView webView, String url) { Log.d(DEBUG_TAG, "onPageFinished " + serverUrl); 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 index 9c6d314f3..24d1dfbc4 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activities/AllActivitiesFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/activities/AllActivitiesFragment.java @@ -42,7 +42,7 @@ import com.seafile.seadroid2.ui.markdown.MarkdownActivity; import com.seafile.seadroid2.ui.media.image_preview.ImagePreviewActivity; import com.seafile.seadroid2.ui.media.player.exoplayer.CustomExoVideoPlayerActivity; -import com.seafile.seadroid2.ui.webview.SeaWebViewActivity; +import com.seafile.seadroid2.ui.sdoc.SDocWebViewActivity; import com.seafile.seadroid2.view.TipsViews; import java.io.File; @@ -279,7 +279,7 @@ private void open(RepoModel repoModel, ActivityModel activityModel) { } else if (activityModel.name.endsWith(Constants.Format.DOT_SDOC)) { - SeaWebViewActivity.openSdoc(getContext(), activityModel.repo_name, activityModel.repo_id, activityModel.path); + SDocWebViewActivity.openSdoc(getContext(), activityModel.repo_name, activityModel.repo_id, activityModel.path); } else if (Utils.isVideoFile(activityModel.name)) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraSyncAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraSyncAdapter.java deleted file mode 100644 index d0d93f299..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraSyncAdapter.java +++ /dev/null @@ -1,731 +0,0 @@ -package com.seafile.seadroid2.ui.camera_upload; - -import static android.app.PendingIntent.FLAG_IMMUTABLE; - -import android.accounts.Account; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.AbstractThreadedSyncAdapter; -import android.content.ComponentName; -import android.content.ContentProviderClient; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.SyncResult; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.IBinder; -import android.provider.MediaStore; -import android.util.Log; - -import androidx.core.app.NotificationCompat; - -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.framework.datastore.sp.SettingsManager; -import com.seafile.seadroid2.account.SupportAccountManager; -import com.seafile.seadroid2.framework.data.CameraSyncEvent; -import com.seafile.seadroid2.framework.datastore.DataManager; -import com.seafile.seadroid2.framework.data.SeafRepo; -import com.seafile.seadroid2.framework.datastore.StorageManager; -import com.seafile.seadroid2.ui.CustomNotificationBuilder; -import com.seafile.seadroid2.ui.account.AccountsActivity; -import com.seafile.seadroid2.ui.settings.SettingsActivity; -import com.seafile.seadroid2.framework.util.CameraSyncStatus; -import com.seafile.seadroid2.framework.util.SLogs; -import com.seafile.seadroid2.framework.util.Utils; - -import java.io.File; -import java.net.HttpURLConnection; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.regex.Pattern; - -/** - * Sync adapter for media upload. - *

- * This class uploads images/videos from the gallery to the configured seafile account. - * It is not called directly, but managed by the Android Sync Manager instead. - */ -@Deprecated -public class CameraSyncAdapter extends AbstractThreadedSyncAdapter { - public CameraSyncAdapter(Context context, boolean autoInitialize) { - super(context, autoInitialize); - } - - @Override - public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { - - } -// private static final String DEBUG_TAG = "CameraSyncAdapter"; -// -// private ContentResolver contentResolver; -// -// private SettingsManager settingsMgr = SettingsManager.getInstance(); -// private CameraUploadDBHelper dbHelper; -// -// private String targetRepoId; -// private String targetRepoName; -// private List bucketList; -// -// private final String BASE_DIR = "My Photos"; -// -// /** -// * Will be set to true if the current sync has been cancelled. -// */ -// private boolean cancelled = false; -// -// /** -// * Media files we have sent over to the TransferService. Thread-safe. -// */ -// private List tasksInProgress = new ArrayList<>(); -// -// TransferService txService = null; -// -// ServiceConnection mConnection = new ServiceConnection() { -// @Override -// public void onServiceConnected(ComponentName className, IBinder service) { -// // this will run in a foreign thread! -// -// TransferService.TransferBinder binder = (TransferService.TransferBinder) service; -// synchronized (CameraSyncAdapter.this) { -// txService = binder.getService(); -// } -// // Log.d(DEBUG_TAG, "connected to TransferService"); -// } -// -// @Override -// public void onServiceDisconnected(ComponentName arg0) { -// // this will run in a foreign thread! -// // Log.d(DEBUG_TAG, "disconnected from TransferService, aborting sync"); -// -// onSyncCanceled(); -// synchronized (CameraSyncAdapter.this) { -// txService = null; -// } -// } -// }; -// -// /** -// * Set up the sync adapter -// */ -// public CameraSyncAdapter(Context context) { -// /* -// * autoInitialize is set to false because we need to handle initialization -// * ourselves in performSync() (resetting the photo database). -// */ -// super(context, false); -// -// // Log.d(DEBUG_TAG, "CameraSyncAdapter created."); -// -// contentResolver = context.getContentResolver(); -// dbHelper = CameraUploadDBHelper.getInstance(); -// } -// -// private synchronized void startTransferService() { -// if (txService != null) -// return; -// -// Intent bIntent = new Intent(getContext(), TransferService.class); -// getContext().bindService(bIntent, mConnection, Context.BIND_AUTO_CREATE); -// } -// -// private boolean isCancelled() { -// synchronized (this) { -// return cancelled; -// } -// } -// -// /** -// * Check if repository on the server exists. -// * -// * @param dataManager -// * @return -// * @throws SeafException -// */ -// private boolean validateRepository(DataManager dataManager) throws SeafException { -// List repos = dataManager.getReposFromServer(); -// -// for (SeafRepo repo : repos) { -// if (repo.getRepoId().equals(targetRepoId) && repo.getRepoName().equals(targetRepoName)) -// return true; -// } -// -// return false; -// } -// -// @Override -// public void onSecurityException(android.accounts.Account account, Bundle extras, String authority, SyncResult syncResult) { -// super.onSecurityException(account, extras, authority, syncResult); -// Log.e(DEBUG_TAG, syncResult.toString()); -// } -// -// @Override -// public boolean onUnsyncableAccount() { -// Log.e(DEBUG_TAG, "onUnsyncableAccount"); -// return super.onUnsyncableAccount(); -// } -// -// @Override -// public void onSyncCanceled(Thread thread) { -// super.onSyncCanceled(thread); -// Log.e(DEBUG_TAG, "onSyncCanceled ->" + thread.getName()); -// } -// -// @Override -// public void onSyncCanceled() { -// super.onSyncCanceled(); -// Log.e(DEBUG_TAG, "onSyncCanceled"); -// synchronized (this) { -// cancelled = true; -// } -// } -// -// @Override -// public void onPerformSync(android.accounts.Account account, -// Bundle extras, String authority, -// ContentProviderClient provider, -// SyncResult syncResult) { -// -// SLogs.d("onPerformSync"); -// -// synchronized (this) { -// cancelled = false; -// } -// SeadroidApplication.getInstance().setScanUploadStatus(CameraSyncStatus.SCANNING); -// EventBus.getDefault().post(new CameraSyncEvent("start")); -// /*Log.i(DEBUG_TAG, "Syncing images and video to " + account); -// -// Log.d(DEBUG_TAG, "Selected buckets for camera upload: "+settingsMgr.getCameraUploadBucketList()); -// Log.d(DEBUG_TAG, "is video upload allowed: "+settingsMgr.isVideosUploadAllowed()); -// Log.d(DEBUG_TAG, "is data plan allowed: "+settingsMgr.isDataPlanAllowed()); -// -// Log.d(DEBUG_TAG, "Media buckets available on this system: ");*/ -//// for (GalleryBucketUtils.Bucket bucket: GalleryBucketUtils.getMediaBuckets(getContext())) { -// // Log.d(DEBUG_TAG, "Bucket id="+bucket.id+" name="+bucket.name); -//// } -// -// // resync all media -//// if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE)) { -//// Log.i(DEBUG_TAG, "Doing a full resync"); -//// dbHelper.cleanPhotoCache(); -//// } -// -// if (!settingsMgr.checkCameraUploadNetworkAvailable()) { -// // Log.d(DEBUG_TAG, "Not syncing because of data plan restriction."); -// // treat dataPlan abort the same way as a network connection error -// syncResult.stats.numIoExceptions++; -// SeadroidApplication.getInstance().setScanUploadStatus(CameraSyncStatus.NETWORK_UNAVAILABLE); -// EventBus.getDefault().post(new CameraSyncEvent("noNetwork")); -// return; -// } -// -// Account seafileAccount = SupportAccountManager.getInstance().getSeafileAccount(account); -// DataManager dataManager = new DataManager(seafileAccount); -// -// /** -// * this should never occur, as camera upload is supposed to be disabled once the camera upload -// * account signs out. -// */ -// if (!seafileAccount.hasValidToken()) { -// Log.d(DEBUG_TAG, "This account has no auth token. Disable camera upload."); -// syncResult.stats.numAuthExceptions++; -// -// // we're logged out on this account. disable camera upload. -// ContentResolver.cancelSync(account, CameraUploadManager.AUTHORITY); -// ContentResolver.setIsSyncable(account, CameraUploadManager.AUTHORITY, 0); -// return; -// } -// -// // make copies so we're unaffected by sudden settings changes -// targetRepoId = settingsMgr.getCameraUploadRepoId(); -// targetRepoName = settingsMgr.getCameraUploadRepoName(); -// bucketList = settingsMgr.getCameraUploadBucketList(); -// -// try { -// // Log.d(DEBUG_TAG, "Validating target repository..."); -// -// // make sure the repo exists -// if (!validateRepository(dataManager)) { -// /** -// * this is a HARD error (see below). The user has to fix up the settings to make it -// * work again. -// * -// * We do /not/ disable camera upload, as this might hide the problem from the user. -// * instead we should display an error -// */ -// Log.e(DEBUG_TAG, "Sync aborted because the target repository does not exist"); -// syncResult.databaseError = true; -// showNotificationRepoError(); -// return; -// } -// -// // Log.d(DEBUG_TAG, "connecting to TransferService"); -// startTransferService(); -// -// // wait for TransferService to connect -// // Log.d(DEBUG_TAG, "waiting for transfer service"); -// int timeout = 1000; // wait up to a second -// while (!isCancelled() && timeout > 0 && txService == null) { -// // Log.d(DEBUG_TAG, "waiting for transfer service"); -// Thread.sleep(100); -// timeout -= 100; -// } -// -// if (txService == null) { -// Log.e(DEBUG_TAG, "TransferService did not come up in time, aborting sync"); -// syncResult.delayUntil = 60; -// return; -// } -// -// uploadImages(syncResult, dataManager); -// -// if (settingsMgr.isVideosUploadAllowed()) { -// uploadVideos(syncResult, dataManager); -// } -// -// if (isCancelled()) { -// // Log.i(DEBUG_TAG, "sync was cancelled."); -// } else { -// // Log.i(DEBUG_TAG, "sync finished successfully."); -// } -// // Log.d(DEBUG_TAG, "syncResult: " + syncResult); -// -// } catch (SeafException e) { -// switch (e.getCode()) { -// /* -// * here we have basically two scenarios -// * SOFT) the error can be resolved without user interaction -// * --> mark syncResult as SOFT error (eg. stats.numIoExceptions) and return. -// * --> SyncManager will retry later -// * HARD) the error can ONLY be resolved by the user performing actions on the device -// * --> mark syncResult as HARD error and give user a popup information. -// */ -// case HttpURLConnection.HTTP_UNAUTHORIZED: -// // hard error -> we cannot recover without user interaction -// syncResult.stats.numAuthExceptions++; -// // Log.i(DEBUG_TAG, "sync aborted because of authentication error.", e); -// showNotificationAuthError(); -// break; -// default: -// syncResult.stats.numIoExceptions++; -// // Log.i(DEBUG_TAG, "sync aborted because of IO or server-side error.", e); -// break; -// } -// } catch (Exception e) { -// Log.e(DEBUG_TAG, "sync aborted because an unknown error", e); -// syncResult.stats.numParseExceptions++; -// } finally { -// if (txService != null) { -// -// // Log.d(DEBUG_TAG, "Cancelling remaining pending tasks (if any)"); -// txService.cancelUploadTasksByIds(tasksInProgress); -// -// // Log.d(DEBUG_TAG, "disconnecting from TransferService"); -// getContext().unbindService(mConnection); -// txService = null; -// } -// } -// SeadroidApplication.getInstance().setScanUploadStatus(CameraSyncStatus.SCAN_END); -// SettingsManager.getInstance().saveUploadCompletedTime(Utils.getSyncCompletedTime()); -// EventBus.getDefault().post(new CameraSyncEvent("end")); -// } -// -// private void uploadImages(SyncResult syncResult, DataManager dataManager) throws SeafException, InterruptedException { -// SLogs.d("========Starting to upload images..."); -// // Log.d(DEBUG_TAG, "Starting to upload images..."); -// -// if (isCancelled()) -// return; -// -// List selectedBuckets = new ArrayList<>(); -// if (bucketList.size() > 0) { -// selectedBuckets = bucketList; -// } else { -// List allBuckets = GalleryBucketUtils.getMediaBuckets(SeadroidApplication.getAppContext()); -// for (GalleryBucketUtils.Bucket bucket : allBuckets) { -// selectedBuckets.add(bucket.id); -// } -// } -// -// String[] selectionArgs = selectedBuckets.toArray(new String[]{}); -// String selection = MediaStore.Images.ImageColumns.BUCKET_ID + " IN " + varArgs(selectedBuckets.size()); -// -// // Log.d(DEBUG_TAG, "ContentResolver selection='"+selection+"' selectionArgs='"+Arrays.deepToString(selectionArgs)+"'"); -// -// // fetch all new images from the ContentProvider since our last sync -// Cursor cursor = contentResolver.query( -// MediaStore.Images.Media.EXTERNAL_CONTENT_URI, -// new String[]{ -// MediaStore.Images.Media._ID, -// MediaStore.Images.Media.DATA, -// MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME -// }, -// selection, -// selectionArgs, -// MediaStore.Images.ImageColumns.DATE_ADDED + " ASC" -// ); -// -// try { -// if (cursor == null) { -// SLogs.e("ContentResolver query failed!"); -// return; -// } -// // Log.d(DEBUG_TAG, "i see " + cursor.getCount() + " new images."); -// SLogs.d("===i see " + cursor.getCount() + " images."); -// if (cursor.getCount() > 0) { -// // create directories for media buckets -// createDirectories(dataManager); -// iterateCursor(syncResult, dataManager, cursor, "images"); -// -// if (isCancelled()) -// return; -// -// } -// } finally { -// if (cursor != null) -// cursor.close(); -// } -// } -// -// private void uploadVideos(SyncResult syncResult, DataManager dataManager) throws SeafException, InterruptedException { -// SLogs.d("Starting to upload videos..."); -// // Log.d(DEBUG_TAG, "Starting to upload videos..."); -// -// if (isCancelled()) -// return; -// -// List selectedBuckets = new ArrayList<>(); -// if (bucketList.size() > 0) { -// selectedBuckets = bucketList; -// } else { -// List allBuckets = GalleryBucketUtils.getMediaBuckets(SeadroidApplication.getAppContext()); -// for (GalleryBucketUtils.Bucket bucket : allBuckets) { -// selectedBuckets.add(bucket.id); -// } -// } -// -// String[] selectionArgs = selectedBuckets.toArray(new String[]{}); -// String selection = MediaStore.Video.VideoColumns.BUCKET_ID + " IN " + varArgs(selectedBuckets.size()); -// -// // Log.d(DEBUG_TAG, "ContentResolver selection='"+selection+"' selectionArgs='"+Arrays.deepToString(selectionArgs)+"'"); -// // fetch all new videos from the ContentProvider since our last sync -// Cursor cursor = contentResolver.query( -// MediaStore.Video.Media.EXTERNAL_CONTENT_URI, -// new String[]{ -// MediaStore.Video.Media._ID, -// MediaStore.Video.Media.DATA, -// MediaStore.Video.VideoColumns.BUCKET_DISPLAY_NAME -// }, -// selection, -// selectionArgs, -// MediaStore.Video.VideoColumns.DATE_ADDED + " ASC" -// ); -// -// try { -// if (cursor == null) { -// SLogs.e("ContentResolver query failed!"); -// return; -// } -// // Log.d(DEBUG_TAG, "i see " + cursor.getCount() + " new videos."); -// SLogs.d("=====i see " + cursor.getCount() + " videos."); -// if (cursor.getCount() > 0) { -// // create directories for media buckets -// createDirectories(dataManager); -// iterateCursor(syncResult, dataManager, cursor, "video"); -// -// if (isCancelled()) -// return; -// -// } -// } finally { -// if (cursor != null) -// cursor.close(); -// } -// -// } -// -// private String varArgs(int count) { -// String[] chars = new String[count]; -// Arrays.fill(chars, "?"); -// return "( " + Joiner.on(", ").join(chars) + " )"; -// } -// -// -// /** -// * Create all the subdirectories on the server for the buckets that are about to be uploaded. -// * -// * @param dataManager -// * @throws SeafException -// */ -// private void createDirectories(DataManager dataManager) throws SeafException { -// List buckets = GalleryBucketUtils.getMediaBuckets(getContext()); -// -// // create base directory -// forceCreateDirectory(dataManager, "/", BASE_DIR); -// for (GalleryBucketUtils.Bucket bucket : buckets) { -// -// // the user has selected specific buckets: only create directories for these -// if (!bucketList.isEmpty() && !bucketList.contains(bucket.id)) { -// continue; -// } -// -//// // auto-guessing is on: create directories for camera buckets -//// if (bucketList.isEmpty() && !bucket.isCameraBucket) -//// continue; -// -// forceCreateDirectory(dataManager, BASE_DIR, bucket.name); -// // update our cache for that server. we will use it later -// dataManager.getDirentsFromServer(targetRepoId, Utils.pathJoin(BASE_DIR, bucket.name)); -// } -// } -// -// /** -// * Create a directory, rename a file away if necessary, -// * -// * @param dataManager -// * @param parent parent dir -// * @param dir directory to create -// * @throws SeafException -// */ -// private void forceCreateDirectory(DataManager dataManager, String parent, String dir) throws SeafException { -// List dirs = dataManager.getDirentsFromServer(targetRepoId, parent); -// boolean found = false; -// for (SeafDirent dirent : dirs) { -// if (dirent.name.equals(dir) && dirent.isDir()) { -// found = true; -// } else if (dirent.name.equals(dir) && !dirent.isDir()) { -// // there is already a file. move it away. -// String newFilename = getContext().getString(R.string.camera_sync_rename_file, dirent.name); -// dataManager.rename(targetRepoId, -// Utils.pathJoin(Utils.pathJoin("/", parent), dirent.name), -// newFilename, -// false); -// } -// } -// if (!found) -// dataManager.createNewDir(targetRepoId, Utils.pathJoin("/", parent), dir); -// } -// -// /** -// * Iterate through the content provider and upload all files -// * -// * @param syncResult -// * @param dataManager -// * @param cursor -// * @throws SeafException -// */ -// private void iterateCursor(SyncResult syncResult, DataManager dataManager, Cursor cursor, String media) throws SeafException, InterruptedException { -// -// tasksInProgress.clear(); -// File file; -// // upload them one by one -// while (!isCancelled() && cursor.moveToNext()) { -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { -// if (media.equals("images")) { -// String image_id = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)); -// Uri image_uri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, image_id); -// if (image_uri == null) { -// syncResult.stats.numSkippedEntries++; -// continue; -// } -// file = new File(Utils.getRealPathFromURI(SeadroidApplication.getAppContext(), image_uri, media)); -// } else { -// String video_id = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)); -// Uri video_uri = Uri.withAppendedPath(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, video_id); -// if (video_uri == null) { -// syncResult.stats.numSkippedEntries++; -// continue; -// } -// file = new File(Utils.getRealPathFromURI(SeadroidApplication.getAppContext(), video_uri, media)); -// } -// } else { -// int dataColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); -// if (cursor.getString(dataColumn) == null) { -// syncResult.stats.numSkippedEntries++; -// continue; -// } -// file = new File(cursor.getString(dataColumn)); -// -// } -//// Utils.utilsLogInfo(true,"======iterateCursor"); -// int bucketColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME); -// String bucketName = cursor.getString(bucketColumn); -// -// // local file does not exist. some inconsistency in the Media Provider? Ignore and continue -// if (!file.exists()) { -// // Log.d(DEBUG_TAG, "Skipping media "+file+" because it doesn't exist"); -//// Utils.utilsLogInfo(true, "=====Skipping media " + file + " because it doesn't exist"); -// syncResult.stats.numSkippedEntries++; -// continue; -// } -// -// // 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"); -// 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."); -// SLogs.d("=====Skipping media " + file + " because we have uploaded it in the past."); -// continue; -// } -// -// uploadFile(dataManager, file, bucketName); -// } -// SLogs.d("=======waitForUploads==="); -// waitForUploads(); -// checkUploadResult(syncResult); -// } -// -// private void waitForUploads() throws InterruptedException { -// // Log.d(DEBUG_TAG, "wait for transfer service to finish our tasks"); -// WAITLOOP: -// while (!isCancelled()) { -// Thread.sleep(100); // wait -// -// for (int id : tasksInProgress) { -// UploadTaskInfo info = txService.getUploadTaskInfo(id); -// if (info.state == TaskState.INIT || info.state == TaskState.TRANSFERRING) { -// // there is still at least one task pending -// continue WAITLOOP; -// } -// } -// break; -// } -// } -// -// /** -// * Upload is finished. Go through task infos and mark files as uploaded in our DB -// * -// * @param syncResult -// * @throws SeafException -// */ -// private void checkUploadResult(SyncResult syncResult) throws SeafException { -// for (int id : tasksInProgress) { -// UploadTaskInfo info = txService.getUploadTaskInfo(id); -// if (info.err != null) { -// throw info.err; -// } -// if (info.state == TaskState.FINISHED) { -// File file = new File(info.localFilePath); -// dbHelper.markAsUploaded(file); -// syncResult.stats.numInserts++; -// } else { -// throw SeafException.unknownException; -// } -// } -// } -// -// /** -// * Upload a media file to the seafile server. -// * -// * @param dataManager Handle to the seafile server -// * @param file the file to be uploaded -// * @param bucketName the name of the media bucket -// * @throws SeafException -// */ -// private void uploadFile(DataManager dataManager, File file, String bucketName) throws SeafException { -// -// String serverPath = Utils.pathJoin(BASE_DIR, bucketName); -// SLogs.d("=======uploadFile==="); -// List list = dataManager.getCachedDirents(targetRepoId, serverPath); -// if (list == null) { -// 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; -// } -// -// /* -// * We don't want to upload a file twice unless the local and remote files differ. -// * -// * It would be cool if the API2 offered a way to query the hash of a remote file. -// * Currently, comparing the file size is the best we can do. -// */ -// String filename = file.getName(); -// String prefix = filename.substring(0, filename.lastIndexOf(".")); -// String suffix = filename.substring(filename.lastIndexOf(".")); -// 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()) { -// SLogs.d("====File " + file.getName() + " in bucket " + bucketName + " already exists on the server. Skipping."); -// dbHelper.markAsUploaded(file); -// return; -// } -// } -// -// SLogs.d("====uploading file " + file.getName() + " to " + serverPath); -// int taskID = txService.addCameraUploadTask(dataManager.getAccount(), targetRepoId, targetRepoName, -// serverPath, file.getAbsolutePath(), false, false); -// tasksInProgress.add(taskID); -// } -// -// /** -// * Show a notification message that an auth error has occured. -// * This is something the user has to fix. -// */ -// private void showNotificationAuthError() { -// NotificationCompat.Builder mBuilder; -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { -// mBuilder = new NotificationCompat.Builder(getContext(), CustomNotificationBuilder.CHANNEL_ID_ERROR); -// } else { -// mBuilder = new NotificationCompat.Builder(getContext()); -// } -// -// mBuilder.setSmallIcon(R.drawable.icon) -// .setOnlyAlertOnce(true) -// .setContentTitle(getContext().getString(R.string.camera_sync_notification_title_failed)) -// .setContentText(getContext().getString(R.string.camera_sync_notification_auth_error_failed)); -// -// // Creates an explicit intent for an Activity in your app -// Intent resultIntent = new Intent(getContext(), AccountsActivity.class); -// -// resultIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); -// -// PendingIntent dPendingIntent = PendingIntent.getActivity(getContext(), -// (int) System.currentTimeMillis(), -// resultIntent, -// 0); -// -// mBuilder.setContentIntent(dPendingIntent); -// NotificationManager mNotificationManager = -// (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE); -// -// mNotificationManager.notify(0, mBuilder.build()); -// } -// -// /** -// * Show a notification message that the server repo is missing. -// * This is something the user has to fix. -// */ -// private void showNotificationRepoError() { -// NotificationCompat.Builder mBuilder = -// new NotificationCompat.Builder(getContext()) -// .setSmallIcon(R.drawable.icon) -// .setContentTitle(getContext().getString(R.string.camera_sync_notification_title_failed)) -// .setContentText(getContext().getString(R.string.camera_sync_notification_repo_missing_failed)); -// -// // Creates an explicit intent for an Activity in your app -// Intent resultIntent = new Intent(getContext(), SettingsActivity.class); -// -// resultIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); -// -// PendingIntent dPendingIntent = PendingIntent.getActivity(getContext(), -// (int) System.currentTimeMillis(), resultIntent, FLAG_IMMUTABLE); -// -// mBuilder.setContentIntent(dPendingIntent); -// NotificationManager mNotificationManager = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE); -// -// mNotificationManager.notify(0, mBuilder.build()); -// } - -} 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 index 9b0c906e4..82bd9a31e 100644 --- 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 @@ -135,28 +135,20 @@ private void saveSettings() { SystemSwitchUtils.getInstance(this).syncSwitchUtils(); for (Fragment fragment : fragmentList) { - if (fragment instanceof HowToUploadFragment) { + if (fragment instanceof HowToUploadFragment howToUploadFragment) { - HowToUploadFragment howToUploadFragment = (HowToUploadFragment) fragment; AlbumBackupSharePreferenceHelper.writeAllowDataPlanSwitch(howToUploadFragment.getHowToUpload()); - } else if (fragment instanceof WhatToUploadFragment) { - - WhatToUploadFragment whatToUploadFragment = (WhatToUploadFragment) fragment; + } else if (fragment instanceof WhatToUploadFragment whatToUploadFragment) { AlbumBackupSharePreferenceHelper.writeAllowVideoSwitch(whatToUploadFragment.getWhatToUpload()); - - } else if (fragment instanceof BucketsFragment) { - - BucketsFragment bucketsFragment = (BucketsFragment) fragment; + } else if (fragment instanceof BucketsFragment bucketsFragment) { List selectedBuckets = bucketsFragment.getSelectedBuckets(); if (bucketsFragment.isAutoScanSelected()) { selectedBuckets.clear(); } AlbumBackupSharePreferenceHelper.writeBucketIds(selectedBuckets); - } else if (fragment instanceof ObjSelectorFragment) { - - ObjSelectorFragment cloudLibrarySelectorFragment = (ObjSelectorFragment) fragment; + } else if (fragment instanceof ObjSelectorFragment cloudLibrarySelectorFragment) { Pair pair = cloudLibrarySelectorFragment.getBackupInfo(); mAccount = pair.first; repoModel = pair.second; 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 index 50a74e1f9..e5c71c37c 100644 --- 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 @@ -14,6 +14,7 @@ import androidx.preference.PreferenceManager; import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.EncryptUtils; import com.blankj.utilcode.util.FileUtils; import com.blankj.utilcode.util.GsonUtils; import com.seafile.seadroid2.SeadroidApplication; @@ -48,6 +49,7 @@ import com.seafile.seadroid2.framework.util.Utils; import com.seafile.seadroid2.framework.worker.ExistingFileStrategy; import com.seafile.seadroid2.framework.monitor.MonitorDBHelper; +import com.seafile.seadroid2.preferences.Settings; import com.seafile.seadroid2.ssl.CertsDBHelper; import com.seafile.seadroid2.ui.account.AccountService; import com.seafile.seadroid2.ui.camera_upload.CameraUploadDBHelper; @@ -271,7 +273,6 @@ private void queryAlbumSPConfig() { SharedPreferences sharedPref = getSharedPreferences(DataStoreKeys.LATEST_ACCOUNT, Context.MODE_PRIVATE); - String repoId = sharedPref.getString(SettingsManager.SHARED_PREF_CAMERA_UPLOAD_REPO_ID, null); String repoName = sharedPref.getString(SettingsManager.SHARED_PREF_CAMERA_UPLOAD_REPO_NAME, null); Account account = CameraUploadManager.getInstance().getCameraAccount(); @@ -1008,9 +1009,13 @@ private void queryCertsDB() { SLogs.d("--------------------" + table); for (CertEntity entity : list) { SLogs.d(entity.toString()); + + String keyPrefix = EncryptUtils.encryptMD5ToString(entity.url); + Settings.getCommonPreferences().edit().putString(DataStoreKeys.KEY_SERVER_CERT_INFO + "_" + keyPrefix, entity.cert).apply(); } - AppDatabase.getInstance().certDAO().insertAll(list); + //Not stored in the database +// AppDatabase.getInstance().certDAO().insertAll(list); SLogs.d("--------------------" + table + " -> 完成"); } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog/SslConfirmDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog/SslConfirmDialog.java index 0b029fdb9..fc1e7ea64 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog/SslConfirmDialog.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog/SslConfirmDialog.java @@ -15,9 +15,16 @@ import android.widget.LinearLayout; import android.widget.TextView; +import com.google.android.gms.common.internal.Objects; import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.common.base.Preconditions; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.Collections2; +import com.google.common.collect.ComparisonChain; +import com.google.common.collect.Ordering; import com.seafile.seadroid2.R; import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.databinding.DialogSslConfirmBinding; import com.seafile.seadroid2.framework.data.CertificateInfo; import com.seafile.seadroid2.ssl.SSLTrustManager; import com.seafile.seadroid2.ssl.SSLTrustManager.SslFailureReason; @@ -41,15 +48,6 @@ public interface Listener { private Account account; private Listener listener; private X509Certificate certificate; - private TextView messageText; - private TextView commonNameText; - // private TextView altSubjNamesText; - private TextView sha256Text; - private TextView sha1Text; - private TextView md5Text; - private TextView serialNumberText; - private TextView notBeforeText; - private TextView notAfterText; public SslConfirmDialog() { } @@ -69,20 +67,10 @@ public SslConfirmDialog(Account account, X509Certificate certificate, Listener l @Override public Dialog onCreateDialog(Bundle savedInstanceState) { + DialogSslConfirmBinding binding = DialogSslConfirmBinding.inflate(getLayoutInflater()); + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity()); builder.setTitle(getString(R.string.ssl_confirm_title)); - LayoutInflater inflater = getActivity().getLayoutInflater(); - LinearLayout view = (LinearLayout) inflater.inflate(R.layout.dialog_ssl_confirm, null); - - messageText = (TextView) view.findViewById(R.id.message); - commonNameText = (TextView) view.findViewById(R.id.common_name); - // altSubjNamesText = (TextView) view.findViewById(R.id.alt_subj_name); - sha256Text = (TextView) view.findViewById(R.id.sha256); - sha1Text = (TextView) view.findViewById(R.id.sha1); - md5Text = (TextView) view.findViewById(R.id.md5); - serialNumberText = (TextView) view.findViewById(R.id.serial_number); - notBeforeText = (TextView) view.findViewById(R.id.not_before); - notAfterText = (TextView) view.findViewById(R.id.not_after); String host = null; @@ -99,43 +87,45 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { } catch (CertificateParsingException e) { e.printStackTrace(); } + String msg = ""; if (reason == SslFailureReason.CERT_NOT_TRUSTED) { msg = getActivity().getString(R.string.ssl_confirm, host); } else { msg = getActivity().getString(R.string.ssl_confirm_cert_changed, host); } - messageText.setText(msg); + + binding.message.setText(msg); if (cert != null) { CertificateInfo certInfo = new CertificateInfo(cert); - commonNameText.setText(certInfo.getSubjectName()); + binding.commonName.setText(certInfo.getSubjectName()); // String[] subjAltNames = certInfo.getSubjectAltNames(); // altSubjNamesText.setText((subjAltNames.length > 0) ? StringUtils.join(subjAltNames, ", ") : "—"); - sha256Text.setText(getActivity().getString(R.string.sha256, certInfo.getSignature("SHA-256"))); - sha1Text.setText(getActivity().getString(R.string.sha1, certInfo.getSignature("SHA-1"))); - md5Text.setText(getActivity().getString(R.string.md5, certInfo.getSignature("MD5"))); - serialNumberText.setText(getActivity().getString(R.string.serial_number, certInfo.getSerialNumber())); - notBeforeText.setText(getActivity().getString(R.string.not_before, certInfo.getNotBefore().toLocaleString())); - notAfterText.setText(getActivity().getString(R.string.not_after, certInfo.getNotAfter().toLocaleString())); + binding.sha256.setText(getActivity().getString(R.string.sha256, certInfo.getSignature("SHA-256"))); + binding.sha1.setText(getActivity().getString(R.string.sha1, certInfo.getSignature("SHA-1"))); + binding.md5.setText(getActivity().getString(R.string.md5, certInfo.getSignature("MD5"))); + binding.serialNumber.setText(getActivity().getString(R.string.serial_number, certInfo.getSerialNumber())); + binding.notBefore.setText(getActivity().getString(R.string.not_before, certInfo.getNotBefore().toLocaleString())); + binding.notAfter.setText(getActivity().getString(R.string.not_after, certInfo.getNotAfter().toLocaleString())); } else if (certificate != null) { CertificateInfo certInfo = new CertificateInfo(certificate); - commonNameText.setText(certInfo.getSubjectName()); - sha256Text.setText(getActivity().getString(R.string.sha256, certInfo.getSignature("SHA-256"))); - sha1Text.setText(getActivity().getString(R.string.sha1, certInfo.getSignature("SHA-1"))); - md5Text.setText(getActivity().getString(R.string.md5, certInfo.getSignature("MD5"))); - serialNumberText.setText(getActivity().getString(R.string.serial_number, certInfo.getSerialNumber())); - notBeforeText.setText(getActivity().getString(R.string.not_before, certInfo.getNotBefore().toLocaleString())); - notAfterText.setText(getActivity().getString(R.string.not_after, certInfo.getNotAfter().toLocaleString())); + binding.commonName.setText(certInfo.getSubjectName()); + binding.sha256.setText(getActivity().getString(R.string.sha256, certInfo.getSignature("SHA-256"))); + binding.sha1.setText(getActivity().getString(R.string.sha1, certInfo.getSignature("SHA-1"))); + binding.md5.setText(getActivity().getString(R.string.md5, certInfo.getSignature("MD5"))); + binding.serialNumber.setText(getActivity().getString(R.string.serial_number, certInfo.getSerialNumber())); + binding.notBefore.setText(getActivity().getString(R.string.not_before, certInfo.getNotBefore().toLocaleString())); + binding.notAfter.setText(getActivity().getString(R.string.not_after, certInfo.getNotAfter().toLocaleString())); } else { String not_available = getActivity().getString(R.string.not_available); - commonNameText.setText(not_available); - sha256Text.setText(not_available); - sha1Text.setText(not_available); - md5Text.setText(not_available); - serialNumberText.setText(not_available); - notBeforeText.setText(not_available); - notAfterText.setText(not_available); + binding.commonName.setText(not_available); + binding.sha256.setText(not_available); + binding.sha1.setText(not_available); + binding.md5.setText(not_available); + binding.serialNumber.setText(not_available); + binding.notBefore.setText(not_available); + binding.notAfter.setText(not_available); } builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { @@ -153,7 +143,8 @@ public void onClick(DialogInterface dialog, int which) { listener.onRejected(); } }); - builder.setView(view); + + builder.setView(binding.getRoot()); return builder.show(); } 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 index f782a29dc..99da550c8 100644 --- 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 @@ -7,7 +7,7 @@ import com.blankj.utilcode.util.ToastUtils; import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.switchmaterial.SwitchMaterial; +import com.google.android.material.materialswitch.MaterialSwitch; import com.google.android.material.textfield.TextInputLayout; import com.seafile.seadroid2.R; import com.seafile.seadroid2.listener.OnCreateDirentShareLinkListener; @@ -86,14 +86,14 @@ protected void initView(LinearLayout containerView) { // getResources().getInteger(R.integer.minimum_password_length) // )); - SwitchMaterial passwordSwitch = getDialogView().findViewById(R.id.add_password); + MaterialSwitch passwordSwitch = getDialogView().findViewById(R.id.add_password); passwordSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { passwordTextInput.setVisibility(isChecked ? View.VISIBLE : View.GONE); }); TextInputLayout daysTextInput = getDialogView().findViewById(R.id.days_text_input); - SwitchMaterial switchMaterial = getDialogView().findViewById(R.id.add_expiration); - switchMaterial.setOnCheckedChangeListener((buttonView, isChecked) -> { + MaterialSwitch daysSwitch = getDialogView().findViewById(R.id.add_expiration); + daysSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { daysTextInput.setVisibility(isChecked ? View.VISIBLE : View.GONE); }); } @@ -133,7 +133,7 @@ private String getErrorMsg(String errMsg) { private boolean checkData() { - SwitchMaterial passwordSwitch = getDialogView().findViewById(R.id.add_password); + MaterialSwitch passwordSwitch = getDialogView().findViewById(R.id.add_password); if (passwordSwitch.isChecked()) { EditText editText = getDialogView().findViewById(R.id.password); String password = editText.getText().toString(); @@ -144,8 +144,8 @@ private boolean checkData() { } } - SwitchMaterial switchMaterial = getDialogView().findViewById(R.id.add_expiration); - if (switchMaterial.isChecked()) { + MaterialSwitch daysSwitch = getDialogView().findViewById(R.id.add_expiration); + if (daysSwitch.isChecked()) { EditText daysEditText = getDialogView().findViewById(R.id.days); String daysText = daysEditText.getText().toString(); 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 index b8ebc629a..18ac86595 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/main/MainActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/main/MainActivity.java @@ -639,15 +639,15 @@ public boolean onPrepareOptionsMenu(Menu menu) { menuBinding.createRepo.setVisible(false); if (getNavContext().hasParentWritePermission()) { menuBinding.add.setEnabled(true); - menuBinding.edit.setEnabled(true); + menuBinding.select.setEnabled(true); } else { menuBinding.add.setEnabled(false); - menuBinding.edit.setEnabled(false); + menuBinding.select.setEnabled(false); } } else { menuBinding.createRepo.setVisible(true); menuBinding.add.setVisible(false); - menuBinding.edit.setVisible(false); + menuBinding.select.setVisible(false); } menuBinding.sortGroup.setVisible(true); @@ -655,7 +655,7 @@ public boolean onPrepareOptionsMenu(Menu menu) { menuBinding.sortGroup.setVisible(false); menuBinding.createRepo.setVisible(false); menuBinding.add.setVisible(false); - menuBinding.edit.setVisible(false); + menuBinding.select.setVisible(false); } updateMenu(); @@ -688,7 +688,7 @@ public boolean onOptionsItemSelected(MenuItem item) { Intent newIntent = new Intent(this, TransferActivity.class); newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(newIntent); - } else if (item.getItemId() == R.id.edit) { + } else if (item.getItemId() == R.id.select) { if (binding.pager.getCurrentItem() == INDEX_LIBRARY_TAB) { getReposFragment().startContextualActionMode(); } @@ -734,7 +734,7 @@ public boolean onMenuItemActionExpand(@NonNull MenuItem item) { menuIdState.put("sortGroup", menuBinding.sortGroup.isVisible()); menuIdState.put("createRepo", menuBinding.createRepo.isVisible()); menuIdState.put("add", menuBinding.add.isVisible()); - menuIdState.put("edit", menuBinding.edit.isVisible()); + menuIdState.put("select", menuBinding.select.isVisible()); menuIdState.put("transferList", menuBinding.transferList.isVisible()); Optional optional = checkServerInfo(); @@ -745,7 +745,7 @@ public boolean onMenuItemActionExpand(@NonNull MenuItem item) { menuBinding.sortGroup.setVisible(false); menuBinding.createRepo.setVisible(false); menuBinding.add.setVisible(false); - menuBinding.edit.setVisible(false); + menuBinding.select.setVisible(false); menuBinding.transferList.setVisible(false); return true; @@ -758,7 +758,7 @@ public boolean onMenuItemActionCollapse(@NonNull MenuItem item) { menuBinding.sortGroup.setVisible(Boolean.TRUE.equals(menuIdState.get("sortGroup"))); menuBinding.createRepo.setVisible(Boolean.TRUE.equals(menuIdState.get("createRepo"))); menuBinding.add.setVisible(Boolean.TRUE.equals(menuIdState.get("add"))); - menuBinding.edit.setVisible(Boolean.TRUE.equals(menuIdState.get("edit"))); + menuBinding.select.setVisible(Boolean.TRUE.equals(menuIdState.get("select"))); menuBinding.transferList.setVisible(Boolean.TRUE.equals(menuIdState.get("transferList"))); invalidateMenu(); @@ -817,7 +817,7 @@ private static class MenuBinding { public MenuItem createRepo; public MenuItem add; - public MenuItem edit; + public MenuItem select; public MenuItem transferList; @@ -844,7 +844,7 @@ public static MenuBinding inflate(Menu menu, MenuInflater inflater) { binding1.createRepo = menu.findItem(R.id.create_repo); binding1.add = menu.findItem(R.id.add); - binding1.edit = menu.findItem(R.id.edit); + binding1.select = menu.findItem(R.id.select); binding1.transferList = menu.findItem(R.id.transfer_tasks); return binding1; } 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 index 954bc025c..a03840998 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickFragment.java @@ -61,9 +61,9 @@ import com.seafile.seadroid2.framework.util.SLogs; import com.seafile.seadroid2.framework.util.Utils; import com.seafile.seadroid2.framework.worker.BackgroundJobManagerImpl; -import com.seafile.seadroid2.framework.worker.download.DownloadWorker; import com.seafile.seadroid2.framework.worker.TransferEvent; import com.seafile.seadroid2.framework.worker.TransferWorker; +import com.seafile.seadroid2.framework.worker.download.DownloadWorker; import com.seafile.seadroid2.framework.worker.upload.UploadFileManuallyWorker; import com.seafile.seadroid2.framework.worker.upload.UploadFolderFileAutomaticallyWorker; import com.seafile.seadroid2.framework.worker.upload.UploadMediaFileAutomaticallyWorker; @@ -82,8 +82,8 @@ import com.seafile.seadroid2.ui.markdown.MarkdownActivity; import com.seafile.seadroid2.ui.media.image_preview.ImagePreviewActivity; import com.seafile.seadroid2.ui.media.player.exoplayer.CustomExoVideoPlayerActivity; +import com.seafile.seadroid2.ui.sdoc.SDocWebViewActivity; import com.seafile.seadroid2.ui.selector.ObjSelectorActivity; -import com.seafile.seadroid2.ui.webview.SeaWebViewActivity; import com.seafile.seadroid2.view.TipsViews; import java.io.File; @@ -995,7 +995,7 @@ private void open(DirentModel dirent) { } if (fileName.endsWith(Constants.Format.DOT_SDOC)) { - SeaWebViewActivity.openSdoc(getContext(), repoModel.repo_name, repoModel.repo_id, dirent.parent_dir + dirent.name); + SDocWebViewActivity.openSdoc(getContext(), repoModel.repo_name, repoModel.repo_id, dirent.parent_dir + dirent.name); return; } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocService.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocService.java new file mode 100644 index 000000000..8010a80a4 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocService.java @@ -0,0 +1,34 @@ +package com.seafile.seadroid2.ui.sdoc; + +import com.seafile.seadroid2.framework.data.model.sdoc.MetadataConfigModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocCommentWrapperModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocDetailModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocRecordWrapperModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocWrapperModel; +import com.seafile.seadroid2.framework.data.model.user.UserWrapperModel; + +import io.reactivex.Single; +import retrofit2.http.GET; +import retrofit2.http.Path; +import retrofit2.http.Query; + +public interface SDocService { + @GET("api2/repos/{repo_id}/file/detail/") + Single getFileDetail(@Path("repo_id") String repoId, @Query("p") String path); + + @GET("api/v2.1/repos/{repo_id}/related-users/") + Single getRelatedUsers(@Path("repo_id") String repoId); + + @GET("api/v2.1/repos/{repo_id}/metadata/") + Single getMetadata(@Path("repo_id") String repoId); + + @GET("api/v2.1/repos/{repo_id}/metadata/record/") + Single getRecords(@Path("repo_id") String repoId, @Query("parent_dir") String parentDir, @Query("name") String name); + + // + @GET("api/v1/docs/{uuid}/comment/") + Single getComments(@Path("uuid") String uuid); + + @GET("api/v1/docs/{uuid}/") + Single getElements(@Path("uuid") String uuid); +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocViewModel.java new file mode 100644 index 000000000..d0e157108 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocViewModel.java @@ -0,0 +1,223 @@ +package com.seafile.seadroid2.ui.sdoc; + +import android.text.TextUtils; + +import androidx.lifecycle.MutableLiveData; + +import com.blankj.utilcode.util.CloneUtils; +import com.blankj.utilcode.util.CollectionUtils; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.framework.data.model.sdoc.MetadataConfigModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocCommentModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocCommentWrapperModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocDetailModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocPageOptionsModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocProfileConfigModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocRecordWrapperModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocWrapperModel; +import com.seafile.seadroid2.framework.data.model.user.UserWrapperModel; +import com.seafile.seadroid2.framework.http.HttpIO; +import com.seafile.seadroid2.framework.util.SLogs; +import com.seafile.seadroid2.framework.util.StringUtils; +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; + +import java.util.Comparator; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import io.reactivex.Single; +import io.reactivex.functions.Consumer; +import io.reactivex.functions.Function3; + +public class SDocViewModel extends BaseViewModel { + + private final MutableLiveData _fileProfileConfigLiveData = new MutableLiveData<>(); + private final MutableLiveData _sdocRecordLiveData = new MutableLiveData<>(); + private final MutableLiveData _sdocCommentLiveData = new MutableLiveData<>(); + private final MutableLiveData> _sdocElementListLiveData = new MutableLiveData<>(); + + public MutableLiveData getFileDetailLiveData() { + return _fileProfileConfigLiveData; + } + + public MutableLiveData getSdocRecordLiveData() { + return _sdocRecordLiveData; + } + + public MutableLiveData getSdocCommentLiveData() { + return _sdocCommentLiveData; + } + + public MutableLiveData> getSdocElementLiveData() { + return _sdocElementListLiveData; + } + + public void initSDocConfig(String repoId, String path) { + Single userSingle = HttpIO.getCurrentInstance().execute(SDocService.class).getRelatedUsers(repoId); + Single metadataSingle = HttpIO.getCurrentInstance().execute(SDocService.class).getMetadata(repoId); + Single detailSingle = HttpIO.getCurrentInstance().execute(SDocService.class).getFileDetail(repoId, path); + + Single s = Single.zip(detailSingle, userSingle, metadataSingle, new Function3() { + @Override + public SDocProfileConfigModel apply(SDocDetailModel docDetailModel, UserWrapperModel userWrapperModel, MetadataConfigModel metadataConfigModel) throws Exception { + SDocProfileConfigModel configModel = new SDocProfileConfigModel(); + configModel.setDetail(docDetailModel); + configModel.setUsers(userWrapperModel); + configModel.setMetadata(metadataConfigModel); + return configModel; + } + }); + + addSingleDisposable(s, new Consumer() { + @Override + public void accept(SDocProfileConfigModel sDocProfileConfigModel) throws Exception { + getFileDetailLiveData().setValue(sDocProfileConfigModel); + } + }); + } + + public void getRecords(String repoId, String path) { + if (TextUtils.isEmpty(path) || TextUtils.equals("/", path)) { + return; + } + + String parent_dir; + String name; + + // 1. /a/b/c/t.txt + // 2. /a/t.txt + // 3. /t.txt + // 4. t.txt + // 5. / + if (path.contains("/")) { + parent_dir = path.substring(0, path.lastIndexOf("/")); + name = path.substring(path.lastIndexOf("/") + 1); + } else { + parent_dir = null; + name = path; + } + + Single single = HttpIO.getCurrentInstance().execute(SDocService.class).getRecords(repoId, parent_dir, name); + addSingleDisposable(single, new Consumer() { + @Override + public void accept(SDocRecordWrapperModel sDocRecordWrapperModel) throws Exception { + getSdocRecordLiveData().setValue(sDocRecordWrapperModel); + } + }); + } + + public static final List _AllowedElementTypes = List.of("header1", "header2", "header3"); + + public void getSDocElements(SDocPageOptionsModel pageOptionsModel) { + if (TextUtils.isEmpty(pageOptionsModel.seadocServerUrl)) { + return; + } + + String sdocServerUrl = pageOptionsModel.seadocServerUrl; + if (!sdocServerUrl.endsWith("/")) { + sdocServerUrl = sdocServerUrl + "/"; + } + + Account curAccount = SupportAccountManager.getInstance().getCurrentAccount(); + Account partialAccount = CloneUtils.deepClone(curAccount, Account.class); + partialAccount.setToken(pageOptionsModel.seadocAccessToken); + partialAccount.setServer(sdocServerUrl); + + Single single = HttpIO.getInstanceByAccount(partialAccount).execute(SDocService.class).getElements(pageOptionsModel.docUuid); + addSingleDisposable(single, new Consumer() { + @Override + public void accept(SDocWrapperModel wrapperModel) throws Exception { + + if (wrapperModel == null || wrapperModel.elements == null) { + getSdocElementLiveData().setValue(null); + return; + } + + List newList = wrapperModel.elements.stream().filter(new Predicate() { + @Override + public boolean test(SDocModel sDocModel) { + if (!_AllowedElementTypes.contains(sDocModel.type)) { + return false; + } + + if (TextUtils.isEmpty(sDocModel.text) && CollectionUtils.isEmpty(sDocModel.children)) { + return false; + } + + return true; + } + }).map(new Function() { + @Override + public SDocModel apply(SDocModel sDocModel) { + if (!TextUtils.isEmpty(sDocModel.text)) { + return sDocModel; + } + + if (CollectionUtils.isEmpty(sDocModel.children)) { + return sDocModel; + } + + String text = ""; + for (SDocModel child : sDocModel.children) { + if (!TextUtils.isEmpty(child.text)) { + String nt = StringUtils.trim(child.text, "\n").trim(); + text = text.concat(nt); + } + } + sDocModel.text = text; + return sDocModel; + } + }).collect(Collectors.toList()); + + getSdocElementLiveData().setValue(newList); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + SLogs.e(throwable); + } + }); + + } + + public void getSDocComments(SDocPageOptionsModel pageOptionsModel) { + if (TextUtils.isEmpty(pageOptionsModel.seadocServerUrl)) { + return; + } + + String sdocServerUrl = pageOptionsModel.seadocServerUrl; + if (!sdocServerUrl.endsWith("/")) { + sdocServerUrl = sdocServerUrl + "/"; + } + + Account curAccount = SupportAccountManager.getInstance().getCurrentAccount(); + Account partialAccount = CloneUtils.deepClone(curAccount, Account.class); + partialAccount.setToken(pageOptionsModel.seadocAccessToken); + partialAccount.setServer(sdocServerUrl); + + Single commentSingle = HttpIO.getInstanceByAccount(partialAccount).execute(SDocService.class).getComments(pageOptionsModel.docUuid); + addSingleDisposable(commentSingle, new Consumer() { + @Override + public void accept(SDocCommentWrapperModel sDocCommentWrapperModel) throws Exception { + sDocCommentWrapperModel.comments = sDocCommentWrapperModel.comments.stream().sorted(new Comparator() { + @Override + public int compare(SDocCommentModel o1, SDocCommentModel o2) { + return -o1.created_at.compareTo(o2.created_at); + } + }).collect(Collectors.toList()); + + getSdocCommentLiveData().setValue(sDocCommentWrapperModel); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + SLogs.e(throwable); + } + }); + + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocWebViewActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocWebViewActivity.java new file mode 100644 index 000000000..2a1d4f43b --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocWebViewActivity.java @@ -0,0 +1,339 @@ +package com.seafile.seadroid2.ui.sdoc; + +import android.content.Context; +import android.content.Intent; + +import android.content.res.Configuration; +import android.os.Bundle; + +import android.text.TextUtils; +import android.view.View; +import android.webkit.ValueCallback; +import android.webkit.WebChromeClient; +import android.webkit.WebView; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.appcompat.widget.Toolbar; +import androidx.core.util.Consumer; +import androidx.core.widget.NestedScrollView; +import androidx.lifecycle.Observer; +import androidx.webkit.WebSettingsCompat; +import androidx.webkit.WebViewFeature; + +import com.blankj.utilcode.util.ActivityUtils; +import com.blankj.utilcode.util.GsonUtils; +import com.blankj.utilcode.util.ToastUtils; +import com.google.android.gms.tasks.Continuation; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.annotation.Unstable; +import com.seafile.seadroid2.databinding.ActivitySeaWebviewProBinding; +import com.seafile.seadroid2.databinding.ToolbarActionbarProgressBarBinding; +import com.seafile.seadroid2.enums.WebViewPreviewType; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocPageOptionsModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocProfileConfigModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocRecordWrapperModel; +import com.seafile.seadroid2.framework.util.SLogs; +import com.seafile.seadroid2.framework.util.StringUtils; +import com.seafile.seadroid2.ui.base.BaseActivityWithVM; +import com.seafile.seadroid2.ui.sdoc.comments.SDocCommentsActivity; +import com.seafile.seadroid2.ui.sdoc.directory.SDocDirectoryDialog; +import com.seafile.seadroid2.ui.sdoc.profile.SDocProfileDialog; +import com.seafile.seadroid2.ui.webview.SeaWebViewActivity; +import com.seafile.seadroid2.view.webview.PreloadWebView; +import com.seafile.seadroid2.view.webview.SeaWebView; + +import org.json.JSONException; +import org.json.JSONObject; + +import kotlin.Pair; + +@Unstable +public class SDocWebViewActivity extends BaseActivityWithVM { + private ActivitySeaWebviewProBinding binding; + private ToolbarActionbarProgressBarBinding toolBinding; + + private SeaWebView mWebView; + private String repoId; + private String path; + private String targetUrl; + + private SDocProfileConfigModel configModel; + + public static void openSdoc(Context context, String repoName, String repoID, String path) { + Intent intent = new Intent(context, SeaWebViewActivity.class); + intent.putExtra("previewType", WebViewPreviewType.SDOC.name()); + intent.putExtra("repoName", repoName); + intent.putExtra("repoID", repoID); + intent.putExtra("filePath", path); + ActivityUtils.startActivity(intent); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivitySeaWebviewProBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + toolBinding = ToolbarActionbarProgressBarBinding.bind(binding.toolProgressBar.getRoot()); + + init(); + + initUI(); + + initViewModel(); + + //let's go + mWebView.load(targetUrl); + + getViewModel().initSDocConfig(repoId, path); + } + + private void init() { + Intent intent = getIntent(); + + if (!intent.hasExtra("previewType")) { + throw new IllegalArgumentException("need a previewType param"); + } + + String previewType = intent.getStringExtra("previewType"); + if (!WebViewPreviewType.contains(previewType)) { + throw new IllegalArgumentException("need a previewType param"); + } + + WebViewPreviewType previewTypeEnum = WebViewPreviewType.valueOf(previewType); + + if (previewTypeEnum == WebViewPreviewType.SDOC) { + String repoName = intent.getStringExtra("repoName"); + repoId = intent.getStringExtra("repoID"); + path = intent.getStringExtra("filePath"); + + if (TextUtils.isEmpty(repoId) || TextUtils.isEmpty(path)) { + throw new IllegalArgumentException("repoId or path is null"); + } + + Account account = SupportAccountManager.getInstance().getCurrentAccount(); + if (account != null) { + targetUrl = account.server + "lib/" + repoId + "/file" + path; + } else { + throw new IllegalArgumentException("no login"); + } + } else { + throw new IllegalArgumentException("previewType is not SDOC"); + } + + } + + private void initUI() { + Toolbar toolbar = toolBinding.toolbarActionbar; + toolbar.setTitle(""); + setSupportActionBar(toolbar); + toolbar.setNavigationOnClickListener(v -> { + finish(); + }); + + mWebView = PreloadWebView.getInstance().getWebView(this); + + if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { + WebSettingsCompat.setAlgorithmicDarkeningAllowed(mWebView.getSettings(), true); + } + + NestedScrollView.LayoutParams ll = new NestedScrollView.LayoutParams(-1, -1); + mWebView.setLayoutParams(ll); + binding.nsv.addView(mWebView); + + //chrome client + mWebView.setWebChromeClient(mWebChromeClient); + + binding.sdocDirectory.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showDirectoryDialog(); + } + }); + binding.sdocProfile.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showProfileDialog(); + } + }); + binding.sdocComment.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showCommentsActivity(); + } + }); + + getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + if (mWebView != null && mWebView.canGoBack()) { + mWebView.goBack(); + } else { + finish(); + } + } + }); + } + + private void initViewModel() { + getViewModel().getFileDetailLiveData().observe(this, new Observer() { + @Override + public void onChanged(SDocProfileConfigModel sDocProfileConfigModel) { + configModel = sDocProfileConfigModel; + hideProgressBar(); + } + }); + getViewModel().getSdocRecordLiveData().observe(this, new Observer() { + @Override + public void onChanged(SDocRecordWrapperModel sDocRecordWrapperModel) { + SDocProfileDialog dialog = SDocProfileDialog.newInstance(configModel.detail, sDocRecordWrapperModel, configModel.users.user_list); + dialog.show(getSupportFragmentManager(), SDocProfileDialog.class.getSimpleName()); + } + }); + } + + private void showDirectoryDialog() { + getSDocConfigData(new Consumer() { + @Override + public void accept(SDocPageOptionsModel model) { + if (TextUtils.isEmpty(model.seadocServerUrl) || TextUtils.isEmpty(model.docUuid)) { + return; + } + + SDocDirectoryDialog dialog = SDocDirectoryDialog.newInstance(model); + dialog.show(getSupportFragmentManager(), SDocDirectoryDialog.class.getSimpleName()); + } + }); + } + + private void showProfileDialog() { + if (configModel == null) { + return; + } + + if (configModel.metadata.enabled) { + getViewModel().getRecords(repoId, path); + } else { + SDocProfileDialog dialog = SDocProfileDialog.newInstance(configModel.detail, configModel.users.user_list); + dialog.show(getSupportFragmentManager(), SDocProfileDialog.class.getSimpleName()); + } + } + + private void showCommentsActivity() { + getSDocConfigData(new Consumer() { + @Override + public void accept(SDocPageOptionsModel model) { + SDocCommentsActivity.start(SDocWebViewActivity.this, model); + } + }); + } + + private void getSDocConfigData(Consumer continuation) { + String js = + "(function() {" + + " if (window.app && window.app.pageOptions) {" + + " return JSON.stringify(window.app.pageOptions);" + + " } else {" + + " return null;" + + " }" + + "})();"; + mWebView.evaluateJavascript(js, new ValueCallback() { + @Override + public void onReceiveValue(String value) { + SLogs.e(value); + if (!TextUtils.isEmpty(value)) { + value = StringUtils.deString(value).replace("\\", ""); + SDocPageOptionsModel configModel1 = GsonUtils.fromJson(value, SDocPageOptionsModel.class); + if (configModel1 != null) { + continuation.accept(configModel1); + SLogs.d("获取的 PageOption 对象数据: " + configModel1); + } else { + SLogs.d("获取的 PageOption 对象数据是空的"); + } + + } else { + SLogs.d("doc uuid is empty."); + ToastUtils.showShort("doc uuid is empty."); + } + } + }); + } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + } + + + private int curProgress = 0; + private final WebChromeClient mWebChromeClient = new WebChromeClient() { + @Override + public void onProgressChanged(WebView view, int newProgress) { + super.onProgressChanged(view, newProgress); + curProgress = newProgress; + setBarProgress(curProgress); + } + + @Override + public void onReceivedTitle(WebView view, String title) { + super.onReceivedTitle(view, title); + toolBinding.toolbarActionbar.setTitle(title); + } + }; + + private void setBarProgress(int p) { + toolBinding.toolProgressBar.setProgress(p, true); + + if (p != 100) { + if (toolBinding.toolProgressBar.getVisibility() != View.VISIBLE) { + toolBinding.toolProgressBar.setVisibility(View.VISIBLE); + } + } + + hideProgressBar(); + } + + private void hideProgressBar() { + if (configModel != null && curProgress == 100) { + toolBinding.toolProgressBar.setVisibility(View.GONE); + } + } + + @Override + protected void onPause() { + super.onPause(); + if (mWebView != null) { + mWebView.onPause(); + } + } + + @Override + protected void onStart() { + super.onStart(); + if (mWebView != null) { + mWebView.onResume(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + destroyWebView(); + } + + public void destroyWebView() { + if (mWebView == null) { + return; + } + + binding.nsv.removeView(mWebView); + + mWebView.loadUrl("about:blank"); + mWebView.stopLoading(); + mWebView.destroy(); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentAdapter.java new file mode 100644 index 000000000..7037b50fd --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentAdapter.java @@ -0,0 +1,198 @@ +package com.seafile.seadroid2.ui.sdoc.comments; + +import static com.seafile.seadroid2.config.Constants.DP.DP_4; +import static com.seafile.seadroid2.config.Constants.DP.DP_8; + +import android.content.Context; +import android.text.TextUtils; +import android.text.method.LinkMovementMethod; +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.blankj.utilcode.util.ScreenUtils; +import com.blankj.utilcode.util.SizeUtils; +import com.bumptech.glide.Glide; +import com.google.android.flexbox.FlexboxLayout; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.databinding.ItemSdocCommentBinding; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocCommentModel; +import com.seafile.seadroid2.ui.base.adapter.BaseAdapter; +import com.yydcdut.markdown.MarkdownConfiguration; +import com.yydcdut.markdown.MarkdownProcessor; +import com.yydcdut.markdown.MarkdownTextView; +import com.yydcdut.markdown.loader.DefaultLoader; +import com.yydcdut.markdown.syntax.text.TextFactory; + +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +public class SDocCommentAdapter extends BaseAdapter { + public static int SCREEN_WIDTH = ScreenUtils.getScreenWidth(); + public static int WIDTH = SizeUtils.dp2px(120); + public static int IMAGE_WIDTH = (SCREEN_WIDTH - WIDTH) / 3; + + private MarkdownProcessor processor; + + private MarkdownProcessor getProcessor() { + if (processor != null) { + return processor; + } + + MarkdownConfiguration rxMDConfiguration = new MarkdownConfiguration.Builder(getContext()) + .setRxMDImageLoader(new DefaultLoader(getContext()))//default image loader + .setDefaultImageSize(100, 100)//default image width & height + .build(); + processor = new MarkdownProcessor(getContext()); + processor.config(rxMDConfiguration); + processor.factory(TextFactory.create()); + return processor; + } + + @Override + protected void onBindViewHolder(@NonNull SDocCommentViewHolder holder, int i, @Nullable SDocCommentModel model) { + if (model == null) { + return; + } + + // + Glide.with(holder.binding.commentUserAvatar) + .load(model.avatar_url) + .into(holder.binding.commentUserAvatar); + + holder.binding.commentNickName.setText(model.user_name); + holder.binding.commentTime.setText(model.getCreatedAtFriendlyText()); + +// if (TextUtils.isEmpty(model.resolved) || "false".equals(model.resolved.toLowerCase(Locale.getDefault()))) { +// holder.binding.commentContentContainer.setBackgroundResource(R.drawable.shape_stroke1_radius8_solid_grey); +// } else { + holder.binding.commentContentContainer.setBackgroundResource(R.drawable.shape_stroke1_radius8_solid_white); +// } + + holder.binding.commentContentContainer.removeAllViews(); + appendTextToFlex(holder.binding.commentContentContainer, model.comment); + + } + + +// //![](https://dev.seafile.com/e-Hek4ng6iRbCw7h-bXsc6jA.png)\n是是是\n +// private void addViews(SDocCommentModel model, SDocCommentViewHolder holder) { +// int index = 0; +// for (RichEditText.RichContentModel commentModel : model.commentList) { +// //0 is text, 1 is image +// if (commentModel.type == 0) { +// appendTextToFlex(holder.binding.commentContentContainer, commentModel.content, model.isContainImage); +// } else { +// appendImageToFlex(holder.binding.commentContentContainer, model.commentList, commentModel.content, index); +// index++; +// } +// } +// } + + + @NonNull + @Override + protected SDocCommentViewHolder onCreateViewHolder(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) { + ItemSdocCommentBinding binding = ItemSdocCommentBinding.inflate(LayoutInflater.from(context), viewGroup, false); + return new SDocCommentViewHolder(binding); + } + + +// public void appendImageToFlex(FlexboxLayout parent, List commentList, String url, int position) { +// LayoutImageBinding fileBinding = LayoutImageBinding.inflate(LayoutInflater.from(getContext())); +// fileBinding.uploadImage.setScaleType(ImageView.ScaleType.CENTER_CROP); +// +// FlexboxLayout.LayoutParams f = new FlexboxLayout.LayoutParams(IMAGE_WIDTH, IMAGE_WIDTH); +// f.setMargins(DP_4, DP_4, DP_4, DP_4); +// fileBinding.getRoot().setLayoutParams(f); +// +// Glide.with(getContext()) +// .load(GlideLoadConfig.getGlideUrl(url)) +// .apply(GlideLoadConfig.getOptions(IMAGE_WIDTH, IMAGE_WIDTH)) +// .into(fileBinding.uploadImage); +// fileBinding.uploadImage.setOnClickListener(new View.OnClickListener() { +// @Override +// public void onClick(View v) { +// List ll = commentList.stream().filter(f -> f.type == 1).map(m -> m.content).collect(Collectors.toList()); +// MediaPlayerModel model = new MediaPlayerModel(); +// model.index = position; +// model.urls = ll; +// MediaPlayerActivity.startThis(getContext(), model); +// } +// }); +// +// +// parent.addView(fileBinding.getRoot()); +// } + + public void appendTextToFlex(FlexboxLayout parent, String comment) { + MarkdownTextView textView = new MarkdownTextView(getContext()); + textView.setPadding(DP_4, DP_8, DP_4, DP_8); +// textView.setText(getProcessor().parse(comment)); + textView.setText(comment); + textView.setMovementMethod(LinkMovementMethod.getInstance()); + textView.setId(android.R.id.text1); + textView.setTextSize(14); + textView.setTextColor(ContextCompat.getColor(getContext(), R.color.material_blue_grey_900)); + + FlexboxLayout.LayoutParams f = new FlexboxLayout.LayoutParams(-1, -2); + parent.addView(textView, f); + } + + public void submitData(List list) { + if (CollectionUtils.isEmpty(list)) { + submitList(list); + return; + } + + if (getItems().isEmpty()) { + submitList(list); + } else { + DiffUtil.DiffResult result = 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) { + SDocCommentModel newT = getItems().get(oldItemPosition); + SDocCommentModel oldT = list.get(newItemPosition); + return newT.id == oldT.id; + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + SDocCommentModel newT = getItems().get(oldItemPosition); + SDocCommentModel oldT = list.get(newItemPosition); + + return newT.id == oldT.id + && Objects.equals(newT.resolved, oldT.resolved) + && Objects.equals(newT.created_at, oldT.created_at) + && Objects.equals(newT.updated_at, oldT.updated_at) + && TextUtils.equals(newT.avatar_url, oldT.avatar_url) + && TextUtils.equals(newT.user_contact_email, oldT.user_contact_email) + && TextUtils.equals(newT.user_email, oldT.user_email) + && TextUtils.equals(newT.user_name, oldT.user_name) + && TextUtils.equals(newT.comment, oldT.comment) + && TextUtils.equals(newT.parent_path, oldT.parent_path) + && TextUtils.equals(newT.item_name, oldT.item_name); + } + }); + + setItems(list); + result.dispatchUpdatesTo(this); + } + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserAdapter.java new file mode 100644 index 000000000..f36926541 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserAdapter.java @@ -0,0 +1,54 @@ +package com.seafile.seadroid2.ui.sdoc.comments; + +import android.content.Context; +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 com.bumptech.glide.Glide; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.databinding.ItemUserAvatarBinding; +import com.seafile.seadroid2.framework.data.model.user.UserModel; +import com.seafile.seadroid2.ui.base.adapter.BaseAdapter; + +public class SDocCommentUserAdapter extends BaseAdapter { + @Override + protected void onBindViewHolder(@NonNull SDocCommentUserViewHolder holder, int i, @Nullable UserModel model) { + + if (i == 0) { + setMargins(holder.binding.itemUserContainer, 0, 0, 0, 0); + } + + if (model == null || TextUtils.isEmpty(model.getAvatarUrl())) { + // + Glide.with(holder.binding.imageView) + .load(R.drawable.default_avatar) + .into(holder.binding.imageView); + } else { + // + Glide.with(holder.binding.imageView) + .load(model.getAvatarUrl()) + .into(holder.binding.imageView); + } + + } + + @NonNull + @Override + protected SDocCommentUserViewHolder onCreateViewHolder(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) { + ItemUserAvatarBinding binding = ItemUserAvatarBinding.inflate(LayoutInflater.from(context), viewGroup, false); + return new SDocCommentUserViewHolder(binding); + } + + public void setMargins(View v, int l, int t, int r, int b) { + if (v.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { + ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) v.getLayoutParams(); + p.setMargins(l, t, r, b); + v.requestLayout(); + } + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserViewHolder.java new file mode 100644 index 000000000..2cd81d913 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserViewHolder.java @@ -0,0 +1,17 @@ +package com.seafile.seadroid2.ui.sdoc.comments; + +import androidx.annotation.NonNull; + +import com.seafile.seadroid2.databinding.ItemUserAvatarBinding; +import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; + +public class SDocCommentUserViewHolder extends BaseViewHolder { + + public ItemUserAvatarBinding binding; + + public SDocCommentUserViewHolder(@NonNull ItemUserAvatarBinding binding) { + super(binding.getRoot()); + + this.binding = binding; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentViewHolder.java new file mode 100644 index 000000000..e23915268 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentViewHolder.java @@ -0,0 +1,17 @@ +package com.seafile.seadroid2.ui.sdoc.comments; + +import androidx.annotation.NonNull; + +import com.seafile.seadroid2.databinding.ItemSdocCommentBinding; +import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; + +public class SDocCommentViewHolder extends BaseViewHolder { + + public ItemSdocCommentBinding binding; + + public SDocCommentViewHolder(@NonNull ItemSdocCommentBinding binding) { + super(binding.getRoot()); + + this.binding = binding; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentsActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentsActivity.java new file mode 100644 index 000000000..20e40e77d --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentsActivity.java @@ -0,0 +1,184 @@ +package com.seafile.seadroid2.ui.sdoc.comments; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.WindowManager; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.Observer; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.chad.library.adapter4.BaseQuickAdapter; +import com.chad.library.adapter4.QuickAdapterHelper; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.databinding.ActivitySdocCommentBinding; +import com.seafile.seadroid2.databinding.ToolbarActionbarBinding; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocCommentModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocCommentWrapperModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocPageOptionsModel; +import com.seafile.seadroid2.ui.base.BaseActivityWithVM; +import com.seafile.seadroid2.ui.sdoc.SDocViewModel; + +public class SDocCommentsActivity extends BaseActivityWithVM { + private ActivitySdocCommentBinding binding; + private ToolbarActionbarBinding bindingOfToolbar; + + private Toolbar toolbar; + + private SDocCommentAdapter adapter; + private SDocCommentUserAdapter userAdapter; + + private SDocPageOptionsModel pageOptionsModel; + + public static void start(Context context, SDocPageOptionsModel pageModel) { + Intent starter = new Intent(context, SDocCommentsActivity.class); + starter.putExtra("pageOption", pageModel); + context.startActivity(starter); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivitySdocCommentBinding.inflate(getLayoutInflater()); + bindingOfToolbar = ToolbarActionbarBinding.bind(binding.toolbar.getRoot()); + + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + + setContentView(binding.getRoot()); + + if (getIntent() == null || !getIntent().hasExtra("pageOption")) { + throw new IllegalArgumentException("pageOption is null"); + } + + pageOptionsModel = getIntent().getParcelableExtra("pageOption"); + + initView(); + + initViewModel(); + + initAdapter(); + + refreshData(); + } + + private void initView() { + toolbar = bindingOfToolbar.toolbarActionbar; + + toolbar.setTitle(""); + setSupportActionBar(toolbar); + toolbar.setTitle(pageOptionsModel.docName); + + toolbar.setNavigationOnClickListener(v -> { + finish(); + }); + + //refresh listener + binding.swipeRefreshLayout.setOnRefreshListener(this::refreshData); + + binding.rv.setLayoutManager(new LinearLayoutManager(this)); +// // +// LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); +// binding.rvUserList.setLayoutManager(linearLayoutManager); +// binding.photoView.setOnClickListener(new View.OnClickListener() { +// @Override +// public void onClick(View v) { +// showPickPhotoSheetDialog(true); +// } +// }); +// +// binding.submit.setOnClickListener(new View.OnClickListener() { +// @Override +// public void onClick(View v) { +// submitData(); +// } +// }); +// +// binding.richEditText.setOnRichAtListener(new OnRichAtListener() { +// @Override +// public void onCall(EditText editText) { +// showCollaboratorSelector(editText); +// } +// }); + } + + protected void initViewModel() { + getViewModel().getRefreshLiveData().observe(this, new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + binding.swipeRefreshLayout.setRefreshing(aBoolean); + } + }); + +// getViewModel().getPostCommentLiveData().observe(this, new Observer() { +// @Override +// public void onChanged(TableRowCommentModel model) { +// //remove all +// binding.richEditText.removeAllViews(); +// +// refreshData(true); +// } +// }); + +// getViewModel().getUserListLiveData().observe(this, new Observer>() { +// @Override +// public void onChanged(List relatedUserModels) { +// userAdapter.submitList(relatedUserModels); +// } +// }); + getViewModel().getSdocCommentLiveData().observe(this, new Observer() { + @Override + public void onChanged(SDocCommentWrapperModel sDocCommentWrapperModel) { + adapter.setStateViewEnable(true); + + adapter.submitData(sDocCommentWrapperModel.comments); + } + }); + } + + + private void initAdapter() { +// userAdapter = new SDocCommentUserAdapter(); +// userAdapter.setAnimationEnable(true); +// +// userAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() { +// @Override +// public void onClick(@NonNull BaseQuickAdapter baseQuickAdapter, @NonNull View view, int i) { +// if (CollectionUtils.isEmpty(userAdapter.getItems())) { +// return; +// } +// +//// showCollaboratorSelector(); +// } +// }); +// binding.rvUserList.setAdapter(userAdapter); +// +// if (CollectionUtils.isEmpty(strategyModel.participants)) { +// binding.rvUserList.setVisibility(View.GONE); +// } else { +// userAdapter.submitList(strategyModel.participants); +// } + + adapter = new SDocCommentAdapter(); + adapter.setStateViewLayout(this, R.layout.layout_empty); + adapter.setStateViewEnable(false); + adapter.setAnimationEnable(true); + + adapter.addOnItemChildClickListener(R.id.comment_more, new BaseQuickAdapter.OnItemChildClickListener() { + @Override + public void onItemClick(@NonNull BaseQuickAdapter baseQuickAdapter, @NonNull View view, int i) { + + } + }); + + QuickAdapterHelper helper = new QuickAdapterHelper.Builder(adapter).build(); + binding.rv.setAdapter(helper.getAdapter()); + } + + private void refreshData() { + getViewModel().getSDocComments(pageOptionsModel); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/directory/SDocDirectoryAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/directory/SDocDirectoryAdapter.java new file mode 100644 index 000000000..98f3505ef --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/directory/SDocDirectoryAdapter.java @@ -0,0 +1,59 @@ +package com.seafile.seadroid2.ui.sdoc.directory; + +import static com.seafile.seadroid2.config.Constants.DP.DP_8; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.seafile.seadroid2.databinding.ItemSdocDirectoryBinding; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocModel; +import com.seafile.seadroid2.ui.base.adapter.BaseAdapter; +import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; + +public class SDocDirectoryAdapter extends BaseAdapter { + + private final int _paddingStart = DP_8; + + @NonNull + @Override + protected SDocDirectoryHolder onCreateViewHolder(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) { + ItemSdocDirectoryBinding binding = ItemSdocDirectoryBinding.inflate(LayoutInflater.from(context)); + return new SDocDirectoryHolder(binding); + } + + @Override + protected void onBindViewHolder(@NonNull SDocDirectoryHolder holder, int i, @Nullable SDocModel sDocModel) { + if (sDocModel == null) { + return; + } + + int padding; + if ("header1".equals(sDocModel.type)) { + padding = _paddingStart; + } else if ("header2".equals(sDocModel.type)) { + padding = _paddingStart * 3; + } else if ("header3".equals(sDocModel.type)) { + padding = _paddingStart * 6; + } else { + return; + } + + + holder.binding.title.setPadding(padding, 0, 0, 0); + holder.binding.title.setText(sDocModel.text); + } + + + public static class SDocDirectoryHolder extends BaseViewHolder { + ItemSdocDirectoryBinding binding; + + public SDocDirectoryHolder(@NonNull ItemSdocDirectoryBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/directory/SDocDirectoryDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/directory/SDocDirectoryDialog.java new file mode 100644 index 000000000..16103d29d --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/directory/SDocDirectoryDialog.java @@ -0,0 +1,137 @@ +package com.seafile.seadroid2.ui.sdoc.directory; + +import android.annotation.SuppressLint; +import android.app.Dialog; +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 androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.blankj.utilcode.util.ToastUtils; +import com.chad.library.adapter4.BaseQuickAdapter; +import com.chad.library.adapter4.QuickAdapterHelper; +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.android.material.internal.ViewUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.databinding.DialogSdocDirectoryBinding; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocPageOptionsModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocWrapperModel; +import com.seafile.seadroid2.ui.sdoc.SDocViewModel; + +import java.util.List; + +public class SDocDirectoryDialog extends BottomSheetDialogFragment { + private SDocViewModel viewModel; + + private SDocPageOptionsModel pageOptionsModel; + + private DialogSdocDirectoryBinding binding; + private SDocDirectoryAdapter adapter; + + public static SDocDirectoryDialog newInstance(SDocPageOptionsModel pageOptionsModel) { + + Bundle args = new Bundle(); + args.putParcelable("pageOption", pageOptionsModel); + SDocDirectoryDialog fragment = new SDocDirectoryDialog(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() == null || !getArguments().containsKey("pageOption")) { + throw new IllegalArgumentException("pageOption is null"); + } + + pageOptionsModel = getArguments().getParcelable("pageOption"); + viewModel = new ViewModelProvider(this).get(SDocViewModel.class); + } + + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = DialogSdocDirectoryBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @SuppressLint("RestrictedApi") + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(requireContext()); +// bottomSheetDialog.setContentView(binding.getRoot()); +// +// View bottomSheetInternal = bottomSheetDialog.findViewById(R.id.design_bottom_sheet); +//// BottomSheetBehavior.from(bottomSheetInternal).setPeekHeight(800); +// +// View bottomSheetContent = bottomSheetInternal.findViewById(R.id.bottom_drawer_2); +// ViewUtils.doOnApplyWindowInsets(bottomSheetContent, new ViewUtils.OnApplyWindowInsetsListener() { +// @Override +// public WindowInsetsCompat onApplyWindowInsets(View view, WindowInsetsCompat insets, ViewUtils.RelativePadding initialPadding) { +// // Add the inset in the inner NestedScrollView instead to make the edge-to-edge behavior +// // consistent - i.e., the extra padding will only show at the bottom of all content, i.e., +// // only when you can no longer scroll down to show more content. +// ViewCompat.setPaddingRelative(bottomSheetContent, +// initialPadding.start, +// initialPadding.top, +// initialPadding.end, +// initialPadding.bottom + insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom); +// return insets; +// } +// }); + + return bottomSheetDialog; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + binding.rv.setLayoutManager(new LinearLayoutManager(requireContext())); + + adapter = new SDocDirectoryAdapter(); + adapter.setAnimationEnable(true); + adapter.setStateViewLayout(requireContext(),R.layout.layout_empty); + adapter.setStateViewEnable(false); + adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() { + @Override + public void onClick(@NonNull BaseQuickAdapter baseQuickAdapter, @NonNull View view, int i) { + SDocModel sDocModel = adapter.getItems().get(i); + ToastUtils.showLong(sDocModel.text); + dismiss(); + } + }); + + QuickAdapterHelper helper = new QuickAdapterHelper.Builder(adapter).build(); + binding.rv.setAdapter(helper.getAdapter()); + + initViewModel(); + + load(); + } + + private void initViewModel() { + viewModel.getSdocElementLiveData().observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(List sDocModels) { + adapter.setStateViewEnable(true); + adapter.submitList(sDocModels); + } + }); + } + + private void load() { + viewModel.getSDocElements(pageOptionsModel); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/profile/SDocProfileDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/profile/SDocProfileDialog.java new file mode 100644 index 000000000..d795dc554 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/profile/SDocProfileDialog.java @@ -0,0 +1,715 @@ +package com.seafile.seadroid2.ui.sdoc.profile; + + +import static com.seafile.seadroid2.config.Constants.DP.DP_4; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.graphics.Color; +import android.os.Build; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.SizeUtils; +import com.bumptech.glide.Glide; +import com.google.android.flexbox.FlexboxLayout; +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.android.material.card.MaterialCardView; +import com.google.android.material.imageview.ShapeableImageView; +import com.google.android.material.internal.ViewUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.config.ColumnType; +import com.seafile.seadroid2.config.GlideLoadConfig; +import com.seafile.seadroid2.databinding.DialogSdocProfileBinding; +import com.seafile.seadroid2.framework.data.model.sdoc.MetadataConfigDataModel; +import com.seafile.seadroid2.framework.data.model.sdoc.MetadataModel; +import com.seafile.seadroid2.framework.data.model.sdoc.OptionsTagModel; +import com.seafile.seadroid2.framework.data.model.sdoc.RecordResultModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocDetailModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocRecordWrapperModel; +import com.seafile.seadroid2.framework.data.model.user.UserModel; +import com.seafile.seadroid2.framework.util.SLogs; +import com.seafile.seadroid2.framework.util.Utils; + +import java.lang.reflect.Field; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + + +public class SDocProfileDialog extends BottomSheetDialogFragment { + + private SDocDetailModel docDetailModel; + private SDocRecordWrapperModel recordWrapperModel; + private ArrayList relatedUsers; + + public static SDocProfileDialog newInstance(SDocDetailModel docDetailModel, SDocRecordWrapperModel recordWrapperModel, List relatedUsers) { + Bundle args = new Bundle(); + args.putParcelable("detailModel", docDetailModel); + + if (!CollectionUtils.isEmpty(relatedUsers)) { + args.putParcelable("recordModel", recordWrapperModel); + } + + if (!CollectionUtils.isEmpty(relatedUsers)) { + args.putParcelableArrayList("relatedUsers", new ArrayList<>(relatedUsers)); + + } + SDocProfileDialog fragment = new SDocProfileDialog(); + fragment.setArguments(args); + return fragment; + } + + public static SDocProfileDialog newInstance(SDocDetailModel docDetailModel, List relatedUsers) { + return newInstance(docDetailModel, null, relatedUsers); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getArguments() == null || !getArguments().containsKey("detailModel")) { + throw new IllegalArgumentException("detailModel is null"); + } + + docDetailModel = getArguments().getParcelable("detailModel"); + recordWrapperModel = getArguments().getParcelable("recordModel"); + relatedUsers = getArguments().getParcelableArrayList("relatedUsers"); + + if (docDetailModel == null) { + throw new IllegalArgumentException("detail is null"); + } + + initFixedValueIfMetadataNotEnable(); + } + + DialogSdocProfileBinding profileBinding; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + profileBinding = DialogSdocProfileBinding.inflate(inflater, container, false); + return profileBinding.getRoot(); + } + + @SuppressLint("RestrictedApi") + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(requireContext()); +// View bottomSheetInternal = bottomSheetDialog.findViewById(R.id.design_bottom_sheet); +// BottomSheetBehavior.from(bottomSheetInternal).setPeekHeight(800); + +// View bottomSheetContent = bottomSheetInternal.findViewById(R.id.bottom_drawer_2); +// ViewUtils.doOnApplyWindowInsets(bottomSheetContent, new ViewUtils.OnApplyWindowInsetsListener() { +// @Override +// public WindowInsetsCompat onApplyWindowInsets(View view, WindowInsetsCompat insets, ViewUtils.RelativePadding initialPadding) { +// // Add the inset in the inner NestedScrollView instead to make the edge-to-edge behavior +// // consistent - i.e., the extra padding will only show at the bottom of all content, i.e., +// // only when you can no longer scroll down to show more content. +// ViewCompat.setPaddingRelative(bottomSheetContent, +// initialPadding.start, +// initialPadding.top, +// initialPadding.end, +// initialPadding.bottom + insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom); +// return insets; +// } +// }); + + return bottomSheetDialog; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + setData(profileBinding.detailsContainer); + } + + private void initFixedValueIfMetadataNotEnable() { + if (recordWrapperModel != null) { + return; + } + recordWrapperModel = new SDocRecordWrapperModel(); + + RecordResultModel sizeModel = new RecordResultModel(); + sizeModel._size = docDetailModel.getSize(); + sizeModel._file_modifier = docDetailModel.getLastModifierEmail(); + sizeModel._file_mtime = docDetailModel.getLastModified(); + recordWrapperModel.results = new ArrayList<>(); + recordWrapperModel.results.add(sizeModel); + + recordWrapperModel.metadata = new ArrayList<>(); + + MetadataModel sizeMetadataModel = new MetadataModel(); + sizeMetadataModel.key = "_size"; + sizeMetadataModel.name = "_size"; + sizeMetadataModel.type = ColumnType.NUMBER; + recordWrapperModel.metadata.add(sizeMetadataModel); + + MetadataModel modifierMetadataModel = new MetadataModel(); + modifierMetadataModel.key = "_file_modifier"; + modifierMetadataModel.name = "_file_modifier"; + modifierMetadataModel.type = ColumnType.TEXT; + recordWrapperModel.metadata.add(modifierMetadataModel); + + MetadataModel mTimeMetadataModel = new MetadataModel(); + mTimeMetadataModel.key = "_file_mtime"; + mTimeMetadataModel.name = "_file_mtime"; + mTimeMetadataModel.type = ColumnType.DATE; + recordWrapperModel.metadata.add(mTimeMetadataModel); + + } + + private void setData(LinearLayout parent) { + for (MetadataModel metadata : recordWrapperModel.metadata) { + if ("_file_modifier".equals(metadata.key)) { + metadata.type = "collaborator"; + metadata.value = CollectionUtils.newArrayList(getValueByKey(metadata.name)); + } else { + metadata.value = getValueByKey(metadata.name); + } + } + + for (MetadataModel metadata : recordWrapperModel.metadata) { + if (metadata.key.startsWith("_")) { + if (_fixedField.contains(metadata.key)) { + addMetadataView(parent, metadata); + } + } else { + addMetadataView(parent, metadata); + } + } + } + + private void addMetadataView(LinearLayout parent, MetadataModel metadata) { + parseViewByType(getContext(), parent, metadata); + } + + private final List _fixedField = List.of("_size", "_file_modifier", "_file_mtime", "_description", "_collaborators", "_reviewer", "_status"); + + private Object getValueByKey(String key) { + RecordResultModel model = recordWrapperModel.results.get(0); + try { + Field field = RecordResultModel.class.getDeclaredField(key); + field.setAccessible(true); + return field.get(model); + } catch (NoSuchFieldException | IllegalAccessException e) { + SLogs.e(e); + return null; + } + } + + private FlexboxLayout.LayoutParams getFlexParams() { + FlexboxLayout.LayoutParams flexLayoutParams = new FlexboxLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + flexLayoutParams.bottomMargin = DP_4; + flexLayoutParams.rightMargin = DP_4; + return flexLayoutParams; + } + + private int getResNameByKey(String key) { + switch (key) { + case "_description": + return R.string.description; + case "_file_modifier": + return R.string._last_modifier; + case "_file_mtime": + return R.string._last_modified_time; + case "_status": + return R.string._file_status; + case "_collaborators": + return R.string._file_collaborators; + case "_size": + return R.string._size; + case "_reviewer": + return R.string._reviewer; + case "_in_progress": + return R.string._in_progress; + case "_in_review": + return R.string._in_review; + case "_done": + return R.string._done; + case "_outdated": + return R.string._outdated; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return Resources.ID_NULL; + } + return 0; + } + + public void parseViewByType(Context context, LinearLayout parent, MetadataModel metadata) { + final String type = metadata.type; + LinearLayout view = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.layout_details_keyview_valuecontainer, null); + + int resStrId = getResNameByKey(metadata.key); + if (resStrId != 0) { + view.findViewById(R.id.text_title).setText(resStrId); + } else { + view.findViewById(R.id.text_title).setText(metadata.name); + } + + view.findViewById(R.id.text_icon).setImageResource(getIconByColumnType(metadata.type)); + + LinearLayout.LayoutParams ll = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + ll.topMargin = SizeUtils.dp2px(8); + view.setLayoutParams(ll); + parent.addView(view); + + if (!TextUtils.equals(ColumnType.RATE, type) && metadata.value == null) { + + View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_textview, null); + ltr.findViewById(R.id.text_view).setText(R.string.empty); + + view.findViewById(R.id.flex_box).addView(ltr, getFlexParams()); + return; + } + + if (TextUtils.equals(ColumnType.TEXT, type)) { + parseText(view, metadata); + } else if (TextUtils.equals(ColumnType.LONG_TEXT, type)) { + parseLongText(view, metadata); + } else if (TextUtils.equals(ColumnType.NUMBER, type)) { + parseNumber(view, metadata); + } else if (TextUtils.equals(ColumnType.DATE, type)) { + parseDate(view, metadata); + } else if (TextUtils.equals(ColumnType.DURATION, type)) { + parseDuration(view, metadata); + } else if (TextUtils.equals(ColumnType.COLLABORATOR, type)) { + parseCollaborator(view, metadata); + } else if (TextUtils.equals(ColumnType.SINGLE_SELECT, type)) { + parseSingleSelect(view, metadata); + } else if (TextUtils.equals(ColumnType.MULTIPLE_SELECT, type)) { + parseMultiSelect(view, metadata); + } else if (TextUtils.equals(ColumnType.EMAIL, type)) { + parseText(view, metadata); + } else if (TextUtils.equals(ColumnType.URL, type)) { + parseText(view, metadata); + } else if (TextUtils.equals(ColumnType.RATE, type)) { + parseRate(view, metadata); + } else if (TextUtils.equals(ColumnType.GEOLOCATION, type)) { +// parseGeoLocation(view, model); + } +// else if (TextUtils.equals(ColumnType.IMAGE, type)) { +// parseImage(view, model); +// } else if (TextUtils.equals(ColumnType.FILE, type)) { +// parseFile(view, model); +// } else if (TextUtils.equals(ColumnType.CHECKBOX, type)) { +// parseCheckbox(view, model); +// }else if (TextUtils.equals(ColumnType.LINK, type)) { +// parseLink(view, model); +// } else if (TextUtils.equals(ColumnType.DIGITAL_SIGN, type)) { +// parseDigitalSign(view, workFlowModel, model); +// } + } + + private int getIconByColumnType(String type) { + switch (type) { + case ColumnType.TEXT: + return R.drawable.ic_single_line_text; + case ColumnType.COLLABORATOR: + return R.drawable.ic_user_collaborator; + case ColumnType.IMAGE: + return R.drawable.ic_picture; + case ColumnType.FILE: + return R.drawable.ic_file_alt_solid; + case ColumnType.DATE: + return R.drawable.ic_calendar_alt_solid; + case ColumnType.SINGLE_SELECT: + return R.drawable.ic_single_election; + case ColumnType.DURATION: + return R.drawable.ic_duration; + case ColumnType.MULTIPLE_SELECT: + return R.drawable.ic_multiple_selection; + case ColumnType.CHECKBOX: + return R.drawable.ic_check_square_solid; + case ColumnType.GEOLOCATION: + return R.drawable.ic_location; + case ColumnType.EMAIL: + return R.drawable.ic_email; + case ColumnType.LONG_TEXT: + return R.drawable.ic_long_text; + case ColumnType.NUMBER: + return R.drawable.ic_number; + case ColumnType.RATE: + return R.drawable.ic_star2; + case ColumnType.URL: + return R.drawable.ic_url; + case ColumnType.LINK: + return R.drawable.ic_links; + } + + return R.drawable.ic_single_line_text; + } + + private void parseText(LinearLayout view, MetadataModel model) { + if (model.value instanceof String) { + View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_textview, null); + ltr.findViewById(R.id.text_view).setText(model.value.toString()); + + view.findViewById(R.id.flex_box).addView(ltr, getFlexParams()); + } + } + + private void parseLongText(LinearLayout view, MetadataModel model) { + if (model.value instanceof String) { + View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_textview, null); + ltr.findViewById(R.id.text_view).setText(model.value.toString()); + + view.findViewById(R.id.flex_box).addView(ltr, getFlexParams()); + } + } + + + private void parseDuration(LinearLayout view, MetadataModel model) { + if (model.value instanceof String) { + View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_textview, null); + ltr.findViewById(R.id.text_view).setText(model.value.toString()); + + view.findViewById(R.id.flex_box).addView(ltr, getFlexParams()); + } + } + + private void parseNumber(LinearLayout view, MetadataModel model) { + if (model.value instanceof Number number) { + View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_textview, null); + + ltr.findViewById(R.id.text_view).setText(Utils.readableFileSize(number.intValue())); + + view.findViewById(R.id.flex_box).addView(ltr, getFlexParams()); + } + } + + private void parseDate(LinearLayout view, MetadataModel model) { + if (model.value instanceof OffsetDateTime date) { + View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_textview, null); + + String temp = date.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME).replace("T", " "); + ltr.findViewById(R.id.text_view).setText(temp); + + view.findViewById(R.id.flex_box).addView(ltr, getFlexParams()); + } + } + +// private void parseGeoLocation(LinearLayout view, MetadataModel model) { +// if (model.value instanceof LinkedTreeMap) { +// LinkedTreeMap treeMap = (LinkedTreeMap) model.value; +// ColumnDataModel columnDataModel = model.column_data; +// +// String geo_format = columnDataModel.geo_format; +// +// String content = null; +// if (TextUtils.equals("lng_lat", geo_format)) { +// String lat = treeMap.get("lat").toString(); +// String lng = treeMap.get("lng").toString(); +// content = lat + "," + lng; +// } else if (TextUtils.equals("geolocation", geo_format)) { +// String province = treeMap.get("province").toString(); +// String city = treeMap.get("city").toString(); +// String dis = treeMap.get("district").toString(); +// String detail = treeMap.get("detail").toString(); +// content = province + city + dis + detail; +// } else if (TextUtils.equals("country_region", geo_format)) { +// content = treeMap.get("country_region").toString(); +// } else if (TextUtils.equals("province", geo_format)) { +// content = treeMap.get("province").toString(); +// } else if (TextUtils.equals("province_city", geo_format)) { +// String province = treeMap.get("province").toString(); +// String city = treeMap.get("city").toString(); +// content = province + city; +// } else if (TextUtils.equals("province_city_district", geo_format)) { +// String province = treeMap.get("province").toString(); +// String city = treeMap.get("city").toString(); +// String dis = treeMap.get("district").toString(); +// content = province + city + dis; +// } +// +// view.findViewById(R.id.text_view).setText(content); +// } +// } + + //container + private void parseCollaborator(LinearLayout view, MetadataModel model) { + if (model.value instanceof ArrayList) { + ArrayList arrayList = (ArrayList) model.value; + for (String userEmail : arrayList) { + UserModel userModel = getRelatedUserByEmail(userEmail); + if (userModel == null) { + continue; + } + + View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_avatar_username_round, null); + TextView user_name_text_view = ltr.findViewById(R.id.user_name); + ShapeableImageView imageView = ltr.findViewById(R.id.user_avatar); + Glide.with(imageView) + .load(userModel.getAvatarUrl()) + .apply(GlideLoadConfig.getOptions()) + .into(imageView); + + user_name_text_view.setText(userModel.getName()); + view.findViewById(R.id.flex_box).addView(ltr, getFlexParams()); + } + } + } + + private UserModel getRelatedUserByEmail(String email) { + if (relatedUsers == null) { + return null; + } + + Optional op = relatedUsers.stream().filter(f -> f.getEmail().equals(email)).findFirst(); + return op.orElse(null); + } + + private void parseSingleSelect(LinearLayout view, MetadataModel model) { + if (model.configData == null || CollectionUtils.isEmpty(model.configData)) { + return; + } + + MetadataConfigDataModel configDataModel = model.configData.get(0); + if (configDataModel.options == null || CollectionUtils.isEmpty(configDataModel.options)) { + return; + } + + if (model.value instanceof String && !TextUtils.isEmpty(model.value.toString())) { + String value = (String) model.value; + + OptionsTagModel option = configDataModel.options.stream().filter(f -> f.id.equals(value)).findFirst().get(); + View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_detail_text_round, null); + TextView textView = ltr.findViewById(R.id.text); + MaterialCardView cardView = ltr.findViewById(R.id.card_view); + + int resStrId = getResNameByKey(option.id); + if (resStrId != 0) { + textView.setText(resStrId); + } else { + textView.setText(option.name); + } + + +// if (!TextUtils.isEmpty(option.borderColor)) { +// } + + if (!TextUtils.isEmpty(option.textColor)) { + textView.setTextColor(Color.parseColor(option.textColor)); + } + + if (!TextUtils.isEmpty(option.color)) { + cardView.setCardBackgroundColor(Color.parseColor(option.color)); + } + + view.findViewById(R.id.flex_box).addView(ltr, getFlexParams()); + } + } + + private void parseMultiSelect(LinearLayout view, MetadataModel model) { + if (model.configData == null || CollectionUtils.isEmpty(model.configData)) { + return; + } + + MetadataConfigDataModel configDataModel = model.configData.get(0); + if (configDataModel.options == null || CollectionUtils.isEmpty(configDataModel.options)) { + return; + } + + if (model.value instanceof ArrayList) { + ArrayList arrayList = (ArrayList) model.value; + for (String key : arrayList) { + OptionsTagModel option = configDataModel.options.stream().filter(f -> f.id.equals(key)).findFirst().get(); + View ltr = LayoutInflater.from(view.getContext()).inflate(R.layout.layout_detail_text_round, null); + TextView textView = ltr.findViewById(R.id.text); + MaterialCardView cardView = ltr.findViewById(R.id.card_view); + + int resStrId = getResNameByKey(option.id); + if (resStrId != 0) { + textView.setText(resStrId); + } else { + textView.setText(option.name); + } + + +// if (!TextUtils.isEmpty(option.borderColor)) { +// } + + if (!TextUtils.isEmpty(option.textColor)) { + textView.setTextColor(Color.parseColor(option.textColor)); + } + + if (!TextUtils.isEmpty(option.color)) { + cardView.setCardBackgroundColor(Color.parseColor(option.color)); + } + + view.findViewById(R.id.flex_box).addView(ltr, getFlexParams()); + } + } + } + + +// private void parseImage(LinearLayout view, MetadataModel model) { +// if (model.value instanceof ArrayList) { +// FlexboxLayout flexboxLayout = view.findViewById(R.id.flex_box); +// +// int wh = SizeUtils.dp2px(24); +// FlexboxLayout.LayoutParams flexLayoutParams = new FlexboxLayout.LayoutParams(wh, wh); +// flexLayoutParams.bottomMargin = DP_4; +// flexLayoutParams.rightMargin = DP_4; +// +// ArrayList imgList = (ArrayList) model.value; +// for (int i = 0; i < imgList.size(); i++) { +// String url = imgList.get(i); +// ImageView imageView = new ImageView(flexboxLayout.getContext()); +// Glide.with(imageView) +// .load(GlideLoadConfig.getGlideUrl(url)) +// .apply(GlideLoadConfig.getOptions()) +// .into(imageView); +// flexboxLayout.addView(imageView, flexLayoutParams); +// } +// } +// } + +// private void parseFile(LinearLayout view, MetadataModel model) { +// if (model.value instanceof ArrayList) { +// FlexboxLayout flexboxLayout = view.findViewById(R.id.flex_box); +// +// int wh = SizeUtils.dp2px(24); +// FlexboxLayout.LayoutParams flexLayoutParams = new FlexboxLayout.LayoutParams(wh, wh); +// flexLayoutParams.bottomMargin = DP_4; +// flexLayoutParams.rightMargin = DP_4; +// +// ArrayList> fileList = (ArrayList>) model.value; +// for (LinkedTreeMap treeMap : fileList) { +// ImageView imageView = new ImageView(flexboxLayout.getContext()); +// String url = treeMap.get("url").toString(); +// +// int rid = MimeTypes.getFileIconFromExtension(url); +// imageView.setImageResource(rid); +// flexboxLayout.addView(imageView, flexLayoutParams); +// } +// } +// } + + private void parseRate(LinearLayout view, MetadataModel model) { + + if (model.configData == null || CollectionUtils.isEmpty(model.configData)) { + return; + } + + MetadataConfigDataModel configDataModel = model.configData.get(0); + + int wh = SizeUtils.dp2px(16); + FlexboxLayout.LayoutParams flexLayoutParams = new FlexboxLayout.LayoutParams(wh, wh); + flexLayoutParams.rightMargin = DP_4; + + int selectCount = 0; + if (model.value != null) { + selectCount = ((Number) model.value).intValue(); + } + + for (int i = 0; i < configDataModel.rate_max_number; i++) { + ImageView ltr = new ImageView(view.getContext()); + + int t; + if (i < selectCount) { + if (TextUtils.isEmpty(configDataModel.rate_style_color)) { + t = ContextCompat.getColor(requireContext(), R.color.grey); + } else { + t = Color.parseColor(configDataModel.rate_style_color); + } + } else { + t = ContextCompat.getColor(requireContext(), R.color.light_gray); + } + + ColorStateList stateList = ColorStateList.valueOf(t); + + ltr.setImageTintList(stateList); + ltr.setImageResource(R.drawable.ic_star2); + + view.findViewById(R.id.flex_box).addView(ltr, flexLayoutParams); + } + } + + private void getRateColor() { + + } +// +// private void parseCheckbox(LinearLayout view, MetadataModel model) { +// FlexboxLayout flexboxLayout = view.findViewById(R.id.flex_box); +// FlexboxLayout.LayoutParams flexLayoutParams = new FlexboxLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); +// flexLayoutParams.bottomMargin = DP_4; +// flexLayoutParams.rightMargin = DP_4; +// flexLayoutParams.height = SizeUtils.dp2px(30); +// +// AppCompatCheckBox checkBox = new AppCompatCheckBox(view.getContext()); +// checkBox.setText(""); +// checkBox.setClickable(false); +// if (model.value instanceof Boolean) { +// checkBox.setChecked((Boolean) model.value); +// } +// flexboxLayout.addView(checkBox, flexLayoutParams); +// } + +// private void parseLink(LinearLayout view, MetadataModel model) { +// +// if (model.value instanceof ArrayList) { +// +// FlexboxLayout flexboxLayout = view.findViewById(R.id.flex_box); +// FlexboxLayout.LayoutParams flexLayoutParams = new FlexboxLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); +// flexLayoutParams.bottomMargin = DP_4; +// flexLayoutParams.rightMargin = DP_4; +// +// ArrayList> arrayList = (ArrayList>) model.value; +// if (!CollectionUtils.isEmpty(arrayList)) { +// LinkedTreeMap hashMap = arrayList.get(0); +// View ltr = LayoutInflater.from(flexboxLayout.getContext()).inflate(R.layout.layout_text_round, null); +// TextView textView = ltr.findViewById(R.id.text); +// textView.setMaxLines(1); +// textView.setEllipsize(TextUtils.TruncateAt.END); +// textView.setText(hashMap.get("display_value")); +// flexboxLayout.addView(ltr, flexLayoutParams); +// +// // +// flexboxLayout.addView(TaskViews.getInstance().getSingleLineTextView(context, "...")); +// } +// } +// } +// +// private void parseDigitalSign(LinearLayout view, WorkFlowSimplifyModel workFlowModel, MetadataModel model) { +// if (model.value instanceof LinkedTreeMap) { +// FlexboxLayout flexboxLayout = view.findViewById(R.id.flex_box); +// FlexboxLayout.LayoutParams flexLayoutParams = new FlexboxLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); +// flexLayoutParams.bottomMargin = DP_4; +// flexLayoutParams.rightMargin = DP_4; +// +// LinkedTreeMap treeMap = (LinkedTreeMap) model.value; +// +// ///digital-signs/2023-05/chaohui.wang%40seafile.com-1684141281136.png +// String url = treeMap.get("sign_image_url").toString(); +// +// //https://dev.seatable.cn/thumbnail/workspace/246/asset/cf317040-a299-4311-a565-27d8b3dde319/digital-signs/2023-05/chaohui.wang%40seafile.com-1684141281136.png?size=256 +// url = IO.getSingleton().getHostUrl() + "thumbnail/workspace/" + workFlowModel.id + "/asset/" + workFlowModel.dtable_uuid + url; +// +// ImageView imageView = Views.TableActivityViews.getImageViewWithWhAndUrl(flexboxLayout.getContext(), 24, url); +// flexboxLayout.addView(imageView, flexLayoutParams); +// } +// } +} 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 aa2f65de7..e6e9d330d 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 @@ -9,7 +9,6 @@ import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; -import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -51,7 +50,7 @@ import com.seafile.seadroid2.ui.main.MainActivity; import com.seafile.seadroid2.ui.media.image_preview.ImagePreviewActivity; import com.seafile.seadroid2.ui.media.player.exoplayer.CustomExoVideoPlayerActivity; -import com.seafile.seadroid2.ui.webview.SeaWebViewActivity; +import com.seafile.seadroid2.ui.sdoc.SDocWebViewActivity; import com.seafile.seadroid2.view.TipsViews; import java.io.File; @@ -376,7 +375,7 @@ public void accept(RepoModel repoModel) throws Exception { private void open(RepoModel repoModel, SearchModel searchedFile, String fileName, String filePath) { if (fileName.endsWith(Constants.Format.DOT_SDOC)) { - SeaWebViewActivity.openSdoc(this, repoModel.repo_name, repoModel.repo_id, filePath); + SDocWebViewActivity.openSdoc(this, repoModel.repo_name, repoModel.repo_id, filePath); } else if (Utils.isViewableImage(fileName) && !repoModel.encrypted) { // Encrypted repo does not support gallery, // because pic thumbnail under encrypted repo was not supported at the server side diff --git a/app/src/main/java/com/seafile/seadroid2/ui/settings/TabSettingsFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/settings/TabSettingsFragment.java index 972821b80..4d8ec45f7 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/settings/TabSettingsFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/settings/TabSettingsFragment.java @@ -9,6 +9,7 @@ import android.os.Bundle; import android.text.Spanned; import android.text.TextUtils; +import android.view.View; import androidx.activity.result.ActivityResult; import androidx.activity.result.ActivityResultCallback; @@ -70,6 +71,7 @@ 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.widget.prefs.ButtonPreference; import java.util.ArrayList; import java.util.List; @@ -176,8 +178,6 @@ private void initPref() { initAccountPref(); - initClientEncryptPref(); - initSignOutPref(); initAlbumBackupPref(); @@ -206,19 +206,15 @@ private void initAccountPref() { gestureSwitch = findPreference(getString(R.string.pref_key_gesture_lock)); } - private void initClientEncryptPref() { - Preference clientEncPref = findPreference(getString(R.string.pref_key_security_client_encrypt)); - if (clientEncPref == null) { - return; - } - - ServerInfo serverInfo = SupportAccountManager.getInstance().getServerInfo(currentAccount); - // Client side encryption for encrypted Library - clientEncPref.setVisible(serverInfo.canLocalDecrypt()); - } - private void initSignOutPref() { //sign out +// ButtonPreference buttonPreference = findPreference(getString(R.string.pref_key_sign_out)); +// buttonPreference.getButton().setOnClickListener(new View.OnClickListener() { +// @Override +// public void onClick(View v) { +// onPreferenceSignOutClicked(); +// } +// }); findPreference(getString(R.string.pref_key_sign_out)).setOnPreferenceClickListener(preference -> { onPreferenceSignOutClicked(); return true; @@ -437,13 +433,6 @@ public void onChanged(Boolean aBoolean) { } }); - Settings.CLIENT_ENCRYPT_SWITCH.observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(Boolean aBoolean) { - SLogs.e("client encrypt switch:" + aBoolean); - } - }); - ////////////////// /// album backup ////////////////// @@ -715,7 +704,7 @@ private void updateAlbumBackupSelectedRepoSummary() { Account camAccount = CameraUploadManager.getInstance().getCameraAccount(); RepoConfig repoConfig = AlbumBackupSharePreferenceHelper.readRepoConfig(); if (camAccount != null && repoConfig != null) { - mAlbumBackupRepo.setSummary(camAccount.getSignature() + "/" + repoConfig.getRepoName()); + mAlbumBackupRepo.setSummary(repoConfig.getRepoName()); } else { mAlbumBackupRepo.setSummary(null); } @@ -743,7 +732,7 @@ private void updateFolderBackupSelectedRepoAndFolderSummary() { RepoConfig repoConfig = FolderBackupSharePreferenceHelper.readRepoConfig(); if (repoConfig != null && !TextUtils.isEmpty(repoConfig.getRepoName())) { - mFolderBackupSelectRepo.setSummary(repoConfig.getEmail() + "/" + repoConfig.getRepoName()); + mFolderBackupSelectRepo.setSummary(repoConfig.getRepoName()); } else { mFolderBackupSelectRepo.setSummary(getString(R.string.folder_backup_select_repo_hint)); } 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 index d514d3f03..4b799c2f3 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/star/StarredQuickFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/star/StarredQuickFragment.java @@ -26,24 +26,24 @@ import com.seafile.seadroid2.SeafException; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; +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.databinding.LayoutFrameSwipeRvBinding; +import com.seafile.seadroid2.framework.data.db.entities.StarredModel; +import com.seafile.seadroid2.framework.data.model.ResultModel; import com.seafile.seadroid2.framework.datastore.DataManager; +import com.seafile.seadroid2.framework.util.Utils; import com.seafile.seadroid2.ui.WidgetUtils; import com.seafile.seadroid2.ui.base.fragment.BaseFragmentWithVM; -import com.seafile.seadroid2.bottomsheetmenu.BottomSheetHelper; -import com.seafile.seadroid2.databinding.LayoutFrameSwipeRvBinding; -import com.seafile.seadroid2.framework.data.model.ResultModel; -import com.seafile.seadroid2.framework.data.db.entities.StarredModel; import com.seafile.seadroid2.ui.file.FileActivity; import com.seafile.seadroid2.ui.main.MainActivity; import com.seafile.seadroid2.ui.main.MainViewModel; import com.seafile.seadroid2.ui.markdown.MarkdownActivity; import com.seafile.seadroid2.ui.media.image_preview.ImagePreviewActivity; import com.seafile.seadroid2.ui.media.player.exoplayer.CustomExoVideoPlayerActivity; -import com.seafile.seadroid2.ui.webview.SeaWebViewActivity; -import com.seafile.seadroid2.framework.util.Utils; +import com.seafile.seadroid2.ui.sdoc.SDocWebViewActivity; import com.seafile.seadroid2.view.TipsViews; import java.io.File; @@ -245,7 +245,7 @@ private void open(StarredModel model) { } else if (model.obj_name.endsWith(Constants.Format.DOT_SDOC)) { - SeaWebViewActivity.openSdoc(getContext(), model.repo_name, model.repo_id, model.path); + SDocWebViewActivity.openSdoc(getContext(), model.repo_name, model.repo_id, model.path); } else if (Utils.isVideoFile(model.obj_name)) { 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 e09bd07b1..bd991a90b 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 @@ -5,14 +5,11 @@ import android.content.res.Configuration; import android.os.Bundle; import android.text.TextUtils; +import android.view.MenuItem; import android.view.View; import android.webkit.WebChromeClient; import android.webkit.WebView; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import androidx.activity.EdgeToEdge; import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.appcompat.widget.Toolbar; @@ -21,10 +18,11 @@ import androidx.webkit.WebViewFeature; import com.blankj.utilcode.util.ActivityUtils; +import com.blankj.utilcode.util.ToastUtils; +import com.google.android.material.navigation.NavigationBarView; 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.databinding.ActivitySeaWebviewBinding; import com.seafile.seadroid2.databinding.ActivitySeaWebviewProBinding; import com.seafile.seadroid2.databinding.ToolbarActionbarProgressBarBinding; @@ -33,8 +31,6 @@ import com.seafile.seadroid2.view.webview.PreloadWebView; import com.seafile.seadroid2.view.webview.SeaWebView; -import java.io.Serializable; - public class SeaWebViewActivity extends BaseActivity { private ActivitySeaWebviewBinding binding; private ToolbarActionbarProgressBarBinding toolBinding; @@ -43,15 +39,6 @@ public class SeaWebViewActivity extends BaseActivity { private String targetUrl; - public static void openSdoc(Context context, String repoName, String repoID, String path) { - Intent intent = new Intent(context, SeaWebViewActivity.class); - intent.putExtra("previewType", WebViewPreviewType.SDOC.name()); - intent.putExtra("repoName", repoName); - intent.putExtra("repoID", repoID); - intent.putExtra("filePath", path); - ActivityUtils.startActivity(intent); - } - public static void openUrl(Context context, String url) { Intent intent = new Intent(context, SeaWebViewActivity.class); intent.putExtra("previewType", WebViewPreviewType.URL.name()); diff --git a/app/src/main/java/com/seafile/seadroid2/view/HideBottomBehavior.java b/app/src/main/java/com/seafile/seadroid2/view/HideBottomBehavior.java new file mode 100644 index 000000000..c4d2e2efa --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/HideBottomBehavior.java @@ -0,0 +1,43 @@ +package com.seafile.seadroid2.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.view.ViewCompat; + +import com.blankj.utilcode.util.BarUtils; +import com.blankj.utilcode.util.SizeUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.framework.util.SLogs; + +public class HideBottomBehavior extends CoordinatorLayout.Behavior { + + public HideBottomBehavior() { + } + + public HideBottomBehavior(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull LinearLayout child, View dependency) { + return dependency.getId() == R.id.appbar; + } + + private boolean isInit = true; + + @Override + public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull LinearLayout child, @NonNull View dependency) { + if (isInit) { + isInit = false; + } else { + float y = dependency.getY(); + child.setTranslationY(-(y)); + } + return true; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/KeyViews.java b/app/src/main/java/com/seafile/seadroid2/view/KeyViews.java new file mode 100644 index 000000000..37a2648fb --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/KeyViews.java @@ -0,0 +1,220 @@ +package com.seafile.seadroid2.view; + +import android.content.Context; +import android.text.TextUtils; +import android.util.Pair; +import android.view.Gravity; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.blankj.utilcode.util.NumberUtils; +import com.blankj.utilcode.util.SizeUtils; +import com.google.android.flexbox.FlexWrap; +import com.google.android.flexbox.FlexboxLayout; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.config.Constants; +import com.seafile.seadroid2.framework.data.model.sdoc.MetadataConfigDataModel; + +import java.math.BigDecimal; +import java.util.Locale; + +public class KeyViews { + public int DP_2 = Constants.DP.DP_2; + public int DP_4 = Constants.DP.DP_4; + public int DP_8 = Constants.DP.DP_8; + public int DP_16 = Constants.DP.DP_16; + public int DP_32 = Constants.DP.DP_32; + + public static int DEFAULT_VIEW_WIDTH = SizeUtils.dp2px(150); + public static int DEFAULT_VIEW_HEIGHT = SizeUtils.dp2px(30); + public static int DEFAULT_IMAGE_VIEW_WH = SizeUtils.dp2px(96); + + // + private static volatile KeyViews mSingleton = null; + + public static KeyViews getInstance() { + if (mSingleton == null) { + synchronized (KeyViews.class) { + if (mSingleton == null) { + mSingleton = new KeyViews(); + } + } + } + return mSingleton; + } + + public void buildEditableText(Context context, boolean editable, LinearLayout parentContainer, String defaultText) { + + } + + private Pair getFlexContainer(Context context, LinearLayout parentContainer, String key) { + FlexboxLayout flexboxContainer = parentContainer.findViewWithTag(key); + boolean isAdded = flexboxContainer != null; + if (flexboxContainer != null) { + flexboxContainer.removeAllViews(); + } else { + flexboxContainer = new FlexboxLayout(context); + LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(-1, -2); + llp.topMargin = DP_8; + llp.bottomMargin = DP_16; + flexboxContainer.setLayoutParams(llp); + flexboxContainer.setTag(key); + flexboxContainer.setFlexWrap(FlexWrap.WRAP); + } + return new Pair<>(isAdded, flexboxContainer); + } + + + public TextView getSingleLineTextView(Context context, String string) { + TextView textView = new TextView(context); + textView.setTextSize(14); + textView.setTextColor(context.getColor(R.color.light_grey)); + textView.setMaxLines(1); + textView.setEllipsize(TextUtils.TruncateAt.END); + textView.setText(string); + textView.setGravity(Gravity.CENTER_VERTICAL); + LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(DEFAULT_VIEW_WIDTH, DEFAULT_VIEW_HEIGHT); + textView.setLayoutParams(llp); + textView.setPadding(0, DP_2, DP_2, DP_2); + return textView; + } + + + public String getNormalFormatNumber(String t, MetadataConfigDataModel columnDataModel) { + return getFormatNumber(t, columnDataModel, false); + } + + public String getNoDotCharNumber(String t, String format) { + if (TextUtils.isEmpty(format)) { + return t; + } + + //set default value + if (!"number".equals(format)) { + return t; + } + //1.234567890111E12 + + if (TextUtils.isEmpty(t)) { + return t; + } + + //Scientific notation + if (t.contains(".") && t.toLowerCase(Locale.getDefault()).contains("e")) { + BigDecimal bd = new BigDecimal(t); + return bd.toPlainString(); + } else if (t.contains(".")) { + int decimalIndex = t.indexOf("."); + if (decimalIndex >= 0) { + String[] ts = t.split("\\."); + String decimalPart = ts[1]; + double decimalValue = Double.parseDouble("0." + decimalPart); + if (decimalValue == 0) { + return ts[0]; + } else { + return t; + } + } + } + + + return t; + } + + /** + * @param isPercentTypeMultiplyBy100 true: if type is percent, multiply by 100 + */ + public String getFormatNumber(String t, MetadataConfigDataModel columnDataModel, boolean isPercentTypeMultiplyBy100) { + if (TextUtils.isEmpty(t)) { + return t; + } + + if (columnDataModel == null) { + return t; + } + + //set default value + if ("number".equals(columnDataModel.format)) { + return getNoDotCharNumber(t, columnDataModel.format); + } + + if ("percent".equals(columnDataModel.format)) { + if (t.contains("%")) { + t = t.replace("%", ""); + } + double d = Double.parseDouble(t); + if (isPercentTypeMultiplyBy100) { + d = d * 100; + } + + String r; + if (columnDataModel.enable_precision) { + r = NumberUtils.format(d, columnDataModel.precision); + } else { + r = String.valueOf(d); + } + + return r + "%"; + } else if ("yuan".equals(columnDataModel.format)) { + if (t.contains("¥")) { + t = t.replace("¥", ""); + } + double d = Double.parseDouble(t); + + String r; + if (columnDataModel.enable_precision) { + r = NumberUtils.format(d, columnDataModel.precision); + } else { + r = String.valueOf(d); + } + return "¥" + r; + } else if ("dollar".equals(columnDataModel.format)) { + if (t.contains("$")) { + t = t.replace("$", ""); + } + + double d = Double.parseDouble(t); + + String r; + if (columnDataModel.enable_precision) { + r = NumberUtils.format(d, columnDataModel.precision); + } else { + r = String.valueOf(d); + } + + return "$" + r; + } else if ("euro".equals(columnDataModel.format)) { + if (t.contains("€")) { + t = t.replace("€", ""); + } + double d = Double.parseDouble(t); + + String r; + if (columnDataModel.enable_precision) { + r = NumberUtils.format(d, columnDataModel.precision); + } else { + r = String.valueOf(d); + } + return "€" + r; + } else if ("custom_currency".equals(columnDataModel.format)) { + if (t.contains(columnDataModel.currency_symbol)) { + t = t.replace(columnDataModel.currency_symbol, ""); + } + + double d = Double.parseDouble(t); + String r; + if (columnDataModel.enable_precision) { + r = NumberUtils.format(d, columnDataModel.precision); + } else { + r = String.valueOf(d); + } + + if ("before".equals(columnDataModel.currency_symbol_position)) { + return columnDataModel.currency_symbol + r; + } else { + return r + columnDataModel.currency_symbol; + } + } + return t; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/MaxHeightScrollView.java b/app/src/main/java/com/seafile/seadroid2/view/MaxHeightScrollView.java new file mode 100644 index 000000000..47cfffba5 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/MaxHeightScrollView.java @@ -0,0 +1,57 @@ +package com.seafile.seadroid2.view; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.widget.ScrollView; + +import com.blankj.utilcode.util.SizeUtils; +import com.seafile.seadroid2.R; + + +public class MaxHeightScrollView extends ScrollView { + private int maxHeight = -1; + + public MaxHeightScrollView(Context context) { + super(context); + } + + public MaxHeightScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + if (!isInEditMode()) { + init(context, attrs); + } + } + + public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + if (!isInEditMode()) { + init(context, attrs); + } + } + + public void setMaxHeight(int maxHeight) { + this.maxHeight = maxHeight; + requestLayout(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int heightSpec = heightMeasureSpec; + if (maxHeight > 0) { + heightSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST); + } + super.onMeasure(widthMeasureSpec, heightSpec); + } + + private void init(Context context, AttributeSet attrs) { + if (attrs != null) { + TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView); + int DEFAULT_HEIGHT = SizeUtils.dp2px(200); + maxHeight = styledAttrs.getDimensionPixelSize(R.styleable.MaxHeightScrollView_max_height, DEFAULT_HEIGHT); + + styledAttrs.recycle(); + } + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/NestedWebView.java b/app/src/main/java/com/seafile/seadroid2/view/NestedWebView.java new file mode 100644 index 000000000..1215cfe65 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/NestedWebView.java @@ -0,0 +1,545 @@ +package com.seafile.seadroid2.view; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewParent; +import android.webkit.WebView; +import android.widget.OverScroller; + +import androidx.annotation.NonNull; +import androidx.core.view.NestedScrollingChild3; +import androidx.core.view.NestedScrollingChildHelper; +import androidx.core.view.ViewCompat; + +import org.jetbrains.annotations.Nullable; + +/** + * WebView compatible with CoordinatorLayout by snachmsm + * The implementation based on NestedScrollView of design library androidx v1.0.1 + */ +public class NestedWebView extends WebView implements NestedScrollingChild3 { + + private static final String TAG = "NestedWebView"; + private static final int INVALID_POINTER = -1; + + private final int[] mScrollOffset = new int[2]; + private final int[] mScrollConsumed = new int[2]; + + private int mLastMotionY; + private NestedScrollingChildHelper mChildHelper; + private boolean mIsBeingDragged = false; + private VelocityTracker mVelocityTracker; + private int mTouchSlop; + private int mActivePointerId = INVALID_POINTER; + private int mNestedYOffset; + private OverScroller mScroller; + private int mMinimumVelocity; + private int mMaximumVelocity; + private int mLastScrollerY; + + public NestedWebView(Context context) { + this(context, null); + } + + public NestedWebView(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.webViewStyle); + } + + public NestedWebView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setOverScrollMode(WebView.OVER_SCROLL_NEVER); + initScrollView(); + mChildHelper = new NestedScrollingChildHelper(this); + setNestedScrollingEnabled(true); + } + + private void initScrollView() { + mScroller = new OverScroller(getContext()); + final ViewConfiguration configuration = ViewConfiguration.get(getContext()); + mTouchSlop = configuration.getScaledTouchSlop(); + mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); + mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + final int action = ev.getAction(); + if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { // most common + return true; + } + + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_MOVE: + final int activePointerId = mActivePointerId; + if (activePointerId == INVALID_POINTER) { + break; + } + + final int pointerIndex = ev.findPointerIndex(activePointerId); + if (pointerIndex == -1) { + Log.e(TAG, "Invalid pointerId=" + activePointerId + + " in onInterceptTouchEvent"); + break; + } + + final int y = (int) ev.getY(pointerIndex); + final int yDiff = Math.abs(y - mLastMotionY); + if (yDiff > mTouchSlop + && (getNestedScrollAxes() & ViewCompat.SCROLL_AXIS_VERTICAL) == 0) { + mIsBeingDragged = true; + mLastMotionY = y; + initVelocityTrackerIfNotExists(); + mVelocityTracker.addMovement(ev); + mNestedYOffset = 0; + final ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + } + break; + case MotionEvent.ACTION_DOWN: + mLastMotionY = (int) ev.getY(); + mActivePointerId = ev.getPointerId(0); + + initOrResetVelocityTracker(); + mVelocityTracker.addMovement(ev); + + mScroller.computeScrollOffset(); + mIsBeingDragged = !mScroller.isFinished(); + + startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + mIsBeingDragged = false; + mActivePointerId = INVALID_POINTER; + recycleVelocityTracker(); + if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) { + ViewCompat.postInvalidateOnAnimation(this); + } + stopNestedScroll(); + break; + case MotionEvent.ACTION_POINTER_UP: + onSecondaryPointerUp(ev); + break; + } + + return mIsBeingDragged; + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent ev) { + initVelocityTrackerIfNotExists(); + + MotionEvent vtev = MotionEvent.obtain(ev); + + final int actionMasked = ev.getActionMasked(); + + if (actionMasked == MotionEvent.ACTION_DOWN) { + mNestedYOffset = 0; + } + vtev.offsetLocation(0, mNestedYOffset); + + switch (actionMasked) { + case MotionEvent.ACTION_DOWN: + if ((mIsBeingDragged = !mScroller.isFinished())) { + final ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + } + + if (!mScroller.isFinished()) { + abortAnimatedScroll(); + } + + mLastMotionY = (int) ev.getY(); + mActivePointerId = ev.getPointerId(0); + startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH); + break; + case MotionEvent.ACTION_MOVE: + final int activePointerIndex = ev.findPointerIndex(mActivePointerId); + if (activePointerIndex == -1) { + Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); + break; + } + + final int y = (int) ev.getY(activePointerIndex); + int deltaY = mLastMotionY - y; + if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset, + ViewCompat.TYPE_TOUCH)) { + deltaY -= mScrollConsumed[1]; + mNestedYOffset += mScrollOffset[1]; + } + if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) { + final ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + mIsBeingDragged = true; + if (deltaY > 0) { + deltaY -= mTouchSlop; + } else { + deltaY += mTouchSlop; + } + } + if (mIsBeingDragged) { + mLastMotionY = y - mScrollOffset[1]; + + final int oldY = getScrollY(); + final int range = getScrollRange(); + + // Calling overScrollByCompat will call onOverScrolled, which + // calls onScrollChanged if applicable. + if (overScrollByCompat(0, deltaY, 0, oldY, 0, range, 0, + 0, true) && !hasNestedScrollingParent(ViewCompat.TYPE_TOUCH)) { + mVelocityTracker.clear(); + } + + final int scrolledDeltaY = getScrollY() - oldY; + final int unconsumedY = deltaY - scrolledDeltaY; + + mScrollConsumed[1] = 0; + + dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset, + ViewCompat.TYPE_TOUCH, mScrollConsumed); + + mLastMotionY -= mScrollOffset[1]; + mNestedYOffset += mScrollOffset[1]; + } + break; + case MotionEvent.ACTION_UP: + final VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); + if ((Math.abs(initialVelocity) > mMinimumVelocity)) { + if (!dispatchNestedPreFling(0, -initialVelocity)) { + dispatchNestedFling(0, -initialVelocity, true); + fling(-initialVelocity); + } + } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, + getScrollRange())) { + ViewCompat.postInvalidateOnAnimation(this); + } + mActivePointerId = INVALID_POINTER; + endDrag(); + break; + case MotionEvent.ACTION_CANCEL: + if (mIsBeingDragged) { + if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, + getScrollRange())) { + ViewCompat.postInvalidateOnAnimation(this); + } + } + mActivePointerId = INVALID_POINTER; + endDrag(); + break; + case MotionEvent.ACTION_POINTER_DOWN: + final int index = ev.getActionIndex(); + mLastMotionY = (int) ev.getY(index); + mActivePointerId = ev.getPointerId(index); + break; + case MotionEvent.ACTION_POINTER_UP: + onSecondaryPointerUp(ev); + mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId)); + break; + } + + if (mVelocityTracker != null) { + mVelocityTracker.addMovement(vtev); + } + vtev.recycle(); + return super.onTouchEvent(ev); + } + + private void abortAnimatedScroll() { + mScroller.abortAnimation(); + stopNestedScroll(ViewCompat.TYPE_NON_TOUCH); + } + + private void endDrag() { + mIsBeingDragged = false; + + recycleVelocityTracker(); + stopNestedScroll(); + } + + private void onSecondaryPointerUp(MotionEvent ev) { + final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + final int newPointerIndex = pointerIndex == 0 ? 1 : 0; + mLastMotionY = (int) ev.getY(newPointerIndex); + mActivePointerId = ev.getPointerId(newPointerIndex); + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + } + } + } + + private void fling(int velocityY) { + int height = getHeight(); + mScroller.fling(getScrollX(), getScrollY(), // start + 0, velocityY, // velocities + 0, 0, // x + Integer.MIN_VALUE, Integer.MAX_VALUE, // y + 0, height / 2); + runAnimatedScroll(true); + } + + private void runAnimatedScroll(boolean participateInNestedScrolling) { + if (participateInNestedScrolling) { + startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH); + } else { + stopNestedScroll(ViewCompat.TYPE_NON_TOUCH); + } + mLastScrollerY = getScrollY(); + ViewCompat.postInvalidateOnAnimation(this); + } + + @Override + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + if (disallowIntercept) { + recycleVelocityTracker(); + } + super.requestDisallowInterceptTouchEvent(disallowIntercept); + } + + private void initOrResetVelocityTracker() { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } else { + mVelocityTracker.clear(); + } + } + + private void initVelocityTrackerIfNotExists() { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + } + + private void recycleVelocityTracker() { + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + + @Override + protected boolean overScrollBy(int deltaX, int deltaY, + int scrollX, int scrollY, + int scrollRangeX, int scrollRangeY, + int maxOverScrollX, int maxOverScrollY, + boolean isTouchEvent) { + // this is causing double scroll call (doubled speed), but this WebView isn't overscrollable + // all overscrolls are passed to appbar, so commenting this out during drag + if (!mIsBeingDragged) + overScrollByCompat(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, + maxOverScrollX, maxOverScrollY, isTouchEvent); + // without this call webview won't scroll to top when url change or when user pick input + // (webview should move a bit making input still in viewport when "adjustResize") + return true; + } + + int getScrollRange() { + //Using scroll range of webview instead of childs as NestedScrollView does. + return computeVerticalScrollRange(); + } + + @Override + public boolean isNestedScrollingEnabled() { + return mChildHelper.isNestedScrollingEnabled(); + } + + @Override + public void setNestedScrollingEnabled(boolean enabled) { + mChildHelper.setNestedScrollingEnabled(enabled); + } + + @Override + public boolean startNestedScroll(int axes, int type) { + return mChildHelper.startNestedScroll(axes, type); + } + + @Override + public boolean startNestedScroll(int axes) { + return startNestedScroll(axes, ViewCompat.TYPE_TOUCH); + } + + @Override + public void stopNestedScroll(int type) { + mChildHelper.stopNestedScroll(type); + } + + @Override + public void stopNestedScroll() { + stopNestedScroll(ViewCompat.TYPE_TOUCH); + } + + @Override + public boolean hasNestedScrollingParent(int type) { + return mChildHelper.hasNestedScrollingParent(type); + } + + @Override + public boolean hasNestedScrollingParent() { + return hasNestedScrollingParent(ViewCompat.TYPE_TOUCH); + } + + @Override + public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, + int[] offsetInWindow) { + return dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, + offsetInWindow, ViewCompat.TYPE_TOUCH); + } + + @Override + public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, + int[] offsetInWindow, int type) { + return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, + offsetInWindow, type); + } + + @Override + public void dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, + @Nullable int[] offsetInWindow, int type, @NonNull int[] consumed) { + mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, + offsetInWindow, type, consumed); + } + + @Override + public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { + return dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, ViewCompat.TYPE_TOUCH); + } + + + @Override + public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, int type) { + return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type); + } + + @Override + public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { + return mChildHelper.dispatchNestedFling(velocityX, velocityY, false); + } + + @Override + public boolean dispatchNestedPreFling(float velocityX, float velocityY) { + return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); + } + + @Override + public int getNestedScrollAxes() { + return ViewCompat.SCROLL_AXIS_VERTICAL; + } + + @Override + public void computeScroll() { + if (mScroller.isFinished()) { + return; + } + + mScroller.computeScrollOffset(); + final int y = mScroller.getCurrY(); + int unconsumed = y - mLastScrollerY; + mLastScrollerY = y; + + // Nested Scrolling Pre Pass + mScrollConsumed[1] = 0; + dispatchNestedPreScroll(0, unconsumed, mScrollConsumed, null, + ViewCompat.TYPE_NON_TOUCH); + unconsumed -= mScrollConsumed[1]; + + + if (unconsumed != 0) { + // Internal Scroll + final int oldScrollY = getScrollY(); + overScrollByCompat(0, unconsumed, getScrollX(), oldScrollY, 0, getScrollRange(), + 0, 0, false); + final int scrolledByMe = getScrollY() - oldScrollY; + unconsumed -= scrolledByMe; + + // Nested Scrolling Post Pass + mScrollConsumed[1] = 0; + dispatchNestedScroll(0, 0, 0, unconsumed, mScrollOffset, + ViewCompat.TYPE_NON_TOUCH, mScrollConsumed); + unconsumed -= mScrollConsumed[1]; + } + + if (unconsumed != 0) { + abortAnimatedScroll(); + } + + if (!mScroller.isFinished()) { + ViewCompat.postInvalidateOnAnimation(this); + } + } + + // copied from NestedScrollView exacly as it looks, leaving overscroll related code, maybe future use + private boolean overScrollByCompat(int deltaX, int deltaY, + int scrollX, int scrollY, + int scrollRangeX, int scrollRangeY, + int maxOverScrollX, int maxOverScrollY, + boolean isTouchEvent) { + final int overScrollMode = getOverScrollMode(); + final boolean canScrollHorizontal = + computeHorizontalScrollRange() > computeHorizontalScrollExtent(); + final boolean canScrollVertical = + computeVerticalScrollRange() > computeVerticalScrollExtent(); + final boolean overScrollHorizontal = overScrollMode == View.OVER_SCROLL_ALWAYS + || (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal); + final boolean overScrollVertical = overScrollMode == View.OVER_SCROLL_ALWAYS + || (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical); + + int newScrollX = scrollX + deltaX; + if (!overScrollHorizontal) { + maxOverScrollX = 0; + } + + int newScrollY = scrollY + deltaY; + if (!overScrollVertical) { + maxOverScrollY = 0; + } + + // Clamp values if at the limits and record + final int left = -maxOverScrollX; + final int right = maxOverScrollX + scrollRangeX; + final int top = -maxOverScrollY; + final int bottom = maxOverScrollY + scrollRangeY; + + boolean clampedX = false; + if (newScrollX > right) { + newScrollX = right; + clampedX = true; + } else if (newScrollX < left) { + newScrollX = left; + clampedX = true; + } + + boolean clampedY = false; + if (newScrollY > bottom) { + newScrollY = bottom; + clampedY = true; + } else if (newScrollY < top) { + newScrollY = top; + clampedY = true; + } + + if (clampedY && !hasNestedScrollingParent(ViewCompat.TYPE_NON_TOUCH)) { + mScroller.springBack(newScrollX, newScrollY, 0, 0, 0, getScrollRange()); + } + + onOverScrolled(newScrollX, newScrollY, clampedX, clampedY); + + return clampedX || clampedY; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/DeletableEditText.java b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/DeletableEditText.java new file mode 100755 index 000000000..795dba8de --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/DeletableEditText.java @@ -0,0 +1,80 @@ +/* +Copyright 2017 yangchong211(github.com/yangchong211) + +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.view.rich_edittext; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; + +import androidx.appcompat.widget.AppCompatEditText; + +/** + *

+ *     @author 杨充
+ *     blog  : https://github.com/yangchong211
+ *     time  : 2016/3/31
+ *     desc  : 自定义EditText
+ *     revise: 主要用途是处理软键盘回删按钮backSpace时回调OnKeyListener
+ * 
+ */ +public class DeletableEditText extends AppCompatEditText { + + private DeleteInputConnection inputConnection; + + public DeletableEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + public DeletableEditText(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public DeletableEditText(Context context) { + super(context); + init(); + } + + + private void init(){ + inputConnection = new DeleteInputConnection(null,true); + } + + /** + * 当输入法和EditText建立连接的时候会通过这个方法返回一个InputConnection。 + * 我们需要代理这个方法的父类方法生成的InputConnection并返回我们自己的代理类。 + * @param outAttrs attrs + * @return + */ + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + //inputConnection = new DeleteInputConnection(super.onCreateInputConnection(outAttrs), true); + inputConnection.setTarget(super.onCreateInputConnection(outAttrs)); + return inputConnection; + } + + /** + * 设置格键删除监听事件,主要是解决少部分手机,使用搜狗输入法无法响应当内容为空时的删除逻辑 + * @param listener listener + */ + public void setBackSpaceListener(DeleteInputConnection.BackspaceListener listener){ + inputConnection.setBackspaceListener(listener); + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/DeleteInputConnection.java b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/DeleteInputConnection.java new file mode 100644 index 000000000..2916e6466 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/DeleteInputConnection.java @@ -0,0 +1,117 @@ +/* +Copyright 2017 yangchong211(github.com/yangchong211) + +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.view.rich_edittext; + +import android.view.KeyEvent; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputConnectionWrapper; + +import com.seafile.seadroid2.framework.util.SLogs; + +/** + *
+ *     @author 杨充
+ *     blog  : https://github.com/yangchong211
+ *     time  : 2019/07/18
+ *     desc  : 自定义InputConnectionWrapper
+ *     revise:
+ * 
+ */ +public class DeleteInputConnection extends InputConnectionWrapper { + + private BackspaceListener mBackspaceListener; + + public interface BackspaceListener { + /** + * @return true 代表消费了这个事件 + */ + boolean onBackspace(); + } + + public void setBackspaceListener(BackspaceListener backspaceListener) { + this.mBackspaceListener = backspaceListener; + } + + + public DeleteInputConnection(InputConnection target, boolean mutable) { + super(target, mutable); + } + + /** + * 提交文本 + * 输入法输入了字符,包括表情,字母、文字、数字和符号等内容,会回调该方法 + * + * @param text text + * @param newCursorPosition 新索引位置 + * @return + */ + @Override + public boolean commitText(CharSequence text, int newCursorPosition) { + return super.commitText(text, newCursorPosition); + } + + /** + * 按键事件 + * 在commitText方法中, + * 如果执行父类的 commitText(即super.commitText(text, newCursorPosition))那么表示不拦截, + * 如果返回false则表示拦截 + *

+ * 当有按键输入时,该方法会被回调。比如点击退格键时,搜狗输入法应该就是通过调用该方法, + * 发送keyEvent的,但谷歌输入法却不会调用该方法,而是调用下面的deleteSurroundingText()方法。 + * 网上说少数低端手机,无法响应退格键删除功能,后期找华为p9手机,安装搜狗输入法测试 + * + * @param event event事件 + * @return + */ + @Override + public boolean sendKeyEvent(KeyEvent event) { + SLogs.d("DeletableEditText---sendKeyEvent--"); + if (event.getKeyCode() == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) { + if (mBackspaceListener != null && mBackspaceListener.onBackspace()) { + return true; + } + } + return super.sendKeyEvent(event); + } + + /** + * 删除操作 + * 有文本删除操作时(剪切,点击退格键),会触发该方法 + * + * @param beforeLength beforeLength + * @param afterLength afterLength + * @return + */ + @Override + public boolean deleteSurroundingText(int beforeLength, int afterLength) { + SLogs.d("DeletableEditText---deleteSurroundingText--" + beforeLength + "----" + afterLength); + if (beforeLength == 1 && afterLength == 0) { + return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) + && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); + } + return super.deleteSurroundingText(beforeLength, afterLength); + } + + /** + * 结 束组合文本输入的时候,回调该方法 + * + * @return + */ + @Override + public boolean finishComposingText() { + return super.finishComposingText(); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/OnRichAtListener.java b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/OnRichAtListener.java new file mode 100644 index 000000000..874aedd3d --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/OnRichAtListener.java @@ -0,0 +1,23 @@ +/* +Copyright 2017 yangchong211(github.com/yangchong211) + +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.view.rich_edittext; + +import android.widget.EditText; + +public interface OnRichAtListener { + + void onCall(EditText editText); +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/OnRichImageClickListener.java b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/OnRichImageClickListener.java new file mode 100644 index 000000000..a2016f8ea --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/OnRichImageClickListener.java @@ -0,0 +1,23 @@ +/* +Copyright 2017 yangchong211(github.com/yangchong211) + +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.view.rich_edittext; + +import android.view.View; + +public interface OnRichImageClickListener { + + void onClick(View view, String imagePath); +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/RichEditText.java b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/RichEditText.java new file mode 100644 index 000000000..c5d3f5412 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/rich_edittext/RichEditText.java @@ -0,0 +1,371 @@ +package com.seafile.seadroid2.view.rich_edittext; + +import android.content.Context; +import android.net.Uri; +import android.text.InputFilter; +import android.text.Spanned; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +import com.blankj.utilcode.util.SizeUtils; +import com.bumptech.glide.Glide; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.config.Constants; +import com.seafile.seadroid2.databinding.LayoutUploadFileBinding; +import com.seafile.seadroid2.view.MaxHeightScrollView; + +import java.util.ArrayList; +import java.util.List; + +public class RichEditText extends MaxHeightScrollView { + public int DP_2 = Constants.DP.DP_2; + public int DP_4 = Constants.DP.DP_4; + public int DP_8 = Constants.DP.DP_8; + public int DP_16 = Constants.DP.DP_16; + public int DP_32 = Constants.DP.DP_32; + + + private static final int VIEW_TAG_VALUE_IMAGE_URI = 0x2000002; + private static final int VIEW_TAG_VALUE_IMAGE_URL = 0x2000003; + + private static final String VIEW_TAG_KV_INPUT = "input"; + private static final String VIEW_TAG_KV_IMAGE = "image"; + + private LinearLayout container; + private LayoutInflater inflater; + private OnKeyListener keyListener; + private OnRichAtListener onRichAtListener; + + private OnClickListener onCloseClickListener; + + private OnFocusChangeListener focusListener; + + private OnRichImageClickListener onRichImageStatusChangeListener; + + private EditText lastFocusEdit; + + private int removingImageIndex = 0; + + public RichEditText(Context context) { + super(context); + + init(context); + } + + public RichEditText(Context context, AttributeSet attrs) { + super(context, attrs); + + init(context); + } + + public RichEditText(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + inflater = LayoutInflater.from(context); + + initLayoutView(context); + + initListener(); + + initFirstEditText(); + } + + private void initLayoutView(Context context) { + container = new LinearLayout(context); + container.setOrientation(LinearLayout.VERTICAL); + + LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + addView(container, layoutParams); + } + + private void initFirstEditText() { + LinearLayout.LayoutParams firstEditParam = new LinearLayout.LayoutParams(-1, -2); + EditText firstEdit = buildEditText(); + + container.addView(firstEdit, firstEditParam); + lastFocusEdit = firstEdit; + } + + private void initListener() { + keyListener = (v, keyCode, event) -> { + if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DEL) { + EditText edit = (EditText) v; + onBackspacePress(edit); + } + return false; + }; + + // + onCloseClickListener = v -> { + if (v.getId() == R.id.remove) { + FrameLayout parentView = (FrameLayout) v.getParent(); + remove(parentView); + } + }; + + // + focusListener = (v, hasFocus) -> { + if (hasFocus) { + lastFocusEdit = (EditText) v; + } + }; + } + + + private void onBackspacePress(EditText editText) { + if (editText == null) { + return; + } + + int startSelection = editText.getSelectionStart(); + if (startSelection != 0) { + return; + } + + int indexOfChild = container.indexOfChild(editText); + if (indexOfChild <= 0) { + return; + } + + View preChildView = container.getChildAt(indexOfChild - 1); + if (null == preChildView) { + return; + } + + if (preChildView instanceof FrameLayout) { + remove(preChildView); + } else if (preChildView instanceof EditText) { + String str1 = editText.getText().toString(); + + EditText preEditText = (EditText) preChildView; + String str2 = preEditText.getText().toString(); + + container.removeView(editText); + + preEditText.setText(String.format("%s%s", str2, str1)); + preEditText.requestFocus(); + preEditText.setSelection(str2.length(), str2.length()); + + lastFocusEdit = preEditText; + } + } + + public void remove(View view) { + removingImageIndex = container.indexOfChild(view); + container.removeView(view); + mergeEditText(); + } + + public void setOnRichImageStatusChangeListener(OnRichImageClickListener l) { + this.onRichImageStatusChangeListener = l; + } + + + public void setOnRichAtListener(OnRichAtListener onRichAtListener) { + this.onRichAtListener = onRichAtListener; + } + + public void insertImage(Uri uri) { + if (null == uri) { + return; + } + + int lastEditIndex = container.indexOfChild(lastFocusEdit); + if (lastEditIndex + 1 < container.getChildCount()) { + View v = container.getChildAt(lastEditIndex + 1); + if (v instanceof FrameLayout) { + addImageViewAtIndex(lastEditIndex + 1, uri); + addEditTextAtIndex(lastEditIndex + 2, ""); + } else { + addImageViewAtIndex(lastEditIndex + 1, uri); + } + } else { + addImageViewAtIndex(-1, uri); + addEditTextAtIndex(-1, ""); + } + } + + private void addEditTextAtIndex(final int index, CharSequence editStr) { + EditText editText = buildEditText(); + editText.setText(editStr); + + if (index == -1) { + container.addView(editText); + } else { + container.addView(editText, index); + } + + lastFocusEdit = editText; + lastFocusEdit.requestFocus(); + lastFocusEdit.setSelection(editStr.length(), editStr.length()); + } + + private void addImageViewAtIndex(final int index, final Uri uri) { + LayoutUploadFileBinding uploadFileBinding = LayoutUploadFileBinding.inflate(inflater); + uploadFileBinding.getRoot().setTag(VIEW_TAG_KV_IMAGE); + uploadFileBinding.getRoot().setTag(VIEW_TAG_VALUE_IMAGE_URI, uri.toString()); + uploadFileBinding.remove.setOnClickListener(onCloseClickListener); + uploadFileBinding.uploadImage.setOnClickListener(v -> { + if (onRichImageStatusChangeListener != null) { + onRichImageStatusChangeListener.onClick(uploadFileBinding.uploadImage, uri.toString()); + } + }); + + + LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(SizeUtils.dp2px(96), SizeUtils.dp2px(96)); + llp.topMargin = DP_2; + llp.bottomMargin = DP_2; + uploadFileBinding.getRoot().setLayoutParams(llp); + + Glide.with(this) + .load(uri) + .centerCrop() + .into(uploadFileBinding.uploadImage); + + if (index == -1) { + container.addView(uploadFileBinding.getRoot()); + } else { + container.addView(uploadFileBinding.getRoot(), index); + } + } + + private void mergeEditText() { + try { + View preView = container.getChildAt(removingImageIndex - 1); + View nextView = container.getChildAt(removingImageIndex); + if (preView instanceof EditText && nextView instanceof EditText) { + EditText preEdit = (EditText) preView; + + EditText nextEdit = (EditText) nextView; + String str1 = preEdit.getText().toString(); + String str2 = nextEdit.getText().toString(); + String mergeText = ""; + if (str2.length() > 0) { + mergeText = str1 + "\n" + str2; + } else { + mergeText = str1; + } + + container.removeView(nextEdit); + preEdit.setText(mergeText); + //设置光标的定位 + preEdit.requestFocus(); + preEdit.setSelection(str1.length(), str1.length()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void updateUploadState(String uri, String url) { + int c = container.getChildCount(); + if (c == 0) { + return; + } + View frame = null; + for (int i = 0; i < c; i++) { + String key = container.getChildAt(i).getTag().toString(); + if (!VIEW_TAG_KV_IMAGE.equals(key)) { + continue; + } + String uriKey = container.getChildAt(i).getTag(VIEW_TAG_VALUE_IMAGE_URI).toString(); + if (!uriKey.equals(uri)) { + continue; + } + frame = container.getChildAt(i); + break; + } + + if (null == frame) { + return; + } + frame.setTag(VIEW_TAG_VALUE_IMAGE_URL, url); + frame.findViewById(R.id.upload_progress_bar).setVisibility(View.GONE); + frame.findViewById(R.id.remove).setVisibility(View.VISIBLE); + } + + + private EditText buildEditText() { + EditText editText = new DeletableEditText(getContext()); + LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + editText.setLayoutParams(layoutParams); + editText.setTextSize(16); + editText.setCursorVisible(true); + editText.setBackground(null); + editText.setOnKeyListener(keyListener); + editText.setOnFocusChangeListener(focusListener); + editText.setPadding(0, DP_4, 0, DP_4); + editText.setTag(VIEW_TAG_KV_INPUT); + editText.setFilters(new InputFilter[]{new InputFilter() { + @Override + public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { + if (TextUtils.equals("@", source)) { + if (onRichAtListener != null) { + onRichAtListener.onCall(editText); + } + } + return null; + } + }}); + return editText; + } + + public List buildRichEditData() { + List dataList = new ArrayList<>(); + int num = container.getChildCount(); + for (int index = 0; index < num; index++) { + View itemView = container.getChildAt(index); + RichContentModel richContentModel = new RichContentModel(); + if (itemView instanceof EditText) { + EditText item = (EditText) itemView; + richContentModel.content = item.getText().toString(); + richContentModel.type = 0; + } else if (itemView instanceof FrameLayout) { + Object obj = itemView.getTag(VIEW_TAG_VALUE_IMAGE_URL); + if (null == obj) { + return null; + } + + richContentModel.content = obj.toString(); + richContentModel.type = 1; + } + if (!TextUtils.isEmpty(richContentModel.content)) { + dataList.add(richContentModel); + } + } + return dataList; + } + + public void removeAllViews() { + if (container != null) { + container.removeAllViews(); + + initFirstEditText(); + } + } + + public static class RichContentModel{ + public RichContentModel() { + } + + public RichContentModel(int type, String content) { + this.type = type; + this.content = content; + } + + /** + * content type, 0 is text, 1 is image + */ + public int type = 0; + public String content; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/PreloadWebView.java b/app/src/main/java/com/seafile/seadroid2/view/webview/PreloadWebView.java index 24c8885df..cfef3de47 100644 --- a/app/src/main/java/com/seafile/seadroid2/view/webview/PreloadWebView.java +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/PreloadWebView.java @@ -5,6 +5,7 @@ import android.content.MutableContextWrapper; import android.os.Looper; +import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeadroidApplication; import java.util.Stack; @@ -35,7 +36,9 @@ public void preload() { } private SeaWebView buildWebView() { - return new SeaWebView(new MutableContextWrapper(SeadroidApplication.getAppContext())); + SeaWebView webView = new SeaWebView(new MutableContextWrapper(SeadroidApplication.getAppContext())); + webView.setId(R.id.webview); + return webView; } /** 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 7d37b0781..cc877c532 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 @@ -12,8 +12,9 @@ import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.view.NestedWebView; -public class SeaWebView extends WebView { +public class SeaWebView extends NestedWebView { public static final String PATH_ACCOUNT_LOGIN = "accounts/login/"; public static String URL_LOGIN = null; @@ -34,10 +35,10 @@ public SeaWebView(@NonNull Context context, @Nullable AttributeSet attrs, int de init(); } - public SeaWebView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - init(); - } +// public SeaWebView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { +// super(context, attrs, defStyleAttr, defStyleRes); +// init(); +// } @SuppressLint("SetJavaScriptEnabled") private void init() { diff --git a/app/src/main/java/com/seafile/seadroid2/widget/prefs/ButtonPreference.java b/app/src/main/java/com/seafile/seadroid2/widget/prefs/ButtonPreference.java new file mode 100644 index 000000000..40a73c3e3 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/widget/prefs/ButtonPreference.java @@ -0,0 +1,57 @@ +package com.seafile.seadroid2.widget.prefs; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import com.google.android.material.button.MaterialButton; +import com.seafile.seadroid2.R; + +public class ButtonPreference extends Preference { + public ButtonPreference(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + public ButtonPreference(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + public ButtonPreference(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public ButtonPreference(@NonNull Context context) { + super(context); + init(); + } + + private void init() { + setLayoutResource(R.layout.layout_logout_view); + } + + public MaterialButton getButton() { + return button; + } + + MaterialButton button; + + @Override + protected void onClick() { + super.onClick(); + } + + @Override + public void onBindViewHolder(@NonNull PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + + button = (MaterialButton) holder.findViewById(android.R.id.title); + button.setText(getTitle()); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/widget/prefs/CardPreferenceCategory.java b/app/src/main/java/com/seafile/seadroid2/widget/prefs/CardPreferenceCategory.java new file mode 100644 index 000000000..c6b18af94 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/widget/prefs/CardPreferenceCategory.java @@ -0,0 +1,49 @@ +package com.seafile.seadroid2.widget.prefs; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceViewHolder; + +import com.seafile.seadroid2.R; + +public class CardPreferenceCategory extends PreferenceCategory { + public CardPreferenceCategory(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + public CardPreferenceCategory(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + public CardPreferenceCategory(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public CardPreferenceCategory(@NonNull Context context) { + super(context); + init(); + } + + private void init() { + setLayoutResource(R.layout.preference_cardbox); + } + + @Override + public void onBindViewHolder(@NonNull PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + + TextView titleView = (TextView) holder.findViewById(android.R.id.title); + TextView summaryView = (TextView) holder.findViewById(android.R.id.summary); + titleView.setText(getTitle()); + summaryView.setText(getSummary()); + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/widget/TipTextPreference.java b/app/src/main/java/com/seafile/seadroid2/widget/prefs/TipTextPreference.java similarity index 93% rename from app/src/main/java/com/seafile/seadroid2/widget/TipTextPreference.java rename to app/src/main/java/com/seafile/seadroid2/widget/prefs/TipTextPreference.java index 1cce26c8f..106a38b10 100644 --- a/app/src/main/java/com/seafile/seadroid2/widget/TipTextPreference.java +++ b/app/src/main/java/com/seafile/seadroid2/widget/prefs/TipTextPreference.java @@ -1,15 +1,13 @@ -package com.seafile.seadroid2.widget; +package com.seafile.seadroid2.widget.prefs; import android.content.Context; import android.util.AttributeSet; -import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; -import com.blankj.utilcode.util.ToastUtils; import com.seafile.seadroid2.R; public class TipTextPreference extends Preference { diff --git a/app/src/main/res/drawable-xhdpi/tip_no_items.png b/app/src/main/res/drawable-xhdpi/tip_no_items.png new file mode 100644 index 0000000000000000000000000000000000000000..3ad016f578447db4352f1a9eb52787d4b19c00e8 GIT binary patch literal 4221 zcmXX}c{r5a8y*QGTgW!qV=|JANeJ1-PK-4>V^?+!$y$+hEF<3-*_9^yk|j&BWi%1V zzB96A&t87-_w{?O>pj=~+|T{o=RD{9^~M?9)nd8;xc~xzm~^y}#vl+C_rD*Q8el4o zcv^uZ(n#M_14w6PW)>G0S65dzHa7P5_Li5IXJ=<8{tF6)GBq{zE5HB&$N&uR000aDJU%|YzP=8W{)+!H00{(u0XM)LC{0gKkByD}?FGaD z0BpCmwg4YM2?&7ruj&7IWn~3m0L@=#00ZFfNq+wP2`GU^fC2FQe=#~by0f#hy}kX{ z1mOR5+}HpB&;?8c3=Ux67y!AjumH3Jw*lM$U;?aw2T%n9sP^^s0cZa5<;&j{=mjuP zB9qAg_^UiPH~{tnoPnXv&dz|#Tw7ZMJpWpqoSXpWOG`_?fBy!$j*gBN%AZ97Z{QPX zeAfi@yP1iehV{zv{*pM~8iH$czBp#6WOtucuD2=s{$=*Ad-+FAd1}X|r#xNcG+K{h zE0)T_F2in^N<6V_!1Xdf|J+slU$A zYeh+!{sZ%L%3Izxr4uGT*Oz(&axZ<hO&>#FULp7Ntya$1)C+>yi|B+H1>NGR%*N!p^qhJN8xCnM@ICD2Op#oCka{^uc1WAx z|FfmqVV!1>=Al9LRbAd)ft%V8wdC|7BdBnf=~?vPi|3v?A&Ivk@tLJf#hQ@n>tSEq z)`=4pgeTeI0%v)Sib0c2QUplo#0Q?f68o+hSh-GL4(4;yIIl^=1?5`nLx%B0=Uq#m zLz9eRb_D-H$n6bCDr4?DTLH!fm%h*9Xo7NmBcf-H|3{`9+McJx%rtax=BkJ~N09p* z;_8~FGotNPhPCxvx!RAoll!_W8hP+8pZW3f7-xCqiBv8Q7O?iQc9AS%y4Bv}@OE|) zLi61H>|e;;H_=(OBdR>aahUQ4A6#uss2W5eU~*3ra(nO|32}w#c~Lec$<~`w<5g3B z-wTrR7MDTmsBBRG! zk*Q}F^brdNS}i;pHHzLuLcOHTS2liYmAi%(DI$Ae!M@|Nom&5JqK?hVCqf+L3vwkL7kyW9({4(J=c7FGMBWgLbf#%6j#0tq3e zDQ0E<92W6R4=fG5y)GQSMZ4}kdN|0NkvMaDN-gWPkQz>Yqaf{8=Rjd4*_#a}>JpnlHvP_}$#ldrSP&sQ zF{h{OXxl$tv*+mr{@08=0lhrGKX;Z6Jd>c}_V0faW>FfDMrk)MwbCecz{PAQ{nG32 zIgC0Lt9Wzk$V*^M!}S0jF{;onQ0yqh+_c_uFs%-Ti{7aeQHg~`;n5Ke4898jr2De+ zha)-$s%;Y&Wp6>Xf?HqSwICK>4d+|SsrzKfFl$s<_FF(V0C6zy&=BSrcbG+1tn|@T zvHPy%Fhd7dlA}+;FR$!yo_-$aQoQsns_>F6x!J?d{V|Puo5-I_ph3bXcNN2ABZV3| zydYXyE%0rTDWVuHTV%qxyPHEp|XGl{r}&&wMuTLQkauhSQ2hC$zqd zd{kDUj{x<6(EjQ8P96QI{RjtgX>lZqs`DZ!yCE*s{p@+d^tqJii-lCiSadpVrFJdB z4Me<%HuZL%7l*1(C=brvrjlo8=a$rDm9nqR)B+V%;3aNAIdFwj0zVZ`nDbr~nlGeTmkCVUd?sr_gKDWaOc+BUvPqK!ex8rGIbl?_qo6-;>mdm;E9 z=dzjj+!&q8aV$wn#1Ilo8ffTk;fNq`MVjA1-#k%&T7zdyI^*hS`VmE|Y3h4kWmObP zrwvW&9Q_&OTG()~5|-y__G^y?WNe2{D=^Yghr=SX4Y1r5%3&93oS7{WcX-e3yha=2 zLSBYmKQF<_UQ@icvqDj0jr^kU_APyi4Eq%sg(iK;ZV(QW{>n%1dd5M23t~%?5L8+~ z$Sa1#!N63V&TWf)l%poYj1eC$dgbBkP%C?sfzeIYD~}zXzlWmJHF4PL$8x%VvdEJQ zrv470GEol%q0dDPn!7H!524D#LmGz^k`yzwGIj&TlEG4*{%}}~d*)$6s{1g?&U@EJ z;(lL$@#X`|M$AgZcai&oP&yS%V{iiMwWZdWXy{mN08bLWKKy`eUqVl$MW^T6h&pyU zgZj?0M+W+B43L83Rqi%2nc9gr*VEUoY5sbk6(&v9nV_UDL4D zgC3+q3tUF5-=$fiEQ#%~A6rZ={dG5O~{4O6Qh2P%=3#PAtzBWMf-tK*=b$pGby`0C+ zz)BQau_6}0VlzC`5GuG>F!#O43>`j#a`n2srE=Dp&7&Y2V

6*t-9^M(!fJ$p~s+ zprIgQb}ZkZM#Csr<_N2bY`|ZV;iJq;E1}0USefk%4n-5_Bf{UViLGhw?m?G%k}Yud zJ@fGc@yY(STA5CtR#LzW70}Q$eF$A-QniVe<)P^skFa1fVKG#6!sQYjb>3`^%?yfv zt(c)pz)AD_uT`RUk&_!t!%NHZjX>P&mS_77v_!0Jme89`F^gOOj=6KKuqyePqwIrE zShjs^XNgYNJXVu-U$5a|dK)PX*nfZ(N(?XA{EkU?u=WX)sp^TvIg>v18B-0T&>u_U zUTxuvB5Ooo`zNSinK@j#xc#XcsST5UxaEmX6+@v&RQM_eQXzG)IXeQAmKc9NS9BUR zUsJK_9jUL8hY4XA#j@poUbtmRDz^*fwICwD9TWmDSipoct*WTPvL(LIE+fg4~2)gYvb#tjbq+zL^fBz#t_ECG92?=Vw-x3 zGoaJzsJn#;q2ca&L*756W0xMtA1vo%US6 zX$$zrFE7uxWZB`b6!-5nBuQQ&xZ?T3B)hN6Q5ndPFNa|fiMQ^@)iS7yc2_G7!pZzj zlp+`IYALKbA;v>0-r#)FFXYMkv!xkfO;^GH!D-3J_Fk zZ}vm^8h=$MWdzcz$IGP$cSj3dz7Q&XL!XL<v^0V|HDO^#bF(|=@IDo}O3f#gx7(x5_Jc&j1FMX=#jQ?}(@P_wUm|4pP#HpX{TL1O=L`kB5Xu%2^*~5YrxyC+{tZc>M*loORAXb66ywr=Tod*1VuXT)zw@r zD|+GFkkqK??(h4A?BQ!|JNEjfN0h|_p4Lv|;pv-N99&`KtyiuF#?dBkajqISKgnhu z4Sw1DL*2F3s#_Nj`r)63z%JXI99ztCA+z#>#;GCU=7cQySv!h$)?AL%SuJdspJn;v zm80E3b>>33rL6>ey;XEmg;(vYowb6?5ZMh6sjIE84xH3=K1sRZo-Rnq|EFS2I2HA+ zMN#$2gXyttSQ-VolHIOkzE;N}+;34Lu{-TzS=SpNglsCuzO$AKGuGwtx_mY^DBjrt bMAKQ@ZZFbbi*Ez?hXv_q+(nkFIXw9vww-pr literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/baseline_delete_20.xml b/app/src/main/res/drawable/baseline_delete_20.xml new file mode 100644 index 000000000..08172bb13 --- /dev/null +++ b/app/src/main/res/drawable/baseline_delete_20.xml @@ -0,0 +1,36 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_calendar_alt_solid.xml b/app/src/main/res/drawable/ic_calendar_alt_solid.xml new file mode 100644 index 000000000..028421067 --- /dev/null +++ b/app/src/main/res/drawable/ic_calendar_alt_solid.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_check_square_solid.xml b/app/src/main/res/drawable/ic_check_square_solid.xml new file mode 100644 index 000000000..e251c6c4c --- /dev/null +++ b/app/src/main/res/drawable/ic_check_square_solid.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_duration.xml b/app/src/main/res/drawable/ic_duration.xml new file mode 100644 index 000000000..12972fb5c --- /dev/null +++ b/app/src/main/res/drawable/ic_duration.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_email.xml b/app/src/main/res/drawable/ic_email.xml new file mode 100644 index 000000000..79c91cb4d --- /dev/null +++ b/app/src/main/res/drawable/ic_email.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_file_alt_solid.xml b/app/src/main/res/drawable/ic_file_alt_solid.xml new file mode 100644 index 000000000..ed3ad1697 --- /dev/null +++ b/app/src/main/res/drawable/ic_file_alt_solid.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_links.xml b/app/src/main/res/drawable/ic_links.xml new file mode 100644 index 000000000..43fc4c7bd --- /dev/null +++ b/app/src/main/res/drawable/ic_links.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_location.xml b/app/src/main/res/drawable/ic_location.xml new file mode 100644 index 000000000..8195fd482 --- /dev/null +++ b/app/src/main/res/drawable/ic_location.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_long_text.xml b/app/src/main/res/drawable/ic_long_text.xml new file mode 100644 index 000000000..ad2942619 --- /dev/null +++ b/app/src/main/res/drawable/ic_long_text.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_multiple_selection.xml b/app/src/main/res/drawable/ic_multiple_selection.xml new file mode 100644 index 000000000..6a5d53834 --- /dev/null +++ b/app/src/main/res/drawable/ic_multiple_selection.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_number.xml b/app/src/main/res/drawable/ic_number.xml new file mode 100644 index 000000000..f6b7e21eb --- /dev/null +++ b/app/src/main/res/drawable/ic_number.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_picture.xml b/app/src/main/res/drawable/ic_picture.xml new file mode 100644 index 000000000..3d16723db --- /dev/null +++ b/app/src/main/res/drawable/ic_picture.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_single_election.xml b/app/src/main/res/drawable/ic_single_election.xml new file mode 100644 index 000000000..72f3182c6 --- /dev/null +++ b/app/src/main/res/drawable/ic_single_election.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_single_line_text.xml b/app/src/main/res/drawable/ic_single_line_text.xml new file mode 100644 index 000000000..95dfa614f --- /dev/null +++ b/app/src/main/res/drawable/ic_single_line_text.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_star2.xml b/app/src/main/res/drawable/ic_star2.xml new file mode 100644 index 000000000..3077dd5bc --- /dev/null +++ b/app/src/main/res/drawable/ic_star2.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_url.xml b/app/src/main/res/drawable/ic_url.xml new file mode 100644 index 000000000..e9745ae41 --- /dev/null +++ b/app/src/main/res/drawable/ic_url.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_user_collaborator.xml b/app/src/main/res/drawable/ic_user_collaborator.xml new file mode 100644 index 000000000..eb65d12d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_user_collaborator.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/indicator_drawable_2.xml b/app/src/main/res/drawable/indicator_drawable_2.xml deleted file mode 100644 index 5df9eb433..000000000 --- a/app/src/main/res/drawable/indicator_drawable_2.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_password_toggle.xml b/app/src/main/res/drawable/selector_password_toggle.xml new file mode 100644 index 000000000..1148b17a1 --- /dev/null +++ b/app/src/main/res/drawable/selector_password_toggle.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/indicator_drawable.xml b/app/src/main/res/drawable/shape_solid_ff_radius_8.xml similarity index 64% rename from app/src/main/res/drawable/indicator_drawable.xml rename to app/src/main/res/drawable/shape_solid_ff_radius_8.xml index fa55bc439..f5b590238 100644 --- a/app/src/main/res/drawable/indicator_drawable.xml +++ b/app/src/main/res/drawable/shape_solid_ff_radius_8.xml @@ -1,6 +1,5 @@ - + - + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_solid_grey100_radius_8.xml b/app/src/main/res/drawable/shape_solid_grey100_radius_8.xml new file mode 100644 index 000000000..073a68784 --- /dev/null +++ b/app/src/main/res/drawable/shape_solid_grey100_radius_8.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_stroke1_radius8_solid_grey.xml b/app/src/main/res/drawable/shape_stroke1_radius8_solid_grey.xml new file mode 100644 index 000000000..2213cd04b --- /dev/null +++ b/app/src/main/res/drawable/shape_stroke1_radius8_solid_grey.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_stroke1_radius8_solid_white.xml b/app/src/main/res/drawable/shape_stroke1_radius8_solid_white.xml new file mode 100644 index 000000000..c3c79cb42 --- /dev/null +++ b/app/src/main/res/drawable/shape_stroke1_radius8_solid_white.xml @@ -0,0 +1,8 @@ + + + + + + \ 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 cff6027f0..bc1a2183a 100644 --- a/app/src/main/res/layout/account_detail.xml +++ b/app/src/main/res/layout/account_detail.xml @@ -7,34 +7,31 @@ - + android:paddingHorizontal="@dimen/app_padding_horizontal"> - - + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/app_widget_top_margin_small" + android:hint="@string/email_hint" + app:endIconDrawable="@drawable/clear_text" + app:endIconMode="clear_text"> - - - - - - - - - + + + + - - - - - - - - + + + android:layout_marginBottom="@dimen/et_margin_bottom" + android:importantForAutofill="auto" + android:inputType="textPassword" /> + + - - + android:layout_marginTop="@dimen/app_widget_top_margin_large" + android:layout_marginEnd="@dimen/app_padding_horizontal" /> diff --git a/app/src/main/res/layout/account_list_footer.xml b/app/src/main/res/layout/account_list_footer.xml index 9f11c3d19..01ff7433c 100644 --- a/app/src/main/res/layout/account_list_footer.xml +++ b/app/src/main/res/layout/account_list_footer.xml @@ -14,5 +14,6 @@ android:layout_marginHorizontal="16dp" android:text="@string/add_account" android:textAlignment="center" + android:textColor="@color/white" android:textSize="@dimen/long_btn_txt_size" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_sdoc_comment.xml b/app/src/main/res/layout/activity_sdoc_comment.xml new file mode 100644 index 000000000..5a4010868 --- /dev/null +++ b/app/src/main/res/layout/activity_sdoc_comment.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_sea_webview.xml b/app/src/main/res/layout/activity_sea_webview.xml index 4110d1950..2df67fa36 100644 --- a/app/src/main/res/layout/activity_sea_webview.xml +++ b/app/src/main/res/layout/activity_sea_webview.xml @@ -1,17 +1,38 @@ - + android:layout_height="match_parent"> + + - + - + + + + + - \ No newline at end of file + android:layout_height="match_parent" + android:fillViewport="true" + android:fitsSystemWindows="true" + app:layout_behavior="@string/appbar_scrolling_view_behavior"> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_sea_webview_pro.xml b/app/src/main/res/layout/activity_sea_webview_pro.xml index 522a79a81..6820b1abd 100644 --- a/app/src/main/res/layout/activity_sea_webview_pro.xml +++ b/app/src/main/res/layout/activity_sea_webview_pro.xml @@ -12,15 +12,16 @@ android:fitsSystemWindows="true"> + app:layout_scrollFlags="scroll|enterAlways" + app:toolbarId="@+id/toolbar_actionbar"> + layout="@layout/toolbar_actionbar_progress_bar" + app:layout_collapseMode="parallax" /> @@ -29,27 +30,61 @@ android:id="@+id/nsv" android:layout_width="match_parent" android:layout_height="match_parent" - android:fillViewport="false" + android:fillViewport="true" android:fitsSystemWindows="true" app:layout_behavior="@string/appbar_scrolling_view_behavior"> - + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_sdoc_directory.xml b/app/src/main/res/layout/dialog_sdoc_directory.xml new file mode 100644 index 000000000..52f52f3ab --- /dev/null +++ b/app/src/main/res/layout/dialog_sdoc_directory.xml @@ -0,0 +1,24 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_sdoc_profile.xml b/app/src/main/res/layout/dialog_sdoc_profile.xml new file mode 100644 index 000000000..1d72c36c2 --- /dev/null +++ b/app/src/main/res/layout/dialog_sdoc_profile.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_ssl_confirm.xml b/app/src/main/res/layout/dialog_ssl_confirm.xml index 326eac4b0..70c75583d 100644 --- a/app/src/main/res/layout/dialog_ssl_confirm.xml +++ b/app/src/main/res/layout/dialog_ssl_confirm.xml @@ -1,122 +1,125 @@ - - - + android:layout_height="match_parent"> + + - - - - + android:layout_margin="16dp" + android:orientation="vertical"> + android:orientation="vertical" + android:textColor="@color/black"> + + + + + + + + + + + + + + + + + android:text="@string/period_of_validity" + android:textColor="@color/black" + android:textSize="@dimen/dialog_msg_title_txt_size" + android:textStyle="bold" /> - - - - - - - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/item_dirent.xml b/app/src/main/res/layout/item_dirent.xml index fbaa61f7c..909e9dc60 100644 --- a/app/src/main/res/layout/item_dirent.xml +++ b/app/src/main/res/layout/item_dirent.xml @@ -102,15 +102,12 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> - + app:dividerInsetStart="@dimen/rv_item_icon_width" + app:layout_constraintBottom_toBottomOf="parent" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_repo.xml b/app/src/main/res/layout/item_repo.xml index 8b93c2c08..2e43f96c0 100644 --- a/app/src/main/res/layout/item_repo.xml +++ b/app/src/main/res/layout/item_repo.xml @@ -72,4 +72,13 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_sdoc_comment.xml b/app/src/main/res/layout/item_sdoc_comment.xml new file mode 100644 index 000000000..ee802319c --- /dev/null +++ b/app/src/main/res/layout/item_sdoc_comment.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_sdoc_directory.xml b/app/src/main/res/layout/item_sdoc_directory.xml new file mode 100644 index 000000000..814086dc7 --- /dev/null +++ b/app/src/main/res/layout/item_sdoc_directory.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_starred.xml b/app/src/main/res/layout/item_starred.xml index d1e20d7ba..59a802eed 100644 --- a/app/src/main/res/layout/item_starred.xml +++ b/app/src/main/res/layout/item_starred.xml @@ -15,7 +15,7 @@ android:layout_centerVertical="true" android:layout_marginVertical="4dp" android:contentDescription="@null" - android:padding="8dp" + android:padding="12dp" android:scaleType="centerCrop" android:src="@drawable/baseline_repo_24" app:layout_constraintBottom_toBottomOf="parent" diff --git a/app/src/main/res/layout/item_user_avatar.xml b/app/src/main/res/layout/item_user_avatar.xml new file mode 100644 index 000000000..42d1799e7 --- /dev/null +++ b/app/src/main/res/layout/item_user_avatar.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_avatar_username_round.xml b/app/src/main/res/layout/layout_avatar_username_round.xml new file mode 100644 index 000000000..d1383ab2a --- /dev/null +++ b/app/src/main/res/layout/layout_avatar_username_round.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_detail_text_round.xml b/app/src/main/res/layout/layout_detail_text_round.xml new file mode 100644 index 000000000..2acc09fa4 --- /dev/null +++ b/app/src/main/res/layout/layout_detail_text_round.xml @@ -0,0 +1,40 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_details_keyview_valuecontainer.xml b/app/src/main/res/layout/layout_details_keyview_valuecontainer.xml new file mode 100644 index 000000000..ef1c101e0 --- /dev/null +++ b/app/src/main/res/layout/layout_details_keyview_valuecontainer.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_empty.xml b/app/src/main/res/layout/layout_empty.xml new file mode 100644 index 000000000..dce427ed0 --- /dev/null +++ b/app/src/main/res/layout/layout_empty.xml @@ -0,0 +1,29 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_image.xml b/app/src/main/res/layout/layout_image.xml new file mode 100644 index 000000000..3ae1608ab --- /dev/null +++ b/app/src/main/res/layout/layout_image.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_logout_view.xml b/app/src/main/res/layout/layout_logout_view.xml new file mode 100644 index 000000000..23df0393a --- /dev/null +++ b/app/src/main/res/layout/layout_logout_view.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_textview.xml b/app/src/main/res/layout/layout_textview.xml new file mode 100644 index 000000000..435dacf15 --- /dev/null +++ b/app/src/main/res/layout/layout_textview.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_upload_file.xml b/app/src/main/res/layout/layout_upload_file.xml new file mode 100644 index 000000000..8f818e769 --- /dev/null +++ b/app/src/main/res/layout/layout_upload_file.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/preference_cardbox.xml b/app/src/main/res/layout/preference_cardbox.xml new file mode 100644 index 000000000..2e8c4d8bb --- /dev/null +++ b/app/src/main/res/layout/preference_cardbox.xml @@ -0,0 +1,29 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_dialog_share_password.xml b/app/src/main/res/layout/view_dialog_share_password.xml index e02402b60..7cd2ad5fc 100644 --- a/app/src/main/res/layout/view_dialog_share_password.xml +++ b/app/src/main/res/layout/view_dialog_share_password.xml @@ -9,7 +9,7 @@ android:layout_marginHorizontal="4dp" android:orientation="vertical"> - - -

+ + android:title="@string/tabs_library" + app:showAsAction="ifRoom" /> + android:title="@string/tabs_starred" + app:showAsAction="ifRoom" /> + android:title="@string/tabs_activity" + app:showAsAction="ifRoom" /> + android:title="@string/settings" + app:showAsAction="ifRoom" /> \ No newline at end of file diff --git a/app/src/main/res/menu/browser_menu.xml b/app/src/main/res/menu/browser_menu.xml index dcbf154c1..2152704c1 100644 --- a/app/src/main/res/menu/browser_menu.xml +++ b/app/src/main/res/menu/browser_menu.xml @@ -57,7 +57,8 @@ - + + + android:title="@string/menu_action_sort_directories_first" + android:visible="false" /> @@ -85,8 +87,8 @@ app:showAsAction="never" /> diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 5ca3c8e25..7c6a333f9 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -480,7 +480,6 @@ 파일 업로드 중... 파일 다운로드 중... 업로드를 끝냈습니다 - %1$d%% %2$s 파일 %1$d개 업로드 중(%2$d%%) @@ -628,4 +627,6 @@ 최근 수정 오름 차순 폴더 먼저 + + diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml index 902354cdc..f0ddbbce5 100644 --- a/app/src/main/res/values-night/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -33,7 +33,8 @@ - - - - - - - - - - - - - - - + + + + +