From 18d89c186e103ef480dbd971e576dd4b81826d84 Mon Sep 17 00:00:00 2001 From: zhwanng <48609908+zhwanng@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:46:21 +0800 Subject: [PATCH 01/17] Update build.gradle bump to v3.0.7, build 158 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 7111c400d..7ed04e2c4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,8 +14,8 @@ android { targetSdkVersion rootProject.ext.targetSdkVersion compileSdk rootProject.ext.compileSdkVersion - versionCode 157 - versionName "3.0.6" + versionCode 158 + versionName "3.0.7" multiDexEnabled true resValue "string", "authorities", defaultConfig.applicationId + '.debug.cameraupload.provider' From 6edbdc5f0005adefc58af83beb90eb5787cfca44 Mon Sep 17 00:00:00 2001 From: zhwanng <48609908+zhwanng@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:16:54 +0800 Subject: [PATCH 02/17] new Image preview UI --- app/build.gradle | 2 + app/src/main/AndroidManifest.xml | 1 + .../framework/data/db/dao/DirentDAO.java | 14 +- .../data/db/entities/StarredModel.java | 3 +- .../data/model/activities/ActivityModel.java | 4 +- .../data/model/search/SearchModel.java | 3 +- .../data/repository/DirentsRepository.java | 2 +- .../seadroid2/framework/util/TakeCameras.java | 13 +- .../media/image_preview/CarouselAdapter.java | 73 -- .../image_preview/CarouselItemViewHolder.java | 48 -- .../image_preview/ImagePreviewActivity.java | 13 +- .../image_preview/ImagePreviewViewModel.java | 71 +- .../ui/media/image_preview/PhotoFragment.java | 94 ++- .../media/image_preview2/CarouselAdapter.java | 160 +++++ .../CarouselImagePreviewActivity.java | 420 +++++++++++ .../CarouselItem.java | 4 +- .../CarouselItemViewHolder.java} | 23 +- ...nterScaleXYRecyclerViewScrollListener.java | 121 ++++ .../image_preview2/GravitySnapHelper.java | 679 ++++++++++++++++++ .../image_preview2/LinearEdgeDecoration.kt | 62 ++ .../seadroid2/ui/repo/RepoQuickFragment.java | 9 +- .../seadroid2/ui/repo/RepoViewModel.java | 4 +- .../ui/sdoc/SDocWebViewActivity.java | 5 +- .../ui/selector/ObjSelectorActivity.java | 40 +- .../ui/transfer_list/TransferActivity.java | 19 +- .../activity_carousel_image_preview.xml | 127 ++++ .../main/res/layout/fragment_photo_view.xml | 1 + .../layout/item_carousel_item_vertical.xml | 49 +- .../main/res/menu/menu_image_list_preview.xml | 16 + app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/colors_material.xml | 2 + app/src/main/res/values/dimens.xml | 4 + app/src/main/res/values/ids.xml | 2 + app/src/main/res/values/strings.xml | 1 + build_count.txt | 2 +- 35 files changed, 1859 insertions(+), 233 deletions(-) delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/CarouselAdapter.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/CarouselItemViewHolder.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java rename app/src/main/java/com/seafile/seadroid2/ui/media/{image_preview => image_preview2}/CarouselItem.java (93%) rename app/src/main/java/com/seafile/seadroid2/ui/media/{image_preview/CarouselItemListener.java => image_preview2/CarouselItemViewHolder.java} (55%) create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CenterScaleXYRecyclerViewScrollListener.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/GravitySnapHelper.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/LinearEdgeDecoration.kt create mode 100644 app/src/main/res/layout/activity_carousel_image_preview.xml create mode 100644 app/src/main/res/menu/menu_image_list_preview.xml diff --git a/app/build.gradle b/app/build.gradle index 7ed04e2c4..ab7864caf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -191,6 +191,8 @@ android { implementation "androidx.media3:media3-exoplayer:$media3_version" implementation "androidx.media3:media3-ui:$media3_version" +// implementation 'com.github.rubensousa:gravitysnaphelper:2.2.2' + implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.core:core-splashscreen:1.0.1' implementation "androidx.appcompat:appcompat:1.7.0" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2846f793c..57b667591 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -211,6 +211,7 @@ + > getListByAccount(String related_account); @Query("select * from dirents where parent_dir = :parent_dir and repo_id = :repo_id") - Single> getListByParentPath(String repo_id, String parent_dir); - - @Query("select * from dirents where parent_dir = :parent_dir and repo_id = :repo_id") - Single> getImageListByParentPath(String repo_id, String parent_dir); - + Single> getListByParentPathAsync(String repo_id, String parent_dir); @Query("select * from dirents where parent_dir = :parent_dir and repo_id = :repo_id") List getListByParentPathSync(String repo_id, String parent_dir); + @Query("select * from dirents where type='file' and parent_dir = :parent_dir and repo_id = :repo_id") + Single> getFileListByParentPath(String repo_id, String parent_dir); + + /** + * get special one by full_path + */ @Query("select * from dirents where full_path = :full_path and repo_id = :repo_id") - Single> getListByFullPath(String repo_id, String full_path); + Single> getListByFullPathAsync(String repo_id, String full_path); @Query("select * from dirents where full_path = :full_path and repo_id = :repo_id limit 1") List getListByFullPathSync(String repo_id, String full_path); diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/StarredModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/StarredModel.java index 360cb8d70..61496e97e 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/StarredModel.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/StarredModel.java @@ -60,7 +60,7 @@ public boolean isRepo() { return TextUtils.equals("/", path) && is_dir; } - public static DirentModel converterThis2DirentModel(StarredModel model) { + public static DirentModel convert2DirentModel(StarredModel model) { DirentModel d = new DirentModel(); d.full_path = model.path; d.type = model.is_dir ? "dir" : "file"; @@ -68,6 +68,7 @@ public static DirentModel converterThis2DirentModel(StarredModel model) { d.name = model.obj_name; d.repo_id = model.repo_id; d.repo_name = model.repo_name; + d.parent_dir = Utils.getParentPath(model.path); return d; } diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/activities/ActivityModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/activities/ActivityModel.java index 08cffe25f..53cdffc6f 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/data/model/activities/ActivityModel.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/activities/ActivityModel.java @@ -47,7 +47,7 @@ public boolean isDir() { } - public static DirentModel converterThis2DirentModel(ActivityModel model){ + public static DirentModel convert2DirentModel(ActivityModel model){ DirentModel d = new DirentModel(); d.full_path = model.path; d.type = model.obj_type; @@ -55,7 +55,7 @@ public static DirentModel converterThis2DirentModel(ActivityModel model){ d.name = model.name; d.repo_id = model.repo_id; d.repo_name = model.repo_name; - + d.parent_dir = Utils.getParentPath(model.path); return d; } } diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/search/SearchModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/search/SearchModel.java index 51d5cb61d..16eea0fae 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/data/model/search/SearchModel.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/search/SearchModel.java @@ -66,7 +66,7 @@ public int getIcon() { return Icons.getFileIcon(getTitle()); } - public static DirentModel converterThis2DirentModel(SearchModel model) { + public static DirentModel convert2DirentModel(SearchModel model) { DirentModel d = new DirentModel(); d.full_path = model.fullpath; d.type = model.is_dir ? "dir" : "file"; @@ -76,6 +76,7 @@ public static DirentModel converterThis2DirentModel(SearchModel model) { d.repo_name = model.repo_name; d.last_modified_at = model.last_modified; d.size = model.size; + d.parent_dir = Utils.getParentPath(model.fullpath); return d; } diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/repository/DirentsRepository.java b/app/src/main/java/com/seafile/seadroid2/framework/data/repository/DirentsRepository.java index 809e37401..10b39602a 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/data/repository/DirentsRepository.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/repository/DirentsRepository.java @@ -27,7 +27,7 @@ public DirentsRepository(RepoService service, DirentDAO dao) { public Single> getDirents(String repoId, String parent_dir) { // 先尝试从数据库中获取数据 - Single> localData = dao.getListByParentPath(repoId, parent_dir) + Single> localData = dao.getListByParentPathAsync(repoId, parent_dir) .onErrorResumeNext(throwable -> Single.just(Collections.emptyList())); // 发起网络请求并保存到数据库 diff --git a/app/src/main/java/com/seafile/seadroid2/framework/util/TakeCameras.java b/app/src/main/java/com/seafile/seadroid2/framework/util/TakeCameras.java index 6ae1bdc0d..3c147fdea 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/util/TakeCameras.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/util/TakeCameras.java @@ -7,6 +7,8 @@ import androidx.core.content.FileProvider; import com.blankj.utilcode.util.PathUtils; +import com.blankj.utilcode.util.TimeUtils; +import com.google.android.gms.common.util.DataUtils; import com.seafile.seadroid2.BuildConfig; import java.io.File; @@ -35,15 +37,22 @@ public static File getMediaStoragePath(String folderName) { public static Pair buildTakePhotoUri(Context context) { File parentFolder = getMediaStoragePath("images"); - String fileName = "sf_photo_" + System.currentTimeMillis() + ".jpg"; + String fileName = getOneNewName("jpg"); File file = new File(parentFolder, fileName); Uri uri = FileProvider.getUriForFile(context, BuildConfig.FILE_PROVIDER_AUTHORITIES, file); return new Pair<>(uri, file); } + private static String getOneNewName(String prefix) { + long mills = System.currentTimeMillis(); + long seconds = mills / 1000; + String date = TimeUtils.millis2String(mills, "yyyyMMdd"); + return date + "_" + seconds + "." + prefix; + } + public static Pair buildTakeVideoUri(Context context) { File parentFolder = getMediaStoragePath("videos"); - String fileName = "sf_video" + System.currentTimeMillis() + ".mp4"; + String fileName = getOneNewName("mp4"); File file = new File(parentFolder, fileName); Uri uri = FileProvider.getUriForFile(context, BuildConfig.FILE_PROVIDER_AUTHORITIES, file); return new Pair<>(uri, file); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/CarouselAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/CarouselAdapter.java deleted file mode 100644 index 54053a91f..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/CarouselAdapter.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.seafile.seadroid2.ui.media.image_preview; - -import android.view.LayoutInflater; -import android.view.ViewGroup; - -import androidx.annotation.LayoutRes; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.ListAdapter; - -import com.seafile.seadroid2.framework.data.db.entities.DirentModel; - -/** - * An adapter that displays {@link CarouselItem}s for a Carousel. - */ -class CarouselAdapter extends ListAdapter { - - private static final DiffUtil.ItemCallback DIFF_CALLBACK = - new DiffUtil.ItemCallback() { - @Override - public boolean areItemsTheSame( - @NonNull DirentModel oldItem, @NonNull DirentModel newItem) { - // User properties may have changed if reloaded from the DB, but ID is fixed - return oldItem == newItem; - } - - @Override - public boolean areContentsTheSame( - @NonNull DirentModel oldItem, @NonNull DirentModel newItem) { - return false; - } - }; - - private final CarouselItemListener listener; - @LayoutRes - private final int itemLayoutRes; - - CarouselAdapter(CarouselItemListener listener, @LayoutRes int itemLayoutRes) { - super(DIFF_CALLBACK); - this.listener = listener; - this.itemLayoutRes = itemLayoutRes; - } - - @NonNull - @Override - public CarouselItemViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int pos) { - return new CarouselItemViewHolder( - LayoutInflater.from(viewGroup.getContext()) - .inflate(itemLayoutRes, viewGroup, false), listener); - } - - @Override - public void onBindViewHolder(@NonNull CarouselItemViewHolder carouselItemViewHolder, int pos) { - carouselItemViewHolder.bind(getItem(pos)); - } - -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/CarouselItemViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/CarouselItemViewHolder.java deleted file mode 100644 index e2c3bf60b..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/CarouselItemViewHolder.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.seafile.seadroid2.ui.media.image_preview; - - -import android.view.View; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.framework.data.db.entities.DirentModel; - -/** - * An {@link RecyclerView.ViewHolder} that displays an item inside a Carousel. - */ -class CarouselItemViewHolder extends RecyclerView.ViewHolder { - - private final ImageView imageView; - private final CarouselItemListener listener; - - CarouselItemViewHolder(@NonNull View itemView, CarouselItemListener listener) { - super(itemView); - imageView = itemView.findViewById(R.id.carousel_image_view); - this.listener = listener; - } - - void bind(DirentModel item) { -// Glide.with(imageView.getContext()).load(item.full_path).centerCrop().into(imageView); -// imageView.setContentDescription(imageView.getResources().getString(item.getContentDescRes())); -// itemView.setOnClickListener(v -> listener.onItemClicked(item, getBindingAdapterPosition())); - } -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewActivity.java index 46b08dad9..e827618e0 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewActivity.java @@ -50,20 +50,20 @@ public static Intent startThisFromRepo(Context context, DirentModel direntModel) public static Intent startThisFromStarred(Context context, StarredModel starredModel) { Intent intent = new Intent(context, ImagePreviewActivity.class); - intent.putExtra("dirent", StarredModel.converterThis2DirentModel(starredModel)); + intent.putExtra("dirent", StarredModel.convert2DirentModel(starredModel)); return intent; } public static Intent startThisFromActivity(Context context, ActivityModel starredModel) { Intent intent = new Intent(context, ImagePreviewActivity.class); - intent.putExtra("dirent", ActivityModel.converterThis2DirentModel(starredModel)); + intent.putExtra("dirent", ActivityModel.convert2DirentModel(starredModel)); return intent; } public static void startThisFromSearch(Context context, SearchModel starredModel) { Intent intent = new Intent(context, ImagePreviewActivity.class); - intent.putExtra("dirent", SearchModel.converterThis2DirentModel(starredModel)); + intent.putExtra("dirent", SearchModel.convert2DirentModel(starredModel)); context.startActivity(intent); } @@ -145,8 +145,12 @@ public void onChanged(Boolean aBoolean) { getViewModel().getStarLiveData().observe(this, new Observer() { @Override public void onChanged(Boolean aBoolean) { + isDataOperated = true; + if (aBoolean) { - isDataOperated = true; + + }else{ + } ToastUtils.showLong(aBoolean ? R.string.star_file_succeed : R.string.star_file_failed); } @@ -159,6 +163,7 @@ public void onChanged(List direntModels) { if (CollectionUtils.isEmpty(direntModels)) { direntModels = CollectionUtils.newArrayList(currentDirent); } + direntList = direntModels; notifyFragmentList(); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewViewModel.java index 79db9d4b4..119b9402d 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewViewModel.java @@ -1,12 +1,14 @@ package com.seafile.seadroid2.ui.media.image_preview; import android.text.TextUtils; +import android.util.Pair; import androidx.lifecycle.MutableLiveData; import com.blankj.utilcode.util.CollectionUtils; import com.blankj.utilcode.util.ToastUtils; import com.seafile.seadroid2.R; +import com.seafile.seadroid2.SeafException; import com.seafile.seadroid2.framework.data.db.AppDatabase; import com.seafile.seadroid2.framework.data.db.entities.DirentModel; import com.seafile.seadroid2.framework.data.db.entities.RepoModel; @@ -15,7 +17,6 @@ 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.ui.repo.RepoService; import com.seafile.seadroid2.framework.http.HttpIO; import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; import com.seafile.seadroid2.ui.star.StarredService; @@ -26,12 +27,19 @@ import java.util.stream.Collectors; import io.reactivex.Single; +import io.reactivex.functions.BiFunction; import io.reactivex.functions.Consumer; import okhttp3.RequestBody; +import prettify.parser.Util; public class ImagePreviewViewModel extends BaseViewModel { - private MutableLiveData> ListLiveData = new MutableLiveData<>(); + private final MutableLiveData> ListLiveData = new MutableLiveData<>(); private final MutableLiveData StarLiveData = new MutableLiveData<>(); + private final MutableLiveData>> _repoAndListLiveData = new MutableLiveData<>(); + + public MutableLiveData>> getRepoAndListLiveData() { + return _repoAndListLiveData; + } public MutableLiveData getStarLiveData() { return StarLiveData; @@ -65,13 +73,65 @@ public void accept(Throwable throwable) throws Exception { }); } + public void load(String repoId, String parentPath, String name, boolean isLoadOtherImagesInSameDirectory) { + if (TextUtils.isEmpty(repoId) || TextUtils.isEmpty(parentPath) || TextUtils.isEmpty(name)) { + return; + } + + getRefreshLiveData().setValue(true); + + Single> repoSingle = AppDatabase.getInstance().repoDao().getRepoById(repoId); + + + Single> fileSingle; + if (isLoadOtherImagesInSameDirectory) { + fileSingle = AppDatabase.getInstance().direntDao().getFileListByParentPath(repoId, parentPath); + } else { + String fullPath = Utils.pathJoin(parentPath, name); + fileSingle = AppDatabase.getInstance().direntDao().getListByFullPathAsync(repoId, fullPath); + } + + Single>> single = Single.zip(repoSingle, fileSingle, new BiFunction, List, Pair>>() { + @Override + public Pair> apply(List models, List direntModels) throws Exception { + if (CollectionUtils.isEmpty(models)) { + throw SeafException.notFoundException; + } + + RepoModel repoModel = models.get(0); + List dirents = direntModels.stream() + .filter(f -> Utils.isViewableImage(f.name)) + .collect(Collectors.toList()); + + return new Pair<>(repoModel, dirents); + } + }); + + addSingleDisposable(single, new Consumer>>() { + @Override + public void accept(Pair> repoModelListPair) throws Exception { + getRefreshLiveData().setValue(false); + getRepoAndListLiveData().setValue(repoModelListPair); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getRefreshLiveData().setValue(false); + SeafException seafException = getExceptionByThrowable(throwable); + getSeafExceptionLiveData().setValue(seafException); + } + }); + + + } + public void loadData(String repoID, String parentPath) { if (TextUtils.isEmpty(parentPath)) { getListLiveData().setValue(CollectionUtils.newArrayList()); return; } - Single> single = AppDatabase.getInstance().direntDao().getListByParentPath(repoID, parentPath); + Single> single = AppDatabase.getInstance().direntDao().getListByParentPathAsync(repoID, parentPath); addSingleDisposable(single, new Consumer>() { @Override public void accept(List direntModels) throws Exception { @@ -87,7 +147,7 @@ public void accept(List direntModels) throws Exception { public void download(String repoID, String fullPath) { - Single> single = AppDatabase.getInstance().direntDao().getListByFullPath(repoID, fullPath); + Single> single = AppDatabase.getInstance().direntDao().getListByFullPathAsync(repoID, fullPath); addSingleDisposable(single, new Consumer>() { @Override public void accept(List direntModels) throws Exception { @@ -141,8 +201,7 @@ public void unStar(String repoId, String path) { public void accept(ResultModel resultModel) throws Exception { getRefreshLiveData().setValue(false); - getStarLiveData().setValue(true); - ToastUtils.showLong(R.string.unstar); + getStarLiveData().setValue(false); } }, new Consumer() { @Override diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/PhotoFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/PhotoFragment.java index d735d9062..b71c3192a 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/PhotoFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/PhotoFragment.java @@ -10,48 +10,60 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.webkit.CookieManagerCompat; -import androidx.webkit.UserAgentMetadata; import com.blankj.utilcode.util.EncodeUtils; import com.blankj.utilcode.util.SizeUtils; import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.GlideException; -import com.bumptech.glide.load.resource.gif.GifDrawable; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.target.Target; import com.github.chrisbanes.photoview.OnPhotoTapListener; -import com.github.chrisbanes.photoview.PhotoView; import com.seafile.seadroid2.R; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.compat.ContextCompatKt; import com.seafile.seadroid2.databinding.FragmentPhotoViewBinding; import com.seafile.seadroid2.framework.data.db.entities.DirentModel; import com.seafile.seadroid2.framework.datastore.DataManager; import com.seafile.seadroid2.framework.util.GlideApp; -import com.seafile.seadroid2.framework.util.SLogs; import com.seafile.seadroid2.ui.base.fragment.BaseFragment; import java.io.File; -import java.util.Locale; public class PhotoFragment extends BaseFragment { private Account account; - private DirentModel direntModel; + + private String repoId, repoName, fullPath; private OnPhotoTapListener onPhotoTapListener; private FragmentPhotoViewBinding binding; + private boolean isLight = true; public void setOnPhotoTapListener(OnPhotoTapListener onPhotoTapListener) { this.onPhotoTapListener = onPhotoTapListener; } + public static PhotoFragment newInstance(String repoId, String repoName, String fullPath) { + + Bundle args = new Bundle(); + args.putString("repoId", repoId); + args.putString("repoName", repoName); + args.putString("fullPath", fullPath); + + PhotoFragment fragment = new PhotoFragment(); + fragment.setArguments(args); + return fragment; + } + public static PhotoFragment newInstance(DirentModel direntModel) { Bundle args = new Bundle(); - args.putParcelable("dirent", direntModel); + + args.putString("repoId", direntModel.repo_id); + args.putString("repoName", direntModel.repo_name); + args.putString("fullPath", direntModel.full_path); PhotoFragment fragment = new PhotoFragment(); fragment.setArguments(args); return fragment; @@ -68,11 +80,13 @@ public void onCreate(@Nullable Bundle savedInstanceState) { return; } - direntModel = args.getParcelable("dirent"); - if (null == direntModel) { - throw new IllegalArgumentException("DirentModel is null"); + if (!args.containsKey("repoId")) { + throw new IllegalStateException("repoId is null"); } + repoId = args.getString("repoId"); + repoName = args.getString("repoName"); + fullPath = args.getString("fullPath"); } @Nullable @@ -86,14 +100,21 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - binding.photoView.setZoomable(true); binding.photoView.setZoomTransitionDuration(300); - binding.photoView.setMaximumScale(5f); - binding.photoView.setMinimumScale(0.8f); + binding.photoView.setMaximumScale(3f); + binding.photoView.setMinimumScale(1f); binding.photoView.setOnPhotoTapListener(new OnPhotoTapListener() { @Override public void onPhotoTap(ImageView view, float x, float y) { + if (!isLight){ + binding.rootLayout.setBackgroundColor(ContextCompatKt.getColorCompat(requireContext(),R.color.material_grey_100)); + }else{ + binding.rootLayout.setBackgroundColor(ContextCompatKt.getColorCompat(requireContext(),R.color.material_grey_919)); + } + + isLight =!isLight; + if (onPhotoTapListener != null) { onPhotoTapListener.onPhotoTap(view, x, y); } @@ -102,7 +123,7 @@ public void onPhotoTap(ImageView view, float x, float y) { ProgressBar progressBar = view.findViewById(R.id.progress_bar); - File file = DataManager.getLocalRepoFile(account, direntModel.repo_id, direntModel.repo_name, direntModel.full_path); + File file = DataManager.getLocalRepoFile(account, repoId, repoName, fullPath); if (file.exists()) { progressBar.setVisibility(View.GONE); @@ -142,6 +163,47 @@ public boolean onResourceReady(Drawable resource, Object model, Target .into(binding.photoView); } + private void loadImage() { + + File file = DataManager.getLocalRepoFile(account, repoId, repoName, fullPath); + if (file.exists()) { + binding.progressBar.setVisibility(View.GONE); + + GlideApp.with(requireContext()) + .load(file) + .into(binding.photoView); + return; + } + + String url = getUrl(); + if (url == null) { + binding.photoView.setImageResource(R.drawable.icon_image_error_filled); + return; + } + + RequestOptions opt = new RequestOptions() + .skipMemoryCache(true) + .error(R.drawable.icon_image_error_filled) + .diskCacheStrategy(DiskCacheStrategy.NONE); + GlideApp.with(requireContext()) + .load(url) + .apply(opt) + .fitCenter() + .listener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + binding.progressBar.setVisibility(View.GONE); + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + binding.progressBar.setVisibility(View.GONE); + return false; + } + }) + .into(binding.photoView); + } private String getUrl() { Account account = SupportAccountManager.getInstance().getCurrentAccount(); @@ -157,6 +219,6 @@ private String getUrl() { // return String.format("%srepo/%s/raw%s", account.getServer(), repoId, fileFullPath); int size = SizeUtils.dp2px(300); - return String.format("%sapi2/repos/%s/thumbnail/?p=%s&size=%s", account.getServer(), direntModel.repo_id, EncodeUtils.urlEncode(direntModel.full_path), size); + return String.format("%sapi2/repos/%s/thumbnail/?p=%s&size=%s", account.getServer(), repoId, EncodeUtils.urlEncode(fullPath), size); } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselAdapter.java new file mode 100644 index 000000000..3e824b608 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselAdapter.java @@ -0,0 +1,160 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.seafile.seadroid2.ui.media.image_preview2; + +import android.content.Context; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.LayoutRes; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; + +import com.blankj.utilcode.util.EncodeUtils; +import com.blankj.utilcode.util.ScreenUtils; +import com.blankj.utilcode.util.SizeUtils; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.request.RequestOptions; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.framework.data.db.entities.DirentModel; +import com.seafile.seadroid2.framework.util.GlideApp; + +public class CarouselAdapter extends ListAdapter { + + private static final DiffUtil.ItemCallback DIFF_CALLBACK = + new DiffUtil.ItemCallback<>() { + @Override + public boolean areItemsTheSame( + @NonNull DirentModel oldItem, @NonNull DirentModel newItem) { + // User properties may have changed if reloaded from the DB, but ID is fixed + return oldItem == newItem; + } + + @Override + public boolean areContentsTheSame( + @NonNull DirentModel oldItem, @NonNull DirentModel newItem) { + return false; + } + }; + + private final CarouselItemListener listener; + @LayoutRes + private final int itemLayoutRes; + private int sidePadding; + private int itemWidth; + + public interface CarouselItemListener { + void onItemClicked(DirentModel item, int position); + } + + public CarouselAdapter(Context context, CarouselItemListener listener) { + super(DIFF_CALLBACK); + this.listener = listener; + this.itemLayoutRes = R.layout.item_carousel_item_vertical; + + itemWidth = context.getResources().getDimensionPixelSize(R.dimen.carousel_item_width); + int itemMargin = context.getResources().getDimensionPixelSize(R.dimen.carousel_item_margin); + + int screenWidth = ScreenUtils.getAppScreenWidth(); + sidePadding = (screenWidth - itemWidth) / 2 - itemMargin * 2; + } + + public CarouselAdapter(Context context, CarouselItemListener listener, @LayoutRes int itemLayoutRes) { + super(DIFF_CALLBACK); + this.listener = listener; + this.itemLayoutRes = itemLayoutRes; + + itemWidth = context.getResources().getDimensionPixelSize(R.dimen.carousel_item_width); + int itemMargin = context.getResources().getDimensionPixelSize(R.dimen.carousel_item_margin); + + + int screenWidth = ScreenUtils.getAppScreenWidth(); + sidePadding = (screenWidth - itemWidth) / 2 - itemMargin * 2; + } + + @Override + public DirentModel getItem(int position) { + return super.getItem(position); + } + + @NonNull + @Override + public CarouselItemViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int pos) { + return new CarouselItemViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(itemLayoutRes, viewGroup, false)); + } + + @Override + public void onBindViewHolder(@NonNull CarouselItemViewHolder carouselItemViewHolder, int pos) { + bind(carouselItemViewHolder, pos); + } + + + private void bind(CarouselItemViewHolder holder, int pos) { + DirentModel model = getItem(pos); + + holder.itemView.setOnClickListener(v -> { + listener.onItemClicked(model, pos); + }); + + if (TextUtils.isEmpty(model.name)) { + holder.itemView.getLayoutParams().width = sidePadding; + holder.imageView.setVisibility(View.INVISIBLE); + return; + } + + holder.imageView.setVisibility(View.VISIBLE); + holder.itemView.getLayoutParams().width = itemWidth; + + String url = getUrl(model.repo_id, model.full_path); + if (url == null) { + holder.imageView.setImageResource(R.drawable.icon_image_error_filled); + return; + } + + RequestOptions opt = new RequestOptions() + .skipMemoryCache(true) + .error(R.drawable.icon_image_error_filled) + .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC); + GlideApp.with(holder.imageView) + .load(url) + .apply(opt) + .fitCenter() + .into(holder.imageView); + } + + private String getUrl(String repoId, String fullPath) { + Account account = SupportAccountManager.getInstance().getCurrentAccount(); + if (account == null) { + return null; + } + +// //https://dev.seafile.com/seafhttp/repos/4809a6f3-250c-4435-bdd8-b68f34c128d1/files//6f64603fd19f9ec45d05ec379e69e22.gif/?op=download +// //https://dev.seafile.com/seahub/repo/4809a6f3-250c-4435-bdd8-b68f34c128d1/raw/6f64603fd19f9ec45d05ec379e69e22.gif +// if (direntModel.name.toLowerCase().endsWith(".gif")) { +// return String.format(Locale.ROOT, "%srepo/%s/raw/%s", account.getServer(), direntModel.repo_id, direntModel.name); +// } + +// return String.format("%srepo/%s/raw%s", account.getServer(), repoId, fileFullPath); + int size = SizeUtils.dp2px(300); + return String.format("%sapi2/repos/%s/thumbnail/?p=%s&size=%s", account.getServer(), repoId, EncodeUtils.urlEncode(fullPath), size); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java new file mode 100644 index 000000000..d71ec250f --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java @@ -0,0 +1,420 @@ +package com.seafile.seadroid2.ui.media.image_preview2; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Pair; +import android.view.Gravity; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +import androidx.activity.OnBackPressedCallback; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewpager2.widget.ViewPager2; + +import com.blankj.utilcode.util.BarUtils; +import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.NetworkUtils; +import com.blankj.utilcode.util.ScreenUtils; +import com.blankj.utilcode.util.ToastUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.compat.ContextCompatKt; +import com.seafile.seadroid2.databinding.ActivityCarouselImagePreviewBinding; +import com.seafile.seadroid2.framework.data.db.entities.DirentModel; +import com.seafile.seadroid2.framework.data.db.entities.RepoModel; +import com.seafile.seadroid2.framework.util.Objs; +import com.seafile.seadroid2.ui.adapter.ViewPager2Adapter; +import com.seafile.seadroid2.ui.base.BaseActivityWithVM; +import com.seafile.seadroid2.ui.dialog_fragment.DeleteFileDialogFragment; +import com.seafile.seadroid2.ui.dialog_fragment.listener.OnRefreshDataListener; +import com.seafile.seadroid2.ui.media.image_preview.ImagePreviewViewModel; +import com.seafile.seadroid2.ui.media.image_preview.PhotoFragment; + +import java.util.ArrayList; +import java.util.List; + +public class CarouselImagePreviewActivity extends BaseActivityWithVM implements Toolbar.OnMenuItemClickListener { + private ActivityCarouselImagePreviewBinding binding; + + private ViewPager2Adapter adapter; + private CarouselAdapter carouselAdapter; + + private List direntList; + + private boolean isLightMode = true; + private boolean isDataOperated = false; + + private String repoId, repoName, parentDir, name; + private boolean load_other_images_in_same_directory = false; + private int carouselItemWidth, carouselItemMargin; + + public static Intent startThisFromRepo(Context context, DirentModel direntModel) { + Intent intent = new Intent(context, CarouselImagePreviewActivity.class); + intent.putExtra("repo_id", direntModel.repo_id); + intent.putExtra("repo_name", direntModel.repo_name); + intent.putExtra("parent_dir", direntModel.parent_dir); + intent.putExtra("name", direntModel.name); + intent.putExtra("load_other_images_in_same_directory", true);//Load other images in the same folder + return intent; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivityCarouselImagePreviewBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + + // full screen + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + + //init status bar + int color = ContextCompatKt.getColorCompat(this, R.color.material_grey_100_translucent); + BarUtils.setStatusBarColor(this, color); + BarUtils.setStatusBarLightMode(this, isLightMode); + + //init toolbar margin top + ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) binding.toolbarActionbar.getLayoutParams(); + layoutParams.topMargin = BarUtils.getStatusBarHeight(); + + carouselItemWidth = getResources().getDimensionPixelSize(R.dimen.carousel_item_width); + carouselItemMargin = getResources().getDimensionPixelSize(R.dimen.carousel_item_margin); + + + Toolbar toolbar = getActionBarToolbar(); + setSupportActionBar(toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setTitle(null); + } + + getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + if (isDataOperated) { + setResult(RESULT_OK); + } + finish(); + } + }); + + initParams(); + initView(); + initAdapter(); + initCarouselAdapter(); + initViewModel(); + + getViewModel().load(repoId, parentDir, name, load_other_images_in_same_directory); + } + + private void initParams() { + Intent intent = getIntent(); + if (intent == null) { + throw new RuntimeException("intent is null"); + } + + repoId = intent.getStringExtra("repo_id"); + if (TextUtils.isEmpty(repoId)) { + throw new RuntimeException("repoId is empty"); + } + + repoName = intent.getStringExtra("repo_name"); + parentDir = intent.getStringExtra("parent_dir"); + name = intent.getStringExtra("name"); + load_other_images_in_same_directory = intent.getBooleanExtra("load_other_images_in_same_directory", false); + } + + private void initView() { + View.OnClickListener onClickListener = v -> { + int id = v.getId(); + if (id == R.id.gallery_download_photo) { + downloadFile(); + } else if (id == R.id.gallery_delete_photo) { + deleteFile(); + } else if (id == R.id.gallery_star_photo) { + starFile(); + } else if (id == R.id.gallery_share_photo) { + shareFile(); + } + }; + + binding.galleryDownloadPhoto.setOnClickListener(onClickListener); + binding.galleryDeletePhoto.setOnClickListener(onClickListener); + binding.galleryStarPhoto.setOnClickListener(onClickListener); + binding.gallerySharePhoto.setOnClickListener(onClickListener); + } + + private void initViewModel() { + getViewModel().getRefreshLiveData().observe(this, new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + if (aBoolean) { + showProgressDialog(); + } else { + dismissProgressDialog(); + } + } + }); + + getViewModel().getRepoAndListLiveData().observe(this, new Observer>>() { + @Override + public void onChanged(Pair> pair) { + submitData(pair); + } + }); + + getViewModel().getStarLiveData().observe(this, new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + isDataOperated = true; + + ToastUtils.showLong(aBoolean ? R.string.star_file_succeed : R.string.star_file_failed); + + int index = binding.pager.getCurrentItem(); + direntList.get(index).starred = aBoolean; + + notifyCurrentStarredStatus(); + } + }); + } + + private void initAdapter() { + adapter = new ViewPager2Adapter(this); + + binding.pager.setAdapter(adapter); + binding.pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + + gravitySnapHelper.scrollToPosition(position); + + notifyCurrentStarredStatus(); + } + }); + } + + private final LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); + private final GravitySnapHelper gravitySnapHelper = new GravitySnapHelper(Gravity.CENTER); + + private void initCarouselAdapter() { + + carouselAdapter = new CarouselAdapter(this, new CarouselAdapter.CarouselItemListener() { + @Override + public void onItemClicked(DirentModel item, int snapPosition) { + ToastUtils.showShort("onItemClicked: " + snapPosition); + int pageIndex = binding.pager.getCurrentItem(); + if (pageIndex == snapPosition) { + return; + } + binding.pager.setCurrentItem(snapPosition, true); + } + }); + + binding.recyclerView.setAdapter(carouselAdapter); + binding.recyclerView.setLayoutManager(layoutManager); + + int itemWidth = getResources().getDimensionPixelSize(R.dimen.carousel_item_width); + int screenWidth = ScreenUtils.getAppScreenWidth(); + int itemMargin = getResources().getDimensionPixelSize(R.dimen.carousel_item_margin); + int slidePadding = (screenWidth - itemWidth) / 2 - itemMargin; + + LinearEdgeDecoration decoration = new LinearEdgeDecoration(slidePadding, slidePadding, RecyclerView.HORIZONTAL, false); + binding.recyclerView.addItemDecoration(decoration); + + binding.recyclerView.addOnScrollListener(new CenterScaleXYRecyclerViewScrollListener(this)); + + gravitySnapHelper.setSnapListener(new GravitySnapHelper.SnapListener() { + @Override + public void onSnap(int snapPosition) { + int pageIndex = binding.pager.getCurrentItem(); + if (pageIndex == snapPosition) { + return; + } + + binding.pager.setCurrentItem(snapPosition, true); + } + }); +// gravitySnapHelper.setSnapLastItem(true); + gravitySnapHelper.attachToRecyclerView(binding.recyclerView); + } + + private void submitData(Pair> pair) { + if (pair == null) { + return; + } + + RepoModel repoModel = pair.first; + if (repoModel == null) { + return; + } + + if (!repoModel.hasWritePermission()) { + binding.galleryDeletePhoto.setVisibility(View.GONE); + } + direntList = pair.second; + + + // + + List fragments = new ArrayList<>(); + for (DirentModel direntModel : direntList) { + PhotoFragment photoFragment = PhotoFragment.newInstance(direntModel); + photoFragment.setOnPhotoTapListener((view, x, y) -> hideOrShowToolBar()); + fragments.add(photoFragment); + } + + adapter.addFragments(fragments); + adapter.notifyItemRangeInserted(0, direntList.size()); + + carouselAdapter.submitList(direntList); + + binding.recyclerView.postDelayed(new Runnable() { + @Override + public void run() { + navToSelectedPage(); + } + }, 100); + } + + + private void hideOrShowToolBar() { + binding.galleryToolBar.setVisibility(isLightMode ? View.GONE : View.VISIBLE); + binding.recyclerView.setVisibility(isLightMode ? View.GONE : View.VISIBLE); + binding.toolbarActionbar.setVisibility(isLightMode ? View.INVISIBLE : View.VISIBLE); + + int color = ContextCompatKt.getColorCompat(this, isLightMode ? R.color.material_grey_919_ff : R.color.material_grey_100_translucent); + BarUtils.setNavBarColor(this, color); + BarUtils.setStatusBarColor(this, color); + BarUtils.setStatusBarLightMode(this, !isLightMode); + BarUtils.setNavBarLightMode(this, !isLightMode); + + isLightMode = !isLightMode; + } + + + /** + * Dynamically navigate to the starting page index selected by user + * by default the starting page index is 0 + */ + private void navToSelectedPage() { + int size = direntList.size(); + int index = -1; + + for (int i = 0; i < size; i++) { + if (direntList.get(i).name.equals(name)) { + index = i; + break; + } + } + + if (index != -1) { + binding.pager.setCurrentItem(index, true); + gravitySnapHelper.scrollToPosition(index); + } + } + + private void notifyCurrentStarredStatus() { + DirentModel direntModel = getSelectedDirent(); + if (direntModel == null) { + return; + } + if (direntModel.starred) { + binding.galleryStarPhoto.setImageResource(R.drawable.baseline_starred_32); + } else { + binding.galleryStarPhoto.setImageResource(R.drawable.baseline_star_outline_24); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + Toolbar toolbar = getActionBarToolbar(); + toolbar.inflateMenu(R.menu.menu_image_list_preview); + toolbar.setOnMenuItemClickListener(this); + + return true; + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + } else if (item.getItemId() == R.id.copy) { + ToastUtils.showLong(R.string.file_action_copy); + } else if (item.getItemId() == R.id.info) { + ToastUtils.showLong(R.string.file_action_copy); + } + + return super.onOptionsItemSelected(item); + } + + private DirentModel getSelectedDirent() { + int index = binding.pager.getCurrentItem(); + return carouselAdapter.getItem(index); + } + + private void deleteFile() { + + int position = binding.pager.getCurrentItem(); + DirentModel direntModel = getSelectedDirent(); + + DeleteFileDialogFragment dialogFragment = DeleteFileDialogFragment.newInstance(CollectionUtils.newArrayList(direntModel.uid)); + dialogFragment.setRefreshListener(new OnRefreshDataListener() { + @Override + public void onActionStatus(boolean isDone) { + if (isDone) { + isDataOperated = true; + + direntList.remove(position); + adapter.removeFragment(position); + adapter.notifyItemRemoved(position); + carouselAdapter.notifyItemRemoved(position); + + ToastUtils.showLong(R.string.delete_successful); + + if (adapter.getItemCount() == 0) { + setResult(RESULT_OK); + finish(); + } + } + } + }); + dialogFragment.show(getSupportFragmentManager(), DeleteFileDialogFragment.class.getSimpleName()); + } + + private void starFile() { + if (!NetworkUtils.isConnected()) { + ToastUtils.showLong(R.string.network_down); + return; + } + + DirentModel direntModel = getSelectedDirent(); + if (direntModel.starred) { + getViewModel().unStar(direntModel.repo_id, direntModel.full_path); + } else { + getViewModel().star(direntModel.repo_id, direntModel.full_path); + } + } + + private void shareFile() { + DirentModel direntModel = getSelectedDirent(); + Objs.showCreateShareLinkDialog(this, getSupportFragmentManager(), direntModel, false); + } + + + private void downloadFile() { + isDataOperated = true; + + DirentModel direntModel = getSelectedDirent(); + getViewModel().download(direntModel.repo_id, direntModel.full_path); + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/CarouselItem.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselItem.java similarity index 93% rename from app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/CarouselItem.java rename to app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselItem.java index 821ed483e..202ee4838 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/CarouselItem.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselItem.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.seafile.seadroid2.ui.media.image_preview; +package com.seafile.seadroid2.ui.media.image_preview2; import androidx.annotation.DrawableRes; import androidx.annotation.StringRes; @@ -22,7 +22,7 @@ /** * A data class that holds all information related to an item inside a Carousel. */ -class CarouselItem { +public class CarouselItem { @DrawableRes private final int drawableRes; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/CarouselItemListener.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselItemViewHolder.java similarity index 55% rename from app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/CarouselItemListener.java rename to app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselItemViewHolder.java index 8f1870fd9..f036a3703 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/CarouselItemListener.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselItemViewHolder.java @@ -14,9 +14,24 @@ * limitations under the License. */ -package com.seafile.seadroid2.ui.media.image_preview; +package com.seafile.seadroid2.ui.media.image_preview2; + + +import android.view.View; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.seafile.seadroid2.R; + +public class CarouselItemViewHolder extends RecyclerView.ViewHolder { + + public final ImageView imageView; + + CarouselItemViewHolder(@NonNull View itemView) { + super(itemView); + imageView = itemView.findViewById(R.id.image_view); + } -/** An interface for items in a carousel. */ -interface CarouselItemListener { - void onItemClicked(CarouselItem item, int position); } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CenterScaleXYRecyclerViewScrollListener.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CenterScaleXYRecyclerViewScrollListener.java new file mode 100644 index 000000000..a81e44cbe --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CenterScaleXYRecyclerViewScrollListener.java @@ -0,0 +1,121 @@ +package com.seafile.seadroid2.ui.media.image_preview2; + +import android.content.Context; +import android.os.Handler; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.blankj.utilcode.util.ScreenUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.framework.util.SLogs; + +public class CenterScaleXYRecyclerViewScrollListener extends RecyclerView.OnScrollListener { + + private float maxScale = 1.2f; + private float minScale = 1.0f; + + private final int centerX; + private final float maxDistance;// 12dp is (view width / 2) + margin(2dp) + private final int itemWidth, itemMargin; + + private LinearLayoutManager layoutManager; + + private final Handler handler = new Handler(); + private boolean isPendingUpdate = false; + + public CenterScaleXYRecyclerViewScrollListener(Context context) { + itemWidth = context.getResources().getDimensionPixelSize(R.dimen.carousel_item_width); + itemMargin = context.getResources().getDimensionPixelSize(R.dimen.carousel_item_margin); + maxDistance = (float) itemWidth / 2 + itemMargin; + centerX = ScreenUtils.getAppScreenWidth() / 2; + } + + public CenterScaleXYRecyclerViewScrollListener(Context context, float maxScale, float minScale) { + this.maxScale = maxScale; + this.minScale = minScale; + + itemWidth = context.getResources().getDimensionPixelSize(R.dimen.carousel_item_width); + itemMargin = context.getResources().getDimensionPixelSize(R.dimen.carousel_item_margin); + maxDistance = (float) itemWidth / 2 + itemMargin; + centerX = ScreenUtils.getAppScreenWidth() / 2; + + } + + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + if (newState == RecyclerView.SCROLL_STATE_IDLE) { + adjustChildScale(recyclerView, 0); // 滚动停止时更新缩放状态 + } + } + + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); +// if (isPendingUpdate) return; +// isPendingUpdate = true; + + adjustChildScale(recyclerView, dx); + +// handler.postDelayed(() -> { +// adjustChildScale(recyclerView, dx); +// +// isPendingUpdate = false; +// }, 25); + } + + private void adjustChildScale(RecyclerView recyclerView, int dx) { + if (layoutManager == null) { + layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); + } + + if (layoutManager == null) { + throw new IllegalStateException("LayoutManager is null"); + } + + int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition(); + int lastVisiblePosition = layoutManager.findLastVisibleItemPosition(); + if (firstVisiblePosition == RecyclerView.NO_POSITION || lastVisiblePosition == RecyclerView.NO_POSITION) { + return; // 防止意外状态 + } + + for (int i = firstVisiblePosition; i <= lastVisiblePosition; i++) { + View view = layoutManager.findViewByPosition(i); + if (view == null) { + continue; + } + + int left = view.getLeft(); + if (left == 0) { + continue; + } + + // 计算每个 item 的中心点与屏幕中心的距离 + int viewCenterX = left + itemWidth / 2; + + float distanceFromCenter = Math.abs(centerX - viewCenterX); + + // 1.2f - 4/60 * 0.2f + float scale = maxScale - (distanceFromCenter / maxDistance) * (maxScale - minScale); + scale = Math.max(minScale, scale); // 确保不小于最小缩放比例 + + if (i == firstVisiblePosition) { + SLogs.d("firstVisiblePosition: " + firstVisiblePosition + ", itemWidth: " + itemWidth + ", left: " + left + ", viewCenterX: " + viewCenterX + ", centerX: " + centerX + ", distanceFromCenter: " + distanceFromCenter + ", scale: " + scale); + } + + float alpha = 1.0f - (distanceFromCenter / maxDistance) * 0.8f; // 透明度范围 1.0 到 0.6 + alpha = Math.max(0.8f, alpha); + view.setAlpha(alpha); + + // 仅当 scale 有变化时才更新 + if (Math.abs(view.getScaleX() - scale) > 0.01f) { + view.setScaleX(scale); + view.setScaleY(scale); + } + } + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/GravitySnapHelper.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/GravitySnapHelper.java new file mode 100644 index 000000000..894db1a58 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/GravitySnapHelper.java @@ -0,0 +1,679 @@ +package com.seafile.seadroid2.ui.media.image_preview2; + +import android.util.DisplayMetrics; +import android.view.Gravity; +import android.view.View; +import android.view.animation.DecelerateInterpolator; +import android.widget.Scroller; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.Px; +import androidx.core.text.TextUtilsCompat; +import androidx.core.view.ViewCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.LinearSmoothScroller; +import androidx.recyclerview.widget.LinearSnapHelper; +import androidx.recyclerview.widget.OrientationHelper; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.Locale; + +/** + * A {@link LinearSnapHelper} that allows snapping to an edge or to the center. + *

+ * Possible snap positions: + * {@link Gravity#START}, {@link Gravity#TOP}, {@link Gravity#END}, {@link Gravity#BOTTOM}, + * {@link Gravity#CENTER}. + *

+ * To customize the scroll duration, use {@link GravitySnapHelper#setScrollMsPerInch(float)}. + *

+ * To customize the maximum scroll distance during flings, + * use {@link GravitySnapHelper#setMaxFlingSizeFraction(float)} + * or {@link GravitySnapHelper#setMaxFlingDistance(int)} + */ +public class GravitySnapHelper extends LinearSnapHelper { + + public static final int FLING_DISTANCE_DISABLE = -1; + public static final float FLING_SIZE_FRACTION_DISABLE = -1f; + private int gravity; + private boolean isRtl; + private boolean snapLastItem; + private int nextSnapPosition; + private boolean isScrolling = false; + private boolean snapToPadding = false; + private float scrollMsPerInch = 100f; + private int maxFlingDistance = FLING_DISTANCE_DISABLE; + private float maxFlingSizeFraction = FLING_SIZE_FRACTION_DISABLE; + private OrientationHelper verticalHelper; + private OrientationHelper horizontalHelper; + private SnapListener listener; + private RecyclerView recyclerView; + private RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + GravitySnapHelper.this.onScrollStateChanged(newState); + } + }; + + public GravitySnapHelper(int gravity) { + this(gravity, false, null); + } + + public GravitySnapHelper(int gravity, @NonNull SnapListener snapListener) { + this(gravity, false, snapListener); + } + + public GravitySnapHelper(int gravity, boolean enableSnapLastItem) { + this(gravity, enableSnapLastItem, null); + } + + public GravitySnapHelper(int gravity, boolean enableSnapLastItem, + @Nullable SnapListener snapListener) { + if (gravity != Gravity.START + && gravity != Gravity.END + && gravity != Gravity.BOTTOM + && gravity != Gravity.TOP + && gravity != Gravity.CENTER) { + throw new IllegalArgumentException("Invalid gravity value. Use START " + + "| END | BOTTOM | TOP | CENTER constants"); + } + this.snapLastItem = enableSnapLastItem; + this.gravity = gravity; + this.listener = snapListener; + } + + @Override + public void attachToRecyclerView(@Nullable RecyclerView recyclerView) + throws IllegalStateException { + if (this.recyclerView != null) { + this.recyclerView.removeOnScrollListener(scrollListener); + } + if (recyclerView != null) { + recyclerView.setOnFlingListener(null); + if (gravity == Gravity.START || gravity == Gravity.END) { + isRtl = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) + == ViewCompat.LAYOUT_DIRECTION_RTL; + } + recyclerView.addOnScrollListener(scrollListener); + this.recyclerView = recyclerView; + } else { + this.recyclerView = null; + } + super.attachToRecyclerView(recyclerView); + } + + @Override + @Nullable + public View findSnapView(@NonNull RecyclerView.LayoutManager lm) { + return findSnapView(lm, true); + } + + @Nullable + public View findSnapView(@NonNull RecyclerView.LayoutManager lm, boolean checkEdgeOfList) { + View snapView = null; + + switch (gravity) { + case Gravity.START: + snapView = findView(lm, getHorizontalHelper(lm), Gravity.START, checkEdgeOfList); + break; + case Gravity.END: + snapView = findView(lm, getHorizontalHelper(lm), Gravity.END, checkEdgeOfList); + break; + case Gravity.TOP: + snapView = findView(lm, getVerticalHelper(lm), Gravity.START, checkEdgeOfList); + break; + case Gravity.BOTTOM: + snapView = findView(lm, getVerticalHelper(lm), Gravity.END, checkEdgeOfList); + break; + case Gravity.CENTER: + if (lm.canScrollHorizontally()) { + snapView = findView(lm, getHorizontalHelper(lm), Gravity.CENTER, + checkEdgeOfList); + } else { + snapView = findView(lm, getVerticalHelper(lm), Gravity.CENTER, + checkEdgeOfList); + } + break; + } + if (snapView != null) { + nextSnapPosition = recyclerView.getChildAdapterPosition(snapView); + } else { + nextSnapPosition = RecyclerView.NO_POSITION; + } + return snapView; + } + + @Override + @NonNull + public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, + @NonNull View targetView) { + if (gravity == Gravity.CENTER) { + //noinspection ConstantConditions + return super.calculateDistanceToFinalSnap(layoutManager, targetView); + } + + int[] out = new int[2]; + + if (!(layoutManager instanceof LinearLayoutManager)) { + return out; + } + + LinearLayoutManager lm = (LinearLayoutManager) layoutManager; + + if (lm.canScrollHorizontally()) { + if ((isRtl && gravity == Gravity.END) || (!isRtl && gravity == Gravity.START)) { + out[0] = getDistanceToStart(targetView, getHorizontalHelper(lm)); + } else { + out[0] = getDistanceToEnd(targetView, getHorizontalHelper(lm)); + } + } else if (lm.canScrollVertically()) { + if (gravity == Gravity.TOP) { + out[1] = getDistanceToStart(targetView, getVerticalHelper(lm)); + } else { + out[1] = getDistanceToEnd(targetView, getVerticalHelper(lm)); + } + } + return out; + } + + @Override + @NonNull + public int[] calculateScrollDistance(int velocityX, int velocityY) { + if (recyclerView == null + || (verticalHelper == null && horizontalHelper == null) + || (maxFlingDistance == FLING_DISTANCE_DISABLE + && maxFlingSizeFraction == FLING_SIZE_FRACTION_DISABLE)) { + return super.calculateScrollDistance(velocityX, velocityY); + } + final int[] out = new int[2]; + Scroller scroller = new Scroller(recyclerView.getContext(), + new DecelerateInterpolator()); + int maxDistance = getFlingDistance(); + scroller.fling(0, 0, velocityX, velocityY, + -maxDistance, maxDistance, + -maxDistance, maxDistance); + out[0] = scroller.getFinalX(); + out[1] = scroller.getFinalY(); + return out; + } + + @Nullable + @Override + public RecyclerView.SmoothScroller createScroller(RecyclerView.LayoutManager layoutManager) { + if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider) + || recyclerView == null) { + return null; + } + return new LinearSmoothScroller(recyclerView.getContext()) { + @Override + protected void onTargetFound(View targetView, + RecyclerView.State state, + Action action) { + if (recyclerView == null || recyclerView.getLayoutManager() == null) { + // The associated RecyclerView has been removed so there is no action to take. + return; + } + int[] snapDistances = calculateDistanceToFinalSnap(recyclerView.getLayoutManager(), + targetView); + final int dx = snapDistances[0]; + final int dy = snapDistances[1]; + final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy))); + if (time > 0) { + action.update(dx, dy, time, mDecelerateInterpolator); + } + } + + @Override + protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { + return scrollMsPerInch / displayMetrics.densityDpi; + } + }; + } + + /** + * Sets a {@link SnapListener} to listen for snap events + * + * @param listener a {@link SnapListener} that'll receive snap events or null to clear it + */ + public void setSnapListener(@Nullable SnapListener listener) { + this.listener = listener; + } + + /** + * Changes the gravity of this {@link GravitySnapHelper} + * and dispatches a smooth scroll for the new snap position. + * + * @param newGravity one of the following: {@link Gravity#START}, {@link Gravity#TOP}, + * {@link Gravity#END}, {@link Gravity#BOTTOM}, {@link Gravity#CENTER} + * @param smooth true if we should smooth scroll to new edge, false otherwise + */ + public void setGravity(int newGravity, Boolean smooth) { + if (this.gravity != newGravity) { + this.gravity = newGravity; + updateSnap(smooth, false); + } + } + + /** + * Updates the current view to be snapped + * + * @param smooth true if we should smooth scroll, false otherwise + * @param checkEdgeOfList true if we should check if we're at an edge of the list + * and snap according to {@link GravitySnapHelper#getSnapLastItem()}, + * or false to force snapping to the nearest view + */ + public void updateSnap(Boolean smooth, Boolean checkEdgeOfList) { + if (recyclerView == null || recyclerView.getLayoutManager() == null) { + return; + } + final RecyclerView.LayoutManager lm = recyclerView.getLayoutManager(); + View snapView = findSnapView(lm, checkEdgeOfList); + if (snapView != null) { + int[] out = calculateDistanceToFinalSnap(lm, snapView); + if (smooth) { + recyclerView.smoothScrollBy(out[0], out[1]); + } else { + recyclerView.scrollBy(out[0], out[1]); + } + } + } + + /** + * This method will only work if there's a ViewHolder for the given position. + * + * @return true if scroll was successful, false otherwise + */ + public boolean scrollToPosition(int position) { + if (position == RecyclerView.NO_POSITION) { + return false; + } + return scrollTo(position, false); + } + + /** + * Unlike {@link GravitySnapHelper#scrollToPosition(int)}, + * this method will generally always find a snap view if the position is valid. + *

+ * The smooth scroller from {@link GravitySnapHelper#createScroller(RecyclerView.LayoutManager)} + * will be used, and so will {@link GravitySnapHelper#scrollMsPerInch} for the scroll velocity + * + * @return true if scroll was successful, false otherwise + */ + public boolean smoothScrollToPosition(int position) { + if (position == RecyclerView.NO_POSITION) { + return false; + } + return scrollTo(position, true); + } + + /** + * Get the current gravity being applied + * + * @return one of the following: {@link Gravity#START}, {@link Gravity#TOP}, {@link Gravity#END}, + * {@link Gravity#BOTTOM}, {@link Gravity#CENTER} + */ + public int getGravity() { + return this.gravity; + } + + /** + * Changes the gravity of this {@link GravitySnapHelper} + * and dispatches a smooth scroll for the new snap position. + * + * @param newGravity one of the following: {@link Gravity#START}, {@link Gravity#TOP}, + * {@link Gravity#END}, {@link Gravity#BOTTOM}, {@link Gravity#CENTER} + */ + public void setGravity(int newGravity) { + setGravity(newGravity, true); + } + + /** + * @return true if this SnapHelper should snap to the last item + */ + public boolean getSnapLastItem() { + return snapLastItem; + } + + /** + * Enable snapping of the last item that's snappable. + * The default value is false, because you can't see the last item completely + * if this is enabled. + * + * @param snap true if you want to enable snapping of the last snappable item + */ + public void setSnapLastItem(boolean snap) { + snapLastItem = snap; + } + + /** + * @return last distance set through {@link GravitySnapHelper#setMaxFlingDistance(int)} + * or {@link GravitySnapHelper#FLING_DISTANCE_DISABLE} if we're not limiting the fling distance + */ + public int getMaxFlingDistance() { + return maxFlingDistance; + } + + /** + * Changes the max fling distance in absolute values. + * + * @param distance max fling distance in pixels + * or {@link GravitySnapHelper#FLING_DISTANCE_DISABLE} + * to disable fling limits + */ + public void setMaxFlingDistance(@Px int distance) { + maxFlingDistance = distance; + maxFlingSizeFraction = FLING_SIZE_FRACTION_DISABLE; + } + + /** + * @return last distance set through {@link GravitySnapHelper#setMaxFlingSizeFraction(float)} + * or {@link GravitySnapHelper#FLING_SIZE_FRACTION_DISABLE} + * if we're not limiting the fling distance + */ + public float getMaxFlingSizeFraction() { + return maxFlingSizeFraction; + } + + /** + * Changes the max fling distance depending on the available size of the RecyclerView. + *

+ * Example: if you pass 0.5f and the RecyclerView measures 600dp, + * the max fling distance will be 300dp. + * + * @param fraction size fraction to be used for the max fling distance + * or {@link GravitySnapHelper#FLING_SIZE_FRACTION_DISABLE} + * to disable fling limits + */ + public void setMaxFlingSizeFraction(float fraction) { + maxFlingDistance = FLING_DISTANCE_DISABLE; + maxFlingSizeFraction = fraction; + } + + /** + * @return last scroll speed set through {@link GravitySnapHelper#setScrollMsPerInch(float)} + * or 100f + */ + public float getScrollMsPerInch() { + return scrollMsPerInch; + } + + /** + * Sets the scroll duration in ms per inch. + *

+ * Default value is 100.0f + *

+ * This value will be used in + * {@link GravitySnapHelper#createScroller(RecyclerView.LayoutManager)} + * + * @param ms scroll duration in ms per inch + */ + public void setScrollMsPerInch(float ms) { + scrollMsPerInch = ms; + } + + /** + * @return true if this SnapHelper should snap to the padding. Defaults to false. + */ + public boolean getSnapToPadding() { + return snapToPadding; + } + + /** + * If true, GravitySnapHelper will snap to the gravity edge + * plus any amount of padding that was set in the RecyclerView. + *

+ * The default value is false. + * + * @param snapToPadding true if you want to snap to the padding + */ + public void setSnapToPadding(boolean snapToPadding) { + this.snapToPadding = snapToPadding; + } + + /** + * @return the position of the current view that's snapped + * or {@link RecyclerView#NO_POSITION} in case there's none. + */ + public int getCurrentSnappedPosition() { + if (recyclerView != null && recyclerView.getLayoutManager() != null) { + View snappedView = findSnapView(recyclerView.getLayoutManager()); + if (snappedView != null) { + return recyclerView.getChildAdapterPosition(snappedView); + } + } + return RecyclerView.NO_POSITION; + } + + private int getFlingDistance() { + if (maxFlingSizeFraction != FLING_SIZE_FRACTION_DISABLE) { + if (verticalHelper != null) { + return (int) (recyclerView.getHeight() * maxFlingSizeFraction); + } else if (horizontalHelper != null) { + return (int) (recyclerView.getWidth() * maxFlingSizeFraction); + } else { + return Integer.MAX_VALUE; + } + } else if (maxFlingDistance != FLING_DISTANCE_DISABLE) { + return maxFlingDistance; + } else { + return Integer.MAX_VALUE; + } + } + + /** + * @return true if the scroll will snap to a view, false otherwise + */ + private boolean scrollTo(int position, boolean smooth) { + if (recyclerView.getLayoutManager() != null) { + if (smooth) { + RecyclerView.SmoothScroller smoothScroller + = createScroller(recyclerView.getLayoutManager()); + if (smoothScroller != null) { + smoothScroller.setTargetPosition(position); + recyclerView.getLayoutManager().startSmoothScroll(smoothScroller); + return true; + } + } else { + RecyclerView.ViewHolder viewHolder + = recyclerView.findViewHolderForAdapterPosition(position); + if (viewHolder != null) { + int[] distances = calculateDistanceToFinalSnap(recyclerView.getLayoutManager(), + viewHolder.itemView); + recyclerView.scrollBy(distances[0], distances[1]); + return true; + } + } + } + return false; + } + + private int getDistanceToStart(View targetView, @NonNull OrientationHelper helper) { + int distance; + // If we don't care about padding, just snap to the start of the view + if (!snapToPadding) { + int childStart = helper.getDecoratedStart(targetView); + if (childStart >= helper.getStartAfterPadding() / 2) { + distance = childStart - helper.getStartAfterPadding(); + } else { + distance = childStart; + } + } else { + distance = helper.getDecoratedStart(targetView) - helper.getStartAfterPadding(); + } + return distance; + } + + private int getDistanceToEnd(View targetView, @NonNull OrientationHelper helper) { + int distance; + + if (!snapToPadding) { + int childEnd = helper.getDecoratedEnd(targetView); + if (childEnd >= helper.getEnd() - (helper.getEnd() - helper.getEndAfterPadding()) / 2) { + distance = helper.getDecoratedEnd(targetView) - helper.getEnd(); + } else { + distance = childEnd - helper.getEndAfterPadding(); + } + } else { + distance = helper.getDecoratedEnd(targetView) - helper.getEndAfterPadding(); + } + + return distance; + } + + /** + * Returns the first view that we should snap to. + * + * @param layoutManager the RecyclerView's LayoutManager + * @param helper orientation helper to calculate view sizes + * @param gravity gravity to find the closest view + * @return the first view in the LayoutManager to snap to, or null if we shouldn't snap to any + */ + @Nullable + private View findView(@NonNull RecyclerView.LayoutManager layoutManager, + @NonNull OrientationHelper helper, + int gravity, + boolean checkEdgeOfList) { + + if (layoutManager.getChildCount() == 0 || !(layoutManager instanceof LinearLayoutManager)) { + return null; + } + + final LinearLayoutManager lm = (LinearLayoutManager) layoutManager; + + // If we're at an edge of the list, we shouldn't snap + // to avoid having the last item not completely visible. + if (checkEdgeOfList && (isAtEdgeOfList(lm) && !snapLastItem)) { + return null; + } + + View edgeView = null; + int distanceToTarget = Integer.MAX_VALUE; + final int center; + if (layoutManager.getClipToPadding()) { + center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; + } else { + center = helper.getEnd() / 2; + } + + final boolean snapToStart = (gravity == Gravity.START && !isRtl) + || (gravity == Gravity.END && isRtl); + + final boolean snapToEnd = (gravity == Gravity.START && isRtl) + || (gravity == Gravity.END && !isRtl); + + for (int i = 0; i < lm.getChildCount(); i++) { + View currentView = lm.getChildAt(i); + int currentViewDistance; + if (snapToStart) { + if (!snapToPadding) { + currentViewDistance = Math.abs(helper.getDecoratedStart(currentView)); + } else { + currentViewDistance = Math.abs(helper.getStartAfterPadding() + - helper.getDecoratedStart(currentView)); + } + } else if (snapToEnd) { + if (!snapToPadding) { + currentViewDistance = Math.abs(helper.getDecoratedEnd(currentView) + - helper.getEnd()); + } else { + currentViewDistance = Math.abs(helper.getEndAfterPadding() + - helper.getDecoratedEnd(currentView)); + } + } else { + currentViewDistance = Math.abs(helper.getDecoratedStart(currentView) + + (helper.getDecoratedMeasurement(currentView) / 2) - center); + } + if (currentViewDistance < distanceToTarget) { + distanceToTarget = currentViewDistance; + edgeView = currentView; + } + } + return edgeView; + } + + private boolean isAtEdgeOfList(LinearLayoutManager lm) { + if ((!lm.getReverseLayout() && gravity == Gravity.START) + || (lm.getReverseLayout() && gravity == Gravity.END) + || (!lm.getReverseLayout() && gravity == Gravity.TOP) + || (lm.getReverseLayout() && gravity == Gravity.BOTTOM)) { + return lm.findLastCompletelyVisibleItemPosition() == lm.getItemCount() - 1; + } else if (gravity == Gravity.CENTER) { + return lm.findFirstCompletelyVisibleItemPosition() == 0 + || lm.findLastCompletelyVisibleItemPosition() == lm.getItemCount() - 1; + } else { + return lm.findFirstCompletelyVisibleItemPosition() == 0; + } + } + + /** + * Dispatches a {@link SnapListener#onSnap(int)} event if the snapped position + * is different than {@link RecyclerView#NO_POSITION}. + *

+ * When {@link GravitySnapHelper#findSnapView(RecyclerView.LayoutManager)} returns null, + * {@link GravitySnapHelper#dispatchSnapChangeWhenPositionIsUnknown()} is called + * + * @param newState the new RecyclerView scroll state + */ + private void onScrollStateChanged(int newState) { + if (newState == RecyclerView.SCROLL_STATE_IDLE && listener != null) { + if (isScrolling) { + if (nextSnapPosition != RecyclerView.NO_POSITION) { + listener.onSnap(nextSnapPosition); + } else { + dispatchSnapChangeWhenPositionIsUnknown(); + } + } + } + isScrolling = newState != RecyclerView.SCROLL_STATE_IDLE; + } + + /** + * Calls {@link GravitySnapHelper#findSnapView(RecyclerView.LayoutManager, boolean)} + * without the check for the edge of the list. + *

+ * This makes sure that a position is reported in {@link SnapListener#onSnap(int)} + */ + private void dispatchSnapChangeWhenPositionIsUnknown() { + RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); + if (layoutManager == null) { + return; + } + View snapView = findSnapView(layoutManager, false); + if (snapView == null) { + return; + } + int snapPosition = recyclerView.getChildAdapterPosition(snapView); + if (snapPosition != RecyclerView.NO_POSITION) { + listener.onSnap(snapPosition); + } + } + + private OrientationHelper getVerticalHelper(RecyclerView.LayoutManager layoutManager) { + if (verticalHelper == null || verticalHelper.getLayoutManager() != layoutManager) { + verticalHelper = OrientationHelper.createVerticalHelper(layoutManager); + } + return verticalHelper; + } + + private OrientationHelper getHorizontalHelper(RecyclerView.LayoutManager layoutManager) { + if (horizontalHelper == null || horizontalHelper.getLayoutManager() != layoutManager) { + horizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager); + } + return horizontalHelper; + } + + /** + * A listener that's called when the {@link RecyclerView} used by {@link GravitySnapHelper} + * changes its scroll state to {@link RecyclerView#SCROLL_STATE_IDLE} + * and there's a valid snap position. + */ + public interface SnapListener { + /** + * @param position last position snapped to + */ + void onSnap(int position); + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/LinearEdgeDecoration.kt b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/LinearEdgeDecoration.kt new file mode 100644 index 000000000..c55356299 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/LinearEdgeDecoration.kt @@ -0,0 +1,62 @@ +package com.seafile.seadroid2.ui.media.image_preview2 + +import android.graphics.Rect +import android.view.View +import androidx.annotation.Px +import androidx.recyclerview.widget.RecyclerView + +class LinearEdgeDecoration( + @Px private val startPadding: Int, + @Px private val endPadding: Int = startPadding, + private val orientation: Int = RecyclerView.VERTICAL, + private val inverted: Boolean = false +) : RecyclerView.ItemDecoration() { + + override fun getItemOffsets( + outRect: Rect, view: View, parent: RecyclerView, + state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + + val layoutManager: RecyclerView.LayoutManager = parent.layoutManager!! + val layoutParams = view.layoutParams as RecyclerView.LayoutParams + val position = layoutParams.viewAdapterPosition + val itemCount = layoutManager.itemCount + + if (position == RecyclerView.NO_POSITION || itemCount == 0 + || (position > 0 && position < itemCount - 1) + ) { + return + } + + if (orientation == RecyclerView.HORIZONTAL) { + if (position == 0) { + if (!inverted) { + outRect.left = startPadding + } else { + outRect.right = startPadding + } + } else if (position == itemCount - 1) { + if (!inverted) { + outRect.right = endPadding + } else { + outRect.left = endPadding + } + } + } else { + if (position == 0) { + if (!inverted) { + outRect.top = startPadding + } else { + outRect.bottom = startPadding + } + } else if (position == itemCount - 1) { + if (!inverted) { + outRect.bottom = endPadding + } else { + outRect.top = endPadding + } + } + } + } +} \ No newline at end of file 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 33fb7f094..afb109773 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 @@ -89,6 +89,7 @@ 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.image_preview2.CarouselImagePreviewActivity; import com.seafile.seadroid2.ui.media.player.exoplayer.CustomExoVideoPlayerActivity; import com.seafile.seadroid2.ui.sdoc.SDocWebViewActivity; import com.seafile.seadroid2.ui.search.SearchViewModel; @@ -96,12 +97,10 @@ import com.seafile.seadroid2.view.TipsViews; import java.io.File; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import java.util.stream.Stream; import io.reactivex.functions.Consumer; @@ -430,7 +429,7 @@ private List getDisableMenuIds() { if (baseModel instanceof RepoModel m) { } else if (baseModel instanceof DirentModel m) { - if (m.isDir()){ + if (m.isDir()) { return CollectionUtils.newArrayList(R.id.upload); } } @@ -901,7 +900,7 @@ private void navTo(BaseModel model) { } } else if (model instanceof SearchModel searchModel) { - DirentModel direntModel = SearchModel.converterThis2DirentModel(searchModel); + DirentModel direntModel = SearchModel.convert2DirentModel(searchModel); if (direntModel.isDir()) { String repoId = getNavContext().getRepoModel().repo_id; @@ -1179,7 +1178,7 @@ private void open(DirentModel dirent) { // because pic thumbnail under encrypted repo was not supported at the server side if (Utils.isViewableImage(fileName) && !repoModel.encrypted) { - Intent getIntent = ImagePreviewActivity.startThisFromRepo(requireContext(), dirent); + Intent getIntent = CarouselImagePreviewActivity.startThisFromRepo(requireContext(), dirent); imagePreviewActivityLauncher.launch(getIntent); return; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java index daca7b503..75319cc0b 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java @@ -170,7 +170,7 @@ private void loadDirentsFromLocalWithGalleryViewType(Account account, NavContext String repoId = context.getRepoModel().repo_id; String parentDir = context.getNavPath(); - Single> direntDBSingle = AppDatabase.getInstance().direntDao().getListByParentPath(repoId, parentDir); + Single> direntDBSingle = AppDatabase.getInstance().direntDao().getListByParentPathAsync(repoId, parentDir); addSingleDisposable(direntDBSingle, new Consumer>() { @Override public void accept(List direntModels) throws Exception { @@ -198,7 +198,7 @@ private void loadDirentsFromLocal(Account account, NavContext context) { String repoId = context.getRepoModel().repo_id; String parentDir = context.getNavPath(); - Single> direntDBSingle = AppDatabase.getInstance().direntDao().getListByParentPath(repoId, parentDir); + Single> direntDBSingle = AppDatabase.getInstance().direntDao().getListByParentPathAsync(repoId, parentDir); Single> curParentDownloadedList = AppDatabase.getInstance().fileTransferDAO().getDownloadedListByParentAsync(repoId, parentDir); Single> resultSingle = Single.zip(direntDBSingle, curParentDownloadedList, new BiFunction, List, List>() { 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 index 39cf80e1e..9644fc6f8 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocWebViewActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocWebViewActivity.java @@ -24,6 +24,7 @@ import com.blankj.utilcode.util.ToastUtils; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.annotation.Todo; import com.seafile.seadroid2.annotation.Unstable; import com.seafile.seadroid2.databinding.ActivitySeaWebviewProBinding; import com.seafile.seadroid2.databinding.ToolbarActionbarProgressBarBinding; @@ -41,7 +42,7 @@ import com.seafile.seadroid2.view.webview.PreloadWebView; import com.seafile.seadroid2.view.webview.SeaWebView; -@Unstable +@Todo public class SDocWebViewActivity extends BaseActivityWithVM { private ActivitySeaWebviewProBinding binding; private ToolbarActionbarProgressBarBinding toolBinding; @@ -57,7 +58,7 @@ public class SDocWebViewActivity extends BaseActivityWithVM { * not support, please use SeaWebViewActivity instead */ public static void openSdoc(Context context, String repoName, String repoID, String path) { - Intent intent = new Intent(context, SeaWebViewActivity.class); + Intent intent = new Intent(context, SDocWebViewActivity.class); intent.putExtra("previewType", WebViewPreviewType.SDOC.name()); intent.putExtra("repoName", repoName); intent.putExtra("repoID", repoID); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorActivity.java index 0bd581ed2..453fa45c5 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorActivity.java @@ -273,25 +273,45 @@ public void onResultData(RepoModel uRepoModel) { } private void checkCurrentPathHasWritePermission(java.util.function.Consumer consumer) { - DirentModel direntModel = mNavContext.getTopDirentModel(); - if (!direntModel.isCustomPermission()) { - consumer.accept(direntModel.hasWritePermission()); + BaseModel model = mNavContext.getTopModel(); + + String repo_id = null; + int pNum = 0; + if (model instanceof RepoModel m) { + if (!m.isCustomPermission()) { + consumer.accept(m.hasWritePermission()); + return; + } else { + repo_id = m.repo_id; + pNum = m.getCustomPermissionNum(); + } + } else if (model instanceof DirentModel m) { + if (!m.isCustomPermission()) { + consumer.accept(m.hasWritePermission()); + return; + } else { + repo_id = m.repo_id; + pNum = m.getCustomPermissionNum(); + } } else { - viewModel.getPermissionFromLocal(direntModel.repo_id, direntModel.getCustomPermissionNum(), new Consumer() { - @Override - public void accept(PermissionEntity entity) throws Exception { - consumer.accept(entity != null && entity.create); - } - }); + consumer.accept(false); + return; } - } + viewModel.getPermissionFromLocal(repo_id, pNum, new Consumer() { + @Override + public void accept(PermissionEntity entity) throws Exception { + consumer.accept(entity != null && entity.create); + } + }); + } private void showNewDirDialog() { if (!mNavContext.inRepo()) { ToastUtils.showLong(R.string.choose_a_library); return; } + checkCurrentPathHasWritePermission(new java.util.function.Consumer() { @Override public void accept(Boolean aBoolean) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/transfer_list/TransferActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/transfer_list/TransferActivity.java index a393ca992..502beadf6 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/transfer_list/TransferActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/transfer_list/TransferActivity.java @@ -165,26 +165,13 @@ public boolean onCreateOptionsMenu(Menu menu) { return true; } - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - // MenuItem cancel = menu.findItem(R.id.cancel_transfer_tasks); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - finish(); - return true; - } - return super.onOptionsItemSelected(item); - } - @Override public boolean onMenuItemClick(MenuItem item) { int whichTab = binding.slidingTabs.getSelectedTabPosition(); - if (item.getItemId() == R.id.cancel_transfer_tasks) { + if (item.getItemId() == android.R.id.home) { + finish(); + } else if (item.getItemId() == R.id.cancel_transfer_tasks) { if (whichTab == 0) { getDownloadFragment().cancelAllTasks(); } else { diff --git a/app/src/main/res/layout/activity_carousel_image_preview.xml b/app/src/main/res/layout/activity_carousel_image_preview.xml new file mode 100644 index 000000000..2d2bccad4 --- /dev/null +++ b/app/src/main/res/layout/activity_carousel_image_preview.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_photo_view.xml b/app/src/main/res/layout/fragment_photo_view.xml index d21477f9e..76e9c805b 100644 --- a/app/src/main/res/layout/fragment_photo_view.xml +++ b/app/src/main/res/layout/fragment_photo_view.xml @@ -1,5 +1,6 @@ diff --git a/app/src/main/res/layout/item_carousel_item_vertical.xml b/app/src/main/res/layout/item_carousel_item_vertical.xml index 9b3620380..5afe7c027 100644 --- a/app/src/main/res/layout/item_carousel_item_vertical.xml +++ b/app/src/main/res/layout/item_carousel_item_vertical.xml @@ -1,33 +1,20 @@ - - - - - + + diff --git a/app/src/main/res/menu/menu_image_list_preview.xml b/app/src/main/res/menu/menu_image_list_preview.xml new file mode 100644 index 000000000..043a5241c --- /dev/null +++ b/app/src/main/res/menu/menu_image_list_preview.xml @@ -0,0 +1,16 @@ + +

+ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 605a4e8c4..e7d6739ab 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -60,6 +60,7 @@ @color/material_grey_100 @color/material_grey_900 @color/material_grey_900 + @color/material_grey_100_translucent @color/material_grey_599 diff --git a/app/src/main/res/values/colors_material.xml b/app/src/main/res/values/colors_material.xml index 1651cea50..872410610 100644 --- a/app/src/main/res/values/colors_material.xml +++ b/app/src/main/res/values/colors_material.xml @@ -261,6 +261,7 @@ #FAFAFA #F8F8F8 #F5F5F5 + #88F5F5F5 #F3F3F3 #EEEEEE #E0E0E0 @@ -275,6 +276,7 @@ #222222 #191919 + #00191919 #111111 #ECEFF1 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 4104e264e..476dcf67e 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -190,4 +190,8 @@ 22sp 292.6dp + + 20dp + 40dp + 2dp \ No newline at end of file diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index cc9727188..8f4cae6eb 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -13,4 +13,6 @@ + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ed9ae39c8..49a3b6e6d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -664,6 +664,7 @@ Last modified Ascending Folders first + Detail Size diff --git a/build_count.txt b/build_count.txt index d99e90eb9..8580e7b68 100644 --- a/build_count.txt +++ b/build_count.txt @@ -1 +1 @@ -29 \ No newline at end of file +30 \ No newline at end of file From e3f98912506d5032c6524418f9ea8d774088b728 Mon Sep 17 00:00:00 2001 From: zhwanng <48609908+zhwanng@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:12:02 +0800 Subject: [PATCH 03/17] new pages new Docs Comment page new Sdoc preview page --- app/build.gradle | 24 +- app/src/main/AndroidManifest.xml | 2 +- .../seadroid2/SeadroidApplication.java | 3 +- .../seadroid2/account/AccountUtils.java | 1 + .../account/SupportAccountManager.java | 4 + .../seadroid2/config/GlideLoadConfig.java | 30 +- .../config/WebViewActionConstant.java | 19 + .../framework/data/model/WebRouteModel.java | 11 + .../DocsCommentModel.java} | 12 +- .../docs_comment/DocsCommentWrapperModel.java | 5 + .../DocsCommentsWrapperModel.java | 7 + .../docs_comment/DocsUploadResultModel.java | 7 + ...cDetailModel.java => FileDetailModel.java} | 16 +- ...Model.java => FileProfileConfigModel.java} | 8 +- ...Model.java => FileRecordWrapperModel.java} | 16 +- .../{SDocModel.java => OutlineItemModel.java} | 4 +- .../data/model/sdoc/RecordResultModel.java | 3 + .../model/sdoc/SDocCommentWrapperModel.java | 7 - ...odel.java => SDocOutlineWrapperModel.java} | 4 +- .../framework/http/BaseOkHttpClient.java | 13 +- .../seadroid2/framework/http/HttpIO.java | 11 +- .../framework/http/SafeOkHttpClient.java | 38 +- .../framework/util/ContentResolvers.java | 55 ++ .../seadroid2/framework/util/StringUtils.java | 45 +- .../worker/upload/BaseUploadWorker.java | 26 +- .../seadroid2/preferences/Settings.java | 10 +- .../seafile/seadroid2/ssl/CertsManager.java | 19 +- .../seadroid2/ssl/SSLTrustManager.java | 1 + .../ui/account/AccountViewModel.java | 9 +- .../account/SeafileAuthenticatorActivity.java | 1 - .../ui/activities/AllActivitiesFragment.java | 4 +- .../ui/base/BaseMediaSelectorActivity.java | 174 ++++++ .../ui/base/viewmodel/BaseViewModel.java | 15 +- .../seadroid2/ui/dialog/SslConfirmDialog.java | 4 +- .../SignOutDialogFragment.java | 2 + .../viewmodel/NewDirViewModel.java | 4 +- .../viewmodel/NewRepoViewModel.java | 2 +- .../viewmodel/PasswordViewModel.java | 2 +- .../viewmodel/RenameRepoViewModel.java | 6 +- .../DocsCommentAdapter.java} | 127 +++-- .../DocsCommentUserAdapter.java} | 10 +- .../DocsCommentUserViewHolder.java} | 6 +- .../docs_comment/DocsCommentViewHolder.java | 17 + .../ui/docs_comment/DocsCommentsActivity.java | 330 +++++++++++ .../FileProfileDialog.java} | 73 +-- .../seadroid2/ui/main/MainActivity.java | 5 - .../media/image_info/FileDetailViewModel.java | 7 + .../image_preview/ImagePreviewActivity.java | 1 + .../image_preview/ImagePreviewViewModel.java | 46 +- .../ui/media/image_preview/PhotoFragment.java | 10 +- .../media/image_preview2/CarouselAdapter.java | 21 +- .../CarouselImagePreviewActivity.java | 243 ++++++-- .../CarouselItemViewHolder.java | 37 -- ...nterScaleXYRecyclerViewScrollListener.java | 6 +- .../image_preview2/GravitySnapHelper.java | 7 +- .../image_preview2/LinearEdgeDecoration.kt | 3 +- .../image_preview2/PagerSnapBinders.java | 48 ++ .../seadroid2/ui/repo/RepoQuickAdapter.java | 40 +- .../seadroid2/ui/repo/RepoQuickFragment.java | 3 +- .../seadroid2/ui/repo/RepoViewModel.java | 2 +- .../seadroid2/ui/sdoc/DocsCommentService.java | 63 +++ .../ui/sdoc/DocsCommentViewModel.java | 522 ++++++++++++++++++ .../seadroid2/ui/sdoc/SDocService.java | 34 -- .../seadroid2/ui/sdoc/SDocViewModel.java | 223 -------- .../ui/sdoc/SDocWebViewActivity.java | 188 +++++-- .../sdoc/comments/SDocCommentViewHolder.java | 17 - .../sdoc/comments/SDocCommentsActivity.java | 184 ------ .../sdoc/directory/SDocDirectoryAdapter.java | 59 -- .../ui/sdoc/outline/SDocOutlineAdapter.java | 56 ++ .../ui/sdoc/outline/SDocOutlineDialog.java | 158 ++++++ .../SDocOutlineRemoteDialog.java} | 40 +- .../seadroid2/ui/search/Search2Activity.java | 13 +- .../ui/selector/ObjSelectorActivity.java | 20 +- .../ui/settings/TabSettingsFragment.java | 66 +-- .../ui/star/StarredQuickFragment.java | 12 +- .../seadroid2/view/HideBottomBehavior.java | 1 + .../seafile/seadroid2/view/NestedWebView.java | 4 +- .../view/webview/IWebViewActionStrategy.java | 9 + .../seadroid2/view/webview/SeaWebView.java | 91 ++- .../view/webview/SeaWebViewClient.java | 15 +- .../strategy/AppShowToastStrategy.java | 13 + .../strategy/AppVersionGetStrategy.java | 11 + .../webview/strategy/BaseStrategyModel.java | 5 + .../webview/strategy/PageFinishStrategy.java | 28 + .../strategy/PageStatusColorSetStrategy.java | 37 ++ .../strategy/PageStatusHeightGetStrategy.java | 12 + .../webview/strategy/UnsupportedStrategy.java | 15 + .../widget/SimpleMarkdownParser.java | 130 +++++ .../main/res/drawable/baseline_comment_24.xml | 9 + .../main/res/drawable/baseline_delete_24.xml | 9 + .../main/res/drawable/baseline_info_24.xml | 15 + .../main/res/drawable/baseline_mark_32.xml | 9 + .../main/res/drawable/baseline_outline_24.xml | 24 + .../baseline_sdoc_mark_as_resolved_32.xml | 9 + .../main/res/drawable/baseline_share_24.xml | 9 + .../res/drawable/baseline_star_filled_24.xml | 9 + .../res/drawable/baseline_star_outline_24.xml | 6 +- app/src/main/res/drawable/ic_camera.xml | 9 + .../res/drawable/shape_solid_ff_radius_8.xml | 2 +- .../activity_carousel_image_preview.xml | 29 +- ...c_comment.xml => activity_doc_comment.xml} | 19 +- .../res/layout/activity_sea_webview_pro.xml | 69 +-- ...oc_profile.xml => dialog_file_profile.xml} | 10 + .../main/res/layout/dialog_sdoc_directory.xml | 2 +- .../layout/item_carousel_item_vertical.xml | 21 +- ...sdoc_comment.xml => item_file_comment.xml} | 60 +- ...oc_directory.xml => item_sdoc_outline.xml} | 3 +- app/src/main/res/layout/layout_empty.xml | 2 +- app/src/main/res/layout/layout_image.xml | 3 +- .../menu/bottom_sheet_camera_album_select.xml | 11 + .../main/res/menu/bottom_sheet_op_dirent.xml | 2 +- .../main/res/menu/bottom_sheet_op_repo.xml | 2 +- .../res/menu/menu_comment_mark_delete.xml | 11 + .../menu/transfer_list_multi_choice_menu.xml | 2 +- app/src/main/res/values-night/colors.xml | 2 + app/src/main/res/values-zh-rCN/strings.xml | 8 +- app/src/main/res/values/colors.xml | 2 + app/src/main/res/values/strings.xml | 6 +- app/src/main/res/xml/prefs_settings.xml | 1 + build_count.txt | 2 +- 120 files changed, 2942 insertions(+), 1127 deletions(-) create mode 100644 app/src/main/java/com/seafile/seadroid2/config/WebViewActionConstant.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/WebRouteModel.java rename app/src/main/java/com/seafile/seadroid2/framework/data/model/{sdoc/SDocCommentModel.java => docs_comment/DocsCommentModel.java} (70%) create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/docs_comment/DocsCommentWrapperModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/docs_comment/DocsCommentsWrapperModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/docs_comment/DocsUploadResultModel.java rename app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/{SDocDetailModel.java => FileDetailModel.java} (91%) rename app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/{SDocProfileConfigModel.java => FileProfileConfigModel.java} (78%) rename app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/{SDocRecordWrapperModel.java => FileRecordWrapperModel.java} (57%) rename app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/{SDocModel.java => OutlineItemModel.java} (74%) delete mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocCommentWrapperModel.java rename app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/{SDocWrapperModel.java => SDocOutlineWrapperModel.java} (67%) create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/util/ContentResolvers.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/base/BaseMediaSelectorActivity.java rename app/src/main/java/com/seafile/seadroid2/ui/{sdoc/comments/SDocCommentAdapter.java => docs_comment/DocsCommentAdapter.java} (60%) rename app/src/main/java/com/seafile/seadroid2/ui/{sdoc/comments/SDocCommentUserAdapter.java => docs_comment/DocsCommentUserAdapter.java} (83%) rename app/src/main/java/com/seafile/seadroid2/ui/{sdoc/comments/SDocCommentUserViewHolder.java => docs_comment/DocsCommentUserViewHolder.java} (63%) create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentViewHolder.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentsActivity.java rename app/src/main/java/com/seafile/seadroid2/ui/{sdoc/profile/SDocProfileDialog.java => file_profile/FileProfileDialog.java} (92%) create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/media/image_info/FileDetailViewModel.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselItemViewHolder.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/PagerSnapBinders.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentService.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentViewModel.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocService.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocViewModel.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentViewHolder.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentsActivity.java delete 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/outline/SDocOutlineAdapter.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineDialog.java rename app/src/main/java/com/seafile/seadroid2/ui/sdoc/{directory/SDocDirectoryDialog.java => outline/SDocOutlineRemoteDialog.java} (77%) create mode 100644 app/src/main/java/com/seafile/seadroid2/view/webview/IWebViewActionStrategy.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/webview/strategy/AppShowToastStrategy.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/webview/strategy/AppVersionGetStrategy.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/webview/strategy/BaseStrategyModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/webview/strategy/PageFinishStrategy.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/webview/strategy/PageStatusColorSetStrategy.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/webview/strategy/PageStatusHeightGetStrategy.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/webview/strategy/UnsupportedStrategy.java create mode 100644 app/src/main/java/com/seafile/seadroid2/widget/SimpleMarkdownParser.java create mode 100644 app/src/main/res/drawable/baseline_comment_24.xml create mode 100644 app/src/main/res/drawable/baseline_delete_24.xml create mode 100644 app/src/main/res/drawable/baseline_info_24.xml create mode 100644 app/src/main/res/drawable/baseline_mark_32.xml create mode 100644 app/src/main/res/drawable/baseline_outline_24.xml create mode 100644 app/src/main/res/drawable/baseline_sdoc_mark_as_resolved_32.xml create mode 100644 app/src/main/res/drawable/baseline_share_24.xml create mode 100644 app/src/main/res/drawable/baseline_star_filled_24.xml create mode 100644 app/src/main/res/drawable/ic_camera.xml rename app/src/main/res/layout/{activity_sdoc_comment.xml => activity_doc_comment.xml} (87%) rename app/src/main/res/layout/{dialog_sdoc_profile.xml => dialog_file_profile.xml} (79%) rename app/src/main/res/layout/{item_sdoc_comment.xml => item_file_comment.xml} (56%) rename app/src/main/res/layout/{item_sdoc_directory.xml => item_sdoc_outline.xml} (85%) create mode 100644 app/src/main/res/menu/bottom_sheet_camera_album_select.xml create mode 100644 app/src/main/res/menu/menu_comment_mark_delete.xml diff --git a/app/build.gradle b/app/build.gradle index ab7864caf..21abfdb74 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,7 +14,7 @@ android { targetSdkVersion rootProject.ext.targetSdkVersion compileSdk rootProject.ext.compileSdkVersion - versionCode 158 + versionCode 160 versionName "3.0.7" multiDexEnabled true @@ -184,15 +184,12 @@ android { // Firebase implementation platform('com.google.firebase:firebase-bom:33.2.0') implementation 'com.google.firebase:firebase-crashlytics' - implementation 'com.google.firebase:firebase-analytics' //media3 final def media3_version = '1.4.1' implementation "androidx.media3:media3-exoplayer:$media3_version" implementation "androidx.media3:media3-ui:$media3_version" -// implementation 'com.github.rubensousa:gravitysnaphelper:2.2.2' - implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.core:core-splashscreen:1.0.1' implementation "androidx.appcompat:appcompat:1.7.0" @@ -239,6 +236,17 @@ android { //https://github.com/KunMinX/UnPeek-LiveData implementation 'com.kunminx.arch:unpeek-livedata:7.8.0' + implementation 'org.commonmark:commonmark:0.21.0' +// implementation 'org.commonmark:commonmark-ext-gfm-tables:0.21.0' // 如果需要表格支持 + implementation 'org.commonmark:commonmark-ext-autolink:0.21.0' // 如果需要自动链接支持 + + //https://github.com/lzyzsd/JsBridge + implementation 'com.github.lzyzsd:jsbridge:1.0.4' + +// https://github.com/rubensousa/GravitySnapHelper +// implementation 'com.github.rubensousa:gravitysnaphelper:2.2.2' + + // implementation 'com.blankj:utilcode:1.30.7' @@ -289,20 +297,20 @@ android { implementation 'com.github.yydcdut.RxMarkdown:markdown-processor:v0.1.3' -// //markdown + //https://noties.io/Markwon/ // final def markwon_version = '4.6.2' // implementation "io.noties.markwon:core:$markwon_version" // implementation "io.noties.markwon:editor:$markwon_version" -// implementation "io.noties.markwon:ext-latex:$markwon_version" // implementation "io.noties.markwon:ext-strikethrough:$markwon_version" // implementation "io.noties.markwon:ext-tables:$markwon_version" // implementation "io.noties.markwon:ext-tasklist:$markwon_version" +// implementation "io.noties.markwon:ext-latex:$markwon_version" // implementation "io.noties.markwon:html:$markwon_version" // implementation "io.noties.markwon:image:$markwon_version" // implementation "io.noties.markwon:image-glide:$markwon_version" -// implementation "io.noties.markwon:inline-parser:$markwon_version" // implementation "io.noties.markwon:linkify:$markwon_version" -// implementation "io.noties.markwon:syntax-highlight:$markwon_version" +// implementation "io.noties.markwon:inline-parser:$markwon_version" +// implementation "io.noties.markwon:recycler:$markwon_version" implementation 'com.madgag.spongycastle:core:1.54.0.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 57b667591..7d8fb0aa7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -210,7 +210,7 @@ - + getAccountList() { List list = new ArrayList<>(); diff --git a/app/src/main/java/com/seafile/seadroid2/config/GlideLoadConfig.java b/app/src/main/java/com/seafile/seadroid2/config/GlideLoadConfig.java index 4056c3008..785a181a8 100644 --- a/app/src/main/java/com/seafile/seadroid2/config/GlideLoadConfig.java +++ b/app/src/main/java/com/seafile/seadroid2/config/GlideLoadConfig.java @@ -1,25 +1,29 @@ package com.seafile.seadroid2.config; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.load.model.LazyHeaders; import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.signature.ObjectKey; import com.seafile.seadroid2.R; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.ui.WidgetUtils; public class GlideLoadConfig { -// public static GlideUrl getGlideUrl(String url) { -// -// Account account = SupportAccountManager.getInstance().getCurrentAccount(); -// if (account == null) { -// return new GlideUrl(url, new LazyHeaders.Builder().build()); -// } -// -// String token = account.token; -// -// return new GlideUrl(url, new LazyHeaders.Builder() -// .addHeader("Authorization", "Token " + token) -// .build()); -// } + public static GlideUrl getGlideUrl(String url) { + + Account account = SupportAccountManager.getInstance().getCurrentAccount(); + if (account == null) { + return new GlideUrl(url, new LazyHeaders.Builder().build()); + } + + String token = account.token; + + return new GlideUrl(url, new LazyHeaders.Builder() + .addHeader("Authorization", "Token " + token) + .build()); + } // // public static GlideUrl getGlideUrl(String url, String token) { // return new GlideUrl(url, new LazyHeaders.Builder() diff --git a/app/src/main/java/com/seafile/seadroid2/config/WebViewActionConstant.java b/app/src/main/java/com/seafile/seadroid2/config/WebViewActionConstant.java new file mode 100644 index 000000000..5cdb5fda2 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/config/WebViewActionConstant.java @@ -0,0 +1,19 @@ +package com.seafile.seadroid2.config; + +public class WebViewActionConstant { + public final static String APP_VERSION_GET = "app.version.get"; + public final static String APP_TOAST_SHOW = "app.toast.show"; + public final static String PAGE_FINISH = "page.finish"; + public final static String PAGE_STATUS_COLOR_SET = "page.status.color.set"; + public final static String PAGE_STATUS_HEIGHT_GET = "page.status.height.get"; + + public static class CallJsFunction { + public final static String SDOC_OUTLINES_DATA_GET = "sdoc.outline.data.get"; + public final static String SDOC_OUTLINES_DATA_SELECT = "sdoc.outline.data.select"; + + public static class CallJsFunctionRequestCode { + public final static int REQ = 1001; + } + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/WebRouteModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/WebRouteModel.java new file mode 100644 index 000000000..8fae7b728 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/WebRouteModel.java @@ -0,0 +1,11 @@ +package com.seafile.seadroid2.framework.data.model; + +public class WebRouteModel { + public int v = 1; + public String action; + + /** + * params data + */ + public String data; +} 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/docs_comment/DocsCommentModel.java similarity index 70% rename from app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocCommentModel.java rename to app/src/main/java/com/seafile/seadroid2/framework/data/model/docs_comment/DocsCommentModel.java index 9260574cd..06c7b55cb 100644 --- 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/docs_comment/DocsCommentModel.java @@ -1,13 +1,15 @@ -package com.seafile.seadroid2.framework.data.model.sdoc; +package com.seafile.seadroid2.framework.data.model.docs_comment; import com.google.gson.annotations.JsonAdapter; import com.seafile.seadroid2.framework.data.model.adapter.OffsetDateTimeAdapter; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocCommentReplyModel; +import com.seafile.seadroid2.view.rich_edittext.RichEditText; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.List; -public class SDocCommentModel { +public class DocsCommentModel { public int id; public String item_name; public String parent_path; @@ -16,7 +18,7 @@ public class SDocCommentModel { public List replies; public String comment; public String repo_id; - public String resolved; + public boolean resolved; public String user_contact_email; public String user_email; public String user_name; @@ -29,6 +31,10 @@ public class SDocCommentModel { public String detail; + + public boolean isContainImage = false; + public List commentList; + 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/docs_comment/DocsCommentWrapperModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/docs_comment/DocsCommentWrapperModel.java new file mode 100644 index 000000000..e3506abb4 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/docs_comment/DocsCommentWrapperModel.java @@ -0,0 +1,5 @@ +package com.seafile.seadroid2.framework.data.model.docs_comment; + +public class DocsCommentWrapperModel { + public DocsCommentModel comment; +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/docs_comment/DocsCommentsWrapperModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/docs_comment/DocsCommentsWrapperModel.java new file mode 100644 index 000000000..4ec05381b --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/docs_comment/DocsCommentsWrapperModel.java @@ -0,0 +1,7 @@ +package com.seafile.seadroid2.framework.data.model.docs_comment; + +import java.util.List; + +public class DocsCommentsWrapperModel { + public List comments; +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/docs_comment/DocsUploadResultModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/docs_comment/DocsUploadResultModel.java new file mode 100644 index 000000000..a5d8c2974 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/docs_comment/DocsUploadResultModel.java @@ -0,0 +1,7 @@ +package com.seafile.seadroid2.framework.data.model.docs_comment; + +import java.util.List; + +public class DocsUploadResultModel { + public List relative_path; +} 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/FileDetailModel.java similarity index 91% rename from app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocDetailModel.java rename to app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/FileDetailModel.java index 9f9aa26ba..77ea5f381 100644 --- 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/FileDetailModel.java @@ -13,7 +13,7 @@ import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; -public class SDocDetailModel implements Parcelable { +public class FileDetailModel implements Parcelable { private String type; private String id; @@ -183,10 +183,10 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeByte(this.canEdit ? (byte) 1 : (byte) 0); } - public SDocDetailModel() { + public FileDetailModel() { } - protected SDocDetailModel(Parcel in) { + protected FileDetailModel(Parcel in) { this.type = in.readString(); this.id = in.readString(); this.name = in.readString(); @@ -203,15 +203,15 @@ protected SDocDetailModel(Parcel in) { this.canEdit = in.readByte() != 0; } - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override - public SDocDetailModel createFromParcel(Parcel source) { - return new SDocDetailModel(source); + public FileDetailModel createFromParcel(Parcel source) { + return new FileDetailModel(source); } @Override - public SDocDetailModel[] newArray(int size) { - return new SDocDetailModel[size]; + public FileDetailModel[] newArray(int size) { + return new FileDetailModel[size]; } }; } 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/FileProfileConfigModel.java similarity index 78% rename from app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocProfileConfigModel.java rename to app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/FileProfileConfigModel.java index 658ef12cd..0b63f210d 100644 --- 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/FileProfileConfigModel.java @@ -2,10 +2,10 @@ import com.seafile.seadroid2.framework.data.model.user.UserWrapperModel; -public class SDocProfileConfigModel { +public class FileProfileConfigModel { public UserWrapperModel users; public MetadataConfigModel metadata; - public SDocDetailModel detail; + public FileDetailModel detail; public UserWrapperModel getUsers() { return users; @@ -23,11 +23,11 @@ public void setMetadata(MetadataConfigModel metadata) { this.metadata = metadata; } - public SDocDetailModel getDetail() { + public FileDetailModel getDetail() { return detail; } - public void setDetail(SDocDetailModel detail) { + public void setDetail(FileDetailModel 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/FileRecordWrapperModel.java similarity index 57% rename from app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocRecordWrapperModel.java rename to app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/FileRecordWrapperModel.java index 4f1748c07..a104efff1 100644 --- 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/FileRecordWrapperModel.java @@ -5,7 +5,7 @@ import java.util.List; -public class SDocRecordWrapperModel implements Parcelable { +public class FileRecordWrapperModel implements Parcelable { public List metadata; public List results; @@ -20,23 +20,23 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeTypedList(this.results); } - public SDocRecordWrapperModel() { + public FileRecordWrapperModel() { } - protected SDocRecordWrapperModel(Parcel in) { + protected FileRecordWrapperModel(Parcel in) { this.metadata = in.createTypedArrayList(MetadataModel.CREATOR); this.results = in.createTypedArrayList(RecordResultModel.CREATOR); } - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override - public SDocRecordWrapperModel createFromParcel(Parcel source) { - return new SDocRecordWrapperModel(source); + public FileRecordWrapperModel createFromParcel(Parcel source) { + return new FileRecordWrapperModel(source); } @Override - public SDocRecordWrapperModel[] newArray(int size) { - return new SDocRecordWrapperModel[size]; + public FileRecordWrapperModel[] newArray(int size) { + return new FileRecordWrapperModel[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/OutlineItemModel.java similarity index 74% rename from app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocModel.java rename to app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/OutlineItemModel.java index d8b316122..9ef5cea74 100644 --- 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/OutlineItemModel.java @@ -4,11 +4,11 @@ import java.util.List; -public class SDocModel extends BaseModel { +public class OutlineItemModel extends BaseModel { public String id; public String type; public String text; public boolean indent; - public List children; + public List children; public SDocDataModel data; } 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 index 11386d1ed..e602da6ef 100644 --- 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 @@ -37,6 +37,7 @@ public class RecordResultModel implements Parcelable { public String _suffix; public String _description; public List _owner; + public List _reviewer; @JsonAdapter(RecordResultDeserializer.class) public Map dynamicFields; @@ -49,6 +50,7 @@ public int describeContents() { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeStringList(this._collaborators); + dest.writeStringList(this._reviewer); dest.writeString(this._creator); dest.writeString(this._ctime); dest.writeString(this._file_creator); @@ -80,6 +82,7 @@ public RecordResultModel() { protected RecordResultModel(Parcel in) { this._collaborators = in.createStringArrayList(); + this._reviewer = in.createStringArrayList(); this._creator = in.readString(); this._ctime = in.readString(); this._file_creator = in.readString(); 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 deleted file mode 100644 index 796389d0c..000000000 --- a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocCommentWrapperModel.java +++ /dev/null @@ -1,7 +0,0 @@ -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/SDocWrapperModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocOutlineWrapperModel.java similarity index 67% rename from app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocWrapperModel.java rename to app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/SDocOutlineWrapperModel.java index 03e7cfdea..3452258f7 100644 --- 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/SDocOutlineWrapperModel.java @@ -2,9 +2,9 @@ import java.util.List; -public class SDocWrapperModel { +public class SDocOutlineWrapperModel { public int version; public int format_version; public String last_modify_user; - public List elements; + public List elements; } diff --git a/app/src/main/java/com/seafile/seadroid2/framework/http/BaseOkHttpClient.java b/app/src/main/java/com/seafile/seadroid2/framework/http/BaseOkHttpClient.java index 1e3f73424..0374fc0e2 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/http/BaseOkHttpClient.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/http/BaseOkHttpClient.java @@ -43,6 +43,16 @@ public BaseOkHttpClient(Account account) { protected List getInterceptors() { + List interceptors = getInterceptorsWithoutToken(); + if (account != null && !TextUtils.isEmpty(account.token)) { + interceptors.add(new HeaderInterceptor(account.token)); + } + + return interceptors; + } + + protected List getInterceptorsWithoutToken() { + List interceptors = new ArrayList<>(); //print log @@ -51,9 +61,6 @@ protected List getInterceptors() { loggingInterceptor.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.BASIC); interceptors.add(loggingInterceptor); - if (account != null && !TextUtils.isEmpty(account.token)) { - interceptors.add(new HeaderInterceptor(account.token)); - } // interceptors.add(new AddCookiesInterceptor()); // interceptors.add(new ReceivedCookiesInterceptor()); diff --git a/app/src/main/java/com/seafile/seadroid2/framework/http/HttpIO.java b/app/src/main/java/com/seafile/seadroid2/framework/http/HttpIO.java index 00702a34a..2fd29011f 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/http/HttpIO.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/http/HttpIO.java @@ -80,6 +80,14 @@ public static HttpIO getCurrentInstance() { return INSTANCE; } + public static void removeInstanceByAccount(Account account) { + if (account == null) { + return; + } + + IO_MAP.remove(account.getSignature()); + } + /** * Not logged in/Log in to another server */ @@ -153,6 +161,7 @@ public T execute(Class clazz) { private Retrofit createRetrofit() { Retrofit.Builder rBuilder = new Retrofit.Builder(); + rBuilder.baseUrl(getServerUrl()); rBuilder.addConverterFactory(ConverterFactory.create()); rBuilder.addCallAdapterFactory(RxJava2CallAdapterFactory.create()); @@ -162,7 +171,6 @@ private Retrofit createRetrofit() { return rBuilder.build(); } - public void downloadBinarySync(String url, File destinationFile, ProgressListener callback) throws IOException { OkHttpClient client = getOkHttpClient().getOkClient(); @@ -178,7 +186,6 @@ public void onProgress(long transferSize, long totalSize) { try (BinaryFileDownloader fileDownloader = new BinaryFileDownloader(client, fileWriter)) { fileDownloader.download(url); - } catch (Exception e) { if (callback != null) { callback.isCancelled(); 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 25524a5c1..cc46d68b6 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,6 +2,7 @@ import com.blankj.utilcode.util.CollectionUtils; import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.ssl.CertsManager; import com.seafile.seadroid2.ssl.SSLSeafileSocketFactory; import com.seafile.seadroid2.ssl.SSLTrustManager; @@ -12,6 +13,7 @@ import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; @@ -31,8 +33,28 @@ import okhttp3.Request; public class SafeOkHttpClient extends BaseOkHttpClient { + private final List _interceptors = new ArrayList<>(); + public SafeOkHttpClient(Account account) { super(account); + + _interceptors.addAll(getInterceptors()); + } + + public SafeOkHttpClient(Account account, boolean isCustomToken) { + super(account); + + if (isCustomToken) { + _interceptors.addAll(getInterceptorsWithoutToken()); + } else { + _interceptors.addAll(getInterceptors()); + } + } + + public void addInterceptors(List s) { + if (s != null) { + _interceptors.addAll(s); + } } public static TrustManager[] getTrustManagers() { @@ -69,24 +91,15 @@ public OkHttpClient getOkClient() { //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); + return true; } }); } @@ -129,9 +142,8 @@ public boolean verify(String hostname, SSLSession session) { builder.networkInterceptors().add(REWRITE_CACHE_CONTROL_INTERCEPTOR); //add interceptors - List interceptors = getInterceptors(); - if (!CollectionUtils.isEmpty(interceptors)) { - for (Interceptor i : interceptors) { + if (!CollectionUtils.isEmpty(_interceptors)) { + for (Interceptor i : _interceptors) { builder.interceptors().add(i); } } diff --git a/app/src/main/java/com/seafile/seadroid2/framework/util/ContentResolvers.java b/app/src/main/java/com/seafile/seadroid2/framework/util/ContentResolvers.java new file mode 100644 index 000000000..de86a85ee --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/util/ContentResolvers.java @@ -0,0 +1,55 @@ +package com.seafile.seadroid2.framework.util; + +import android.content.ContentResolver; +import android.database.Cursor; +import android.net.Uri; +import android.provider.MediaStore; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ContentResolvers { + + public static String getFileNameFromUri(ContentResolver contentResolver, Uri uri) { + String fileName = null; + String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME}; + try (Cursor cursor = contentResolver.query(uri, projection, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME); + fileName = cursor.getString(index); + } + } + + return fileName; + } + + // 获取文件内容的方法 + public static byte[] getFileContentFromUri(ContentResolver contentResolver, Uri uri) { + byte[] fileContent = null; + InputStream inputStream = null; + try { + inputStream = contentResolver.openInputStream(uri); + if (inputStream != null) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + fileContent = outputStream.toByteArray(); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return fileContent; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/util/StringUtils.java b/app/src/main/java/com/seafile/seadroid2/framework/util/StringUtils.java index 6d8d5c876..975d1926b 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/util/StringUtils.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/util/StringUtils.java @@ -129,9 +129,13 @@ public static String trimStart(String input, String character) { } /** - *
-     * ""string"" => "string"
-     * 
+ *
+ *
+     *  ""string"" => "string"
+     *  null => null
+     *  "" => ""
+     * 
+ *
*/ public static String deString(String input) { if (TextUtils.isEmpty(input)) { @@ -143,6 +147,25 @@ public static String deString(String input) { return input; } + /** + *
+ *
+     * ""string"" => "string"
+     * null => ""
+     * "" => ""
+     * 
+ *
+ */ + public static String deStringReturnNonNull(String input) { + if (TextUtils.isEmpty(input)) { + return ""; + } + + input = trim(input, "\""); + + return input; + } + public static int getStringAsciiSum(String input) { int sum = 0; for (int i = 0; i < input.length(); i++) { @@ -151,7 +174,7 @@ public static int getStringAsciiSum(String input) { } return sum; } - + public static int getHexStringAsciiSum(String input) { byte[] bytes = hexStringToByteArray(input); @@ -173,4 +196,18 @@ public static byte[] hexStringToByteArray(String s) { } return data; } + + /** + * => org.apache.commons.lang3.StringUtils#countMatches(c,c) + */ + public static int countMatches(String inputStr, String matchStr) { + if (TextUtils.isEmpty(inputStr)) { + return 0; + } + if (TextUtils.isEmpty(matchStr)) { + return 0; + } + + return org.apache.commons.lang3.StringUtils.countMatches(inputStr, matchStr); + } } diff --git a/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/BaseUploadWorker.java b/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/BaseUploadWorker.java index d3742b207..04662712e 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/BaseUploadWorker.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/BaseUploadWorker.java @@ -485,24 +485,25 @@ private void uploadFile(Account account, RepoModel repoModel, FileTransferEntity .build(); newCall = HttpIO.getCurrentInstance().getOkHttpClient().getOkClient().newCall(request); - Response response = newCall.execute(); + String str; + try (Response response = newCall.execute()) { + if (!response.isSuccessful()) { + String b = response.body() != null ? response.body().string() : null; + SLogs.d("result,failed:" + b); - if (!response.isSuccessful()) { - String b = response.body() != null ? response.body().string() : null; - SLogs.d("result,failed:" + b); + // + newCall.cancel(); - // - newCall.cancel(); + //[text={"error": "Out of quota.\n"}] + if (b != null && b.toLowerCase().contains("out of quota")) { + throw SeafException.OUT_OF_QUOTA; + } - //[text={"error": "Out of quota.\n"}] - if (b != null && b.toLowerCase().contains("out of quota")) { - throw SeafException.OUT_OF_QUOTA; + throw SeafException.networkException; } - throw SeafException.networkException; + str = response.body().string(); } - - String str = response.body().string(); String fileId = str.replace("\"", ""); SLogs.d("result,file ID:" + str); @@ -512,7 +513,6 @@ private void uploadFile(Account account, RepoModel repoModel, FileTransferEntity } } - private void uploadBlockFile(Account account, RepoModel repoModel, FileTransferEntity transferEntity) throws SeafException, IOException, JSONException { if (isStopped()) { return; 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 2519d4d0c..2c6441e5e 100644 --- a/app/src/main/java/com/seafile/seadroid2/preferences/Settings.java +++ b/app/src/main/java/com/seafile/seadroid2/preferences/Settings.java @@ -49,6 +49,8 @@ public Settings() { public static SettingsLiveData SPACE_INFO; public static SettingsLiveData NIGHT_MODE; public static SettingsLiveData APP_NIGHT_MODE; + + @Deprecated public static SettingsLiveData USER_GESTURE_LOCK_SWITCH; // public static SettingsLiveData USER_GESTURE_LOCK_TIMESTAMP; @@ -137,9 +139,9 @@ public static void initUserSettings(Account account) { SPACE_INFO = new StringSettingLiveData(_account.getEncryptSignature(), R.string.pref_key_user_space, R.string.settings_account_info_load_data); NIGHT_MODE = new EnumSettingLiveData<>(NightMode.class, _account.getEncryptSignature(), R.string.pref_key_night_mode, R.string.pref_default_value_night_mode); APP_NIGHT_MODE = new EnumSettingLiveData<>(NightMode.class, _account.getEncryptSignature(), R.string.pref_key_current_night_mode, R.string.pref_default_value_night_mode); - CLIENT_ENCRYPT_SWITCH = new BooleanSettingLiveData(_account.getEncryptSignature(), R.string.pref_key_security_client_encrypt); +// CLIENT_ENCRYPT_SWITCH = new BooleanSettingLiveData(_account.getEncryptSignature(), R.string.pref_key_security_client_encrypt); - USER_GESTURE_LOCK_SWITCH = new BooleanSettingLiveData(_account.getEncryptSignature(), R.string.pref_key_gesture_lock); +// USER_GESTURE_LOCK_SWITCH = new BooleanSettingLiveData(_account.getEncryptSignature(), R.string.pref_key_gesture_lock); // USER_GESTURE_LOCK_TIMESTAMP = new LongSettingLiveData(_account.getEncryptSignature(), R.string.pref_key_gesture_lock_timestamp, R.string.pref_default_value_key_gesture_lock_timestamp); @@ -169,9 +171,9 @@ public static void initUserSettings(Account account) { REGISTER_LIST.add(SPACE_INFO); REGISTER_LIST.add(NIGHT_MODE); REGISTER_LIST.add(APP_NIGHT_MODE); - REGISTER_LIST.add(USER_GESTURE_LOCK_SWITCH); +// REGISTER_LIST.add(USER_GESTURE_LOCK_SWITCH); // REGISTER_LIST.add(USER_GESTURE_LOCK_TIMESTAMP); - REGISTER_LIST.add(CLIENT_ENCRYPT_SWITCH); +// REGISTER_LIST.add(CLIENT_ENCRYPT_SWITCH); REGISTER_LIST.add(ALBUM_BACKUP_SWITCH); REGISTER_LIST.add(ALBUM_BACKUP_SELECTED_REPO); REGISTER_LIST.add(ALBUM_BACKUP_STATE); 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 d39f018d5..2cdf472a6 100644 --- a/app/src/main/java/com/seafile/seadroid2/ssl/CertsManager.java +++ b/app/src/main/java/com/seafile/seadroid2/ssl/CertsManager.java @@ -17,7 +17,7 @@ */ public final class CertsManager { - private final Map cachedCerts = Maps.newConcurrentMap(); + private final Map cachedCerts = Maps.newConcurrentMap(); private static CertsManager instance; @@ -36,7 +36,7 @@ public void saveCertForAccount(final Account account, boolean rememberChoice) { } final X509Certificate cert = certs.get(0); - cachedCerts.put(account, cert); + cachedCerts.put(account.getServer(), cert); if (rememberChoice) { // save cert info to shared preferences @@ -46,12 +46,23 @@ public void saveCertForAccount(final Account account, boolean rememberChoice) { } } + public void deleteCertForAccount(final Account account) { + if (account == null) { + return; + } + + cachedCerts.remove(account.getServer()); + + String keyPrefix = EncryptUtils.encryptMD5ToString(account.getServer()); + Settings.getCommonPreferences().edit().remove(DataStoreKeys.KEY_SERVER_CERT_INFO + "_" + keyPrefix).apply(); + } public X509Certificate getCertificate(Account account) { - X509Certificate cert = cachedCerts.get(account); + X509Certificate cert = cachedCerts.get(account.getServer()); 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)) { @@ -60,7 +71,7 @@ public X509Certificate getCertificate(Account account) { cert = CertsHelper.convertToCert(certBase64); if (cert != null) { - cachedCerts.put(account, cert); + cachedCerts.put(account.getServer(), cert); } return cert; 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 fc0ea4f36..c75f1312c 100644 --- a/app/src/main/java/com/seafile/seadroid2/ssl/SSLTrustManager.java +++ b/app/src/main/java/com/seafile/seadroid2/ssl/SSLTrustManager.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Set; +import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/account/AccountViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/account/AccountViewModel.java index a79bcc7e0..57d613dfb 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/account/AccountViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/account/AccountViewModel.java @@ -17,6 +17,7 @@ import com.seafile.seadroid2.framework.http.HttpIO; import com.seafile.seadroid2.account.AccountUtils; import com.seafile.seadroid2.framework.util.DeviceIdManager; +import com.seafile.seadroid2.ssl.CertsManager; import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; import com.seafile.seadroid2.ui.dialog_fragment.SignOutDialogFragment; import com.seafile.seadroid2.ui.main.MainService; @@ -181,7 +182,7 @@ private Call getLoginCall(Account tempAccount, String pwd, String to body.put("client_version", appVersion); body.put("platform_version", Build.VERSION.RELEASE); - Map requestBody = generateRequestBody(body); + Map requestBody = genRequestBody(body); return HttpIO.getInstanceByAccount(tempAccount).execute(AccountService.class).login(headers, requestBody); } @@ -214,9 +215,6 @@ public void accept(Throwable throwable) throws Exception { }); } - /** - * @see SignOutDialogFragment#onPositiveClick() - */ public void deleteAccount(Account account) { Account curAccount = SupportAccountManager.getInstance().getCurrentAccount(); @@ -224,8 +222,11 @@ public void deleteAccount(Account account) { if (curAccount != null && curAccount.equals(account)) { // AccountUtils.logout(account); + } else { + HttpIO.removeInstanceByAccount(account); } + CertsManager.instance().deleteCertForAccount(account); //delete local account SupportAccountManager.getInstance().removeAccount(account.getAndroidAccount(), null, null); 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 a993ea2a5..c38be8735 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 @@ -23,7 +23,6 @@ import androidx.core.app.NavUtils; import androidx.core.app.TaskStackBuilder; -import com.google.firebase.analytics.FirebaseAnalytics; import com.seafile.seadroid2.R; import com.seafile.seadroid2.account.Authenticator; import com.seafile.seadroid2.account.SupportAccountManager; 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 24d1dfbc4..1339ac269 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 @@ -40,7 +40,7 @@ import com.seafile.seadroid2.ui.file.FileActivity; import com.seafile.seadroid2.ui.main.MainActivity; import com.seafile.seadroid2.ui.markdown.MarkdownActivity; -import com.seafile.seadroid2.ui.media.image_preview.ImagePreviewActivity; +import com.seafile.seadroid2.ui.media.image_preview2.CarouselImagePreviewActivity; import com.seafile.seadroid2.ui.media.player.exoplayer.CustomExoVideoPlayerActivity; import com.seafile.seadroid2.ui.sdoc.SDocWebViewActivity; import com.seafile.seadroid2.view.TipsViews; @@ -274,7 +274,7 @@ private void open(RepoModel repoModel, ActivityModel activityModel) { } else if (Utils.isViewableImage(activityModel.name)) { - Intent getIntent = ImagePreviewActivity.startThisFromActivity(requireContext(), activityModel); + Intent getIntent = CarouselImagePreviewActivity.startThisFromActivity(requireContext(), activityModel); imagePreviewActivityLauncher.launch(getIntent); } else if (activityModel.name.endsWith(Constants.Format.DOT_SDOC)) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/base/BaseMediaSelectorActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/base/BaseMediaSelectorActivity.java new file mode 100644 index 000000000..e38c5bb2e --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/base/BaseMediaSelectorActivity.java @@ -0,0 +1,174 @@ +package com.seafile.seadroid2.ui.base; + +import android.Manifest; +import android.net.Uri; +import android.os.Bundle; +import android.view.MenuItem; + +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.PickVisualMediaRequest; +import androidx.activity.result.contract.ActivityResultContracts; + +import com.blankj.utilcode.util.ToastUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.framework.util.SLogs; +import com.seafile.seadroid2.framework.util.TakeCameras; +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.ui.bottomsheetmenu.BottomSheetHelper; +import com.seafile.seadroid2.ui.bottomsheetmenu.OnMenuClickListener; + +import java.io.File; +import java.util.List; + +import kotlin.Pair; + +public class BaseMediaSelectorActivity extends BaseActivityWithVM { + private ActivityResultLauncher cameraPermissionLauncher, cameraPermissionLauncher1, storagePermissionLauncher; + private ActivityResultLauncher takePhotoLauncher; + private ActivityResultLauncher shootVideoLauncher; + private ActivityResultLauncher pickMediaLauncher; + private ActivityResultLauncher pickMultipleMediaLauncher; + private ActivityResultLauncher fileChooseLauncher; + private Pair uriPair; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + initLauncher(); + } + + private void initLauncher() { + cameraPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), new ActivityResultCallback() { + @Override + public void onActivityResult(Boolean result) { + if (result) { + uriPair = TakeCameras.buildTakePhotoUri(BaseMediaSelectorActivity.this); + takePhotoLauncher.launch(uriPair.getFirst()); + } else { + ToastUtils.showLong(R.string.permission_camera); + } + } + }); + + cameraPermissionLauncher1 = registerForActivityResult(new ActivityResultContracts.RequestPermission(), new ActivityResultCallback() { + @Override + public void onActivityResult(Boolean result) { + if (result) { + uriPair = TakeCameras.buildTakePhotoUri(BaseMediaSelectorActivity.this); + shootVideoLauncher.launch(uriPair.getFirst()); + } else { + ToastUtils.showLong(R.string.permission_camera); + } + } + }); + + storagePermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), new ActivityResultCallback() { + @Override + public void onActivityResult(Boolean o) { + if (o) { + fileChooseLauncher.launch("*/*"); + } else { + ToastUtils.showLong(R.string.permission_manage_external_storage_rationale); + } + } + }); + + takePhotoLauncher = registerForActivityResult(new ActivityResultContracts.TakePicture(), new ActivityResultCallback() { + @Override + public void onActivityResult(Boolean o) { + if (!o) { + return; + } + onMediaPicked(uriPair.getFirst()); + } + }); + + shootVideoLauncher = registerForActivityResult(new ActivityResultContracts.CaptureVideo(), new ActivityResultCallback() { + @Override + public void onActivityResult(Boolean o) { + if (!o) { + return; + } + onMediaPicked(uriPair.getFirst()); + } + }); + + pickMediaLauncher = registerForActivityResult(new ActivityResultContracts.PickVisualMedia(), new ActivityResultCallback() { + @Override + public void onActivityResult(Uri o) { + onMediaPicked(o); + } + }); + + pickMultipleMediaLauncher = registerForActivityResult(new ActivityResultContracts.PickMultipleVisualMedia(9), new ActivityResultCallback>() { + @Override + public void onActivityResult(List o) { + for (Uri uri : o) { + onMediaPicked(uri); + } + } + }); + + fileChooseLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), new ActivityResultCallback() { + @Override + public void onActivityResult(Uri o) { + onMediaPicked(o); + } + }); + } + + public void onMediaPicked(Uri uri) { + SLogs.d(uri.toString()); + } + + /////////////////////////////////////////////////////// + + public void showPickPhotoSheetDialog(boolean isPickMultiWhenMenuIdIsViewFile) { + BottomSheetHelper.buildSheet(this, R.menu.bottom_sheet_camera_album_select, new OnMenuClickListener() { + @Override + public void onMenuClick(MenuItem menuItem) { + if (menuItem.getItemId() == R.id.take_photo) { + cameraPermissionLauncher.launch(Manifest.permission.CAMERA); + } else if (menuItem.getItemId() == R.id.view_file) { + if (isPickMultiWhenMenuIdIsViewFile) { + pickMultipleMediaLauncher.launch(new PickVisualMediaRequest.Builder() + .setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE) + .build()); + } else { + pickMediaLauncher.launch(new PickVisualMediaRequest.Builder() + .setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE) + .build()); + } + } + } + }).show(getSupportFragmentManager()); + } + + public void showPickPhotoAndVideoSheetDialog(boolean isPickMultiWhenMenuIdIsViewFile) { + BottomSheetHelper.buildSheet(this, R.menu.bottom_sheet_camera_album_select, new OnMenuClickListener() { + @Override + public void onMenuClick(MenuItem menuItem) { + if (menuItem.getItemId() == R.id.take_photo) { + cameraPermissionLauncher.launch(Manifest.permission.CAMERA); + } else if (menuItem.getItemId() == R.id.view_file) { + if (isPickMultiWhenMenuIdIsViewFile) { + pickMultipleMediaLauncher.launch(new PickVisualMediaRequest.Builder() + .setMediaType(ActivityResultContracts.PickVisualMedia.ImageAndVideo.INSTANCE) + .build()); + } else { + pickMediaLauncher.launch(new PickVisualMediaRequest.Builder() + .setMediaType(ActivityResultContracts.PickVisualMedia.ImageAndVideo.INSTANCE) + .build()); + } + } + } + }).show(getSupportFragmentManager()); + } + + public void pickFile() { + storagePermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE); + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/base/viewmodel/BaseViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/base/viewmodel/BaseViewModel.java index 8cbc4d260..2d99ad020 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/base/viewmodel/BaseViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/base/viewmodel/BaseViewModel.java @@ -13,6 +13,7 @@ import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeadroidApplication; import com.seafile.seadroid2.SeafException; +import com.seafile.seadroid2.annotation.Todo; import com.seafile.seadroid2.framework.data.model.ResultModel; import com.seafile.seadroid2.framework.http.HttpIO; import com.seafile.seadroid2.framework.util.SLogs; @@ -118,7 +119,19 @@ protected void onCleared() { } } - public Map generateRequestBody(Map requestDataMap) { + + @Todo + public Map genObjRequestBody(Map params) { + Map requestBodyMap = new HashMap<>(); + for (Map.Entry entry : params.entrySet()) { + String value = entry.getValue().toString(); + RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), value); + requestBodyMap.put(entry.getKey(), requestBody); + } + return requestBodyMap; + } + + public Map genRequestBody(Map requestDataMap) { Map requestBodyMap = new HashMap<>(); if (requestDataMap == null || requestDataMap.isEmpty()) { requestBodyMap.put("x-test", RequestBody.create(MediaType.parse("multipart/form-data"), "test")); 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 747149ce7..c448c3fc8 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 @@ -115,7 +115,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { binding.notAfter.setText(not_available); } - builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + builder.setPositiveButton(R.string.ignore, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Log.d(DEBUG_TAG, "listener.onAccepted is called"); @@ -123,7 +123,7 @@ public void onClick(DialogInterface dialog, int which) { } }); - builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { + builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Log.d(DEBUG_TAG, "listener.onRejected is called"); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SignOutDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SignOutDialogFragment.java index e92e93430..4be88cd17 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SignOutDialogFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SignOutDialogFragment.java @@ -8,6 +8,7 @@ import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.account.AccountUtils; +import com.seafile.seadroid2.ssl.CertsManager; import com.seafile.seadroid2.ui.base.fragment.CustomDialogFragment; public class SignOutDialogFragment extends CustomDialogFragment { @@ -34,6 +35,7 @@ public int getDialogTitleRes() { protected void onPositiveClick() { Account account = SupportAccountManager.getInstance().getCurrentAccount(); + CertsManager.instance().deleteCertForAccount(account); AccountUtils.logout(account); refreshData(); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/NewDirViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/NewDirViewModel.java index 4fb497a1c..40f1c1db5 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/NewDirViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/NewDirViewModel.java @@ -39,7 +39,7 @@ public void createNewDir(String p, String repo_id) { Map requestDataMap = new HashMap<>(); requestDataMap.put("operation", "mkdir"); - Map bodyMap = generateRequestBody(requestDataMap); + Map bodyMap = genRequestBody(requestDataMap); Single single = HttpIO.getCurrentInstance().execute(DialogService.class).createDir(repo_id, p, bodyMap); addSingleDisposable(single, new Consumer() { @@ -77,7 +77,7 @@ public void createNewFile(String filePathName, String repo_id) { Map requestDataMap = new HashMap<>(); requestDataMap.put("operation", "create"); - Map bodyMap = generateRequestBody(requestDataMap); + Map bodyMap = genRequestBody(requestDataMap); Single single = HttpIO.getCurrentInstance().execute(DialogService.class).createFile(repo_id, filePathName, bodyMap); addSingleDisposable(single, new Consumer() { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/NewRepoViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/NewRepoViewModel.java index 09958fa5c..e3163a7cb 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/NewRepoViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/NewRepoViewModel.java @@ -40,7 +40,7 @@ public void createNewRepo(String repoName, String description, String password) if (!TextUtils.isEmpty(password)) { requestDataMap.put("passwd", password); } - Map bodyMap = generateRequestBody(requestDataMap); + Map bodyMap = genRequestBody(requestDataMap); Single single = HttpIO.getCurrentInstance().execute(DialogService.class).createRepo(bodyMap); addSingleDisposable(single, new Consumer() { @Override diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/PasswordViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/PasswordViewModel.java index 61642b09c..a5dde46cd 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/PasswordViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/PasswordViewModel.java @@ -135,7 +135,7 @@ private void remoteVerify(RepoModel repoModel, String password) { Map requestDataMap = new HashMap<>(); requestDataMap.put("password", password); - Map bodyMap = generateRequestBody(requestDataMap); + Map bodyMap = genRequestBody(requestDataMap); Single netSingle = HttpIO.getCurrentInstance().execute(DialogService.class).setPassword(repoModel.repo_id, bodyMap); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/RenameRepoViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/RenameRepoViewModel.java index 13300d1d5..b9f331461 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/RenameRepoViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/viewmodel/RenameRepoViewModel.java @@ -38,7 +38,7 @@ public void renameRepo(String repoName, String repoId) { Map requestDataMap = new HashMap<>(); requestDataMap.put("repo_name", repoName); - Map bodyMap = generateRequestBody(requestDataMap); + Map bodyMap = genRequestBody(requestDataMap); Single single = HttpIO.getCurrentInstance().execute(DialogService.class).renameRepo(repoId, bodyMap); @@ -64,7 +64,7 @@ public void renameDir(String repoId, String curPath, String newName) { Map requestDataMap = new HashMap<>(); requestDataMap.put("operation", "rename"); requestDataMap.put("newname", newName); - Map bodyMap = generateRequestBody(requestDataMap); + Map bodyMap = genRequestBody(requestDataMap); Single single = HttpIO.getCurrentInstance().execute(DialogService.class).renameDir(repoId, curPath, bodyMap); @@ -90,7 +90,7 @@ public void renameFile(String repoId, String curPath, String newName) { Map requestDataMap = new HashMap<>(); requestDataMap.put("operation", "rename"); requestDataMap.put("newname", newName); - Map bodyMap = generateRequestBody(requestDataMap); + Map bodyMap = genRequestBody(requestDataMap); Single single = HttpIO.getCurrentInstance().execute(DialogService.class).renameFile(repoId, curPath, bodyMap); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentAdapter.java similarity index 60% rename from app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentAdapter.java rename to app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentAdapter.java index 7037b50fd..41014c5fa 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentAdapter.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.sdoc.comments; +package com.seafile.seadroid2.ui.docs_comment; import static com.seafile.seadroid2.config.Constants.DP.DP_4; import static com.seafile.seadroid2.config.Constants.DP.DP_8; @@ -7,7 +7,9 @@ import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -20,9 +22,15 @@ 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.config.GlideLoadConfig; +import com.seafile.seadroid2.databinding.ItemFileCommentBinding; +import com.seafile.seadroid2.databinding.LayoutImageBinding; +import com.seafile.seadroid2.framework.data.model.docs_comment.DocsCommentModel; +import com.seafile.seadroid2.framework.util.GlideApp; +import com.seafile.seadroid2.framework.util.SLogs; import com.seafile.seadroid2.ui.base.adapter.BaseAdapter; +import com.seafile.seadroid2.view.rich_edittext.RichEditText; +import com.seafile.seadroid2.widget.SimpleMarkdownParser; import com.yydcdut.markdown.MarkdownConfiguration; import com.yydcdut.markdown.MarkdownProcessor; import com.yydcdut.markdown.MarkdownTextView; @@ -33,7 +41,7 @@ import java.util.Locale; import java.util.Objects; -public class SDocCommentAdapter extends BaseAdapter { +public class DocsCommentAdapter 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; @@ -56,7 +64,7 @@ private MarkdownProcessor getProcessor() { } @Override - protected void onBindViewHolder(@NonNull SDocCommentViewHolder holder, int i, @Nullable SDocCommentModel model) { + protected void onBindViewHolder(@NonNull DocsCommentViewHolder holder, int i, @Nullable DocsCommentModel model) { if (model == null) { return; } @@ -69,83 +77,88 @@ protected void onBindViewHolder(@NonNull SDocCommentViewHolder holder, int i, @N 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); -// } + if (model.resolved) { + holder.binding.container.setBackgroundResource(R.color.comment_resolved_color); + holder.binding.commentResolved.setVisibility(View.VISIBLE); + } else { + holder.binding.commentResolved.setVisibility(View.INVISIBLE); + holder.binding.container.setBackgroundResource(R.color.window_background_color); + } holder.binding.commentContentContainer.removeAllViews(); - appendTextToFlex(holder.binding.commentContentContainer, model.comment); + addViews(holder, model); } -// //![](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) { + //![](https://dev.seafile.com/e-Hek4ng6iRbCw7h-bXsc6jA.png)\n是是是\n + private void addViews(DocsCommentViewHolder holder, DocsCommentModel model) { + 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++; -// } -// } -// } + if (commentModel.type == 0) { + appendMovementTextToFlex(holder.binding.commentContentContainer, commentModel.content); + } 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); + protected DocsCommentViewHolder onCreateViewHolder(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) { + ItemFileCommentBinding binding = ItemFileCommentBinding.inflate(LayoutInflater.from(context), viewGroup, false); + return new DocsCommentViewHolder(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) { + 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); + + GlideApp.with(getContext()) + .load(url) + .apply(GlideLoadConfig.getOptions()) + .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) { + } + }); + + + parent.addView(fileBinding.getRoot()); + } + + + public void appendMovementTextToFlex(FlexboxLayout parent, String text) { + MarkdownTextView textView = new MarkdownTextView(getContext()); textView.setPadding(DP_4, DP_8, DP_4, DP_8); -// textView.setText(getProcessor().parse(comment)); - textView.setText(comment); + textView.setText(getProcessor().parse(text)); +// textView.setText(text); textView.setMovementMethod(LinkMovementMethod.getInstance()); textView.setId(android.R.id.text1); textView.setTextSize(14); - textView.setTextColor(ContextCompat.getColor(getContext(), R.color.material_blue_grey_900)); + textView.setTextColor(ContextCompat.getColor(getContext(), R.color.item_title_color)); FlexboxLayout.LayoutParams f = new FlexboxLayout.LayoutParams(-1, -2); parent.addView(textView, f); } - public void submitData(List list) { + public void submitData(List list) { if (CollectionUtils.isEmpty(list)) { submitList(list); return; @@ -167,15 +180,15 @@ public int getNewListSize() { @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - SDocCommentModel newT = getItems().get(oldItemPosition); - SDocCommentModel oldT = list.get(newItemPosition); + DocsCommentModel newT = getItems().get(oldItemPosition); + DocsCommentModel 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); + DocsCommentModel newT = getItems().get(oldItemPosition); + DocsCommentModel oldT = list.get(newItemPosition); return newT.id == oldT.id && Objects.equals(newT.resolved, oldT.resolved) diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentUserAdapter.java similarity index 83% rename from app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserAdapter.java rename to app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentUserAdapter.java index f36926541..52dbed7f7 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentUserAdapter.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.sdoc.comments; +package com.seafile.seadroid2.ui.docs_comment; import android.content.Context; import android.text.TextUtils; @@ -15,9 +15,9 @@ import com.seafile.seadroid2.framework.data.model.user.UserModel; import com.seafile.seadroid2.ui.base.adapter.BaseAdapter; -public class SDocCommentUserAdapter extends BaseAdapter { +public class DocsCommentUserAdapter extends BaseAdapter { @Override - protected void onBindViewHolder(@NonNull SDocCommentUserViewHolder holder, int i, @Nullable UserModel model) { + protected void onBindViewHolder(@NonNull DocsCommentUserViewHolder holder, int i, @Nullable UserModel model) { if (i == 0) { setMargins(holder.binding.itemUserContainer, 0, 0, 0, 0); @@ -39,9 +39,9 @@ protected void onBindViewHolder(@NonNull SDocCommentUserViewHolder holder, int i @NonNull @Override - protected SDocCommentUserViewHolder onCreateViewHolder(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) { + protected DocsCommentUserViewHolder onCreateViewHolder(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) { ItemUserAvatarBinding binding = ItemUserAvatarBinding.inflate(LayoutInflater.from(context), viewGroup, false); - return new SDocCommentUserViewHolder(binding); + return new DocsCommentUserViewHolder(binding); } public void setMargins(View v, int l, int t, int r, int b) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentUserViewHolder.java similarity index 63% rename from app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserViewHolder.java rename to app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentUserViewHolder.java index 2cd81d913..9251ed842 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentUserViewHolder.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentUserViewHolder.java @@ -1,15 +1,15 @@ -package com.seafile.seadroid2.ui.sdoc.comments; +package com.seafile.seadroid2.ui.docs_comment; import androidx.annotation.NonNull; import com.seafile.seadroid2.databinding.ItemUserAvatarBinding; import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; -public class SDocCommentUserViewHolder extends BaseViewHolder { +public class DocsCommentUserViewHolder extends BaseViewHolder { public ItemUserAvatarBinding binding; - public SDocCommentUserViewHolder(@NonNull ItemUserAvatarBinding binding) { + public DocsCommentUserViewHolder(@NonNull ItemUserAvatarBinding binding) { super(binding.getRoot()); this.binding = binding; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentViewHolder.java new file mode 100644 index 000000000..9e13d8f22 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentViewHolder.java @@ -0,0 +1,17 @@ +package com.seafile.seadroid2.ui.docs_comment; + +import androidx.annotation.NonNull; + +import com.seafile.seadroid2.databinding.ItemFileCommentBinding; +import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; + +public class DocsCommentViewHolder extends BaseViewHolder { + + public ItemFileCommentBinding binding; + + public DocsCommentViewHolder(@NonNull ItemFileCommentBinding binding) { + super(binding.getRoot()); + + this.binding = binding; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentsActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentsActivity.java new file mode 100644 index 000000000..ae718ec94 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentsActivity.java @@ -0,0 +1,330 @@ +package com.seafile.seadroid2.ui.docs_comment; + +import android.animation.ValueAnimator; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.MenuItem; +import android.view.View; +import android.view.WindowManager; +import android.view.animation.Animation; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.PopupMenu; +import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.Observer; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.KeyboardUtils; +import com.blankj.utilcode.util.ToastUtils; +import com.chad.library.adapter4.BaseQuickAdapter; +import com.chad.library.adapter4.QuickAdapterHelper; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.databinding.ActivityDocCommentBinding; +import com.seafile.seadroid2.databinding.ToolbarActionbarBinding; +import com.seafile.seadroid2.framework.data.model.docs_comment.DocsCommentModel; +import com.seafile.seadroid2.framework.data.model.docs_comment.DocsCommentsWrapperModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocPageOptionsModel; +import com.seafile.seadroid2.framework.util.SLogs; +import com.seafile.seadroid2.ui.base.BaseMediaSelectorActivity; +import com.seafile.seadroid2.ui.bottomsheetmenu.BottomSheetHelper; +import com.seafile.seadroid2.ui.bottomsheetmenu.BottomSheetMenuFragment; +import com.seafile.seadroid2.ui.bottomsheetmenu.OnMenuClickListener; +import com.seafile.seadroid2.ui.sdoc.DocsCommentViewModel; +import com.seafile.seadroid2.view.rich_edittext.RichEditText; + +import java.util.List; + +import io.reactivex.functions.Consumer; + +public class DocsCommentsActivity extends BaseMediaSelectorActivity { + private ActivityDocCommentBinding binding; + private ToolbarActionbarBinding bindingOfToolbar; + + private Toolbar toolbar; + + private DocsCommentAdapter adapter; + private DocsCommentUserAdapter userAdapter; + + private SDocPageOptionsModel pageOptionsModel; + + public static void start(Context context, SDocPageOptionsModel pageModel) { + Intent starter = new Intent(context, DocsCommentsActivity.class); + starter.putExtra("pageOption", pageModel); + context.startActivity(starter); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivityDocCommentBinding.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 final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); + + 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(linearLayoutManager); + + binding.photoView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showPickPhotoSheetDialog(false); + } + }); + +// // +// LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); +// binding.rvUserList.setLayoutManager(linearLayoutManager); + + 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(Boolean model) { + //remove all + binding.richEditText.removeAllViews(); + + + refreshData(); + } + }); + +// 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(DocsCommentsWrapperModel model) { + adapter.setStateViewEnable(true); + adapter.submitData(model.comments); + + linearLayoutManager.smoothScrollToPosition(binding.rv, null, adapter.getItemCount() - 1); + + } + }); + } + + + private void initAdapter() { +// userAdapter = new DocsCommentUserAdapter(); +// 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 DocsCommentAdapter(); + 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) { +// buildMoreDialog(i); + initPopupMenu(i, view); + } + }); + + QuickAdapterHelper helper = new QuickAdapterHelper.Builder(adapter).build(); + binding.rv.setAdapter(helper.getAdapter()); + } + + private void initPopupMenu(int position, View showView) { + DocsCommentModel model = adapter.getItems().get(position); + + PopupMenu popupMenu = new PopupMenu(this, showView); + popupMenu.getMenuInflater().inflate(R.menu.menu_comment_mark_delete, popupMenu.getMenu()); + popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + if (item.getItemId() == R.id.mark_resolve) { + getViewModel().markResolve(pageOptionsModel.seadocServerUrl, pageOptionsModel.seadocAccessToken, pageOptionsModel.docUuid, model.id, new Consumer() { + @Override + public void accept(Long aLong) throws Exception { + model.resolved = true; + adapter.set(position, model); + } + }); + } else if (item.getItemId() == R.id.delete) { + showDeleteDialog(position, model.id); + } + return false; + } + }); + popupMenu.show(); + } + + private void showDeleteDialog(int position, int cId) { + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); + builder.setTitle(R.string.delete_confirm); + builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + getViewModel().delete(pageOptionsModel.seadocServerUrl, pageOptionsModel.seadocAccessToken, pageOptionsModel.docUuid, cId, new Consumer() { + @Override + public void accept(Long aLong) throws Exception { + adapter.removeAt(position); + } + }); + } + }); + + builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + builder.show(); + } + + private void refreshData() { + getViewModel().loadDocComments(pageOptionsModel); + } + + public void onMediaPicked(Uri uri) { + super.onMediaPicked(uri); + uploadFile(uri); + } + + + private void uploadFile(Uri o) { + if (null == o) { + return; + } + + SLogs.d(o.toString()); + + binding.richEditText.insertImage(o); + + ContentResolver contentResolver = getContentResolver(); +// https://dev.seafile.com/seafhttp/repos/bc1db6db-afa8-4e85-8479-6eb3f50f3d1c/files/images/sdoc/e4c22460-e99d-4687-9081-1f30d156625d/image-UFB28ta7TVOskNE0uAZ_Ww.jpeg/?op=download +// https://dev.seafile.com/seahub/lib/bc1db6db-afa8-4e85-8479-6eb3f50f3d1c/file/images/sdoc/e4c22460-e99d-4687-9081-1f30d156625d/image-UFB28ta7TVOskNE0uAZ_Ww.jpeg +// https://dev.seafile.com/seafhttp/files/73372487-3bb8-4f1d-925e-6e21bb72305c/image-UFB28ta7TVOskNE0uAZ_Ww.jpeg +// https://dev.seafile.com/seafhttp/files/b1d6e2d7-8b28-4a8e-af93-f3ca70bf51ea/image-UFB28ta7TVOskNE0uAZ_Ww.jpeg +// https://dev.seafile.com/seahub/api/v2.1/seadoc/download-image/e4c22460-e99d-4687-9081-1f30d156625d/image-UFB28ta7TVOskNE0uAZ_Ww.jpeg + //upload file + getViewModel().uploadFile(contentResolver, o, pageOptionsModel.docUuid, pageOptionsModel.seadocAccessToken, new Consumer() { + @Override + public void accept(String absUrl) throws Exception { + binding.richEditText.updateUploadState(o.toString(), absUrl); + } + }, new Consumer() { + @Override + public void accept(String s) throws Exception { + ToastUtils.showLong(R.string.upload_failed); + } + }); + } + + private void submitData() { + // + KeyboardUtils.hideSoftInput(getWindow()); + + List models = binding.richEditText.buildRichEditData(); + if (CollectionUtils.isEmpty(models)) { + return; + } + + StringBuilder sb = new StringBuilder(); + + models.forEach(f -> { + if (f.type == 0) { + sb.append(f.content).append("\n\n"); + } else if (f.type == 1) { + sb.append("![](").append(f.content).append(")"); + } + }); + + if (TextUtils.isEmpty(sb.toString())) { + return; + } + + getViewModel().postComment(pageOptionsModel, sb.toString(), "0"); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/profile/SDocProfileDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/file_profile/FileProfileDialog.java similarity index 92% rename from app/src/main/java/com/seafile/seadroid2/ui/sdoc/profile/SDocProfileDialog.java rename to app/src/main/java/com/seafile/seadroid2/ui/file_profile/FileProfileDialog.java index a8d9086f7..118c35f94 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/profile/SDocProfileDialog.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/file_profile/FileProfileDialog.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.sdoc.profile; +package com.seafile.seadroid2.ui.file_profile; import static com.seafile.seadroid2.config.Constants.DP.DP_4; @@ -22,8 +22,6 @@ 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; @@ -33,17 +31,16 @@ 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.databinding.DialogFileProfileBinding; +import com.seafile.seadroid2.framework.data.model.sdoc.FileDetailModel; +import com.seafile.seadroid2.framework.data.model.sdoc.FileRecordWrapperModel; 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; @@ -55,15 +52,16 @@ import java.util.List; import java.util.Optional; +public class FileProfileDialog extends BottomSheetDialogFragment { -public class SDocProfileDialog extends BottomSheetDialogFragment { - - private SDocDetailModel docDetailModel; - private SDocRecordWrapperModel recordWrapperModel; + private FileDetailModel docDetailModel; + private FileRecordWrapperModel recordWrapperModel; private ArrayList relatedUsers; + private boolean isShowTitle = false; - public static SDocProfileDialog newInstance(SDocDetailModel docDetailModel, SDocRecordWrapperModel recordWrapperModel, List relatedUsers) { + public static FileProfileDialog newInstance(FileDetailModel docDetailModel, FileRecordWrapperModel recordWrapperModel, List relatedUsers, boolean isShowTitle) { Bundle args = new Bundle(); + args.putBoolean("isShowTitle", isShowTitle); args.putParcelable("detailModel", docDetailModel); if (!CollectionUtils.isEmpty(relatedUsers)) { @@ -74,13 +72,17 @@ public static SDocProfileDialog newInstance(SDocDetailModel docDetailModel, SDoc args.putParcelableArrayList("relatedUsers", new ArrayList<>(relatedUsers)); } - SDocProfileDialog fragment = new SDocProfileDialog(); + FileProfileDialog fragment = new FileProfileDialog(); fragment.setArguments(args); return fragment; } - public static SDocProfileDialog newInstance(SDocDetailModel docDetailModel, List relatedUsers) { - return newInstance(docDetailModel, null, relatedUsers); + public static FileProfileDialog newInstance(FileDetailModel docDetailModel, List relatedUsers) { + return newInstance(docDetailModel, null, relatedUsers, false); + } + + public static FileProfileDialog newInstance(FileDetailModel docDetailModel, List relatedUsers, boolean isShowTitle) { + return newInstance(docDetailModel, null, relatedUsers, isShowTitle); } @Override @@ -91,6 +93,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { throw new IllegalArgumentException("detailModel is null"); } + isShowTitle = getArguments().getBoolean("isShowTitle"); docDetailModel = getArguments().getParcelable("detailModel"); recordWrapperModel = getArguments().getParcelable("recordModel"); relatedUsers = getArguments().getParcelableArrayList("relatedUsers"); @@ -102,12 +105,12 @@ public void onCreate(@Nullable Bundle savedInstanceState) { initFixedValueIfMetadataNotEnable(); } - DialogSdocProfileBinding profileBinding; + private DialogFileProfileBinding profileBinding; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - profileBinding = DialogSdocProfileBinding.inflate(inflater, container, false); + profileBinding = DialogFileProfileBinding.inflate(inflater, container, false); return profileBinding.getRoot(); } @@ -115,33 +118,18 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c @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; + return new BottomSheetDialog(requireContext()); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + if (isShowTitle) { + profileBinding.title.setVisibility(View.VISIBLE); + profileBinding.title.setText(docDetailModel.getName()); + } + setData(profileBinding.detailsContainer); } @@ -149,7 +137,7 @@ private void initFixedValueIfMetadataNotEnable() { if (recordWrapperModel != null) { return; } - recordWrapperModel = new SDocRecordWrapperModel(); + recordWrapperModel = new FileRecordWrapperModel(); RecordResultModel sizeModel = new RecordResultModel(); sizeModel._size = docDetailModel.getSize(); @@ -205,6 +193,7 @@ private void addMetadataView(LinearLayout parent, MetadataModel metadata) { parseViewByType(getContext(), parent, metadata); } + //not support: _tags private final List _fixedField = List.of("_size", "_file_modifier", "_file_mtime", "_description", "_collaborators", "_reviewer", "_status"); private Object getValueByKey(String key) { @@ -250,6 +239,8 @@ private int getResNameByKey(String key) { return R.string._done; case "_outdated": return R.string._outdated; + case "_tags": + return R.string._tags; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { @@ -506,10 +497,6 @@ private void parseSingleSelect(LinearLayout view, MetadataModel model) { textView.setText(option.name); } - -// if (!TextUtils.isEmpty(option.borderColor)) { -// } - if (!TextUtils.isEmpty(option.textColor)) { textView.setTextColor(Color.parseColor(option.textColor)); } 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 3cdc20e98..8703ecb37 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 @@ -37,7 +37,6 @@ import com.blankj.utilcode.util.ToastUtils; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.navigation.NavigationBarView; -import com.google.firebase.analytics.FirebaseAnalytics; import com.google.firebase.crashlytics.FirebaseCrashlytics; import com.seafile.seadroid2.R; import com.seafile.seadroid2.account.Account; @@ -159,11 +158,7 @@ private void initFireBase() { if (account != null) { FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance(); crashlytics.setUserId(account.getSignature()); - - FirebaseAnalytics analytics = FirebaseAnalytics.getInstance(this); - analytics.setUserId(account.getSignature()); } - } private void initOnBackPressedDispatcher() { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_info/FileDetailViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_info/FileDetailViewModel.java new file mode 100644 index 000000000..de54a2ca1 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_info/FileDetailViewModel.java @@ -0,0 +1,7 @@ +package com.seafile.seadroid2.ui.media.image_info; + +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; + +public class FileDetailViewModel extends BaseViewModel { + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewActivity.java index e827618e0..5d67f4232 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewActivity.java @@ -34,6 +34,7 @@ import io.reactivex.functions.Consumer; +@Deprecated public class ImagePreviewActivity extends BaseActivityWithVM { private ActivityImagePreviewBinding binding; private ViewPager2Adapter adapter; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewViewModel.java index 119b9402d..b3a380029 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewViewModel.java @@ -14,11 +14,16 @@ import com.seafile.seadroid2.framework.data.db.entities.RepoModel; import com.seafile.seadroid2.framework.data.model.ResultModel; import com.seafile.seadroid2.framework.data.model.repo.Dirent2Model; +import com.seafile.seadroid2.framework.data.model.sdoc.FileDetailModel; +import com.seafile.seadroid2.framework.data.model.sdoc.MetadataConfigModel; +import com.seafile.seadroid2.framework.data.model.sdoc.FileProfileConfigModel; +import com.seafile.seadroid2.framework.data.model.user.UserWrapperModel; 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.http.HttpIO; import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.ui.sdoc.DocsCommentService; import com.seafile.seadroid2.ui.star.StarredService; import java.util.HashMap; @@ -29,8 +34,8 @@ import io.reactivex.Single; import io.reactivex.functions.BiFunction; import io.reactivex.functions.Consumer; +import io.reactivex.functions.Function3; import okhttp3.RequestBody; -import prettify.parser.Util; public class ImagePreviewViewModel extends BaseViewModel { private final MutableLiveData> ListLiveData = new MutableLiveData<>(); @@ -49,6 +54,43 @@ public MutableLiveData> getListLiveData() { return ListLiveData; } + private final MutableLiveData _fileProfileConfigLiveData = new MutableLiveData<>(); + + public MutableLiveData getFileDetailLiveData() { + return _fileProfileConfigLiveData; + } + + public void getFileDetail(String repoId, String path) { + getRefreshLiveData().setValue(true); + + Single userSingle = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getRelatedUsers(repoId); + Single metadataSingle = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getMetadata(repoId); + Single detailSingle = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getFileDetail(repoId, path); + + Single s = Single.zip(detailSingle, userSingle, metadataSingle, new Function3() { + @Override + public FileProfileConfigModel apply(FileDetailModel docDetailModel, UserWrapperModel userWrapperModel, MetadataConfigModel metadataConfigModel) throws Exception { + FileProfileConfigModel configModel = new FileProfileConfigModel(); + configModel.setDetail(docDetailModel); + configModel.setUsers(userWrapperModel); + configModel.setMetadata(metadataConfigModel); + return configModel; + } + }); + + addSingleDisposable(s, new Consumer() { + @Override + public void accept(FileProfileConfigModel fileProfileConfigModel) throws Exception { + getFileDetailLiveData().setValue(fileProfileConfigModel); + getRefreshLiveData().setValue(false); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getRefreshLiveData().setValue(false); + } + }); + } public void getRepoModelFromDB(String repoId, Consumer consumer) { //from db @@ -170,7 +212,7 @@ public void star(String repoId, String path) { Map requestDataMap = new HashMap<>(); requestDataMap.put("repo_id", repoId); requestDataMap.put("path", path); - Map bodyMap = generateRequestBody(requestDataMap); + Map bodyMap = genRequestBody(requestDataMap); Single single = HttpIO.getCurrentInstance().execute(StarredService.class).star(bodyMap); addSingleDisposable(single, new Consumer() { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/PhotoFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/PhotoFragment.java index b71c3192a..22fe23c69 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/PhotoFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/PhotoFragment.java @@ -40,7 +40,7 @@ public class PhotoFragment extends BaseFragment { private OnPhotoTapListener onPhotoTapListener; private FragmentPhotoViewBinding binding; - private boolean isLight = true; + public void setOnPhotoTapListener(OnPhotoTapListener onPhotoTapListener) { this.onPhotoTapListener = onPhotoTapListener; @@ -107,14 +107,6 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat binding.photoView.setOnPhotoTapListener(new OnPhotoTapListener() { @Override public void onPhotoTap(ImageView view, float x, float y) { - if (!isLight){ - binding.rootLayout.setBackgroundColor(ContextCompatKt.getColorCompat(requireContext(),R.color.material_grey_100)); - }else{ - binding.rootLayout.setBackgroundColor(ContextCompatKt.getColorCompat(requireContext(),R.color.material_grey_919)); - } - - isLight =!isLight; - if (onPhotoTapListener != null) { onPhotoTapListener.onPhotoTap(view, x, y); } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselAdapter.java index 3e824b608..04e15a79a 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselAdapter.java @@ -21,24 +21,28 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.ListAdapter; +import androidx.recyclerview.widget.RecyclerView; import com.blankj.utilcode.util.EncodeUtils; import com.blankj.utilcode.util.ScreenUtils; import com.blankj.utilcode.util.SizeUtils; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.request.RequestOptions; +import com.google.common.base.Strings; import com.seafile.seadroid2.R; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.framework.data.db.entities.DirentModel; import com.seafile.seadroid2.framework.util.GlideApp; -public class CarouselAdapter extends ListAdapter { +public class CarouselAdapter extends ListAdapter { private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback<>() { @@ -110,6 +114,7 @@ public void onBindViewHolder(@NonNull CarouselItemViewHolder carouselItemViewHol private void bind(CarouselItemViewHolder holder, int pos) { DirentModel model = getItem(pos); +// holder.textView.setText(pos + ""); holder.itemView.setOnClickListener(v -> { listener.onItemClicked(model, pos); @@ -157,4 +162,18 @@ private String getUrl(String repoId, String fullPath) { int size = SizeUtils.dp2px(300); return String.format("%sapi2/repos/%s/thumbnail/?p=%s&size=%s", account.getServer(), repoId, EncodeUtils.urlEncode(fullPath), size); } + + + public static class CarouselItemViewHolder extends RecyclerView.ViewHolder { + + public final ImageView imageView; + public final TextView textView; + + CarouselItemViewHolder(@NonNull View itemView) { + super(itemView); + imageView = itemView.findViewById(R.id.image_view); + textView = itemView.findViewById(R.id.text_view); + } + + } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java index d71ec250f..576ece89d 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java @@ -1,5 +1,6 @@ package com.seafile.seadroid2.ui.media.image_preview2; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -12,32 +13,45 @@ import android.view.ViewGroup; import androidx.activity.OnBackPressedCallback; +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; import androidx.lifecycle.Observer; import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager2.widget.ViewPager2; import com.blankj.utilcode.util.BarUtils; import com.blankj.utilcode.util.CollectionUtils; import com.blankj.utilcode.util.NetworkUtils; -import com.blankj.utilcode.util.ScreenUtils; import com.blankj.utilcode.util.ToastUtils; import com.seafile.seadroid2.R; import com.seafile.seadroid2.compat.ContextCompatKt; +import com.seafile.seadroid2.context.CopyMoveContext; import com.seafile.seadroid2.databinding.ActivityCarouselImagePreviewBinding; +import com.seafile.seadroid2.enums.OpType; import com.seafile.seadroid2.framework.data.db.entities.DirentModel; import com.seafile.seadroid2.framework.data.db.entities.RepoModel; +import com.seafile.seadroid2.framework.data.db.entities.StarredModel; +import com.seafile.seadroid2.framework.data.model.activities.ActivityModel; +import com.seafile.seadroid2.framework.data.model.sdoc.FileProfileConfigModel; +import com.seafile.seadroid2.framework.data.model.search.SearchModel; import com.seafile.seadroid2.framework.util.Objs; +import com.seafile.seadroid2.framework.util.Utils; import com.seafile.seadroid2.ui.adapter.ViewPager2Adapter; import com.seafile.seadroid2.ui.base.BaseActivityWithVM; +import com.seafile.seadroid2.ui.dialog_fragment.CopyMoveDialogFragment; import com.seafile.seadroid2.ui.dialog_fragment.DeleteFileDialogFragment; import com.seafile.seadroid2.ui.dialog_fragment.listener.OnRefreshDataListener; +import com.seafile.seadroid2.ui.file_profile.FileProfileDialog; import com.seafile.seadroid2.ui.media.image_preview.ImagePreviewViewModel; import com.seafile.seadroid2.ui.media.image_preview.PhotoFragment; +import com.seafile.seadroid2.ui.selector.ObjSelectorActivity; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; public class CarouselImagePreviewActivity extends BaseActivityWithVM implements Toolbar.OnMenuItemClickListener { @@ -47,15 +61,14 @@ public class CarouselImagePreviewActivity extends BaseActivityWithVM direntList; - + private List carouselDirentList; private boolean isLightMode = true; private boolean isDataOperated = false; private String repoId, repoName, parentDir, name; private boolean load_other_images_in_same_directory = false; - private int carouselItemWidth, carouselItemMargin; - public static Intent startThisFromRepo(Context context, DirentModel direntModel) { + public static Intent startThisFromObjs(Context context, DirentModel direntModel) { Intent intent = new Intent(context, CarouselImagePreviewActivity.class); intent.putExtra("repo_id", direntModel.repo_id); intent.putExtra("repo_name", direntModel.repo_name); @@ -65,6 +78,36 @@ public static Intent startThisFromRepo(Context context, DirentModel direntModel) return intent; } + public static Intent startThisFromStarred(Context context, StarredModel model) { + Intent intent = new Intent(context, CarouselImagePreviewActivity.class); + intent.putExtra("repo_id", model.repo_id); + intent.putExtra("repo_name", model.repo_name); + intent.putExtra("parent_dir", Utils.getParentPath(model.path)); + intent.putExtra("name", model.obj_name); + intent.putExtra("load_other_images_in_same_directory", false);//Load other images in the same folder + return intent; + } + + public static Intent startThisFromActivity(Context context, ActivityModel model) { + Intent intent = new Intent(context, CarouselImagePreviewActivity.class); + intent.putExtra("repo_id", model.repo_id); + intent.putExtra("repo_name", model.repo_name); + intent.putExtra("parent_dir", Utils.getParentPath(model.path)); + intent.putExtra("name", model.name); + intent.putExtra("load_other_images_in_same_directory", false);//Load other images in the same folder + return intent; + } + + public static Intent startThisFromSearch(Context context, SearchModel model) { + Intent intent = new Intent(context, CarouselImagePreviewActivity.class); + intent.putExtra("repo_id", model.repo_id); + intent.putExtra("repo_name", model.repo_name); + intent.putExtra("parent_dir", Utils.getParentPath(model.fullpath)); + intent.putExtra("name", model.name); + intent.putExtra("load_other_images_in_same_directory", false);//Load other images in the same folder + return intent; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -72,7 +115,6 @@ protected void onCreate(Bundle savedInstanceState) { binding = ActivityCarouselImagePreviewBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); - // full screen getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); @@ -85,10 +127,6 @@ protected void onCreate(Bundle savedInstanceState) { ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) binding.toolbarActionbar.getLayoutParams(); layoutParams.topMargin = BarUtils.getStatusBarHeight(); - carouselItemWidth = getResources().getDimensionPixelSize(R.dimen.carousel_item_width); - carouselItemMargin = getResources().getDimensionPixelSize(R.dimen.carousel_item_margin); - - Toolbar toolbar = getActionBarToolbar(); setSupportActionBar(toolbar); if (getSupportActionBar() != null) { @@ -108,8 +146,11 @@ public void handleOnBackPressed() { initParams(); initView(); + initAdapter(); initCarouselAdapter(); + bindPager(); + initViewModel(); getViewModel().load(repoId, parentDir, name, load_other_images_in_same_directory); @@ -184,69 +225,59 @@ public void onChanged(Boolean aBoolean) { notifyCurrentStarredStatus(); } }); + + getViewModel().getFileDetailLiveData().observe(this, new Observer() { + @Override + public void onChanged(FileProfileConfigModel configModel) { + + DirentModel direntModel = getSelectedDirent(); + if (direntModel == null) { + return; + } + + String key = direntModel.full_path; + fileDetailHashMap.put(key, configModel); + + showProfileDialog(configModel); + } + }); } private void initAdapter() { adapter = new ViewPager2Adapter(this); - - binding.pager.setAdapter(adapter); binding.pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { @Override public void onPageSelected(int position) { super.onPageSelected(position); - - gravitySnapHelper.scrollToPosition(position); - notifyCurrentStarredStatus(); } }); + binding.pager.setAdapter(adapter); } private final LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); private final GravitySnapHelper gravitySnapHelper = new GravitySnapHelper(Gravity.CENTER); private void initCarouselAdapter() { - carouselAdapter = new CarouselAdapter(this, new CarouselAdapter.CarouselItemListener() { @Override public void onItemClicked(DirentModel item, int snapPosition) { - ToastUtils.showShort("onItemClicked: " + snapPosition); - int pageIndex = binding.pager.getCurrentItem(); - if (pageIndex == snapPosition) { - return; - } - binding.pager.setCurrentItem(snapPosition, true); + gravitySnapHelper.smoothScrollToPosition(snapPosition); } }); binding.recyclerView.setAdapter(carouselAdapter); binding.recyclerView.setLayoutManager(layoutManager); - int itemWidth = getResources().getDimensionPixelSize(R.dimen.carousel_item_width); - int screenWidth = ScreenUtils.getAppScreenWidth(); - int itemMargin = getResources().getDimensionPixelSize(R.dimen.carousel_item_margin); - int slidePadding = (screenWidth - itemWidth) / 2 - itemMargin; - - LinearEdgeDecoration decoration = new LinearEdgeDecoration(slidePadding, slidePadding, RecyclerView.HORIZONTAL, false); - binding.recyclerView.addItemDecoration(decoration); - binding.recyclerView.addOnScrollListener(new CenterScaleXYRecyclerViewScrollListener(this)); - gravitySnapHelper.setSnapListener(new GravitySnapHelper.SnapListener() { - @Override - public void onSnap(int snapPosition) { - int pageIndex = binding.pager.getCurrentItem(); - if (pageIndex == snapPosition) { - return; - } - - binding.pager.setCurrentItem(snapPosition, true); - } - }); -// gravitySnapHelper.setSnapLastItem(true); gravitySnapHelper.attachToRecyclerView(binding.recyclerView); } + private void bindPager() { + PagerSnapBinders.bind(binding.pager, gravitySnapHelper); + } + private void submitData(Pair> pair) { if (pair == null) { return; @@ -260,9 +291,8 @@ private void submitData(Pair> pair) { if (!repoModel.hasWritePermission()) { binding.galleryDeletePhoto.setVisibility(View.GONE); } - direntList = pair.second; - + direntList = pair.second; // List fragments = new ArrayList<>(); @@ -275,7 +305,12 @@ private void submitData(Pair> pair) { adapter.addFragments(fragments); adapter.notifyItemRangeInserted(0, direntList.size()); - carouselAdapter.submitList(direntList); + carouselDirentList = new ArrayList<>(); + carouselDirentList.add(new DirentModel()); + carouselDirentList.addAll(direntList); + carouselDirentList.add(new DirentModel()); + + carouselAdapter.submitList(carouselDirentList); binding.recyclerView.postDelayed(new Runnable() { @Override @@ -285,12 +320,17 @@ public void run() { }, 100); } - private void hideOrShowToolBar() { binding.galleryToolBar.setVisibility(isLightMode ? View.GONE : View.VISIBLE); binding.recyclerView.setVisibility(isLightMode ? View.GONE : View.VISIBLE); binding.toolbarActionbar.setVisibility(isLightMode ? View.INVISIBLE : View.VISIBLE); + if (isLightMode) { + binding.pager.setBackgroundColor(ContextCompatKt.getColorCompat(this, R.color.material_grey_919)); + } else { + binding.pager.setBackgroundColor(ContextCompatKt.getColorCompat(this, R.color.material_grey_100)); + } + int color = ContextCompatKt.getColorCompat(this, isLightMode ? R.color.material_grey_919_ff : R.color.material_grey_100_translucent); BarUtils.setNavBarColor(this, color); BarUtils.setStatusBarColor(this, color); @@ -300,7 +340,6 @@ private void hideOrShowToolBar() { isLightMode = !isLightMode; } - /** * Dynamically navigate to the starting page index selected by user * by default the starting page index is 0 @@ -317,8 +356,7 @@ private void navToSelectedPage() { } if (index != -1) { - binding.pager.setCurrentItem(index, true); - gravitySnapHelper.scrollToPosition(index); + binding.pager.setCurrentItem(index); } } @@ -327,8 +365,9 @@ private void notifyCurrentStarredStatus() { if (direntModel == null) { return; } + if (direntModel.starred) { - binding.galleryStarPhoto.setImageResource(R.drawable.baseline_starred_32); + binding.galleryStarPhoto.setImageResource(R.drawable.baseline_star_filled_24); } else { binding.galleryStarPhoto.setImageResource(R.drawable.baseline_star_outline_24); } @@ -348,17 +387,25 @@ public boolean onMenuItemClick(MenuItem item) { if (item.getItemId() == android.R.id.home) { finish(); } else if (item.getItemId() == R.id.copy) { - ToastUtils.showLong(R.string.file_action_copy); + copy(); } else if (item.getItemId() == R.id.info) { - ToastUtils.showLong(R.string.file_action_copy); + preShowProfileDialog(); } return super.onOptionsItemSelected(item); } private DirentModel getSelectedDirent() { + if (CollectionUtils.isEmpty(direntList)) { + return null; + } + int index = binding.pager.getCurrentItem(); - return carouselAdapter.getItem(index); + if (index > direntList.size() - 1) { + return null; + } + + return direntList.get(index); } private void deleteFile() { @@ -376,7 +423,10 @@ public void onActionStatus(boolean isDone) { direntList.remove(position); adapter.removeFragment(position); adapter.notifyItemRemoved(position); - carouselAdapter.notifyItemRemoved(position); + + int carouselIndex = position + 1; + carouselDirentList.remove(carouselIndex); + carouselAdapter.notifyItemRemoved(carouselIndex); ToastUtils.showLong(R.string.delete_successful); @@ -417,4 +467,89 @@ private void downloadFile() { getViewModel().download(direntModel.repo_id, direntModel.full_path); } + + private HashMap fileDetailHashMap = new HashMap<>(); + + private void preShowProfileDialog() { + DirentModel direntModel = getSelectedDirent(); + if (direntModel == null) { + return; + } + + String key = direntModel.full_path; + if (fileDetailHashMap.containsKey(key)) { + showProfileDialog(fileDetailHashMap.get(key)); + } else { + getViewModel().getFileDetail(repoId, key); + } + } + + private void showProfileDialog(FileProfileConfigModel model) { + FileProfileDialog detailDialog = FileProfileDialog.newInstance(model.detail, model.users.user_list, true); + detailDialog.show(getSupportFragmentManager(), FileProfileDialog.class.getSimpleName()); + } + + private CopyMoveContext copyMoveContext = null; + + private void copy() { + DirentModel direntModel = getSelectedDirent(); + if (direntModel == null) { + return; + } + + chooseCopyMoveDest(direntModel, OpType.COPY); + } + + /** + * Choose copy/move destination for multiple files + */ + private void chooseCopyMoveDest(DirentModel direntModel, OpType op) { + + copyMoveContext = new CopyMoveContext(repoId, repoName, parentDir, CollectionUtils.newArrayList(direntModel), op); + copyMoveLauncher.launch(ObjSelectorActivity.getStartIntent(this)); + } + + private final ActivityResultLauncher copyMoveLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback() { + @Override + public void onActivityResult(ActivityResult o) { + if (o.getResultCode() != Activity.RESULT_OK || o.getData() == null) { + return; + } + + String dstRepoId = o.getData().getStringExtra(ObjSelectorActivity.DATA_REPO_ID); + String dstDir = o.getData().getStringExtra(ObjSelectorActivity.DATA_DIR); + String disRepoName = o.getData().getStringExtra(ObjSelectorActivity.DATA_REPO_NAME); + + copyMoveContext.setDest(dstRepoId, dstDir, disRepoName); + + doCopyMove(); + } + }); + + private void doCopyMove() { + if (copyMoveContext == null) { + return; + } + + if (!copyMoveContext.checkCopyMoveToSubfolder()) { + ToastUtils.showLong(copyMoveContext.isCopy() + ? R.string.cannot_copy_folder_to_subfolder + : R.string.cannot_move_folder_to_subfolder); + return; + } + + CopyMoveDialogFragment dialogFragment = CopyMoveDialogFragment.newInstance(); + dialogFragment.initData(copyMoveContext); + dialogFragment.setRefreshListener(new OnRefreshDataListener() { + @Override + public void onActionStatus(boolean isDone) { + if (isDone) { + ToastUtils.showLong(copyMoveContext.isCopy() ? R.string.copied_successfully : R.string.moved_successfully); + isDataOperated = true; + } + } + }); + dialogFragment.show(getSupportFragmentManager(), CopyMoveDialogFragment.class.getSimpleName()); + } + } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselItemViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselItemViewHolder.java deleted file mode 100644 index f036a3703..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselItemViewHolder.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.seafile.seadroid2.ui.media.image_preview2; - - -import android.view.View; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.seafile.seadroid2.R; - -public class CarouselItemViewHolder extends RecyclerView.ViewHolder { - - public final ImageView imageView; - - CarouselItemViewHolder(@NonNull View itemView) { - super(itemView); - imageView = itemView.findViewById(R.id.image_view); - } - -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CenterScaleXYRecyclerViewScrollListener.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CenterScaleXYRecyclerViewScrollListener.java index a81e44cbe..9e0dd3d71 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CenterScaleXYRecyclerViewScrollListener.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CenterScaleXYRecyclerViewScrollListener.java @@ -102,9 +102,9 @@ private void adjustChildScale(RecyclerView recyclerView, int dx) { float scale = maxScale - (distanceFromCenter / maxDistance) * (maxScale - minScale); scale = Math.max(minScale, scale); // 确保不小于最小缩放比例 - if (i == firstVisiblePosition) { - SLogs.d("firstVisiblePosition: " + firstVisiblePosition + ", itemWidth: " + itemWidth + ", left: " + left + ", viewCenterX: " + viewCenterX + ", centerX: " + centerX + ", distanceFromCenter: " + distanceFromCenter + ", scale: " + scale); - } +// if (i == firstVisiblePosition) { +// SLogs.d("firstVisiblePosition: " + firstVisiblePosition + ", itemWidth: " + itemWidth + ", left: " + left + ", viewCenterX: " + viewCenterX + ", centerX: " + centerX + ", distanceFromCenter: " + distanceFromCenter + ", scale: " + scale); +// } float alpha = 1.0f - (distanceFromCenter / maxDistance) * 0.8f; // 透明度范围 1.0 到 0.6 alpha = Math.max(0.8f, alpha); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/GravitySnapHelper.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/GravitySnapHelper.java index 894db1a58..7f27ee3a7 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/GravitySnapHelper.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/GravitySnapHelper.java @@ -49,7 +49,7 @@ public class GravitySnapHelper extends LinearSnapHelper { private OrientationHelper horizontalHelper; private SnapListener listener; private RecyclerView recyclerView; - private RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() { + private final RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); @@ -582,8 +582,9 @@ private View findView(@NonNull RecyclerView.LayoutManager layoutManager, - helper.getDecoratedEnd(currentView)); } } else { - currentViewDistance = Math.abs(helper.getDecoratedStart(currentView) - + (helper.getDecoratedMeasurement(currentView) / 2) - center); + int ds = helper.getDecoratedStart(currentView); + int dm = helper.getDecoratedMeasurement(currentView); + currentViewDistance = Math.abs(ds + (dm / 2) - center); } if (currentViewDistance < distanceToTarget) { distanceToTarget = currentViewDistance; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/LinearEdgeDecoration.kt b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/LinearEdgeDecoration.kt index c55356299..b5a86b45c 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/LinearEdgeDecoration.kt +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/LinearEdgeDecoration.kt @@ -23,7 +23,8 @@ class LinearEdgeDecoration( val position = layoutParams.viewAdapterPosition val itemCount = layoutManager.itemCount - if (position == RecyclerView.NO_POSITION || itemCount == 0 + if (position == RecyclerView.NO_POSITION + || itemCount == 0 || (position > 0 && position < itemCount - 1) ) { return diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/PagerSnapBinders.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/PagerSnapBinders.java new file mode 100644 index 000000000..0d227c48d --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/PagerSnapBinders.java @@ -0,0 +1,48 @@ +package com.seafile.seadroid2.ui.media.image_preview2; + +import androidx.viewpager2.widget.ViewPager2; + +import com.seafile.seadroid2.framework.util.SLogs; + +public class PagerSnapBinders { + private static int whoScroll = -1; + + public static void bind(ViewPager2 pager, GravitySnapHelper snapHelper) { + + pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + if (whoScroll == 1) { + whoScroll = -1; + return; + } + + whoScroll = 0; + + SLogs.e("currentPagerPosition: " + position); + position++; + snapHelper.smoothScrollToPosition(position); + } + }); + + snapHelper.setSnapListener(new GravitySnapHelper.SnapListener() { + @Override + public void onSnap(int snapPosition) { + + if (whoScroll == 0) { + whoScroll = -1; + return; + } + + whoScroll = 1; + + SLogs.e("currentSnapPosition: " + snapPosition); + + snapPosition--; + pager.setCurrentItem(snapPosition, true); + } + }); + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java index ba3ce56f4..5f0024f2f 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java @@ -21,6 +21,7 @@ import com.blankj.utilcode.util.SizeUtils; import com.seafile.seadroid2.R; import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.config.AbsLayoutItemType; import com.seafile.seadroid2.config.Constants; import com.seafile.seadroid2.config.GlideLoadConfig; @@ -54,7 +55,7 @@ import java.util.stream.Collectors; public class RepoQuickAdapter extends BaseMultiAdapter { - private final String SERVER = HttpIO.getCurrentInstance().getServerUrl(); + private boolean onActionMode; @@ -570,12 +571,16 @@ private void loadImage(DirentModel direntModel, ImageView imageView) { imageView.setImageResource(direntModel.getIcon()); } else { String url = convertThumbnailUrl(direntModel); - GlideApp.with(getContext()).load(url) - .apply(GlideLoadConfig.getOptions()) - .into(imageView); + if (TextUtils.isEmpty(url)) { + GlideApp.with(getContext()).load(R.drawable.file_image) + .apply(GlideLoadConfig.getOptions()) + .into(imageView); + } else { + GlideApp.with(getContext()).load(url) + .apply(GlideLoadConfig.getOptions()) + .into(imageView); + } } - - } private String convertThumbnailUrl(DirentModel direntModel) { @@ -586,9 +591,30 @@ private String convertMiddleUrl(DirentModel direntModel) { return convertThumbnailUrl(direntModel, 256); } + private String server_url; + + private String getServerUrl() { + if (!TextUtils.isEmpty(server_url)) { + return server_url; + } + + boolean isLogin = SupportAccountManager.getInstance().isLogin(); + if (!isLogin) { + return null; + } + + server_url = HttpIO.getCurrentInstance().getServerUrl(); + return server_url; + } + private String convertThumbnailUrl(DirentModel direntModel, int size) { + String serverUrl = getServerUrl(); + if (TextUtils.isEmpty(serverUrl)) { + return null; + } + String newFilePath = EncodeUtils.urlEncode(direntModel.full_path); - return String.format(Locale.ROOT, "%sapi2/repos/%s/thumbnail/?p=%s&size=%d", SERVER, direntModel.repo_id, newFilePath, size); + return String.format(Locale.ROOT, "%sapi2/repos/%s/thumbnail/?p=%s&size=%d", serverUrl, direntModel.repo_id, newFilePath, size); } public void setOnActionMode(boolean on) { 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 afb109773..5a1bdbce3 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 @@ -88,7 +88,6 @@ 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.image_preview2.CarouselImagePreviewActivity; import com.seafile.seadroid2.ui.media.player.exoplayer.CustomExoVideoPlayerActivity; import com.seafile.seadroid2.ui.sdoc.SDocWebViewActivity; @@ -1178,7 +1177,7 @@ private void open(DirentModel dirent) { // because pic thumbnail under encrypted repo was not supported at the server side if (Utils.isViewableImage(fileName) && !repoModel.encrypted) { - Intent getIntent = CarouselImagePreviewActivity.startThisFromRepo(requireContext(), dirent); + Intent getIntent = CarouselImagePreviewActivity.startThisFromObjs(requireContext(), dirent); imagePreviewActivityLauncher.launch(getIntent); return; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java index 75319cc0b..22621e7c5 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java @@ -852,7 +852,7 @@ private Flowable getStarSingle(String repoId, String path, boolean isStar) { requestDataMap.put("repo_id", repoId); requestDataMap.put("path", path); - Map bodyMap = generateRequestBody(requestDataMap); + Map bodyMap = genRequestBody(requestDataMap); Single single = HttpIO.getCurrentInstance().execute(StarredService.class).star(bodyMap); return single.toFlowable(); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentService.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentService.java new file mode 100644 index 000000000..19f3df13d --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentService.java @@ -0,0 +1,63 @@ +package com.seafile.seadroid2.ui.sdoc; + +import com.seafile.seadroid2.framework.data.model.ResultModel; +import com.seafile.seadroid2.framework.data.model.docs_comment.DocsCommentWrapperModel; +import com.seafile.seadroid2.framework.data.model.docs_comment.DocsCommentsWrapperModel; +import com.seafile.seadroid2.framework.data.model.docs_comment.DocsUploadResultModel; +import com.seafile.seadroid2.framework.data.model.sdoc.FileDetailModel; +import com.seafile.seadroid2.framework.data.model.sdoc.FileRecordWrapperModel; +import com.seafile.seadroid2.framework.data.model.sdoc.MetadataConfigModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocOutlineWrapperModel; +import com.seafile.seadroid2.framework.data.model.user.UserWrapperModel; + +import java.util.Map; + +import io.reactivex.Flowable; +import io.reactivex.Single; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.Multipart; +import retrofit2.http.POST; +import retrofit2.http.PUT; +import retrofit2.http.Part; +import retrofit2.http.PartMap; +import retrofit2.http.Path; +import retrofit2.http.Query; + +public interface DocsCommentService { + @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); + + @Multipart + @POST("api/v2.1/seadoc/upload-image/{sdoc_uuid}/") + Flowable upload(@Path("sdoc_uuid") String docUid, @Part() MultipartBody.Part file, @PartMap Map map); + + @POST("api/v1/docs/{sdoc_uuid}/comment/") + Single postComment(@Path("sdoc_uuid") String uuid, @Body Map map); + + @PUT("api/v1/docs/{sdoc_uuid}/comment/{comment_id}/") + Single markResolved(@Path("sdoc_uuid") String docUid, @Path("comment_id") long commentId, @Body Map map); + + @DELETE("api/v1/docs/{sdoc_uuid}/comment/{comment_id}/") + Single delete(@Path("sdoc_uuid") String docUid, @Path("comment_id") long commentId); + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentViewModel.java new file mode 100644 index 000000000..5d477ba10 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentViewModel.java @@ -0,0 +1,522 @@ +package com.seafile.seadroid2.ui.sdoc; + +import android.content.ContentResolver; +import android.net.Uri; +import android.text.TextUtils; + +import androidx.lifecycle.MutableLiveData; + +import com.blankj.utilcode.util.CloneUtils; +import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.RegexUtils; +import com.blankj.utilcode.util.TimeUtils; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.framework.data.model.ResultModel; +import com.seafile.seadroid2.framework.data.model.docs_comment.DocsCommentWrapperModel; +import com.seafile.seadroid2.framework.data.model.docs_comment.DocsUploadResultModel; +import com.seafile.seadroid2.framework.data.model.sdoc.MetadataConfigModel; +import com.seafile.seadroid2.framework.data.model.docs_comment.DocsCommentModel; +import com.seafile.seadroid2.framework.data.model.docs_comment.DocsCommentsWrapperModel; +import com.seafile.seadroid2.framework.data.model.sdoc.FileDetailModel; +import com.seafile.seadroid2.framework.data.model.sdoc.OutlineItemModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocPageOptionsModel; +import com.seafile.seadroid2.framework.data.model.sdoc.FileProfileConfigModel; +import com.seafile.seadroid2.framework.data.model.sdoc.FileRecordWrapperModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocOutlineWrapperModel; +import com.seafile.seadroid2.framework.data.model.user.UserWrapperModel; +import com.seafile.seadroid2.framework.http.HttpIO; +import com.seafile.seadroid2.framework.util.ContentResolvers; +import com.seafile.seadroid2.framework.util.SLogs; +import com.seafile.seadroid2.framework.util.StringUtils; +import com.seafile.seadroid2.framework.util.Utils; +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.view.rich_edittext.RichEditText; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import io.reactivex.Flowable; +import io.reactivex.Single; +import io.reactivex.functions.Consumer; +import io.reactivex.functions.Function3; +import kotlin.Pair; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; + +public class DocsCommentViewModel extends BaseViewModel { + + private final MutableLiveData _fileProfileConfigLiveData = new MutableLiveData<>(); + private final MutableLiveData _fileRecordLiveData = new MutableLiveData<>(); + private final MutableLiveData _fileCommentLiveData = new MutableLiveData<>(); + private final MutableLiveData> _sdocElementListLiveData = new MutableLiveData<>(); + private final MutableLiveData _postCommentLiveData = new MutableLiveData<>(); + + public MutableLiveData getPostCommentLiveData() { + return _postCommentLiveData; + } + + public MutableLiveData getFileDetailLiveData() { + return _fileProfileConfigLiveData; + } + + public MutableLiveData getSdocRecordLiveData() { + return _fileRecordLiveData; + } + + public MutableLiveData getSdocCommentLiveData() { + return _fileCommentLiveData; + } + + public MutableLiveData> getSdocElementLiveData() { + return _sdocElementListLiveData; + } + + public void loadFileConfig(String repoId, String path) { + Single userSingle = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getRelatedUsers(repoId); + Single metadataSingle = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getMetadata(repoId); + Single detailSingle = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getFileDetail(repoId, path); + + Single s = Single.zip(detailSingle, userSingle, metadataSingle, new Function3() { + @Override + public FileProfileConfigModel apply(FileDetailModel docDetailModel, UserWrapperModel userWrapperModel, MetadataConfigModel metadataConfigModel) throws Exception { + FileProfileConfigModel configModel = new FileProfileConfigModel(); + configModel.setDetail(docDetailModel); + configModel.setUsers(userWrapperModel); + configModel.setMetadata(metadataConfigModel); + return configModel; + } + }); + + addSingleDisposable(s, new Consumer() { + @Override + public void accept(FileProfileConfigModel fileProfileConfigModel) throws Exception { + getFileDetailLiveData().setValue(fileProfileConfigModel); + } + }); + } + + public void loadRecords(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; + } + + if (TextUtils.isEmpty(parent_dir)) { + parent_dir = "/"; + } + + Single single = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getRecords(repoId, parent_dir, name); + addSingleDisposable(single, new Consumer() { + @Override + public void accept(FileRecordWrapperModel fileRecordWrapperModel) throws Exception { + getSdocRecordLiveData().setValue(fileRecordWrapperModel); + } + }); + } + + public static final List _AllowedElementTypes = List.of("header1", "header2", "header3"); + + public void loadSdocElements(SDocPageOptionsModel pageOptionsModel) { + if (TextUtils.isEmpty(pageOptionsModel.seadocServerUrl)) { + return; + } + getRefreshLiveData().setValue(true); + + 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(DocsCommentService.class).getElements(pageOptionsModel.docUuid); + addSingleDisposable(single, new Consumer() { + @Override + public void accept(SDocOutlineWrapperModel 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(OutlineItemModel 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 OutlineItemModel apply(OutlineItemModel sDocModel) { + if (!TextUtils.isEmpty(sDocModel.text)) { + return sDocModel; + } + + if (CollectionUtils.isEmpty(sDocModel.children)) { + return sDocModel; + } + + String text = ""; + for (OutlineItemModel 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); + getRefreshLiveData().setValue(false); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + SLogs.e(throwable); + getRefreshLiveData().setValue(false); + } + }); + } + + public void loadDocComments(SDocPageOptionsModel pageOptionsModel) { + if (TextUtils.isEmpty(pageOptionsModel.seadocServerUrl)) { + return; + } + getRefreshLiveData().setValue(true); + + 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(DocsCommentService.class).getComments(pageOptionsModel.docUuid); + addSingleDisposable(commentSingle, new Consumer() { + @Override + public void accept(DocsCommentsWrapperModel docsCommentsWrapperModel) throws Exception { + docsCommentsWrapperModel.comments = docsCommentsWrapperModel.comments.stream().sorted(new Comparator() { + @Override + public int compare(DocsCommentModel o1, DocsCommentModel o2) { + return o1.created_at.compareTo(o2.created_at); + } + }).map(new Function() { + @Override + public DocsCommentModel apply(DocsCommentModel docsCommentModel) { + + Pair> pair = formatContent(docsCommentModel.comment); + if (pair != null) { + docsCommentModel.commentList = pair.getSecond(); + docsCommentModel.isContainImage = pair.getFirst(); + } + + return docsCommentModel; + } + }).collect(Collectors.toList()); + + getSdocCommentLiveData().setValue(docsCommentsWrapperModel); + getRefreshLiveData().setValue(false); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + SLogs.e(throwable); + getRefreshLiveData().setValue(false); + } + }); + } + + + private Pair> formatContent(String comment) { + if (TextUtils.isEmpty(comment)) { + return null; + } + + List models = CollectionUtils.newArrayList(); + + String[] lines = org.apache.commons.lang3.StringUtils.split(comment, "\n\n"); + for (String line : lines) { + if (TextUtils.isEmpty(line)) { + continue; + } + + int imgLabelCount = StringUtils.countMatches(line, imgPrefix); + int imgMdCount = StringUtils.countMatches(line, imgMdPrefix); + if (imgMdCount == 0 && imgLabelCount == 0) { + RichEditText.RichContentModel m = new RichEditText.RichContentModel(); + m.type = 0; + m.content = line; + models.add(m); + + continue; + } + + if (imgLabelCount > 0) { + models.addAll(getImageLabelResult(line)); + } + + if (imgMdCount > 0) { + models.addAll(getImageMdResult(line)); + } + } + return new Pair<>(true, models); + } + + private final String imgPrefix = " getImageLabelResult(String s) { + List models = CollectionUtils.newArrayList(); + + int start = org.apache.commons.lang3.StringUtils.indexOf(s, imgPrefix); + int end = org.apache.commons.lang3.StringUtils.indexOf(s, imgSuffix); + if (start > 0) { + String startStr = org.apache.commons.lang3.StringUtils.substring(s, 0, start); + RichEditText.RichContentModel m = new RichEditText.RichContentModel(); + m.type = 0; + m.content = startStr; + models.add(m); + } + + String sss = org.apache.commons.lang3.StringUtils.substring(s, start, end + 1); + String a = sss.replaceAll("", " "); + String content = a.trim();// URLEncoder.encode(,"utf-8"); + if (!TextUtils.isEmpty(content) && !TextUtils.equals("null", content.toLowerCase(Locale.getDefault()))) { + RichEditText.RichContentModel m = new RichEditText.RichContentModel(); + m.type = 1; + m.content = content; + models.add(m); + } + + s = s.substring(end + 1); + if (!org.apache.commons.lang3.StringUtils.isEmpty(s)) { + RichEditText.RichContentModel m = new RichEditText.RichContentModel(); + m.type = 0; + m.content = s; + models.add(m); + } + + return models; + } + + private List getImageMdResult(String s) { + List models = CollectionUtils.newArrayList(); + + int start = org.apache.commons.lang3.StringUtils.indexOf(s, imgMdPrefix); + int end = org.apache.commons.lang3.StringUtils.indexOf(s, imgMdSuffix); + if (start > 0) { + //check text before image content + String startStr = org.apache.commons.lang3.StringUtils.substring(s, 0, start); + RichEditText.RichContentModel m = new RichEditText.RichContentModel(); + m.type = 0; + m.content = startStr; + models.add(m); + } + +//![](https://dev.seafile.com/seahub/api/v2.1/seadoc/download-image/e4c22460-e99d-4687-9081-1f30d156625d/image-eEy2ClPsSV6nlef3v1FW-g.jpg) + String sss = org.apache.commons.lang3.StringUtils.substring(s, start + 4, end); + String content = sss.trim();// URLEncoder.encode(,"utf-8"); + if (!TextUtils.isEmpty(content) && !TextUtils.equals("null", content.toLowerCase(Locale.getDefault()))) { + RichEditText.RichContentModel m = new RichEditText.RichContentModel(); + m.type = 1; + m.content = content; + models.add(m); + } + + s = s.substring(end + 1); + if (!org.apache.commons.lang3.StringUtils.isEmpty(s)) { + RichEditText.RichContentModel m = new RichEditText.RichContentModel(); + m.type = 0; + m.content = s; + models.add(m); + } + + return models; + } + + public void markResolve(String sdocServerUrl,String token, String sdocUid, long commentId, Consumer consumer) { + getRefreshLiveData().setValue(true); + + if (!sdocServerUrl.endsWith("/")) { + sdocServerUrl = sdocServerUrl + "/"; + } + + Account curAccount = SupportAccountManager.getInstance().getCurrentAccount(); + Account partialAccount = CloneUtils.deepClone(curAccount, Account.class); + partialAccount.setServer(sdocServerUrl); + partialAccount.setToken(token); + + Map params = new HashMap<>(); + params.put("resolved", true); + + Single resolvedSingle = HttpIO.getInstanceByAccount(partialAccount).execute(DocsCommentService.class).markResolved(sdocUid, commentId, params); + addSingleDisposable(resolvedSingle, new Consumer() { + @Override + public void accept(ResultModel resultModel) throws Exception { + getRefreshLiveData().setValue(false); + if (consumer != null) { + consumer.accept(commentId); + } + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) { + getRefreshLiveData().setValue(false); + } + }); + } + + public void delete(String sdocServerUrl,String token, String sdocUid, long commentId, Consumer consumer) { + getRefreshLiveData().setValue(true); + + if (!sdocServerUrl.endsWith("/")) { + sdocServerUrl = sdocServerUrl + "/"; + } + + Account curAccount = SupportAccountManager.getInstance().getCurrentAccount(); + Account partialAccount = CloneUtils.deepClone(curAccount, Account.class); + partialAccount.setServer(sdocServerUrl); + partialAccount.setToken(token); + + Single resolvedSingle = HttpIO.getInstanceByAccount(partialAccount) + .execute(DocsCommentService.class) + .delete(sdocUid, commentId); + addSingleDisposable(resolvedSingle, new Consumer() { + @Override + public void accept(ResultModel resultModel) throws Exception { + getRefreshLiveData().setValue(false); + if (consumer != null) { + consumer.accept(commentId); + } + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) { + getRefreshLiveData().setValue(false); + } + }); + } + + + //{"relative_path":["/image-UFB28ta7TVOskNE0uAZ_Ww.jpeg"]} + public void uploadFile(ContentResolver contentResolver, Uri uri, String docUid, String token, Consumer consumer, Consumer errorCallBack) { + String fileName = ContentResolvers.getFileNameFromUri(contentResolver, uri); + + byte[] fileContent = ContentResolvers.getFileContentFromUri(contentResolver, uri); + RequestBody body = RequestBody.create(MediaType.parse("application/octet-stream"), fileContent); + MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", fileName, body); + + Map partMap = new HashMap<>(); + partMap.put("authorization", RequestBody.create(MediaType.parse("text"), "Token " + token)); + + Account account = SupportAccountManager.getInstance().getCurrentAccount(); + account.token = token; + Flowable uploadFile = HttpIO.getInstanceByAccount(account).execute(DocsCommentService.class).upload(docUid, filePart, partMap); + + addFlowableDisposable(uploadFile, new Consumer() { + @Override + public void accept(DocsUploadResultModel resultModel) throws Exception { + if (consumer != null) { + + String sName = resultModel.relative_path.get(0); + String sUrl = HttpIO.getCurrentInstance().getServerUrl(); + String absUrl = Utils.pathJoin(sUrl, "api", "v2.1", "seadoc", "download-image", docUid, sName); + + //https://dev.seafile.com/seahub/api/v2.1/seadoc/download-image/e4c22460-e99d-4687-9081-1f30d156625d/+:: + + consumer.accept(absUrl); + } + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + if (errorCallBack != null) { + errorCallBack.accept(uri.toString()); + } + } + }); + + } + + + public void postComment(SDocPageOptionsModel pageOptionsModel, String comment, String elementId) { + getRefreshLiveData().setValue(true); + 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); + + Map detail = new HashMap<>(); + detail.put("element_id", "0"); + detail.put("comment", comment); + + Map params = new HashMap<>(); + params.put("comment", comment); + params.put("detail", detail); + params.put("author", partialAccount.email); + params.put("updated_at", TimeUtils.getNowString()); + + + Single single = HttpIO.getInstanceByAccount(partialAccount).execute(DocsCommentService.class).postComment(pageOptionsModel.docUuid, params); + addSingleDisposable(single, new Consumer() { + @Override + public void accept(DocsCommentWrapperModel docsCommentWrapperModel) throws Exception { + getRefreshLiveData().setValue(false); + getPostCommentLiveData().setValue(true); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + getRefreshLiveData().setValue(false); + + } + }); + } +} 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 deleted file mode 100644 index 8010a80a4..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocService.java +++ /dev/null @@ -1,34 +0,0 @@ -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 deleted file mode 100644 index d0e157108..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocViewModel.java +++ /dev/null @@ -1,223 +0,0 @@ -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 index 9644fc6f8..b992c2ff6 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocWebViewActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocWebViewActivity.java @@ -6,6 +6,7 @@ import android.os.Bundle; import android.text.TextUtils; import android.view.View; +import android.webkit.ConsoleMessage; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebView; @@ -22,28 +23,30 @@ import com.blankj.utilcode.util.ActivityUtils; import com.blankj.utilcode.util.GsonUtils; import com.blankj.utilcode.util.ToastUtils; +import com.github.lzyzsd.jsbridge.CallBackFunction; +import com.seafile.seadroid2.R; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.annotation.Todo; -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.FileProfileConfigModel; +import com.seafile.seadroid2.framework.data.model.sdoc.FileRecordWrapperModel; +import com.seafile.seadroid2.framework.data.model.sdoc.OutlineItemModel; 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.listener.OnItemClickListener; 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.ui.docs_comment.DocsCommentsActivity; +import com.seafile.seadroid2.ui.file_profile.FileProfileDialog; +import com.seafile.seadroid2.ui.sdoc.outline.SDocOutlineDialog; import com.seafile.seadroid2.view.webview.PreloadWebView; import com.seafile.seadroid2.view.webview.SeaWebView; @Todo -public class SDocWebViewActivity extends BaseActivityWithVM { +public class SDocWebViewActivity extends BaseActivityWithVM { private ActivitySeaWebviewProBinding binding; private ToolbarActionbarProgressBarBinding toolBinding; @@ -52,7 +55,7 @@ public class SDocWebViewActivity extends BaseActivityWithVM { private String path; private String targetUrl; - private SDocProfileConfigModel configModel; + private FileProfileConfigModel configModel; /** * not support, please use SeaWebViewActivity instead @@ -75,16 +78,17 @@ protected void onCreate(Bundle savedInstanceState) { toolBinding = ToolbarActionbarProgressBarBinding.bind(binding.toolProgressBar.getRoot()); - init(); initUI(); + init(); + initViewModel(); //let's go mWebView.load(targetUrl); - getViewModel().initSDocConfig(repoId, path); + getViewModel().loadFileConfig(repoId, path); } private void init() { @@ -102,6 +106,7 @@ private void init() { WebViewPreviewType previewTypeEnum = WebViewPreviewType.valueOf(previewType); if (previewTypeEnum == WebViewPreviewType.SDOC) { + String repoName = intent.getStringExtra("repoName"); repoId = intent.getStringExtra("repoID"); path = intent.getStringExtra("filePath"); @@ -119,7 +124,6 @@ private void init() { } else { throw new IllegalArgumentException("previewType is not SDOC"); } - } private void initUI() { @@ -132,21 +136,22 @@ private void initUI() { mWebView = PreloadWebView.getInstance().getWebView(this); + if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { WebSettingsCompat.setAlgorithmicDarkeningAllowed(mWebView.getSettings(), true); } + //chrome client + mWebView.setWebChromeClient(mWebChromeClient); + 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() { + binding.sdocOutline.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - showDirectoryDialog(); + showOutlineDialog(); } }); binding.sdocProfile.setOnClickListener(new View.OnClickListener() { @@ -175,32 +180,46 @@ public void handleOnBackPressed() { } private void initViewModel() { - getViewModel().getFileDetailLiveData().observe(this, new Observer() { + getViewModel().getFileDetailLiveData().observe(this, new Observer() { @Override - public void onChanged(SDocProfileConfigModel sDocProfileConfigModel) { - configModel = sDocProfileConfigModel; + public void onChanged(FileProfileConfigModel fileProfileConfigModel) { + configModel = fileProfileConfigModel; hideProgressBar(); } }); - getViewModel().getSdocRecordLiveData().observe(this, new Observer() { + 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()); + public void onChanged(FileRecordWrapperModel fileRecordWrapperModel) { + FileProfileDialog dialog = FileProfileDialog.newInstance(configModel.detail, fileRecordWrapperModel, configModel.users.user_list, false); + dialog.show(getSupportFragmentManager(), FileProfileDialog.class.getSimpleName()); } }); } - private void showDirectoryDialog() { - getSDocConfigData(new Consumer() { + + private void showOutlineDialog() { + readSDocOutlineList(new Consumer() { @Override - public void accept(SDocPageOptionsModel model) { - if (TextUtils.isEmpty(model.seadocServerUrl) || TextUtils.isEmpty(model.docUuid)) { - return; - } + public void accept(String s) { + SDocOutlineDialog dialog = SDocOutlineDialog.newInstance(s); + dialog.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(OutlineItemModel outlineItemModel, int position) { - SDocDirectoryDialog dialog = SDocDirectoryDialog.newInstance(model); - dialog.show(getSupportFragmentManager(), SDocDirectoryDialog.class.getSimpleName()); + callJsOutline(outlineItemModel); + } + }); + dialog.show(getSupportFragmentManager(), SDocOutlineDialog.class.getSimpleName()); + } + }); + } + + private void callJsOutline(OutlineItemModel outlineItemModel) { + String param = GsonUtils.toJson(outlineItemModel); + mWebView.callJsFunction("sdoc.outline.data.select", param, new CallBackFunction() { + @Override + public void onCallBack(String data) { + SLogs.e(data); } }); } @@ -211,23 +230,23 @@ private void showProfileDialog() { } if (configModel.metadata.enabled) { - getViewModel().getRecords(repoId, path); + getViewModel().loadRecords(repoId, path); } else { - SDocProfileDialog dialog = SDocProfileDialog.newInstance(configModel.detail, configModel.users.user_list); - dialog.show(getSupportFragmentManager(), SDocProfileDialog.class.getSimpleName()); + FileProfileDialog dialog = FileProfileDialog.newInstance(configModel.detail, configModel.users.user_list); + dialog.show(getSupportFragmentManager(), FileProfileDialog.class.getSimpleName()); } } private void showCommentsActivity() { - getSDocConfigData(new Consumer() { + readSDocPageOptionsData(new Consumer() { @Override public void accept(SDocPageOptionsModel model) { - SDocCommentsActivity.start(SDocWebViewActivity.this, model); + DocsCommentsActivity.start(SDocWebViewActivity.this, model); } }); } - private void getSDocConfigData(Consumer continuation) { + private void readSDocPageOptionsData(Consumer continuation) { String js = "(function() {" + " if (window.app && window.app.pageOptions) {" + @@ -239,20 +258,79 @@ private void getSDocConfigData(Consumer continuation) { 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 对象数据是空的"); + SLogs.e("read sodc page options data from web, an exception occurred in the parsing data"); + SLogs.e(value); + ToastUtils.showShort(R.string.unknow_error); } } else { + SLogs.e("read sodc page options data from web: " + value); + ToastUtils.showShort(R.string.unknow_error); + } + } + }); + } + + private void readSDocOutlineList(Consumer continuation) { + String js = + "(function() {" + + " if (window.seadroid && window.seadroid.outlines) {" + + " return JSON.stringify(window.seadroid.outlines);" + + " } else {" + + " return null;" + + " }" + + "})();"; + mWebView.evaluateJavascript(js, new ValueCallback() { + @Override + public void onReceiveValue(String value) { + if (TextUtils.isEmpty(value)) { + SLogs.e(value); + ToastUtils.showShort(R.string.empty_data); + continuation.accept(value); + return; + } + + value = StringUtils.deStringReturnNonNull(value).replace("\\", ""); + if (continuation != null) { + continuation.accept(value); + } + } + }); + } + + @Deprecated + private void readSeafileTokenData(Consumer continuation) { + String js = + "(function() {" + + " if (window.seafile && window.seafile.accessToken) {" + + " return JSON.stringify(window.seafile.accessToken);" + + " } else {" + + " return null;" + + " }" + + "})();"; + mWebView.evaluateJavascript(js, new ValueCallback() { + @Override + public void onReceiveValue(String value) { + SLogs.e(value); + if (TextUtils.isEmpty(value)) { SLogs.d("doc uuid is empty."); - ToastUtils.showShort("doc uuid is empty."); + ToastUtils.showShort("outline is empty."); + return; + } + + value = StringUtils.deStringReturnNonNull(value).replace("\\", ""); + value = StringUtils.deStringReturnNonNull(value); + + if (continuation != null) { + continuation.accept(value); + } else { + } } }); @@ -276,7 +354,31 @@ public void onProgressChanged(WebView view, int newProgress) { @Override public void onReceivedTitle(WebView view, String title) { super.onReceivedTitle(view, title); - toolBinding.toolbarActionbar.setTitle(title); +// toolBinding.toolbarActionbar.setTitle(title); + } + + @Override + public boolean onConsoleMessage(ConsoleMessage consoleMessage) { + if (consoleMessage != null) { + switch (consoleMessage.messageLevel()) { + case ERROR: + SLogs.e("web e log: line: " + consoleMessage.lineNumber() + ", message: " + consoleMessage.message()); + break; + case DEBUG: + SLogs.d("web d log: line: " + consoleMessage.lineNumber() + ", message: " + consoleMessage.message()); + break; + case WARNING: + SLogs.w("web w log: line: " + consoleMessage.lineNumber() + ", message: " + consoleMessage.message()); + break; + case TIP: + SLogs.i("web i log: line: " + consoleMessage.lineNumber() + ", message: " + consoleMessage.message()); + break; + default: + SLogs.e("web default log: line: " + consoleMessage.lineNumber() + ", message: " + consoleMessage.message()); + break; + } + } + return super.onConsoleMessage(consoleMessage); } }; 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 deleted file mode 100644 index e23915268..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentViewHolder.java +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index 20e40e77d..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/comments/SDocCommentsActivity.java +++ /dev/null @@ -1,184 +0,0 @@ -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 deleted file mode 100644 index 98f3505ef..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/directory/SDocDirectoryAdapter.java +++ /dev/null @@ -1,59 +0,0 @@ -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/outline/SDocOutlineAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineAdapter.java new file mode 100644 index 000000000..5d98e72f2 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineAdapter.java @@ -0,0 +1,56 @@ +package com.seafile.seadroid2.ui.sdoc.outline; + +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.ItemSdocOutlineBinding; +import com.seafile.seadroid2.framework.data.model.sdoc.OutlineItemModel; +import com.seafile.seadroid2.ui.base.adapter.BaseAdapter; +import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; + +public class SDocOutlineAdapter extends BaseAdapter { + + private final int _paddingStart = DP_8; + + @NonNull + @Override + protected SDocOutlineHolder onCreateViewHolder(@NonNull Context context, @NonNull ViewGroup viewGroup, int i) { + ItemSdocOutlineBinding binding = ItemSdocOutlineBinding.inflate(LayoutInflater.from(context), viewGroup, false); + return new SDocOutlineHolder(binding); + } + + @Override + protected void onBindViewHolder(@NonNull SDocOutlineHolder holder, int i, @Nullable OutlineItemModel outlineItemModel) { + if (outlineItemModel == null) { + return; + } + + int padding = 0; + if ("header1".equals(outlineItemModel.type)) { + padding = _paddingStart; + } else if ("header2".equals(outlineItemModel.type)) { + padding = _paddingStart * 3; + } else if ("header3".equals(outlineItemModel.type)) { + padding = _paddingStart * 6; + } + + holder.binding.title.setPadding(padding, 0, 0, 0); + holder.binding.title.setText(outlineItemModel.text); + } + + + public static class SDocOutlineHolder extends BaseViewHolder { + ItemSdocOutlineBinding binding; + + public SDocOutlineHolder(@NonNull ItemSdocOutlineBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineDialog.java new file mode 100644 index 000000000..b22ff9314 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineDialog.java @@ -0,0 +1,158 @@ +package com.seafile.seadroid2.ui.sdoc.outline; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.GsonUtils; +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.gson.reflect.TypeToken; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.databinding.DialogSdocDirectoryBinding; +import com.seafile.seadroid2.framework.data.model.sdoc.OutlineItemModel; +import com.seafile.seadroid2.framework.util.StringUtils; +import com.seafile.seadroid2.listener.OnItemClickListener; + +import java.lang.reflect.Type; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class SDocOutlineDialog extends BottomSheetDialogFragment { + + private DialogSdocDirectoryBinding binding; + private SDocOutlineAdapter adapter; + private List outlineItemList; + private OnItemClickListener onItemClickListener; + + public void setOnItemClickListener(OnItemClickListener onItemClickListener) { + this.onItemClickListener = onItemClickListener; + } + + public static SDocOutlineDialog newInstance(String outlineStr) { + Bundle args = new Bundle(); + args.putString("outline_value", outlineStr); + SDocOutlineDialog fragment = new SDocOutlineDialog(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() == null || !getArguments().containsKey("outline_value")) { + throw new IllegalArgumentException("outline_value is null"); + } + + String value = getArguments().getString("outline_value"); + + Type listType = new TypeToken>() { + }.getType(); + + outlineItemList = GsonUtils.fromJson(value, listType); + } + + + @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) { + return new BottomSheetDialog(requireContext()); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + binding.rv.setLayoutManager(new LinearLayoutManager(requireContext())); + + adapter = new SDocOutlineAdapter(); + adapter.setAnimationEnable(true); + adapter.setStateViewLayout(requireContext(), R.layout.layout_empty); + adapter.setStateViewEnable(false); + adapter.addOnItemChildClickListener(R.id.text_container, new BaseQuickAdapter.OnItemChildClickListener() { + @Override + public void onItemClick(@NonNull BaseQuickAdapter baseQuickAdapter, @NonNull View view, int i) { + OutlineItemModel outlineItemModel = adapter.getItems().get(i); + if (onItemClickListener != null) + onItemClickListener.onItemClick(outlineItemModel, i); + + dismiss(); + } + }); + + + QuickAdapterHelper helper = new QuickAdapterHelper.Builder(adapter).build(); + binding.rv.setAdapter(helper.getAdapter()); + + load(); + } + + public static final List _AllowedElementTypes = List.of("header1", "header2", "header3"); + + private void load() { + if (CollectionUtils.isEmpty(outlineItemList)) { + adapter.setStateViewEnable(true); + return; + } + + List newList = outlineItemList.stream().filter(new Predicate() { + @Override + public boolean test(OutlineItemModel 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 OutlineItemModel apply(OutlineItemModel sDocModel) { + if (!TextUtils.isEmpty(sDocModel.text)) { + return sDocModel; + } + + if (CollectionUtils.isEmpty(sDocModel.children)) { + return sDocModel; + } + + String text = ""; + for (OutlineItemModel 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()); + + adapter.setStateViewEnable(true); + adapter.submitList(newList); + } +} 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/outline/SDocOutlineRemoteDialog.java similarity index 77% rename from app/src/main/java/com/seafile/seadroid2/ui/sdoc/directory/SDocDirectoryDialog.java rename to app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineRemoteDialog.java index 16103d29d..cb0c0e19f 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/directory/SDocDirectoryDialog.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineRemoteDialog.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.sdoc.directory; +package com.seafile.seadroid2.ui.sdoc.outline; import android.annotation.SuppressLint; import android.app.Dialog; @@ -9,8 +9,6 @@ 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; @@ -20,29 +18,27 @@ 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.OutlineItemModel; 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 com.seafile.seadroid2.ui.sdoc.DocsCommentViewModel; import java.util.List; -public class SDocDirectoryDialog extends BottomSheetDialogFragment { - private SDocViewModel viewModel; +public class SDocOutlineRemoteDialog extends BottomSheetDialogFragment { + private DocsCommentViewModel viewModel; private SDocPageOptionsModel pageOptionsModel; private DialogSdocDirectoryBinding binding; - private SDocDirectoryAdapter adapter; + private SDocOutlineAdapter adapter; - public static SDocDirectoryDialog newInstance(SDocPageOptionsModel pageOptionsModel) { + public static SDocOutlineRemoteDialog newInstance(SDocPageOptionsModel pageOptionsModel) { Bundle args = new Bundle(); args.putParcelable("pageOption", pageOptionsModel); - SDocDirectoryDialog fragment = new SDocDirectoryDialog(); + SDocOutlineRemoteDialog fragment = new SDocOutlineRemoteDialog(); fragment.setArguments(args); return fragment; } @@ -55,7 +51,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { } pageOptionsModel = getArguments().getParcelable("pageOption"); - viewModel = new ViewModelProvider(this).get(SDocViewModel.class); + viewModel = new ViewModelProvider(this).get(DocsCommentViewModel.class); } @@ -100,15 +96,15 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat super.onViewCreated(view, savedInstanceState); binding.rv.setLayoutManager(new LinearLayoutManager(requireContext())); - adapter = new SDocDirectoryAdapter(); + adapter = new SDocOutlineAdapter(); adapter.setAnimationEnable(true); adapter.setStateViewLayout(requireContext(),R.layout.layout_empty); adapter.setStateViewEnable(false); - adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() { + 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); + public void onClick(@NonNull BaseQuickAdapter baseQuickAdapter, @NonNull View view, int i) { + OutlineItemModel outlineItemModel = adapter.getItems().get(i); + ToastUtils.showLong(outlineItemModel.text); dismiss(); } }); @@ -122,16 +118,16 @@ public void onClick(@NonNull BaseQuickAdapter baseQuickAdapter, @N } private void initViewModel() { - viewModel.getSdocElementLiveData().observe(getViewLifecycleOwner(), new Observer>() { + viewModel.getSdocElementLiveData().observe(getViewLifecycleOwner(), new Observer>() { @Override - public void onChanged(List sDocModels) { + public void onChanged(List outlineItemModels) { adapter.setStateViewEnable(true); - adapter.submitList(sDocModels); + adapter.submitList(outlineItemModels); } }); } private void load() { - viewModel.getSDocElements(pageOptionsModel); + viewModel.loadSdocElements(pageOptionsModel); } } 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 7e5265d6e..3709a375a 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 @@ -33,7 +33,6 @@ import com.chad.library.adapter4.loadState.LoadState; import com.chad.library.adapter4.loadState.trailing.TrailingLoadStateAdapter; import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.firebase.analytics.FirebaseAnalytics; import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeafException; import com.seafile.seadroid2.account.Account; @@ -49,7 +48,7 @@ import com.seafile.seadroid2.ui.base.adapter.LogicLoadMoreAdapter; import com.seafile.seadroid2.ui.file.FileActivity; import com.seafile.seadroid2.ui.main.MainActivity; -import com.seafile.seadroid2.ui.media.image_preview.ImagePreviewActivity; +import com.seafile.seadroid2.ui.media.image_preview2.CarouselImagePreviewActivity; import com.seafile.seadroid2.ui.media.player.exoplayer.CustomExoVideoPlayerActivity; import com.seafile.seadroid2.ui.sdoc.SDocWebViewActivity; import com.seafile.seadroid2.view.TipsViews; @@ -66,6 +65,7 @@ /** * Search Activity */ +@Deprecated public class Search2Activity extends BaseActivityWithVM implements Toolbar.OnMenuItemClickListener { private ActivitySearch2Binding binding; @@ -96,12 +96,6 @@ protected void onCreate(Bundle savedInstanceState) { handleIntent(getIntent()); - - //firebase - event -login - Bundle eventBundle = new Bundle(); - eventBundle.putString(FirebaseAnalytics.Param.METHOD, Search2Activity.class.getSimpleName()); - FirebaseAnalytics.getInstance(this).logEvent(FirebaseAnalytics.Event.SEARCH, eventBundle); - } private void initView(Bundle bundle) { @@ -380,7 +374,8 @@ private void open(RepoModel repoModel, SearchModel searchedFile, String fileName } 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 - ImagePreviewActivity.startThisFromSearch(this, searchedFile); + Intent intent = CarouselImagePreviewActivity.startThisFromSearch(this, searchedFile); + startActivity(intent); } else if (Utils.isVideoFile(fileName)) { // is video file final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); builder.setItems(R.array.video_download_array, new DialogInterface.OnClickListener() { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorActivity.java index 453fa45c5..3d256aaee 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/selector/ObjSelectorActivity.java @@ -1,5 +1,6 @@ package com.seafile.seadroid2.ui.selector; +import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.MenuItem; @@ -19,6 +20,7 @@ import com.github.panpf.recycler.sticky.StickyItemDecoration; import com.seafile.seadroid2.R; import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.config.AbsLayoutItemType; import com.seafile.seadroid2.context.NavContext; import com.seafile.seadroid2.databinding.ActivitySelectorObjBinding; @@ -68,6 +70,12 @@ public class ObjSelectorActivity extends BaseActivity { private ObjSelectorViewModel viewModel; private Account mAccount; + public static Intent getStartIntent(Context context) { + Intent intent = new Intent(context, ObjSelectorActivity.class); + intent.putExtra(ObjSelectorActivity.DATA_ACCOUNT, SupportAccountManager.getInstance().getCurrentAccount()); + return intent; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -112,7 +120,7 @@ public void handleOnBackPressed() { mStep = STEP_CHOOSE_REPO; } - loadData(); + checkLoginState(); } private void initView() { @@ -146,6 +154,16 @@ public void onClick(View view) { }); } + private void checkLoginState() { + boolean isLogin = SupportAccountManager.getInstance().isLogin(); + if (!isLogin) { + binding.ok.setEnabled(false); + binding.newFolder.setEnabled(false); + } + + loadData(); + } + private void onOkClick() { if (!mNavContext.inRepo()) { ToastUtils.showLong(R.string.choose_a_library); 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 a80a83bdf..7c970136b 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 @@ -85,7 +85,7 @@ public class TabSettingsFragment extends RenameSharePreferenceFragmentCompat { private final Account currentAccount = SupportAccountManager.getInstance().getCurrentAccount(); private SettingsFragmentViewModel viewModel; - private SwitchPreferenceCompat gestureSwitch; +// private SwitchPreferenceCompat gestureSwitch; // album backup private SwitchPreferenceCompat mAlbumBackupSwitch; @@ -142,7 +142,7 @@ public void onResume() { public void onFirstResume() { initPref(); - initGestureConfig(); +// initGestureConfig(); initPrefLiveData(); @@ -203,7 +203,7 @@ private void initAccountPref() { }); } - gestureSwitch = findPreference(getString(R.string.pref_key_gesture_lock)); +// gestureSwitch = findPreference(getString(R.string.pref_key_gesture_lock)); } private void initSignOutPref() { @@ -388,8 +388,8 @@ private void onPreferenceSignOutClicked() { } private void initGestureConfig() { - boolean isChecked = Settings.SETTINGS_GESTURE.queryValue(); - gestureSwitch.setChecked(isChecked); +// boolean isChecked = Settings.SETTINGS_GESTURE.queryValue(); +// gestureSwitch.setChecked(isChecked); // Settings.USER_GESTURE_LOCK_SWITCH.putValue(isChecked); } @@ -411,26 +411,26 @@ public void onChanged(String s) { } }); - Settings.USER_GESTURE_LOCK_SWITCH.observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(Boolean aBoolean) { - - if (aBoolean) { - // inverse checked status - Intent newIntent = new Intent(getActivity(), CreateGesturePasswordActivity.class); - newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - gestureLauncher.launch(newIntent); - - } else { - LockPatternUtils mLockPatternUtils = new LockPatternUtils(getActivity()); - mLockPatternUtils.clearLock(); - - Settings.SETTINGS_GESTURE_LOCK_TIMESTAMP.putValue(0L); - } - - Settings.SETTINGS_GESTURE.putValue(aBoolean); - } - }); +// Settings.USER_GESTURE_LOCK_SWITCH.observe(getViewLifecycleOwner(), new Observer() { +// @Override +// public void onChanged(Boolean aBoolean) { +// +// if (aBoolean) { +// // inverse checked status +// Intent newIntent = new Intent(getActivity(), CreateGesturePasswordActivity.class); +// newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); +// gestureLauncher.launch(newIntent); +// +// } else { +// LockPatternUtils mLockPatternUtils = new LockPatternUtils(getActivity()); +// mLockPatternUtils.clearLock(); +// +// Settings.SETTINGS_GESTURE_LOCK_TIMESTAMP.putValue(0L); +// } +// +// Settings.SETTINGS_GESTURE.putValue(aBoolean); +// } +// }); ////////////////// /// album backup @@ -880,14 +880,14 @@ public void onActivityResult(ActivityResult o) { } }); - private final ActivityResultLauncher gestureLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback() { - @Override - public void onActivityResult(ActivityResult o) { - if (o.getResultCode() != RESULT_OK) { - gestureSwitch.setChecked(false); - } - } - }); +// private final ActivityResultLauncher gestureLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback() { +// @Override +// public void onActivityResult(ActivityResult o) { +// if (o.getResultCode() != RESULT_OK) { +// gestureSwitch.setChecked(false); +// } +// } +// }); private final ActivityResultLauncher multiplePermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback>() { @Override 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 d51578abf..8ce181552 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 @@ -28,23 +28,23 @@ import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.annotation.Unstable; -import com.seafile.seadroid2.framework.datastore.sp.SettingsManager; -import com.seafile.seadroid2.ui.bottomsheetmenu.BottomSheetHelper; -import com.seafile.seadroid2.ui.bottomsheetmenu.BottomSheetMenuFragment; -import com.seafile.seadroid2.ui.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.datastore.sp.SettingsManager; 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.ui.bottomsheetmenu.BottomSheetHelper; +import com.seafile.seadroid2.ui.bottomsheetmenu.BottomSheetMenuFragment; +import com.seafile.seadroid2.ui.bottomsheetmenu.OnMenuClickListener; 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.image_preview2.CarouselImagePreviewActivity; import com.seafile.seadroid2.ui.media.player.exoplayer.CustomExoVideoPlayerActivity; import com.seafile.seadroid2.ui.sdoc.SDocWebViewActivity; import com.seafile.seadroid2.view.TipsViews; @@ -245,7 +245,7 @@ private void open(StarredModel model) { } else if (Utils.isViewableImage(model.obj_name)) { - Intent getIntent = ImagePreviewActivity.startThisFromStarred(requireContext(), model); + Intent getIntent = CarouselImagePreviewActivity.startThisFromStarred(requireContext(), model); imagePreviewActivityLauncher.launch(getIntent); } else if (model.obj_name.endsWith(Constants.Format.DOT_SDOC)) { diff --git a/app/src/main/java/com/seafile/seadroid2/view/HideBottomBehavior.java b/app/src/main/java/com/seafile/seadroid2/view/HideBottomBehavior.java index c4d2e2efa..e06b65166 100644 --- a/app/src/main/java/com/seafile/seadroid2/view/HideBottomBehavior.java +++ b/app/src/main/java/com/seafile/seadroid2/view/HideBottomBehavior.java @@ -6,6 +6,7 @@ import android.widget.LinearLayout; import androidx.annotation.NonNull; +import androidx.constraintlayout.widget.ConstraintLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.view.ViewCompat; diff --git a/app/src/main/java/com/seafile/seadroid2/view/NestedWebView.java b/app/src/main/java/com/seafile/seadroid2/view/NestedWebView.java index 1215cfe65..8c632d55c 100644 --- a/app/src/main/java/com/seafile/seadroid2/view/NestedWebView.java +++ b/app/src/main/java/com/seafile/seadroid2/view/NestedWebView.java @@ -17,13 +17,15 @@ import androidx.core.view.NestedScrollingChildHelper; import androidx.core.view.ViewCompat; +import com.github.lzyzsd.jsbridge.BridgeWebView; + 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 { +public class NestedWebView extends BridgeWebView implements NestedScrollingChild3 { private static final String TAG = "NestedWebView"; private static final int INVALID_POINTER = -1; diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/IWebViewActionStrategy.java b/app/src/main/java/com/seafile/seadroid2/view/webview/IWebViewActionStrategy.java new file mode 100644 index 000000000..654104780 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/IWebViewActionStrategy.java @@ -0,0 +1,9 @@ +package com.seafile.seadroid2.view.webview; + +public interface IWebViewActionStrategy { + + /** + * return null means does not callback + */ + String route(String paramsStr); +} 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 cc877c532..c756bed2b 100644 --- a/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebView.java +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebView.java @@ -2,23 +2,37 @@ import android.annotation.SuppressLint; import android.content.Context; +import android.text.TextUtils; import android.util.AttributeSet; import android.webkit.CookieManager; import android.webkit.WebSettings; -import android.webkit.WebView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.blankj.utilcode.util.GsonUtils; +import com.github.lzyzsd.jsbridge.BridgeHandler; +import com.github.lzyzsd.jsbridge.BridgeWebView; +import com.github.lzyzsd.jsbridge.CallBackFunction; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.config.WebViewActionConstant; +import com.seafile.seadroid2.framework.data.model.WebRouteModel; +import com.seafile.seadroid2.framework.util.SLogs; import com.seafile.seadroid2.view.NestedWebView; +import com.seafile.seadroid2.view.webview.strategy.AppShowToastStrategy; +import com.seafile.seadroid2.view.webview.strategy.AppVersionGetStrategy; +import com.seafile.seadroid2.view.webview.strategy.PageFinishStrategy; +import com.seafile.seadroid2.view.webview.strategy.PageStatusHeightGetStrategy; +import com.seafile.seadroid2.view.webview.strategy.UnsupportedStrategy; + +import java.util.Locale; public class SeaWebView extends NestedWebView { public static final String PATH_ACCOUNT_LOGIN = "accounts/login/"; public static String URL_LOGIN = null; - private final SeaWebViewClient mWebViewClient = new SeaWebViewClient(); + private final SeaWebViewClient mWebViewClient = new SeaWebViewClient(this); public SeaWebView(@NonNull Context context) { super(context); @@ -53,8 +67,6 @@ private void init() { webSettings.setDomStorageEnabled(true); webSettings.setDatabaseEnabled(true); webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); -// webSettings.setAppCacheEnabled(true); -// webSettings.setAppCachePath(getContext().getCacheDir().getAbsolutePath() + "sea_web_cache/"); webSettings.setLoadWithOverviewMode(true); webSettings.setUseWideViewPort(true); webSettings.setBlockNetworkImage(false); @@ -66,6 +78,7 @@ private void init() { webSettings.setJavaScriptCanOpenWindowsAutomatically(false); webSettings.setSupportZoom(false); webSettings.setBuiltInZoomControls(false); + webSettings.setDatabaseEnabled(true); webSettings.setDisplayZoomControls(false); webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); webSettings.setDefaultTextEncodingName("UTF-8"); @@ -75,6 +88,8 @@ private void init() { cookieManager.setAcceptCookie(true); this.setWebViewClient(mWebViewClient); + + registerCommonHandler(); } public void load(String targetUrl) { @@ -84,4 +99,72 @@ public void load(String targetUrl) { public void loadDirectly(String targetUrl) { mWebViewClient.loadWithoutToken(targetUrl, this); } + + private final String jsCallMethodName = "callAndroidFunction"; + + private void registerCommonHandler() { + // + this.registerHandler(jsCallMethodName, new BridgeHandler() { + @Override + public void handler(String data, CallBackFunction function) { + callAndroidFunction(data, function); + } + }); + } + + private void callAndroidFunction(String data, CallBackFunction function) { + if (TextUtils.isEmpty(data) + || data.toLowerCase(Locale.getDefault()).equals("null") + || data.toLowerCase(Locale.getDefault()).equals("undefined")) { + return; + } + + try { + WebRouteModel model = GsonUtils.fromJson(data, WebRouteModel.class); + String result = getString(model); + if (!TextUtils.isEmpty(result)) { + function.onCallBack(result); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + private String getString(WebRouteModel model) { + IWebViewActionStrategy strategy = switch (model.action) { + case WebViewActionConstant.PAGE_FINISH -> new PageFinishStrategy(getContext()); + case WebViewActionConstant.APP_VERSION_GET -> new AppVersionGetStrategy(); + case WebViewActionConstant.APP_TOAST_SHOW -> new AppShowToastStrategy(); + case WebViewActionConstant.PAGE_STATUS_HEIGHT_GET -> new PageStatusHeightGetStrategy(); +// case WebViewActionConstant.PAGE_STATUS_COLOR_SET: +// strategy = new PageStatusColorSetStrategy(getContext()); +// break; + default -> new UnsupportedStrategy(); + }; + + return strategy.route(model.data); + } + + public void callJsFunction(String action, String data) { + callJsFunction(action, data, null); + } + + public void callJsFunction(String action, String data, CallBackFunction callback) { + WebRouteModel model = new WebRouteModel(); + model.action = action; + model.data = data; + model.v = 2; + String g = GsonUtils.toJson(model); + SLogs.d("callJsFunction param => " + g); + callHandler("callJsFunction", g, new CallBackFunction() { + @Override + public void onCallBack(String data) { + SLogs.d("callJsFunction callback data => " + data); + if (callback != null) { + callback.onCallBack(data); + } + } + }); + } } diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java b/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java index b29d1799c..af8a31288 100644 --- a/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java @@ -8,6 +8,8 @@ import android.webkit.WebView; import android.webkit.WebViewClient; +import com.github.lzyzsd.jsbridge.BridgeWebView; +import com.github.lzyzsd.jsbridge.BridgeWebViewClient; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.framework.util.SLogs; @@ -16,7 +18,11 @@ import java.util.HashMap; import java.util.Map; -public class SeaWebViewClient extends WebViewClient { +public class SeaWebViewClient extends BridgeWebViewClient { + public SeaWebViewClient(BridgeWebView webView) { + super(webView); + } + @Override public boolean shouldOverrideUrlLoading(WebView wb, WebResourceRequest request) { String url = request.getUrl().toString(); @@ -71,8 +77,6 @@ public void loadWithoutToken(String targetUrl, WebView wb) { } public void loadWithToken(String targetUrl, WebView wb, boolean isRedirect) { - SLogs.d("targetUrl: " + targetUrl); - mOriginTargetUrl = targetUrl; if (isRedirect) { @@ -83,9 +87,12 @@ public void loadWithToken(String targetUrl, WebView wb, boolean isRedirect) { map.put("Authorization", "Token " + account.token); } - wb.loadUrl(buildUrl(mOriginTargetUrl), map); + String rUrl = buildUrl(mOriginTargetUrl); + SLogs.d("targetUrl: " + rUrl); + wb.loadUrl(rUrl, map); } else { wb.loadUrl(mOriginTargetUrl); + SLogs.d("targetUrl: " + mOriginTargetUrl); } } diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/AppShowToastStrategy.java b/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/AppShowToastStrategy.java new file mode 100644 index 000000000..50e68df2c --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/AppShowToastStrategy.java @@ -0,0 +1,13 @@ +package com.seafile.seadroid2.view.webview.strategy; + +import com.blankj.utilcode.util.ToastUtils; +import com.seafile.seadroid2.view.webview.IWebViewActionStrategy; + + +public class AppShowToastStrategy implements IWebViewActionStrategy { + @Override + public String route(String paramsStr) { + ToastUtils.showLong(paramsStr); + return null; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/AppVersionGetStrategy.java b/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/AppVersionGetStrategy.java new file mode 100644 index 000000000..53eb6ce24 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/AppVersionGetStrategy.java @@ -0,0 +1,11 @@ +package com.seafile.seadroid2.view.webview.strategy; + +import com.blankj.utilcode.util.AppUtils; +import com.seafile.seadroid2.view.webview.IWebViewActionStrategy; + +public class AppVersionGetStrategy implements IWebViewActionStrategy { + @Override + public String route(String paramsStr) { + return AppUtils.getAppVersionName(); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/BaseStrategyModel.java b/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/BaseStrategyModel.java new file mode 100644 index 000000000..2c314c19c --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/BaseStrategyModel.java @@ -0,0 +1,5 @@ +package com.seafile.seadroid2.view.webview.strategy; + +public class BaseStrategyModel { + public String action; +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/PageFinishStrategy.java b/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/PageFinishStrategy.java new file mode 100644 index 000000000..10816d68f --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/PageFinishStrategy.java @@ -0,0 +1,28 @@ +package com.seafile.seadroid2.view.webview.strategy; + +import android.app.Activity; +import android.content.Context; +import android.content.MutableContextWrapper; + +import com.seafile.seadroid2.view.webview.IWebViewActionStrategy; + +public class PageFinishStrategy implements IWebViewActionStrategy { + private final Context context; + + public PageFinishStrategy(Context context) { + this.context = context; + } + + @Override + public String route(String paramsStr) { + if (context != null) { + MutableContextWrapper c = (MutableContextWrapper) context; + if (c.getBaseContext() instanceof Activity activity) { + activity.finish(); + } else { + throw new IllegalArgumentException("Context is not activity"); + } + } + return null; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/PageStatusColorSetStrategy.java b/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/PageStatusColorSetStrategy.java new file mode 100644 index 000000000..14d64ba8d --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/PageStatusColorSetStrategy.java @@ -0,0 +1,37 @@ +package com.seafile.seadroid2.view.webview.strategy; + +import android.app.Activity; +import android.content.Context; +import android.content.MutableContextWrapper; + +import androidx.appcompat.app.AppCompatActivity; + +import com.seafile.seadroid2.annotation.Todo; +import com.seafile.seadroid2.annotation.Unstable; +import com.seafile.seadroid2.view.webview.IWebViewActionStrategy; + + +@Unstable +@Todo +public class PageStatusColorSetStrategy implements IWebViewActionStrategy { + private Context context; + + public PageStatusColorSetStrategy(Context context) { + this.context = context; + } + + @Override + public String route(String paramsStr) { + if (context != null) { + MutableContextWrapper c = (MutableContextWrapper) context; + if (c.getBaseContext() instanceof Activity) { + AppCompatActivity a = (AppCompatActivity) c.getBaseContext(); + //todo + } else { + throw new IllegalArgumentException("Context is not activity"); + } + } + + return null; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/PageStatusHeightGetStrategy.java b/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/PageStatusHeightGetStrategy.java new file mode 100644 index 000000000..316b7f7f4 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/PageStatusHeightGetStrategy.java @@ -0,0 +1,12 @@ +package com.seafile.seadroid2.view.webview.strategy; + +import com.blankj.utilcode.util.BarUtils; +import com.seafile.seadroid2.view.webview.IWebViewActionStrategy; + +public class PageStatusHeightGetStrategy implements IWebViewActionStrategy { + @Override + public String route(String paramsStr) { + //page.status.height.get + return String.valueOf(BarUtils.getStatusBarHeight()); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/UnsupportedStrategy.java b/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/UnsupportedStrategy.java new file mode 100644 index 000000000..12855f130 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/strategy/UnsupportedStrategy.java @@ -0,0 +1,15 @@ +package com.seafile.seadroid2.view.webview.strategy; + + +import com.blankj.utilcode.util.ToastUtils; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.view.webview.IWebViewActionStrategy; + + +public class UnsupportedStrategy implements IWebViewActionStrategy { + @Override + public String route(String paramsStr) { + ToastUtils.showLong(R.string.not_supported); + return null; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/widget/SimpleMarkdownParser.java b/app/src/main/java/com/seafile/seadroid2/widget/SimpleMarkdownParser.java new file mode 100644 index 000000000..e9eae5e9c --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/widget/SimpleMarkdownParser.java @@ -0,0 +1,130 @@ +package com.seafile.seadroid2.widget; + +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.BulletSpan; +import android.text.style.ImageSpan; +import android.text.style.StyleSpan; +import android.text.style.URLSpan; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.target.CustomTarget; +import com.bumptech.glide.request.transition.Transition; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SimpleMarkdownParser { + public static void setMarkdown(TextView textView, String markdown) { + SpannableStringBuilder spannable = parseMarkdown(textView, markdown); + textView.setText(spannable); + } + + private static SpannableStringBuilder parseMarkdown(TextView textView, String markdown) { + SpannableStringBuilder builder = new SpannableStringBuilder(); + + // 1. 解析粗斜体:***富文本*** + applyPattern(builder, "\\*\\*\\*(.*?)\\*\\*\\*", match -> { + builder.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), + match.start(1), match.end(1), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + }); + + // 2. 解析引用:> ***引用*** + applyPattern(builder, "^>\\s?(.*)", match -> { + builder.setSpan(new StyleSpan(Typeface.ITALIC), + match.start(1), match.end(1), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + }); + + // 3. 解析无序列表:* 1 + applyPattern(builder, "^\\*\\s(.*)", match -> { + builder.setSpan(new BulletSpan(20), + match.start(1), match.end(1), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + }); + + // 4. 解析有序列表:1. 1 + applyPattern(builder, "^\\d+\\.\\s(.*)", match -> { + builder.setSpan(new StyleSpan(Typeface.BOLD), + match.start(1), match.end(1), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + }); + + // 5. 解析链接:[https://xxx](xxx) + applyPattern(builder, "\\[(.*?)\\]\\((.*?)\\)", match -> { + builder.setSpan(new URLSpan(match.group(2)), + match.start(1), match.end(1), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + builder.replace(match.start(2), match.end(2), ""); // 删除链接部分 + }); + + // 6. 解析图片:![1.jpg](https://xxx) + applyPattern(builder, "!\\[(.*?)\\]\\((.*?)\\)", match -> { + String imageUrl = match.group(2); + builder.replace(match.start(), match.end(), match.group(1)); // 替换为占位符文字 + int start = match.start(); + int end = start + match.group(1).length(); + + // 异步加载图片 + loadImageAsync(textView, imageUrl, drawable -> { + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM); + builder.setSpan(imageSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + textView.setText(builder); // 更新 TextView + }); + }); + return builder; + } + + private static void applyPattern(SpannableStringBuilder stringBuilder, String regex, MatchHandler handler) { + Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE); + Matcher matcher = pattern.matcher(stringBuilder); + while (matcher.find()) { + handler.handleMatch(matcher); + } + } + + private static void loadImageAsync(TextView textView, String imageUrl, ImageLoaderCallback callback) { + +// Glide.with(textView.getContext()) +// .load(imageUrl) +// .into(new CustomTarget() { +// @Override +// public void onResourceReady(@NonNull Drawable resource, Transition transition) { +// // 设置图片尺寸,适配 TextView +// int maxWidth = textView.getWidth() - textView.getPaddingLeft() - textView.getPaddingRight(); +// int width = Math.min(resource.getIntrinsicWidth(), maxWidth); +// int height = width * resource.getIntrinsicHeight() / resource.getIntrinsicWidth(); +// resource.setBounds(0, 0, width, height); +// +// // 替换文字为图片 +// ImageSpan imageSpan = new ImageSpan(resource, ImageSpan.ALIGN_BOTTOM); +// builder.setSpan(imageSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); +// +// // 更新 TextView 内容 +// textView.setText(builder); +// } +// +// @Override +// public void onLoadCleared(Drawable placeholder) { +// // 可选:当图片加载被取消时的处理 +// } +// }); + } + + interface MatchHandler { + void handleMatch(Matcher match); + } + + interface ImageLoaderCallback { + void onImageLoaded(@NonNull Drawable drawable); + } +} diff --git a/app/src/main/res/drawable/baseline_comment_24.xml b/app/src/main/res/drawable/baseline_comment_24.xml new file mode 100644 index 000000000..52191e667 --- /dev/null +++ b/app/src/main/res/drawable/baseline_comment_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/baseline_delete_24.xml b/app/src/main/res/drawable/baseline_delete_24.xml new file mode 100644 index 000000000..8603e59d2 --- /dev/null +++ b/app/src/main/res/drawable/baseline_delete_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/baseline_info_24.xml b/app/src/main/res/drawable/baseline_info_24.xml new file mode 100644 index 000000000..4ae16d020 --- /dev/null +++ b/app/src/main/res/drawable/baseline_info_24.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_mark_32.xml b/app/src/main/res/drawable/baseline_mark_32.xml new file mode 100644 index 000000000..b24b5151c --- /dev/null +++ b/app/src/main/res/drawable/baseline_mark_32.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/baseline_outline_24.xml b/app/src/main/res/drawable/baseline_outline_24.xml new file mode 100644 index 000000000..e06100424 --- /dev/null +++ b/app/src/main/res/drawable/baseline_outline_24.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/baseline_sdoc_mark_as_resolved_32.xml b/app/src/main/res/drawable/baseline_sdoc_mark_as_resolved_32.xml new file mode 100644 index 000000000..5291f7c0e --- /dev/null +++ b/app/src/main/res/drawable/baseline_sdoc_mark_as_resolved_32.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/baseline_share_24.xml b/app/src/main/res/drawable/baseline_share_24.xml new file mode 100644 index 000000000..ec5a0fac2 --- /dev/null +++ b/app/src/main/res/drawable/baseline_share_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/baseline_star_filled_24.xml b/app/src/main/res/drawable/baseline_star_filled_24.xml new file mode 100644 index 000000000..40437ec7c --- /dev/null +++ b/app/src/main/res/drawable/baseline_star_filled_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/baseline_star_outline_24.xml b/app/src/main/res/drawable/baseline_star_outline_24.xml index 0735fb743..2a5e48606 100644 --- a/app/src/main/res/drawable/baseline_star_outline_24.xml +++ b/app/src/main/res/drawable/baseline_star_outline_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_camera.xml b/app/src/main/res/drawable/ic_camera.xml new file mode 100644 index 000000000..60bd629b1 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/shape_solid_ff_radius_8.xml b/app/src/main/res/drawable/shape_solid_ff_radius_8.xml index f5b590238..7dd98821a 100644 --- a/app/src/main/res/drawable/shape_solid_ff_radius_8.xml +++ b/app/src/main/res/drawable/shape_solid_ff_radius_8.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_carousel_image_preview.xml b/app/src/main/res/layout/activity_carousel_image_preview.xml index 2d2bccad4..bb2ada195 100644 --- a/app/src/main/res/layout/activity_carousel_image_preview.xml +++ b/app/src/main/res/layout/activity_carousel_image_preview.xml @@ -32,6 +32,7 @@ android:layout_height="wrap_content" android:background="@color/bar_background_translucent_color" android:clipToPadding="false" + android:paddingTop="4dp" app:layout_constraintBottom_toTopOf="@+id/gallery_tool_bar" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> @@ -44,7 +45,6 @@ android:gravity="center_horizontal" android:orientation="horizontal" android:paddingTop="4dp" - android:paddingBottom="8dp" android:visibility="visible" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" @@ -54,12 +54,11 @@ android:id="@+id/gallery_download_photo" android:layout_width="36dp" android:layout_height="36dp" - android:layout_marginEnd="12dp" android:foreground="?selectableItemBackground" android:scaleType="centerInside" android:src="@drawable/action_download" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="@+id/gallery_delete_photo" + app:layout_constraintEnd_toStartOf="@+id/gallery_delete_photo" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:tint="@color/material_grey_599" /> @@ -68,10 +67,9 @@ android:id="@+id/gallery_delete_photo" android:layout_width="36dp" android:layout_height="36dp" - android:layout_marginEnd="12dp" android:foreground="?selectableItemBackground" android:scaleType="centerInside" - android:src="@drawable/action_delete" + android:src="@drawable/baseline_delete_24" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/gallery_star_photo" app:layout_constraintStart_toEndOf="@+id/gallery_download_photo" @@ -82,7 +80,6 @@ android:id="@+id/gallery_star_photo" android:layout_width="36dp" android:layout_height="36dp" - android:layout_marginEnd="12dp" android:foreground="?selectableItemBackground" android:scaleType="centerInside" android:src="@drawable/baseline_star_outline_24" @@ -98,7 +95,7 @@ android:layout_height="36dp" android:foreground="?selectableItemBackground" android:scaleType="centerInside" - android:src="@drawable/action_share" + android:src="@drawable/baseline_share_24" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/gallery_star_photo" @@ -106,22 +103,4 @@ app:tint="@color/material_grey_599" /> - - - - \ 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_doc_comment.xml similarity index 87% rename from app/src/main/res/layout/activity_sdoc_comment.xml rename to app/src/main/res/layout/activity_doc_comment.xml index 5a4010868..10e8f772b 100644 --- a/app/src/main/res/layout/activity_sdoc_comment.xml +++ b/app/src/main/res/layout/activity_doc_comment.xml @@ -4,11 +4,12 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="@color/window_background_color" android:orientation="vertical"> + layout="@layout/toolbar_actionbar" /> + android:paddingBottom="32dp" + tools:listitem="@layout/item_file_comment" /> @@ -33,7 +32,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:visibility="gone"> + android:visibility="visible"> - - \ 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 6820b1abd..dd31333f6 100644 --- a/app/src/main/res/layout/activity_sea_webview_pro.xml +++ b/app/src/main/res/layout/activity_sea_webview_pro.xml @@ -36,55 +36,58 @@ - - - - - - - - - - - - + android:layout_height="match_parent" + android:orientation="horizontal"> - + - - + + - + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:foreground="@drawable/selection_control_ripple" + android:scaleType="centerInside" + android:src="@drawable/baseline_comment_24" + app:tint="@color/bottom_bar_icon_tint_color" /> + + diff --git a/app/src/main/res/layout/dialog_sdoc_profile.xml b/app/src/main/res/layout/dialog_file_profile.xml similarity index 79% rename from app/src/main/res/layout/dialog_sdoc_profile.xml rename to app/src/main/res/layout/dialog_file_profile.xml index 1d72c36c2..f508990ee 100644 --- a/app/src/main/res/layout/dialog_sdoc_profile.xml +++ b/app/src/main/res/layout/dialog_file_profile.xml @@ -9,6 +9,16 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> + + diff --git a/app/src/main/res/layout/dialog_sdoc_directory.xml b/app/src/main/res/layout/dialog_sdoc_directory.xml index 52f52f3ab..983488696 100644 --- a/app/src/main/res/layout/dialog_sdoc_directory.xml +++ b/app/src/main/res/layout/dialog_sdoc_directory.xml @@ -19,6 +19,6 @@ style="@style/Widget.FastRecyclerView.Style" android:layout_width="match_parent" android:layout_height="match_parent" - tools:listitem="@layout/item_sdoc_directory" /> + tools:listitem="@layout/item_sdoc_outline" /> diff --git a/app/src/main/res/layout/item_carousel_item_vertical.xml b/app/src/main/res/layout/item_carousel_item_vertical.xml index 5afe7c027..816bdbf7c 100644 --- a/app/src/main/res/layout/item_carousel_item_vertical.xml +++ b/app/src/main/res/layout/item_carousel_item_vertical.xml @@ -1,20 +1,27 @@ - + android:foreground="?attr/selectableItemBackground" + android:paddingVertical="4dp"> - + + + diff --git a/app/src/main/res/layout/item_sdoc_comment.xml b/app/src/main/res/layout/item_file_comment.xml similarity index 56% rename from app/src/main/res/layout/item_sdoc_comment.xml rename to app/src/main/res/layout/item_file_comment.xml index 073f33b27..e0a8d03e3 100644 --- a/app/src/main/res/layout/item_sdoc_comment.xml +++ b/app/src/main/res/layout/item_file_comment.xml @@ -1,9 +1,11 @@ + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ 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_outline.xml similarity index 85% rename from app/src/main/res/layout/item_sdoc_directory.xml rename to app/src/main/res/layout/item_sdoc_outline.xml index 814086dc7..82e25e870 100644 --- a/app/src/main/res/layout/item_sdoc_directory.xml +++ b/app/src/main/res/layout/item_sdoc_outline.xml @@ -1,8 +1,9 @@ diff --git a/app/src/main/res/layout/layout_empty.xml b/app/src/main/res/layout/layout_empty.xml index dce427ed0..2c832f2f4 100644 --- a/app/src/main/res/layout/layout_empty.xml +++ b/app/src/main/res/layout/layout_empty.xml @@ -24,6 +24,6 @@ android:layout_height="wrap_content" android:layout_marginTop="20dp" android:gravity="center_horizontal" - android:text="@string/empty" /> + android:text="@string/empty_data" /> \ 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 index 3ae1608ab..94e99c426 100644 --- a/app/src/main/res/layout/layout_image.xml +++ b/app/src/main/res/layout/layout_image.xml @@ -1,8 +1,7 @@ + android:layout_height="match_parent"> + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/bottom_sheet_op_dirent.xml b/app/src/main/res/menu/bottom_sheet_op_dirent.xml index 88213bf53..1fb7c17f4 100644 --- a/app/src/main/res/menu/bottom_sheet_op_dirent.xml +++ b/app/src/main/res/menu/bottom_sheet_op_dirent.xml @@ -32,7 +32,7 @@ android:title="@string/file_action_rename" /> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_comment_mark_delete.xml b/app/src/main/res/menu/menu_comment_mark_delete.xml new file mode 100644 index 000000000..e64d5eaf5 --- /dev/null +++ b/app/src/main/res/menu/menu_comment_mark_delete.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/transfer_list_multi_choice_menu.xml b/app/src/main/res/menu/transfer_list_multi_choice_menu.xml index 83667981d..69e15b1bc 100644 --- a/app/src/main/res/menu/transfer_list_multi_choice_menu.xml +++ b/app/src/main/res/menu/transfer_list_multi_choice_menu.xml @@ -10,7 +10,7 @@ diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 2313886bd..20f9e2d13 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -91,4 +91,6 @@ #5e5e5e #2c2c2c + + @color/material_grey_919 diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index d689bcc15..0d6b5b538 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -3,6 +3,7 @@ 欢迎使用Seafile 请选择一个账户开始使用 编辑帐户 + 忽略 删除帐户 删除 已删除 @@ -615,6 +616,7 @@ Email: support@seafile.com
开启 跟随系统 暂无 + 暂无数据 设置权限 预览和下载 @@ -631,15 +633,19 @@ Email: support@seafile.com
最后修改 升序 文件夹优先 + 详情 大小 修改者 修改时间 + 文件审核人 文件协作人 文件状态 进行中 审核中 - 完成 + 已完成 已过期 + 标记为已解决 + 你确定想要删除此条内容? diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index e7d6739ab..c67f29fd7 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -96,4 +96,6 @@ #88000000 + + @color/material_grey_103 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 49a3b6e6d..70fdab007 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,6 +5,7 @@ Welcome to Seafile Choose an account to start Edit + Ignore Delete Delete Deleted @@ -647,6 +648,7 @@ On Follow system Empty + Empty data Set permission Preview and download @@ -677,8 +679,10 @@ In review Done Outdated + Tags - + Mark as resolved + Are you sure you want to delete this item? diff --git a/app/src/main/res/xml/prefs_settings.xml b/app/src/main/res/xml/prefs_settings.xml index e22842324..1e396ef02 100644 --- a/app/src/main/res/xml/prefs_settings.xml +++ b/app/src/main/res/xml/prefs_settings.xml @@ -27,6 +27,7 @@ android:summaryOn="@string/gesture_lock_on" android:title="@string/gesture_lock" app:iconSpaceReserved="false" + app:isPreferenceVisible="false" app:useSimpleSummaryProvider="true" /> Date: Mon, 16 Dec 2024 16:11:57 +0800 Subject: [PATCH 04/17] Update CarouselImagePreviewActivity.java --- .../image_preview2/CarouselImagePreviewActivity.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java index 576ece89d..8cc95cd14 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java @@ -132,6 +132,14 @@ protected void onCreate(Bundle savedInstanceState) { if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setTitle(null); + + toolbar.setNavigationOnClickListener(v -> { + if (isDataOperated) { + setResult(RESULT_OK); + } + finish(); + }); + } getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) { From dbc0900ace191df69ca94a395b966136415d06d8 Mon Sep 17 00:00:00 2001 From: zhwanng <48609908+zhwanng@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:53:52 +0800 Subject: [PATCH 05/17] add image-only-preview page --- app/src/main/AndroidManifest.xml | 1 + .../framework/http/BaseOkHttpClient.java | 2 +- .../notification/base/BaseNotification.java | 1 - .../ui/docs_comment/DocsCommentAdapter.java | 10 +- .../ui/media/image_preview/PhotoFragment.java | 68 +++----- .../CarouselImagePreviewActivity.java | 11 +- .../OnlyImagePreviewActivity.java | 156 ++++++++++++++++++ .../seadroid2/ui/repo/RepoQuickAdapter.java | 8 +- .../seadroid2/ui/repo/RepoViewModel.java | 1 - .../ui/repo/{ => vh}/AccountViewHolder.java | 2 +- .../{ => vh}/DirentGalleryViewHolder.java | 3 +- .../repo/{ => vh}/DirentGridViewHolder.java | 3 +- .../ui/repo/{ => vh}/DirentViewHolder.java | 2 +- .../ui/repo/{ => vh}/RepoViewHolder.java | 2 +- .../repo/{ => vh}/UnsupportedViewHolder.java | 2 +- .../seadroid2/ui/search/SearchService.java | 1 + .../layout/activity_only_image_preview.xml | 43 +++++ app/src/main/res/layout/layout_image.xml | 6 +- 18 files changed, 257 insertions(+), 65 deletions(-) create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/OnlyImagePreviewActivity.java rename app/src/main/java/com/seafile/seadroid2/ui/repo/{ => vh}/AccountViewHolder.java (90%) rename app/src/main/java/com/seafile/seadroid2/ui/repo/{ => vh}/DirentGalleryViewHolder.java (80%) rename app/src/main/java/com/seafile/seadroid2/ui/repo/{ => vh}/DirentGridViewHolder.java (80%) rename app/src/main/java/com/seafile/seadroid2/ui/repo/{ => vh}/DirentViewHolder.java (90%) rename app/src/main/java/com/seafile/seadroid2/ui/repo/{ => vh}/RepoViewHolder.java (90%) rename app/src/main/java/com/seafile/seadroid2/ui/repo/{ => vh}/UnsupportedViewHolder.java (90%) create mode 100644 app/src/main/res/layout/activity_only_image_preview.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7d8fb0aa7..673f3f2be 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -212,6 +212,7 @@ + getInterceptorsWithoutToken() { //print log HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC); - loggingInterceptor.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.BASIC); +// loggingInterceptor.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.BASIC); interceptors.add(loggingInterceptor); // interceptors.add(new AddCookiesInterceptor()); diff --git a/app/src/main/java/com/seafile/seadroid2/framework/notification/base/BaseNotification.java b/app/src/main/java/com/seafile/seadroid2/framework/notification/base/BaseNotification.java index 7e1707778..d8f1b9b1d 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/notification/base/BaseNotification.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/notification/base/BaseNotification.java @@ -46,7 +46,6 @@ public BaseNotification(Context context) { hasPermission = i == android.content.pm.PackageManager.PERMISSION_GRANTED; } - init(); } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentAdapter.java index 41014c5fa..a23625181 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentAdapter.java @@ -29,6 +29,7 @@ import com.seafile.seadroid2.framework.util.GlideApp; import com.seafile.seadroid2.framework.util.SLogs; import com.seafile.seadroid2.ui.base.adapter.BaseAdapter; +import com.seafile.seadroid2.ui.media.image_preview2.OnlyImagePreviewActivity; import com.seafile.seadroid2.view.rich_edittext.RichEditText; import com.seafile.seadroid2.widget.SimpleMarkdownParser; import com.yydcdut.markdown.MarkdownConfiguration; @@ -37,6 +38,7 @@ import com.yydcdut.markdown.loader.DefaultLoader; import com.yydcdut.markdown.syntax.text.TextFactory; +import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Objects; @@ -130,11 +132,9 @@ public void appendImageToFlex(FlexboxLayout parent, 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); + + OnlyImagePreviewActivity.startThis(getContext(), url); + } }); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/PhotoFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/PhotoFragment.java index 22fe23c69..dd865c41c 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/PhotoFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/PhotoFragment.java @@ -2,6 +2,7 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -37,6 +38,7 @@ public class PhotoFragment extends BaseFragment { private Account account; private String repoId, repoName, fullPath; + private String imageUrl; private OnPhotoTapListener onPhotoTapListener; private FragmentPhotoViewBinding binding; @@ -46,6 +48,14 @@ public void setOnPhotoTapListener(OnPhotoTapListener onPhotoTapListener) { this.onPhotoTapListener = onPhotoTapListener; } + public static PhotoFragment newInstance(String url) { + Bundle args = new Bundle(); + args.putString("image_url", url); + PhotoFragment fragment = new PhotoFragment(); + fragment.setArguments(args); + return fragment; + } + public static PhotoFragment newInstance(String repoId, String repoName, String fullPath) { Bundle args = new Bundle(); @@ -80,13 +90,14 @@ public void onCreate(@Nullable Bundle savedInstanceState) { return; } - if (!args.containsKey("repoId")) { - throw new IllegalStateException("repoId is null"); - } - repoId = args.getString("repoId"); repoName = args.getString("repoName"); fullPath = args.getString("fullPath"); + imageUrl = args.getString("image_url"); + + if (TextUtils.isEmpty(repoId) && TextUtils.isEmpty(imageUrl)) { + throw new IllegalStateException("the args is invalid"); + } } @Nullable @@ -113,49 +124,14 @@ public void onPhotoTap(ImageView view, float x, float y) { } }); - ProgressBar progressBar = view.findViewById(R.id.progress_bar); - - File file = DataManager.getLocalRepoFile(account, repoId, repoName, fullPath); - if (file.exists()) { - progressBar.setVisibility(View.GONE); - - GlideApp.with(requireContext()) - .load(file) - .into(binding.photoView); - return; - } - - String url = getUrl(); - if (url == null) { - binding.photoView.setImageResource(R.drawable.icon_image_error_filled); - return; - } - - RequestOptions opt = new RequestOptions() - .skipMemoryCache(true) - .error(R.drawable.icon_image_error_filled) - .diskCacheStrategy(DiskCacheStrategy.NONE); - GlideApp.with(requireContext()) - .load(url) - .apply(opt) - .fitCenter() - .listener(new RequestListener() { - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { - binding.progressBar.setVisibility(View.GONE); - return false; - } - - @Override - public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { - binding.progressBar.setVisibility(View.GONE); - return false; - } - }) - .into(binding.photoView); + loadImage(); } private void loadImage() { + if (!TextUtils.isEmpty(imageUrl)) { + glideLoad(imageUrl); + return; + } File file = DataManager.getLocalRepoFile(account, repoId, repoName, fullPath); if (file.exists()) { @@ -173,6 +149,10 @@ private void loadImage() { return; } + glideLoad(url); + } + + private void glideLoad(String url) { RequestOptions opt = new RequestOptions() .skipMemoryCache(true) .error(R.drawable.icon_image_error_filled) diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java index 8cc95cd14..0a4f0bb6f 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java @@ -68,6 +68,13 @@ public class CarouselImagePreviewActivity extends BaseActivityWithVM { + private ActivityOnlyImagePreviewBinding binding; + private ViewPager2Adapter adapter; + private List imageUrls; + private int position; + + public static void startThis(Context context, String url) { + Intent intent = new Intent(context, OnlyImagePreviewActivity.class); + intent.putStringArrayListExtra("image_urls", CollectionUtils.newArrayList(url)); + intent.putExtra("position", 0); + context.startActivity(intent); + } + + public static void startThis(Context context, ArrayList urls, int position) { + Intent intent = new Intent(context, OnlyImagePreviewActivity.class); + intent.putStringArrayListExtra("image_urls", urls); + intent.putExtra("position", position); + context.startActivity(intent); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivityOnlyImagePreviewBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + BarUtils.setNavBarVisibility(this, false); + BarUtils.setStatusBarVisibility(this, false); + + initData(); + + initView(); + initViewModel(); + + getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + finish(); + } + }); + } + + @Override + protected void onPostCreate(@Nullable Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + + notifyFragmentList(); + } + + private void initData() { + if (getIntent() == null) { + throw new IllegalArgumentException("Intent is null"); + } + + imageUrls = getIntent().getStringArrayListExtra("image_urls"); + position = getIntent().getIntExtra("position", 0); + } + + private void initView() { + + } + + private void initViewModel() { + getViewModel().getRefreshLiveData().observe(this, new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + if (aBoolean) { + showProgressDialog(); + } else { + dismissProgressDialog(); + } + } + }); + } + + private void notifyFragmentList() { + if (CollectionUtils.isEmpty(imageUrls)) { + return; + } + + adapter = new ViewPager2Adapter(this); + List fragments = new ArrayList<>(); + for (String url : imageUrls) { + PhotoFragment photoFragment = PhotoFragment.newInstance(url); + photoFragment.setOnPhotoTapListener((view, x, y) -> hideOrShowToolBar()); + fragments.add(photoFragment); + } + + adapter.addFragments(fragments); + + binding.pager.setAdapter(adapter); +// binding.pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { +// @Override +// public void onPageSelected(int position) { +// super.onPageSelected(position); +// +// String fs = String.format(Locale.ROOT, "%d/%d", (position + 1), direntList.size()); +// binding.galleryPageIndex.setText(fs); +// +// DirentModel model = direntList.get(position); +// binding.galleryPageName.setText(model.name); +// } +// }); + + binding.pager.setCurrentItem(position); + + } + + private boolean showToolBar = false; + + private void hideOrShowToolBar() { +// binding.galleryToolBar.setVisibility(!showToolBar ? View.VISIBLE : View.GONE); +// binding.pageIndexContainer.setVisibility(!showToolBar ? View.VISIBLE : View.GONE); + showToolBar = !showToolBar; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java index 5f0024f2f..929eee320 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java @@ -1,7 +1,6 @@ package com.seafile.seadroid2.ui.repo; import android.content.Context; -import android.content.res.ColorStateList; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.TextUtils; @@ -44,8 +43,13 @@ import com.seafile.seadroid2.framework.util.GlideApp; import com.seafile.seadroid2.framework.util.Utils; import com.seafile.seadroid2.ui.base.adapter.BaseMultiAdapter; +import com.seafile.seadroid2.ui.repo.vh.AccountViewHolder; +import com.seafile.seadroid2.ui.repo.vh.DirentGalleryViewHolder; +import com.seafile.seadroid2.ui.repo.vh.DirentGridViewHolder; +import com.seafile.seadroid2.ui.repo.vh.DirentViewHolder; +import com.seafile.seadroid2.ui.repo.vh.RepoViewHolder; +import com.seafile.seadroid2.ui.repo.vh.UnsupportedViewHolder; import com.seafile.seadroid2.ui.viewholder.GroupItemViewHolder; -import com.seafile.seadroid2.widget.AnimatedStateListDrawableCompatUtils; import java.util.ArrayList; import java.util.Collections; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java index 22621e7c5..e80886d16 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java @@ -308,7 +308,6 @@ public MutableLiveData> getMenuItemListLiveData() { return _menuItemListLiveData; } - public void getRepoModelAndPermissionEntity(String repoId, boolean isForce, Consumer consumer) { Single>> r = getRepoModelAndAllPermissionSingle(repoId, isForce); addSingleDisposable(r, new Consumer>>() { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/AccountViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/vh/AccountViewHolder.java similarity index 90% rename from app/src/main/java/com/seafile/seadroid2/ui/repo/AccountViewHolder.java rename to app/src/main/java/com/seafile/seadroid2/ui/repo/vh/AccountViewHolder.java index 1fb894211..c6ef02bb0 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/AccountViewHolder.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/vh/AccountViewHolder.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.repo; +package com.seafile.seadroid2.ui.repo.vh; import androidx.annotation.NonNull; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/DirentGalleryViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/vh/DirentGalleryViewHolder.java similarity index 80% rename from app/src/main/java/com/seafile/seadroid2/ui/repo/DirentGalleryViewHolder.java rename to app/src/main/java/com/seafile/seadroid2/ui/repo/vh/DirentGalleryViewHolder.java index dddccb62d..8d74de816 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/DirentGalleryViewHolder.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/vh/DirentGalleryViewHolder.java @@ -1,9 +1,8 @@ -package com.seafile.seadroid2.ui.repo; +package com.seafile.seadroid2.ui.repo.vh; import androidx.annotation.NonNull; import com.seafile.seadroid2.databinding.ItemDirentGalleryBinding; -import com.seafile.seadroid2.databinding.ItemDirentGridBinding; import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; public class DirentGalleryViewHolder extends BaseViewHolder { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/DirentGridViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/vh/DirentGridViewHolder.java similarity index 80% rename from app/src/main/java/com/seafile/seadroid2/ui/repo/DirentGridViewHolder.java rename to app/src/main/java/com/seafile/seadroid2/ui/repo/vh/DirentGridViewHolder.java index 0ce06f4c5..7fb9c6764 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/DirentGridViewHolder.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/vh/DirentGridViewHolder.java @@ -1,8 +1,7 @@ -package com.seafile.seadroid2.ui.repo; +package com.seafile.seadroid2.ui.repo.vh; import androidx.annotation.NonNull; -import com.seafile.seadroid2.databinding.ItemDirentBinding; import com.seafile.seadroid2.databinding.ItemDirentGridBinding; import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/DirentViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/vh/DirentViewHolder.java similarity index 90% rename from app/src/main/java/com/seafile/seadroid2/ui/repo/DirentViewHolder.java rename to app/src/main/java/com/seafile/seadroid2/ui/repo/vh/DirentViewHolder.java index 11f360458..6a8bceb41 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/DirentViewHolder.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/vh/DirentViewHolder.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.repo; +package com.seafile.seadroid2.ui.repo.vh; import androidx.annotation.NonNull; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/vh/RepoViewHolder.java similarity index 90% rename from app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewHolder.java rename to app/src/main/java/com/seafile/seadroid2/ui/repo/vh/RepoViewHolder.java index a754fa84e..8645bc86b 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewHolder.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/vh/RepoViewHolder.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.repo; +package com.seafile.seadroid2.ui.repo.vh; import androidx.annotation.NonNull; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/UnsupportedViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/vh/UnsupportedViewHolder.java similarity index 90% rename from app/src/main/java/com/seafile/seadroid2/ui/repo/UnsupportedViewHolder.java rename to app/src/main/java/com/seafile/seadroid2/ui/repo/vh/UnsupportedViewHolder.java index 0481652b6..f99dd89ca 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/UnsupportedViewHolder.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/vh/UnsupportedViewHolder.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.repo; +package com.seafile.seadroid2.ui.repo.vh; import androidx.annotation.NonNull; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/search/SearchService.java b/app/src/main/java/com/seafile/seadroid2/ui/search/SearchService.java index e05461f7b..5b3387e79 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/search/SearchService.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/search/SearchService.java @@ -12,6 +12,7 @@ public interface SearchService { + //search_ftypes @GET("api2/search/") Single search(@Query("search_repo") String repoId, @Query("q") String q, diff --git a/app/src/main/res/layout/activity_only_image_preview.xml b/app/src/main/res/layout/activity_only_image_preview.xml new file mode 100644 index 000000000..6e9572578 --- /dev/null +++ b/app/src/main/res/layout/activity_only_image_preview.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + \ 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 index 94e99c426..365f7b5b1 100644 --- a/app/src/main/res/layout/layout_image.xml +++ b/app/src/main/res/layout/layout_image.xml @@ -1,12 +1,14 @@ - + android:src="@drawable/shape_solid_grey100_radius_8" + app:shapeAppearance="@style/ShapeCorner4Style" /> \ No newline at end of file From 737f80dfc8e6f9ba696816455648c05dd333e102 Mon Sep 17 00:00:00 2001 From: zhwanng <48609908+zhwanng@users.noreply.github.com> Date: Tue, 17 Dec 2024 16:42:42 +0800 Subject: [PATCH 06/17] update shape --- .../ui/docs_comment/DocsCommentAdapter.java | 20 ++++++++----------- app/src/main/res/layout/layout_image.xml | 1 + 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentAdapter.java index a23625181..e53b12565 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentAdapter.java @@ -118,17 +118,6 @@ protected DocsCommentViewHolder onCreateViewHolder(@NonNull Context context, @No 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); - - GlideApp.with(getContext()) - .load(url) - .apply(GlideLoadConfig.getOptions()) - .into(fileBinding.uploadImage); - fileBinding.uploadImage.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -138,8 +127,15 @@ public void onClick(View v) { } }); + GlideApp.with(getContext()) + .load(url) + .apply(GlideLoadConfig.getOptions()) + .into(fileBinding.uploadImage); + + FlexboxLayout.LayoutParams f = new FlexboxLayout.LayoutParams(IMAGE_WIDTH, IMAGE_WIDTH); + f.setMargins(DP_4, DP_4, DP_4, DP_4); - parent.addView(fileBinding.getRoot()); + parent.addView(fileBinding.getRoot(),f); } diff --git a/app/src/main/res/layout/layout_image.xml b/app/src/main/res/layout/layout_image.xml index 365f7b5b1..9ce235ec1 100644 --- a/app/src/main/res/layout/layout_image.xml +++ b/app/src/main/res/layout/layout_image.xml @@ -8,6 +8,7 @@ android:id="@+id/upload_image" android:layout_width="match_parent" android:layout_height="match_parent" + android:scaleType="centerCrop" android:src="@drawable/shape_solid_grey100_radius_8" app:shapeAppearance="@style/ShapeCorner4Style" /> From 1987d30ad754f5e03b4f344acc47165d5e354904 Mon Sep 17 00:00:00 2001 From: zhwanng <48609908+zhwanng@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:23:30 +0800 Subject: [PATCH 07/17] remove useless code --- .../ui/docs_comment/DocsCommentAdapter.java | 1 - .../docs_comment/DocsCommentUserAdapter.java | 2 ++ .../DocsCommentUserViewHolder.java | 2 ++ .../ui/docs_comment/DocsCommentsActivity.java | 9 +++---- .../ui/sdoc/DocsCommentViewModel.java | 4 ---- .../sdoc/outline/SDocOutlineRemoteDialog.java | 24 +------------------ 6 files changed, 8 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentAdapter.java index e53b12565..b165be586 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentAdapter.java @@ -93,7 +93,6 @@ protected void onBindViewHolder(@NonNull DocsCommentViewHolder holder, int i, @N } - //![](https://dev.seafile.com/e-Hek4ng6iRbCw7h-bXsc6jA.png)\n是是是\n private void addViews(DocsCommentViewHolder holder, DocsCommentModel model) { int index = 0; for (RichEditText.RichContentModel commentModel : model.commentList) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentUserAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentUserAdapter.java index 52dbed7f7..7fa75c505 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentUserAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentUserAdapter.java @@ -11,10 +11,12 @@ import com.bumptech.glide.Glide; import com.seafile.seadroid2.R; +import com.seafile.seadroid2.annotation.Todo; import com.seafile.seadroid2.databinding.ItemUserAvatarBinding; import com.seafile.seadroid2.framework.data.model.user.UserModel; import com.seafile.seadroid2.ui.base.adapter.BaseAdapter; +@Todo public class DocsCommentUserAdapter extends BaseAdapter { @Override protected void onBindViewHolder(@NonNull DocsCommentUserViewHolder holder, int i, @Nullable UserModel model) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentUserViewHolder.java b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentUserViewHolder.java index 9251ed842..bdf791ba8 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentUserViewHolder.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentUserViewHolder.java @@ -2,9 +2,11 @@ import androidx.annotation.NonNull; +import com.seafile.seadroid2.annotation.Todo; import com.seafile.seadroid2.databinding.ItemUserAvatarBinding; import com.seafile.seadroid2.ui.base.viewholder.BaseViewHolder; +@Todo public class DocsCommentUserViewHolder extends BaseViewHolder { public ItemUserAvatarBinding binding; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentsActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentsActivity.java index ae718ec94..1df2df0b9 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentsActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentsActivity.java @@ -283,15 +283,11 @@ private void uploadFile(Uri o) { binding.richEditText.insertImage(o); ContentResolver contentResolver = getContentResolver(); -// https://dev.seafile.com/seafhttp/repos/bc1db6db-afa8-4e85-8479-6eb3f50f3d1c/files/images/sdoc/e4c22460-e99d-4687-9081-1f30d156625d/image-UFB28ta7TVOskNE0uAZ_Ww.jpeg/?op=download -// https://dev.seafile.com/seahub/lib/bc1db6db-afa8-4e85-8479-6eb3f50f3d1c/file/images/sdoc/e4c22460-e99d-4687-9081-1f30d156625d/image-UFB28ta7TVOskNE0uAZ_Ww.jpeg -// https://dev.seafile.com/seafhttp/files/73372487-3bb8-4f1d-925e-6e21bb72305c/image-UFB28ta7TVOskNE0uAZ_Ww.jpeg -// https://dev.seafile.com/seafhttp/files/b1d6e2d7-8b28-4a8e-af93-f3ca70bf51ea/image-UFB28ta7TVOskNE0uAZ_Ww.jpeg -// https://dev.seafile.com/seahub/api/v2.1/seadoc/download-image/e4c22460-e99d-4687-9081-1f30d156625d/image-UFB28ta7TVOskNE0uAZ_Ww.jpeg + //upload file getViewModel().uploadFile(contentResolver, o, pageOptionsModel.docUuid, pageOptionsModel.seadocAccessToken, new Consumer() { @Override - public void accept(String absUrl) throws Exception { + public void accept(String absUrl) { binding.richEditText.updateUploadState(o.toString(), absUrl); } }, new Consumer() { @@ -325,6 +321,7 @@ private void submitData() { return; } + // 0 is root comment getViewModel().postComment(pageOptionsModel, sb.toString(), "0"); } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentViewModel.java index 5d477ba10..960c0a30f 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentViewModel.java @@ -354,7 +354,6 @@ private List getImageMdResult(String s) { models.add(m); } -//![](https://dev.seafile.com/seahub/api/v2.1/seadoc/download-image/e4c22460-e99d-4687-9081-1f30d156625d/image-eEy2ClPsSV6nlef3v1FW-g.jpg) String sss = org.apache.commons.lang3.StringUtils.substring(s, start + 4, end); String content = sss.trim();// URLEncoder.encode(,"utf-8"); if (!TextUtils.isEmpty(content) && !TextUtils.equals("null", content.toLowerCase(Locale.getDefault()))) { @@ -439,7 +438,6 @@ public void accept(Throwable throwable) { } - //{"relative_path":["/image-UFB28ta7TVOskNE0uAZ_Ww.jpeg"]} public void uploadFile(ContentResolver contentResolver, Uri uri, String docUid, String token, Consumer consumer, Consumer errorCallBack) { String fileName = ContentResolvers.getFileNameFromUri(contentResolver, uri); @@ -463,8 +461,6 @@ public void accept(DocsUploadResultModel resultModel) throws Exception { String sUrl = HttpIO.getCurrentInstance().getServerUrl(); String absUrl = Utils.pathJoin(sUrl, "api", "v2.1", "seadoc", "download-image", docUid, sName); - //https://dev.seafile.com/seahub/api/v2.1/seadoc/download-image/e4c22460-e99d-4687-9081-1f30d156625d/+:: - consumer.accept(absUrl); } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineRemoteDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineRemoteDialog.java index cb0c0e19f..1f46cc5a7 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineRemoteDialog.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineRemoteDialog.java @@ -66,29 +66,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c @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; + return new BottomSheetDialog(requireContext()); } @Override From 94ad3b7ad7d5835205da9b01854c01b998aea5f7 Mon Sep 17 00:00:00 2001 From: zhwanng <48609908+zhwanng@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:35:23 +0800 Subject: [PATCH 08/17] Update SDocOutlineRemoteDialog.java --- .../seadroid2/ui/sdoc/outline/SDocOutlineRemoteDialog.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineRemoteDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineRemoteDialog.java index 1f46cc5a7..7e6cd8196 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineRemoteDialog.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineRemoteDialog.java @@ -82,7 +82,6 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat @Override public void onClick(@NonNull BaseQuickAdapter baseQuickAdapter, @NonNull View view, int i) { OutlineItemModel outlineItemModel = adapter.getItems().get(i); - ToastUtils.showLong(outlineItemModel.text); dismiss(); } }); From 8c566ca221085ed7df6a97fb7dde7075197ba0c6 Mon Sep 17 00:00:00 2001 From: zhwanng <48609908+zhwanng@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:10:48 +0800 Subject: [PATCH 09/17] Update SeaWebViewClient.java --- .../seadroid2/view/webview/SeaWebViewClient.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java b/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java index af8a31288..b6da31b3f 100644 --- a/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java @@ -102,19 +102,6 @@ private String buildUrl(String url) { } return Token2SessionConverts.buildUrl(url); - -// // Optimise the code here: -// // The expiry time of each cookie field is not consistent, -// // and it is possible to be redirected to the login page when opening the link -// String cookieStr = SupportCookieManager.getCookie(url); -// String u = url; -// if (TextUtils.isEmpty(cookieStr)) { -// u = Token2SessionConverts.buildUrl(url); -// Log.d(getClass().getSimpleName(), "link redirect to -> " + u); -// } else { -// Log.d(getClass().getSimpleName(), "link to -> " + u); -// } -// return u; } } From 14462c7e8beaf151de9a15bb03df9c670aea3569 Mon Sep 17 00:00:00 2001 From: zhwanng <48609908+zhwanng@users.noreply.github.com> Date: Wed, 25 Dec 2024 18:34:03 +0800 Subject: [PATCH 10/17] Improve ImagePreview page - fix other issues --- app/src/main/AndroidManifest.xml | 1 - .../com/seafile/seadroid2/SeafException.java | 1 + .../seafile/seadroid2/account/Account.java | 2 +- .../seadroid2/account/AccountUtils.java | 9 +- .../seadroid2/config/GlideLoadConfig.java | 24 ++ .../seafile/seadroid2/config/OriGlideUrl.java | 38 +++ .../seafile/seadroid2/context/NavContext.java | 66 ++++ .../data/db/dao/FileTransferDAO.java | 7 +- .../data/db/entities/FileTransferEntity.java | 50 +-- .../framework/data/model/ContextModel.java | 13 + .../model/sdoc/FileProfileConfigModel.java | 18 +- .../data/model/sdoc/MetadataConfigModel.java | 4 + .../data/model/sdoc/SDocPageOptionsModel.java | 182 +--------- .../framework/datastore/DataStoreKeys.java | 2 + .../file_monitor/FileSyncService.java | 1 + .../seadroid2/framework/util/GlideCache.java | 3 +- .../framework/util/ThumbnailUtils.java | 11 + .../worker/BackgroundJobManagerImpl.java | 3 + .../framework/worker/TransferEvent.java | 7 +- .../worker/download/BaseDownloadWorker.java | 2 +- .../worker/upload/BaseUploadWorker.java | 113 ++++--- .../upload/FolderBackupScannerWorker.java | 6 +- .../upload/MediaBackupScannerWorker.java | 3 +- .../upload/UploadFileManuallyWorker.java | 2 +- .../UploadFolderFileAutomaticallyWorker.java | 71 +++- .../UploadMediaFileAutomaticallyWorker.java | 64 +++- .../FileTransferProgressListener.java | 18 +- .../ContextStackPreferenceHelper.java | 35 ++ .../ui/camera_upload/CameraUploadManager.java | 3 + .../DocsCommentViewModel.java | 157 +-------- .../ui/docs_comment/DocsCommentsActivity.java | 10 - .../seadroid2/ui/main/MainActivity.java | 5 +- .../seadroid2/ui/main/MainViewModel.java | 1 + .../seadroid2/ui/media/PhotoViewModel.java | 259 +++++++++++++++ .../image_preview/ImagePreviewActivity.java | 314 ------------------ .../image_preview/ImagePreviewViewModel.java | 30 +- .../ui/media/image_preview/PhotoFragment.java | 244 ++++++++++---- .../media/image_preview2/CarouselAdapter.java | 57 ++-- .../CarouselImagePreviewActivity.java | 143 ++++++-- .../image_preview2/PagerSnapBinders.java | 48 --- .../seadroid2/ui/repo/RepoQuickAdapter.java | 57 ++-- .../seadroid2/ui/repo/RepoQuickFragment.java | 50 ++- .../seadroid2/ui/repo/RepoViewModel.java | 1 + .../seadroid2/ui/repo/ScrollState.java | 8 + .../seadroid2/ui/sdoc/DocsCommentService.java | 2 +- .../seadroid2/ui/sdoc/SDocViewModel.java | 201 +++++++++++ .../ui/sdoc/SDocWebViewActivity.java | 59 +++- .../sdoc/outline/SDocOutlineRemoteDialog.java | 7 +- .../ui/settings/SettingsFragment.java | 4 +- .../ui/settings/TabSettingsFragment.java | 39 ++- .../ui/transfer_list/TransferListAdapter.java | 8 +- .../transfer_list/TransferListFragment.java | 8 - .../ui/transfer_list/UploadListFragment.java | 3 +- .../snap_recyclerview}/GravitySnapHelper.java | 17 +- .../GravitySnapRecyclerView.java | 150 +++++++++ .../OrientationAwareRecyclerView.java | 81 +++++ .../view/webview/OnWebPageListener.java | 7 + .../seadroid2/view/webview/SeaWebView.java | 6 + .../view/webview/SeaWebViewClient.java | 15 + .../main/res/drawable/baseline_delete_24.xml | 19 +- .../res/drawable/baseline_download_24.xml | 15 + .../main/res/drawable/baseline_share_24.xml | 24 +- .../res/drawable/baseline_star_filled_24.xml | 9 - .../res/drawable/baseline_star_outline_24.xml | 9 - .../main/res/drawable/baseline_starred_32.xml | 9 - .../drawable/baseline_starred_filled_24.xml | 9 + .../drawable/baseline_starred_filled_32.xml | 9 + .../drawable/baseline_starred_outline_24.xml | 9 + .../drawable/shape_solid_grey100_radius_4.xml | 5 + .../activity_carousel_image_preview.xml | 13 +- .../res/layout/activity_image_preview.xml | 8 +- .../res/layout/gallery_activity_layout.xml | 131 -------- .../main/res/menu/bottom_navigation_menu.xml | 2 +- .../menu/bottom_sheet_op_transfer_list.xml | 9 +- .../main/res/menu/bottom_sheet_unstarred.xml | 2 +- app/src/main/res/values/attrs.xml | 15 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/values/styles.xml | 4 + 78 files changed, 1775 insertions(+), 1278 deletions(-) create mode 100644 app/src/main/java/com/seafile/seadroid2/config/OriGlideUrl.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/data/model/ContextModel.java create mode 100644 app/src/main/java/com/seafile/seadroid2/framework/util/ThumbnailUtils.java create mode 100644 app/src/main/java/com/seafile/seadroid2/preferences/ContextStackPreferenceHelper.java rename app/src/main/java/com/seafile/seadroid2/ui/{sdoc => docs_comment}/DocsCommentViewModel.java (68%) create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/media/PhotoViewModel.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewActivity.java delete mode 100644 app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/PagerSnapBinders.java create mode 100644 app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocViewModel.java rename app/src/main/java/com/seafile/seadroid2/{ui/media/image_preview2 => view/snap_recyclerview}/GravitySnapHelper.java (98%) create mode 100644 app/src/main/java/com/seafile/seadroid2/view/snap_recyclerview/GravitySnapRecyclerView.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/snap_recyclerview/OrientationAwareRecyclerView.java create mode 100644 app/src/main/java/com/seafile/seadroid2/view/webview/OnWebPageListener.java create mode 100644 app/src/main/res/drawable/baseline_download_24.xml delete mode 100644 app/src/main/res/drawable/baseline_star_filled_24.xml delete mode 100644 app/src/main/res/drawable/baseline_star_outline_24.xml delete mode 100644 app/src/main/res/drawable/baseline_starred_32.xml create mode 100644 app/src/main/res/drawable/baseline_starred_filled_24.xml create mode 100644 app/src/main/res/drawable/baseline_starred_filled_32.xml create mode 100644 app/src/main/res/drawable/baseline_starred_outline_24.xml create mode 100644 app/src/main/res/drawable/shape_solid_grey100_radius_4.xml delete mode 100644 app/src/main/res/layout/gallery_activity_layout.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 673f3f2be..7bb73c189 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -204,7 +204,6 @@ - diff --git a/app/src/main/java/com/seafile/seadroid2/SeafException.java b/app/src/main/java/com/seafile/seadroid2/SeafException.java index 75c7c2d77..15f12c0fe 100644 --- a/app/src/main/java/com/seafile/seadroid2/SeafException.java +++ b/app/src/main/java/com/seafile/seadroid2/SeafException.java @@ -34,6 +34,7 @@ public class SeafException extends Exception { public static final SeafException notFoundException = new SeafException(404, "Not found"); public static final SeafException notFoundUserException = new SeafException(20, "Not logged in"); public static final SeafException notLoggedInException = new SeafException(21, "Not logged in"); + public static final SeafException transferFileException = new SeafException(22, "The file transfer is abnormal"); public static final SeafException OUT_OF_QUOTA = new SeafException(HTTP_ABOVE_QUOTA, SeadroidApplication.getAppContext().getString(R.string.above_quota)); public static final SeafException REQUEST_EXCEPTION = new SeafException(400, "Request Failed"); diff --git a/app/src/main/java/com/seafile/seadroid2/account/Account.java b/app/src/main/java/com/seafile/seadroid2/account/Account.java index cc176c260..215fa791a 100644 --- a/app/src/main/java/com/seafile/seadroid2/account/Account.java +++ b/app/src/main/java/com/seafile/seadroid2/account/Account.java @@ -103,7 +103,7 @@ public String getSpaceUsed() { /** * in fact, the value should be less than 0. - * however, in some cases, it may be 0, and should also return unlimited. + * however, in some cases, it may be 0, and should also return non-limit. * even if the non-limit is returned, App does not need to verify "Out of quota" status. * and the "Out of quota" error will be returned in the file upload result. */ diff --git a/app/src/main/java/com/seafile/seadroid2/account/AccountUtils.java b/app/src/main/java/com/seafile/seadroid2/account/AccountUtils.java index 439fba31a..4df312d1a 100644 --- a/app/src/main/java/com/seafile/seadroid2/account/AccountUtils.java +++ b/app/src/main/java/com/seafile/seadroid2/account/AccountUtils.java @@ -9,6 +9,7 @@ import com.seafile.seadroid2.framework.http.HttpIO; import com.seafile.seadroid2.framework.util.SLogs; import com.seafile.seadroid2.framework.worker.BackgroundJobManagerImpl; +import com.seafile.seadroid2.preferences.ContextStackPreferenceHelper; import com.seafile.seadroid2.preferences.Settings; import com.seafile.seadroid2.ssl.CertsManager; import com.seafile.seadroid2.ui.camera_upload.CameraUploadManager; @@ -17,11 +18,14 @@ public class AccountUtils { public static void logout(Account account) { - // turn off the gesture lock anyway + // turn off the gesture lock GestureLockSharePreferenceHelper.disable(); Settings.initUserSettings(); + // clear + ContextStackPreferenceHelper.clearStack(); + NotificationUtils.cancelAll(); // sign out operations @@ -57,6 +61,9 @@ public static void switchAccount(Account account) { NotificationUtils.cancelAll(); + // clear + ContextStackPreferenceHelper.clearStack(); + // Settings.initUserSettings(); diff --git a/app/src/main/java/com/seafile/seadroid2/config/GlideLoadConfig.java b/app/src/main/java/com/seafile/seadroid2/config/GlideLoadConfig.java index 785a181a8..c8cf72948 100644 --- a/app/src/main/java/com/seafile/seadroid2/config/GlideLoadConfig.java +++ b/app/src/main/java/com/seafile/seadroid2/config/GlideLoadConfig.java @@ -1,5 +1,8 @@ package com.seafile.seadroid2.config; +import androidx.annotation.DrawableRes; + +import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.model.GlideUrl; import com.bumptech.glide.load.model.LazyHeaders; import com.bumptech.glide.request.RequestOptions; @@ -38,12 +41,33 @@ public static RequestOptions getAvatarOptions() { .override(WidgetUtils.getThumbnailWidth(), WidgetUtils.getThumbnailWidth()); } + private final static RequestOptions _cacheableThumbnailOptions = new RequestOptions() + .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) + .error(R.drawable.icon_image_error_filled) + .override(128); + + /** + * Get cacheable thumbnail options, width and height are both 128 + */ + public static RequestOptions getCacheableThumbnailOptions() { + return _cacheableThumbnailOptions; + } + public static RequestOptions getOptions() { return new RequestOptions() + .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) .fallback(R.drawable.file_image) .placeholder(R.drawable.file_image); } + + public static RequestOptions getCustomDrawableOptions(@DrawableRes int resId) { + return new RequestOptions() + .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) + .fallback(resId) + .placeholder(resId); + } + public static RequestOptions getOptions(String key) { return new RequestOptions() .fallback(R.drawable.file_image) diff --git a/app/src/main/java/com/seafile/seadroid2/config/OriGlideUrl.java b/app/src/main/java/com/seafile/seadroid2/config/OriGlideUrl.java new file mode 100644 index 000000000..bd76802b2 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/config/OriGlideUrl.java @@ -0,0 +1,38 @@ +package com.seafile.seadroid2.config; + +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.load.model.Headers; +import com.seafile.seadroid2.framework.util.SLogs; + +import java.net.URL; + +public class OriGlideUrl extends GlideUrl { + private String oriKey; + + public OriGlideUrl(URL url) { + super(url); + } + + public OriGlideUrl(String url) { + super(url); + } + + public OriGlideUrl(String url, String oriKey) { + super(url); + this.oriKey = oriKey; + } + + + public OriGlideUrl(URL url, Headers headers) { + super(url, headers); + } + + public OriGlideUrl(String url, Headers headers) { + super(url, headers); + } + + @Override + public String getCacheKey() { + return oriKey; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/context/NavContext.java b/app/src/main/java/com/seafile/seadroid2/context/NavContext.java index 590b8d29e..91770f282 100644 --- a/app/src/main/java/com/seafile/seadroid2/context/NavContext.java +++ b/app/src/main/java/com/seafile/seadroid2/context/NavContext.java @@ -5,7 +5,9 @@ import com.seafile.seadroid2.framework.data.model.BaseModel; import com.seafile.seadroid2.framework.data.db.entities.DirentModel; import com.seafile.seadroid2.framework.data.db.entities.RepoModel; +import com.seafile.seadroid2.framework.data.model.ContextModel; import com.seafile.seadroid2.framework.util.Utils; +import com.seafile.seadroid2.preferences.ContextStackPreferenceHelper; import java.util.Stack; @@ -32,6 +34,9 @@ public void clear() { } } + /** + * Push a model to the stack + */ public void push(BaseModel model) { if (model instanceof RepoModel) { //clear @@ -39,14 +44,70 @@ public void push(BaseModel model) { //push navStack.push(model); + + saveToSp(); + } else if (model instanceof DirentModel) { //stack navStack.push(model); + saveToSp(); + } else { throw new IllegalArgumentException("model must be RepoMode or DirentsModel."); } } + public void restoreNavContextFromSp() { + + navStack.clear(); + + Stack stack = ContextStackPreferenceHelper.getStack(); + if (stack != null && !stack.isEmpty()) { + for (ContextModel contextModel : stack) { + if (contextModel.type.equals("repo")) { + RepoModel repoModel = new RepoModel(); + repoModel.repo_id = contextModel.repo_id; + repoModel.repo_name = contextModel.repo_name; + navStack.push(repoModel); + } else if (contextModel.type.equals("dirent")) { + DirentModel direntModel = new DirentModel(); + direntModel.repo_id = contextModel.repo_id; + direntModel.repo_name = contextModel.repo_name; + direntModel.full_path = contextModel.full_path; + direntModel.parent_dir = Utils.getParentPath(direntModel.full_path); + direntModel.name = Utils.getFileNameFromPath(contextModel.full_path); + direntModel.uid = direntModel.getUID(); + navStack.push(direntModel); + } + } + } + + } + + private void saveToSp() { + Stack stack = new Stack<>(); + if (!navStack.isEmpty()) { + for (BaseModel baseModel : navStack) { + ContextModel contextModel = new ContextModel(); + + if (baseModel instanceof RepoModel e) { + contextModel.repo_id = e.repo_id; + contextModel.repo_name = e.repo_name; + contextModel.type = "repo"; + contextModel.full_path = "/"; + } else if (baseModel instanceof DirentModel e) { + contextModel.repo_id = e.repo_id; + contextModel.repo_name = e.repo_name; + contextModel.type = "dirent"; + contextModel.full_path = e.full_path; + } + stack.add(contextModel); + } + } + + ContextStackPreferenceHelper.saveStack(stack); + } + public void pop() { if (navStack.empty()) { return; @@ -54,6 +115,9 @@ public void pop() { //stack navStack.pop(); + + saveToSp(); + } public void switchToPath(RepoModel repoModel, String full_path) { @@ -87,6 +151,8 @@ public void switchToPath(RepoModel repoModel, String full_path) { for (DirentModel model : stack) { navStack.push(model); } + + saveToSp(); } /** diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/db/dao/FileTransferDAO.java b/app/src/main/java/com/seafile/seadroid2/framework/data/db/dao/FileTransferDAO.java index 86585bf64..dd645b45c 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/data/db/dao/FileTransferDAO.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/db/dao/FileTransferDAO.java @@ -130,8 +130,11 @@ public interface FileTransferDAO { @Query("select * from file_transfer_list where repo_id = :repoId and transfer_action = :transfer_action order by created_at asc limit :limit offset :offset") List getPageListSync(String repoId, TransferAction transfer_action, int limit, int offset); - @Query("select * from file_transfer_list where related_account = :related_account and transfer_action = :transferAction and full_path = :full_path and data_status = 0 order by created_at") - List getListByFullPathSync(String related_account, TransferAction transferAction, String full_path); + @Query("select * from file_transfer_list where repo_id = :repoId and transfer_action = :transferAction and full_path = :full_path and data_status = 0 order by created_at") + List getListByFullPathSync(String repoId, TransferAction transferAction, String full_path); + + @Query("select * from file_transfer_list where repo_id = :repoId and transfer_action = :transferAction and full_path = :full_path and data_status = 0 order by created_at") + Single> getListByFullPathAsync(String repoId, TransferAction transferAction, String full_path); @Query("select COUNT(*) from file_transfer_list where repo_id = :repoId and full_path = :fullPath and transfer_action = :transfer_action and data_source = :feature and data_status = 0 ") int checkOneByFullPath(String repoId, String fullPath, TransferAction transfer_action, TransferDataSource feature); diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/FileTransferEntity.java b/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/FileTransferEntity.java index 0ca8f6f12..46d39a1c7 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/FileTransferEntity.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/db/entities/FileTransferEntity.java @@ -361,44 +361,6 @@ public static FileTransferEntity convertDirentModel2This(boolean is_block, boole return entity; } - public static FileTransferEntity convertDirentFileModel2This(RepoModel repoModel, String full_path, boolean is_auto_transfer, DirentFileModel direntModel) { - FileTransferEntity entity = new FileTransferEntity(); - entity.full_path = full_path; -// entity.target_path = direntModel.full_path; - entity.data_source = TransferDataSource.DOWNLOAD; - entity.repo_id = repoModel.repo_id; - entity.repo_name = repoModel.repo_name; - entity.related_account = repoModel.related_account; - - - entity.file_id = direntModel.id; - entity.setParent_path(Utils.getParentPath(full_path)); - entity.file_name = direntModel.name; - entity.file_format = FileTools.getFileExtension(entity.full_path); - entity.mime_type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(entity.file_format); - entity.file_size = direntModel.size; - entity.file_md5 = null; - - entity.is_auto_transfer = is_auto_transfer; - -// entity.is_block = repoModel.canLocalDecrypt(); - entity.file_strategy = ExistingFileStrategy.AUTO; - - entity.is_copy_to_local = true; - - long now = System.currentTimeMillis(); - entity.created_at = now; - entity.modified_at = direntModel.mtime * 1000; - entity.action_end_at = 0L; - - entity.transfer_action = TransferAction.DOWNLOAD; - entity.transfer_status = TransferStatus.WAITING; - entity.transfer_result = TransferResult.NO_RESULT; - - entity.uid = entity.getUID(); - - return entity; - } public static FileTransferEntity convertDirentRecursiveModel2This(RepoModel repoModel, DirentRecursiveFileModel model) { @@ -444,7 +406,7 @@ public static FileTransferEntity convertDirentRecursiveModel2This(RepoModel repo return entity; } - public static FileTransferEntity convert2ThisForUploadFileSyncWorker(Account account, RepoModel repoModel, File file, String backupPath) { + public static FileTransferEntity convert2ThisForUploadFileSyncWorker(Account account, File file, String backupPath) { if (!file.isFile()) { return null; } @@ -466,8 +428,8 @@ public static FileTransferEntity convert2ThisForUploadFileSyncWorker(Account acc entity.mime_type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(entity.file_format); // entity.is_block = repoModel.encrypted; - entity.repo_id = repoModel.repo_id; - entity.repo_name = repoModel.repo_name; +// entity.repo_id = repoModel.repo_id; +// entity.repo_name = repoModel.repo_name; entity.related_account = account.getSignature(); entity.data_source = TransferDataSource.FOLDER_BACKUP; entity.created_at = System.currentTimeMillis(); @@ -489,7 +451,7 @@ public static FileTransferEntity convert2ThisForUploadFileSyncWorker(Account acc } - public static FileTransferEntity convert2ThisForUploadMediaSyncWorker(Account account, String repo_id, String repo_name, File file, String parenPath, long dateAdd, boolean isRemoteExists) { + public static FileTransferEntity convert2ThisForUploadMediaSyncWorker(Account account, File file, String parenPath, long dateAdd, boolean isRemoteExists) { long now = System.currentTimeMillis(); FileTransferEntity entity = new FileTransferEntity(); @@ -502,8 +464,8 @@ public static FileTransferEntity convert2ThisForUploadMediaSyncWorker(Account ac entity.file_md5 = FileUtils.getFileMD5ToString(entity.full_path).toLowerCase(); entity.mime_type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(entity.file_format); // entity.is_block = false; //album backup is not store in encrypted repo. - entity.repo_id = repo_id; - entity.repo_name = repo_name; +// entity.repo_id = repo_id; +// entity.repo_name = repo_name; entity.related_account = account.getSignature(); entity.created_at = now; entity.modified_at = now; diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/ContextModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/ContextModel.java new file mode 100644 index 000000000..cce811099 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/ContextModel.java @@ -0,0 +1,13 @@ +package com.seafile.seadroid2.framework.data.model; + +public class ContextModel { + public String repo_id; + public String repo_name; //repo_name + + public String type; //repo/dirent + + /** + * parent_dir + name + */ + public String full_path; +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/FileProfileConfigModel.java b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/FileProfileConfigModel.java index 0b63f210d..13a9549dd 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/FileProfileConfigModel.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/data/model/sdoc/FileProfileConfigModel.java @@ -4,23 +4,23 @@ public class FileProfileConfigModel { public UserWrapperModel users; - public MetadataConfigModel metadata; public FileDetailModel detail; + public MetadataConfigModel metadataConfigModel; - public UserWrapperModel getUsers() { - return users; + public MetadataConfigModel getMetadataConfigModel() { + return metadataConfigModel; } - public void setUsers(UserWrapperModel users) { - this.users = users; + public void setMetadataConfigModel(MetadataConfigModel metadataConfigModel) { + this.metadataConfigModel = metadataConfigModel; } - public MetadataConfigModel getMetadata() { - return metadata; + public UserWrapperModel getUsers() { + return users; } - public void setMetadata(MetadataConfigModel metadata) { - this.metadata = metadata; + public void setUsers(UserWrapperModel users) { + this.users = users; } public FileDetailModel getDetail() { 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 index 78fbf7894..74fe5d623 100644 --- 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 @@ -2,4 +2,8 @@ public class MetadataConfigModel { public boolean enabled; + //public boolean tags_enabled; + //public String tags_lang; + //public String details_settings; + //public boolean ocr_enabled; } 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 index 74e1c0119..cdb715378 100644 --- 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 @@ -13,6 +13,8 @@ public class SDocPageOptionsModel implements Parcelable { public boolean isLocked; public boolean isStarred; + public boolean enableMetadataManagement; + @Override public String toString() { return "SDocPageOptionsModel{" + @@ -42,6 +44,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.repoName); dest.writeByte(this.isLocked ? (byte) 1 : (byte) 0); dest.writeByte(this.isStarred ? (byte) 1 : (byte) 0); + dest.writeByte(this.enableMetadataManagement ? (byte) 1 : (byte) 0); } public SDocPageOptionsModel() { @@ -56,6 +59,7 @@ protected SDocPageOptionsModel(Parcel in) { this.repoName = in.readString(); this.isLocked = in.readByte() != 0; this.isStarred = in.readByte() != 0; + this.enableMetadataManagement = in.readByte() != 0; } public static final Creator CREATOR = new Creator() { @@ -69,180 +73,4 @@ public SDocPageOptionsModel[] newArray(int size) { return new SDocPageOptionsModel[size]; } }; -} - -// -// \ No newline at end of file +} \ No newline at end of file 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 243f74ebf..35871b1be 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 @@ -36,4 +36,6 @@ 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"; + + public static final String KEY_NAV_CONTEXT_STACK = "key_nav_context_stack"; } \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/framework/file_monitor/FileSyncService.java b/app/src/main/java/com/seafile/seadroid2/framework/file_monitor/FileSyncService.java index 18a4d3b0f..ebf7543f9 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/file_monitor/FileSyncService.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/file_monitor/FileSyncService.java @@ -294,6 +294,7 @@ public void onStop(FileAlterationObserver observer) { public void onDestroy() { super.onDestroy(); + SLogs.e("file monitor service destroy"); stopFolderMonitor(); // 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 f69075797..e18e22d25 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 @@ -45,8 +45,7 @@ public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder File[] externalMediaDirs = SeadroidApplication.getAppContext().getExternalMediaDirs(); String rootPath = externalMediaDirs[0].getAbsolutePath(); File dirPath = new File(rootPath + "/GlideCache/"); - builder.setDiskCache(new DiskLruCacheFactory(dirPath.getAbsolutePath(), 1024 * 1024 * 100)); - GlideApp.tearDown(); + builder.setDiskCache(new DiskLruCacheFactory(dirPath.getAbsolutePath(), 1024 * 1024 * 500)); } @Override diff --git a/app/src/main/java/com/seafile/seadroid2/framework/util/ThumbnailUtils.java b/app/src/main/java/com/seafile/seadroid2/framework/util/ThumbnailUtils.java new file mode 100644 index 000000000..28218c789 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/framework/util/ThumbnailUtils.java @@ -0,0 +1,11 @@ +package com.seafile.seadroid2.framework.util; + +import com.blankj.utilcode.util.EncodeUtils; + +public class ThumbnailUtils { + + public static String convertThumbnailUrl(String serverUrl, String repoId, String fullPath) { + String newFilePath = EncodeUtils.urlEncode(fullPath); + return String.format("%sapi2/repos/%s/thumbnail/?p=%s&size=%s", serverUrl, repoId, newFilePath, 128); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/framework/worker/BackgroundJobManagerImpl.java b/app/src/main/java/com/seafile/seadroid2/framework/worker/BackgroundJobManagerImpl.java index bb5a51740..0bce10fdb 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/worker/BackgroundJobManagerImpl.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/worker/BackgroundJobManagerImpl.java @@ -2,6 +2,7 @@ import android.text.TextUtils; +import androidx.work.BackoffPolicy; import androidx.work.Constraints; import androidx.work.Data; import androidx.work.ExistingWorkPolicy; @@ -149,6 +150,7 @@ private OneTimeWorkRequest getMediaUploadRequest() { return oneTimeRequestBuilder(UploadMediaFileAutomaticallyWorker.class) .setConstraints(constraints) + .setBackoffCriteria(BackoffPolicy.LINEAR, 5, TimeUnit.SECONDS) .setInitialDelay(1, TimeUnit.SECONDS) .setId(UploadMediaFileAutomaticallyWorker.UID) .build(); @@ -204,6 +206,7 @@ private OneTimeWorkRequest getFolderUploadRequest() { return oneTimeRequestBuilder(UploadFolderFileAutomaticallyWorker.class) .setConstraints(constraints) + .setBackoffCriteria(BackoffPolicy.LINEAR, 5, TimeUnit.SECONDS) .setInitialDelay(1, TimeUnit.SECONDS) .setId(UploadFolderFileAutomaticallyWorker.UID) .build(); diff --git a/app/src/main/java/com/seafile/seadroid2/framework/worker/TransferEvent.java b/app/src/main/java/com/seafile/seadroid2/framework/worker/TransferEvent.java index 6adf0e260..a747ff15b 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/worker/TransferEvent.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/worker/TransferEvent.java @@ -41,11 +41,16 @@ public class TransferEvent { /** * because of an OUT_OF_QUOTA error, the current upload worker was canceled */ - public static final String EVENT_CANCEL_OUT_OF_QUOTA = "transfer_cancel_with_out_of_quota"; + public static final String EVENT_CANCEL_WITH_OUT_OF_QUOTA = "transfer_cancel_with_out_of_quota"; /** * because of an NETWORK error, the current upload worker was canceled */ public static final String EVENT_CANCEL_WITH_NETWORK_ERR = "transfer_cancel_with_network_err"; + /** + * Manual or passive cancellation + */ + public static final String EVENT_CANCEL_WITH_BY_STOPPED = "transfer_cancel_with_stopped"; + } diff --git a/app/src/main/java/com/seafile/seadroid2/framework/worker/download/BaseDownloadWorker.java b/app/src/main/java/com/seafile/seadroid2/framework/worker/download/BaseDownloadWorker.java index 976508f3d..b108685fd 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/worker/download/BaseDownloadWorker.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/worker/download/BaseDownloadWorker.java @@ -90,7 +90,7 @@ public String isInterrupt(TransferResult result) { } else if (result == TransferResult.FILE_NOT_FOUND) { // finishFlagEvent = null; } else if (result == TransferResult.OUT_OF_QUOTA) { - finishFlagEvent = TransferEvent.EVENT_CANCEL_OUT_OF_QUOTA; + finishFlagEvent = TransferEvent.EVENT_CANCEL_WITH_OUT_OF_QUOTA; } else if (result == TransferResult.NETWORK_CONNECTION) { finishFlagEvent = TransferEvent.EVENT_CANCEL_WITH_NETWORK_ERR; } else if (result == TransferResult.SSL_EXCEPTION) { diff --git a/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/BaseUploadWorker.java b/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/BaseUploadWorker.java index 04662712e..541ab61af 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/BaseUploadWorker.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/BaseUploadWorker.java @@ -49,6 +49,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; +import java.net.SocketException; import java.net.SocketTimeoutException; import java.nio.file.Files; import java.security.NoSuchAlgorithmException; @@ -102,7 +103,14 @@ private TransferResult parseTransferException(Exception e) { return TransferResult.SSL_EXCEPTION; } else if (e instanceof SocketTimeoutException) { return TransferResult.NETWORK_CONNECTION; + } else if (e instanceof SocketException) { +// SocketException exception = (SocketException) e; +// SLogs.e(exception); + return TransferResult.USER_CANCELLED; } else if (e instanceof IOException) { + if (TextUtils.equals("Canceled", e.getMessage())) { + return TransferResult.USER_CANCELLED; + } return TransferResult.NETWORK_CONNECTION; } @@ -140,7 +148,7 @@ public String isInterrupt(TransferResult result) { } else if (result == TransferResult.FILE_NOT_FOUND) { // finishFlagEvent = null; } else if (result == TransferResult.OUT_OF_QUOTA) { - finishFlagEvent = TransferEvent.EVENT_CANCEL_OUT_OF_QUOTA; + finishFlagEvent = TransferEvent.EVENT_CANCEL_WITH_OUT_OF_QUOTA; } else if (result == TransferResult.NETWORK_CONNECTION) { finishFlagEvent = TransferEvent.EVENT_CANCEL_WITH_NETWORK_ERR; } else if (result == TransferResult.SSL_EXCEPTION) { @@ -151,6 +159,8 @@ public String isInterrupt(TransferResult result) { finishFlagEvent = TransferEvent.EVENT_FINISH; } else if (result == TransferResult.UNKNOWN) { finishFlagEvent = TransferEvent.EVENT_FINISH; + } else if (result == TransferResult.USER_CANCELLED) { + finishFlagEvent = TransferEvent.EVENT_CANCEL_WITH_BY_STOPPED; } return finishFlagEvent; @@ -361,6 +371,11 @@ public void onStopped() { // cancelNotification(); + SLogs.e("BaseUploadWorker onStopped"); + currentTransferEntity.transfer_status = TransferStatus.CANCELLED; + currentTransferEntity.transfer_result = TransferResult.USER_CANCELLED; + AppDatabase.getInstance().fileTransferDAO().update(currentTransferEntity); + if (newCall != null && !newCall.isCanceled()) { newCall.cancel(); } @@ -376,59 +391,67 @@ private void notifyProgress(String fileName, int percent) { showForegroundAsync(f); } + private FileTransferEntity currentTransferEntity; + public void transferFile(Account account, FileTransferEntity transferEntity) throws IOException, SeafException, JSONException { SLogs.d("start transfer, full_path: " + transferEntity.full_path); + currentTransferEntity = transferEntity; List repoModels = AppDatabase.getInstance().repoDao().getByIdSync(transferEntity.repo_id); if (CollectionUtils.isEmpty(repoModels)) { - SLogs.d("no repo for repoId: " + transferEntity.repo_id); + SLogs.d("no repo for repoId: " + currentTransferEntity.repo_id); - transferEntity.transfer_status = TransferStatus.FAILED; - transferEntity.transfer_result = TransferResult.CANCELLED; - AppDatabase.getInstance().fileTransferDAO().update(transferEntity); + currentTransferEntity.transfer_status = TransferStatus.FAILED; + currentTransferEntity.transfer_result = TransferResult.CANCELLED; + AppDatabase.getInstance().fileTransferDAO().update(currentTransferEntity); return; } //update modified_at field - transferEntity.modified_at = System.currentTimeMillis(); - AppDatabase.getInstance().fileTransferDAO().update(transferEntity); - notifyProgress(transferEntity.file_name, 0); - SLogs.d("start transfer, target_path: " + transferEntity.target_path); + currentTransferEntity.modified_at = System.currentTimeMillis(); + AppDatabase.getInstance().fileTransferDAO().update(currentTransferEntity); +// + //show notification + notifyProgress(currentTransferEntity.file_name, 0); + SLogs.d("start transfer, target_path: " + currentTransferEntity.target_path); RepoModel repo = repoModels.get(0); - if (repo.canLocalDecrypt()) { - uploadBlockFile(account, repo, transferEntity); - } else { - uploadFile(account, repo, transferEntity); - } + uploadFile(account, repo); + +// RepoModel repo = repoModels.get(0); +// if (repo.canLocalDecrypt()) { +// uploadBlockFile(account, repo, transferEntity); +// } else { +// uploadFile(account, repo, transferEntity); +// } } /** * upload file */ - private void uploadFile(Account account, RepoModel repoModel, FileTransferEntity transferEntity) throws IOException, SeafException { + private void uploadFile(Account account, RepoModel repoModel) throws IOException, SeafException { if (isStopped()) { return; } - File file = new File(transferEntity.full_path); + File file = new File(currentTransferEntity.full_path); if (!file.exists()) { throw SeafException.notFoundException; } - ExistingFileStrategy fileStrategy = transferEntity.file_strategy; + ExistingFileStrategy fileStrategy = currentTransferEntity.file_strategy; if (fileStrategy == ExistingFileStrategy.AUTO) { - fileStrategy = checkRemoteFileExists(account, repoModel, transferEntity); + fileStrategy = checkRemoteFileExists(account, repoModel, currentTransferEntity); } if (fileStrategy == ExistingFileStrategy.SKIP) { - SLogs.d("folder backup: skip file(remote exists): " + transferEntity.target_path); + SLogs.d("folder backup: skip file(remote exists): " + currentTransferEntity.target_path); - transferEntity.transfer_status = TransferStatus.SUCCEEDED; - transferEntity.transfer_result = TransferResult.TRANSMITTED; - AppDatabase.getInstance().fileTransferDAO().update(transferEntity); + currentTransferEntity.transfer_status = TransferStatus.SUCCEEDED; + currentTransferEntity.transfer_result = TransferResult.TRANSMITTED; + AppDatabase.getInstance().fileTransferDAO().update(currentTransferEntity); return; } @@ -437,8 +460,8 @@ private void uploadFile(Account account, RepoModel repoModel, FileTransferEntity MultipartBody.Builder builder = new MultipartBody.Builder(); builder.setType(MultipartBody.FORM); - if (transferEntity.file_strategy == ExistingFileStrategy.REPLACE) { - builder.addFormDataPart("target_file", transferEntity.target_path); + if (currentTransferEntity.file_strategy == ExistingFileStrategy.REPLACE) { + builder.addFormDataPart("target_file", currentTransferEntity.target_path); } else { //parent_dir: / is repo root builder.addFormDataPart("parent_dir", "/"); @@ -446,7 +469,7 @@ private void uploadFile(Account account, RepoModel repoModel, FileTransferEntity // parent_dir is the root directory. // when select the root of the repo, relative_path is null. - String dir = transferEntity.getParent_path(); + String dir = currentTransferEntity.getParent_path(); dir = StringUtils.removeStart(dir, "/"); // builder.addFormDataPart("relative_path", dir); @@ -454,11 +477,11 @@ private void uploadFile(Account account, RepoModel repoModel, FileTransferEntity // - fileTransferProgressListener.setFileTransferEntity(transferEntity); + fileTransferProgressListener.setFileTransferEntity(currentTransferEntity); //db - transferEntity.transfer_status = TransferStatus.IN_PROGRESS; - AppDatabase.getInstance().fileTransferDAO().update(transferEntity); + currentTransferEntity.transfer_status = TransferStatus.IN_PROGRESS; + AppDatabase.getInstance().fileTransferDAO().update(currentTransferEntity); ProgressRequestBody progressRequestBody = new ProgressRequestBody(file, fileTransferProgressListener); @@ -467,7 +490,7 @@ private void uploadFile(Account account, RepoModel repoModel, FileTransferEntity RequestBody requestBody = builder.build(); //get upload link - String uploadUrl = getFileUploadUrl(transferEntity.repo_id, transferEntity.getParent_path(), transferEntity.file_strategy == ExistingFileStrategy.REPLACE); + String uploadUrl = getFileUploadUrl(currentTransferEntity.repo_id, currentTransferEntity.getParent_path(), currentTransferEntity.file_strategy == ExistingFileStrategy.REPLACE); if (TextUtils.isEmpty(uploadUrl)) { throw SeafException.networkException; } @@ -507,7 +530,7 @@ private void uploadFile(Account account, RepoModel repoModel, FileTransferEntity String fileId = str.replace("\"", ""); SLogs.d("result,file ID:" + str); - updateSuccess(transferEntity, fileId, file); + updateSuccess(fileId, file); } catch (Exception e) { throw e; } @@ -744,37 +767,35 @@ private void commitUpload(String link, List blkIds, FileTransferEntity t String fileId = response.body().string(); - updateSuccess(transferEntity, fileId, file); + updateSuccess(fileId, file); } catch (Exception e) { throw e; } } - private void updateSuccess(FileTransferEntity transferEntity, String fileId, File file) { + private void updateSuccess(String fileId, File file) { //db - transferEntity.file_id = fileId; - transferEntity.transferred_size = file.length(); - transferEntity.action_end_at = System.currentTimeMillis(); - transferEntity.modified_at = transferEntity.action_end_at; - transferEntity.file_original_modified_at = file.lastModified(); - transferEntity.transfer_result = TransferResult.TRANSMITTED; - transferEntity.transfer_status = TransferStatus.SUCCEEDED; + currentTransferEntity.file_id = fileId; + currentTransferEntity.transferred_size = file.length(); + currentTransferEntity.action_end_at = System.currentTimeMillis(); + currentTransferEntity.modified_at = currentTransferEntity.action_end_at; + currentTransferEntity.file_original_modified_at = file.lastModified(); + currentTransferEntity.transfer_result = TransferResult.TRANSMITTED; + currentTransferEntity.transfer_status = TransferStatus.SUCCEEDED; - AppDatabase.getInstance().fileTransferDAO().update(transferEntity); + AppDatabase.getInstance().fileTransferDAO().update(currentTransferEntity); //update - List direntList = AppDatabase.getInstance().direntDao().getListByFullPathSync(transferEntity.repo_id, transferEntity.full_path); + List direntList = AppDatabase.getInstance().direntDao().getListByFullPathSync(currentTransferEntity.repo_id, currentTransferEntity.full_path); if (!CollectionUtils.isEmpty(direntList)) { DirentModel direntModel = direntList.get(0); - direntModel.last_modified_at = transferEntity.modified_at; + direntModel.last_modified_at = currentTransferEntity.modified_at; direntModel.id = fileId; - direntModel.size = transferEntity.file_size; - direntModel.transfer_status = transferEntity.transfer_status; + direntModel.size = currentTransferEntity.file_size; + direntModel.transfer_status = currentTransferEntity.transfer_status; AppDatabase.getInstance().direntDao().update(direntModel); } - - } private final int BUFFER_SIZE = 2 * 1024 * 1024; diff --git a/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/FolderBackupScannerWorker.java b/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/FolderBackupScannerWorker.java index 576941ec5..9c8850697 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/FolderBackupScannerWorker.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/FolderBackupScannerWorker.java @@ -126,8 +126,8 @@ private Data getOutData() { } private boolean checkCanScan() { - boolean isOpenBackup = FolderBackupSharePreferenceHelper.readBackupSwitch(); - if (!isOpenBackup) { + boolean isTurnOn = FolderBackupSharePreferenceHelper.readBackupSwitch(); + if (!isTurnOn) { return false; } @@ -212,7 +212,7 @@ private void compareToLocalAndInsert(Account account, RepoModel repoModel, Strin List tList = CollectionUtils.newArrayList(); for (File file : subFiles) { - FileTransferEntity fEntity = FileTransferEntity.convert2ThisForUploadFileSyncWorker(account, repoModel, file, backupPath); + FileTransferEntity fEntity = FileTransferEntity.convert2ThisForUploadFileSyncWorker(account, file, backupPath); if (fEntity != null) { tList.add(fEntity); } diff --git a/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/MediaBackupScannerWorker.java b/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/MediaBackupScannerWorker.java index a54daf2a8..6f0224a1d 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/MediaBackupScannerWorker.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/MediaBackupScannerWorker.java @@ -97,7 +97,6 @@ public Result doWork() { return Result.success(getOutData()); } - //todo String title = getApplicationContext().getString(R.string.settings_camera_upload_info_title); String subTitle = getApplicationContext().getString(R.string.is_scanning); @@ -517,7 +516,7 @@ private void checkAndInsert(String parent, List> fi String parentPath = Utils.pathJoin(parent, "/"); - FileTransferEntity fileTransferEntity = FileTransferEntity.convert2ThisForUploadMediaSyncWorker(account, repoConfig.getRepoID(), repoConfig.getRepoName(), file, parentPath, absPathPair.getThird(), isRemoteExists); + FileTransferEntity fileTransferEntity = FileTransferEntity.convert2ThisForUploadMediaSyncWorker(account, file, parentPath, absPathPair.getThird(), isRemoteExists); transferList.add(fileTransferEntity); } diff --git a/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/UploadFileManuallyWorker.java b/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/UploadFileManuallyWorker.java index 6333bf534..4eaebe57e 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/UploadFileManuallyWorker.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/UploadFileManuallyWorker.java @@ -98,7 +98,7 @@ private ListenableWorker.Result start() { getGeneralNotificationHelper().showErrorNotification(R.string.above_quota, R.string.settings_folder_backup_info_title); AppDatabase.getInstance().fileTransferDAO().cancelWithFileBackup(TransferResult.OUT_OF_QUOTA); - finishFlagEvent = TransferEvent.EVENT_CANCEL_OUT_OF_QUOTA; + finishFlagEvent = TransferEvent.EVENT_CANCEL_WITH_OUT_OF_QUOTA; break; } } catch (Exception e) { diff --git a/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/UploadFolderFileAutomaticallyWorker.java b/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/UploadFolderFileAutomaticallyWorker.java index 174e2813f..2af843848 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/UploadFolderFileAutomaticallyWorker.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/UploadFolderFileAutomaticallyWorker.java @@ -27,6 +27,7 @@ import com.seafile.seadroid2.framework.worker.BackgroundJobManagerImpl; import com.seafile.seadroid2.framework.worker.TransferEvent; import com.seafile.seadroid2.framework.worker.TransferWorker; +import com.seafile.seadroid2.ui.folder_backup.RepoConfig; import java.util.List; import java.util.UUID; @@ -65,36 +66,46 @@ public void onStopped() { super.onStopped(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - SLogs.e("文件夹备份已停止:" + getStopReason()); + SLogs.e("Folder backup stopped, reason:" + getStopReason()); } } + private boolean isFirstShow = true; + private void startShowNotification(){ + if (!isFirstShow) { + return; + } + + isFirstShow = false; + ForegroundInfo foregroundInfo = notificationManager.getForegroundNotification(); + showForegroundAsync(foregroundInfo); + + + sendEvent(TransferEvent.EVENT_TRANSFERRING, TransferDataSource.FILE_BACKUP); + } + private Result start() { // notificationManager.cancel(); + SLogs.d("start upload file worker"); Account account = SupportAccountManager.getInstance().getCurrentAccount(); if (account == null) { return Result.success(); } - boolean isEnable = FolderBackupSharePreferenceHelper.readBackupSwitch(); - if (!isEnable) { + boolean canExec = can(); + if (!canExec) { return Result.success(); } - boolean isUploaded = false; - - - ForegroundInfo foregroundInfo = notificationManager.getForegroundNotification(); - showForegroundAsync(foregroundInfo); + if (repoConfig == null) { + return Result.success(); + } -// notificationManager.showNotification(); + boolean isUploaded = false; String finishFlagEvent = null; - SLogs.d("start upload file worker"); - sendEvent(TransferEvent.EVENT_TRANSFERRING, TransferDataSource.FILE_BACKUP); - while (true) { if (isStopped()) { break; @@ -113,9 +124,14 @@ private Result start() { break; } - FileTransferEntity transferEntity = transferList.get(0); + startShowNotification(); + isUploaded = true; + FileTransferEntity transferEntity = transferList.get(0); + transferEntity.repo_id = repoConfig.getRepoID(); + transferEntity.repo_name = repoConfig.getRepoName(); + try { transferFile(account, transferEntity); @@ -126,15 +142,19 @@ private Result start() { TransferResult transferResult = onException(transferEntity, e); - notifyError(transferResult); + if (!isStopped()) { + + notifyError(transferResult); - sendTransferEvent(transferEntity, false); + sendTransferEvent(transferEntity, false); + } String finishFlag = isInterrupt(transferResult); if (!TextUtils.isEmpty(finishFlag)) { finishFlagEvent = finishFlag; break; } + } } @@ -170,4 +190,25 @@ private Result start() { return Result.success(outputData); } + private RepoConfig repoConfig; + + private boolean can() { + boolean isTurnOn = FolderBackupSharePreferenceHelper.readBackupSwitch(); + if (!isTurnOn) { + return false; + } + + List backupPaths = FolderBackupSharePreferenceHelper.readBackupPathsAsList(); + if (CollectionUtils.isEmpty(backupPaths)) { + return false; + } + + repoConfig = FolderBackupSharePreferenceHelper.readRepoConfig(); + if (repoConfig == null) { + return false; + } + + return true; + } + } diff --git a/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/UploadMediaFileAutomaticallyWorker.java b/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/UploadMediaFileAutomaticallyWorker.java index dd568ac18..9a6c6f590 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/UploadMediaFileAutomaticallyWorker.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/worker/upload/UploadMediaFileAutomaticallyWorker.java @@ -22,12 +22,14 @@ import com.seafile.seadroid2.enums.TransferDataSource; import com.seafile.seadroid2.enums.TransferResult; import com.seafile.seadroid2.framework.datastore.sp_livedata.AlbumBackupSharePreferenceHelper; +import com.seafile.seadroid2.framework.datastore.sp_livedata.FolderBackupSharePreferenceHelper; import com.seafile.seadroid2.framework.notification.AlbumBackupNotificationHelper; import com.seafile.seadroid2.framework.notification.base.BaseTransferNotificationHelper; import com.seafile.seadroid2.framework.util.SLogs; import com.seafile.seadroid2.framework.worker.BackgroundJobManagerImpl; import com.seafile.seadroid2.framework.worker.TransferEvent; import com.seafile.seadroid2.framework.worker.TransferWorker; +import com.seafile.seadroid2.ui.folder_backup.RepoConfig; import java.util.List; import java.util.UUID; @@ -54,9 +56,24 @@ public BaseTransferNotificationHelper getNotification() { return albumNotificationHelper; } + boolean isFirstShow = true; + private void startShowNotification(){ + if (!isFirstShow) { + return; + } + + //send start transfer event + sendEvent(TransferEvent.EVENT_TRANSFERRING, TransferDataSource.ALBUM_BACKUP); + + // show foreground notification + ForegroundInfo foregroundInfo = albumNotificationHelper.getForegroundNotification(); + showForegroundAsync(foregroundInfo); + } + @NonNull @Override public ListenableWorker.Result doWork() { + SLogs.d("start upload media worker"); Account account = SupportAccountManager.getInstance().getCurrentAccount(); if (account == null) { @@ -66,17 +83,14 @@ public ListenableWorker.Result doWork() { return ListenableWorker.Result.success(); } - boolean isEnable = AlbumBackupSharePreferenceHelper.readBackupSwitch(); - if (!isEnable) { - return ListenableWorker.Result.success(); + boolean canExec = can(); + if (!canExec) { + return Result.success(); } - //send start transfer event - sendEvent(TransferEvent.EVENT_TRANSFERRING, TransferDataSource.ALBUM_BACKUP); - - // show foreground notification - ForegroundInfo foregroundInfo = albumNotificationHelper.getForegroundNotification(); - showForegroundAsync(foregroundInfo); + if (repoConfig == null) { + return Result.success(); + } // String finishFlagEvent = null; @@ -86,8 +100,6 @@ public ListenableWorker.Result doWork() { break; } - SLogs.d("start upload media worker"); - List transferList = AppDatabase .getInstance() .fileTransferDAO() @@ -99,11 +111,15 @@ public ListenableWorker.Result doWork() { break; } - FileTransferEntity transferEntity = transferList.get(0); + startShowNotification(); isUploaded = true; - try { + FileTransferEntity transferEntity = transferList.get(0); + transferEntity.repo_id = repoConfig.getRepoID(); + transferEntity.repo_name = repoConfig.getRepoName(); + + try { transferFile(account, transferEntity); sendTransferEvent(transferEntity, true); @@ -113,9 +129,11 @@ public ListenableWorker.Result doWork() { TransferResult transferResult = onException(transferEntity, e); - notifyError(transferResult); + if (!isStopped()) { + notifyError(transferResult); - sendTransferEvent(transferEntity, false); + sendTransferEvent(transferEntity, false); + } String finishFlag = isInterrupt(transferResult); if (!TextUtils.isEmpty(finishFlag)) { @@ -156,4 +174,20 @@ public ListenableWorker.Result doWork() { .build(); return Result.success(outputData); } + + private RepoConfig repoConfig; + + private boolean can() { + boolean isTurnOn = AlbumBackupSharePreferenceHelper.readBackupSwitch(); + if (!isTurnOn) { + return false; + } + + repoConfig = AlbumBackupSharePreferenceHelper.readRepoConfig(); + if (repoConfig == null) { + return false; + } + + return true; + } } diff --git a/app/src/main/java/com/seafile/seadroid2/listener/FileTransferProgressListener.java b/app/src/main/java/com/seafile/seadroid2/listener/FileTransferProgressListener.java index 67ea45d50..426afc6cd 100644 --- a/app/src/main/java/com/seafile/seadroid2/listener/FileTransferProgressListener.java +++ b/app/src/main/java/com/seafile/seadroid2/listener/FileTransferProgressListener.java @@ -43,10 +43,26 @@ public void onProgressNotify(long cur, long total) { fileTransferEntity.transferred_size = cur; - int percent = (int) ((float) cur / (float) total * 100); + int percent = calcu(cur, total); progressListener.onProgressNotify(fileTransferEntity, percent, cur, total); } + public int onProgress(long cur, long total) { + + long nowt = System.currentTimeMillis(); + if (nowt - temp < 1000) { + return -1; + } + + temp = nowt; + return calcu(cur, total); + } + + public int calcu(long cur, long total) { + int percent = (int) ((float) cur / (float) total * 100); + return percent; + } + public interface TransferProgressListener { void onProgressNotify(FileTransferEntity fileTransferEntity, int percent, long transferredSize, long totalSize); } diff --git a/app/src/main/java/com/seafile/seadroid2/preferences/ContextStackPreferenceHelper.java b/app/src/main/java/com/seafile/seadroid2/preferences/ContextStackPreferenceHelper.java new file mode 100644 index 000000000..ae26f1a30 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/preferences/ContextStackPreferenceHelper.java @@ -0,0 +1,35 @@ +package com.seafile.seadroid2.preferences; + +import com.blankj.utilcode.util.GsonUtils; +import com.google.gson.reflect.TypeToken; +import com.seafile.seadroid2.framework.data.model.ContextModel; +import com.seafile.seadroid2.framework.datastore.DataStoreKeys; + +import java.lang.reflect.Type; +import java.util.Stack; + +public class ContextStackPreferenceHelper { + private static final String STACK_KEY = DataStoreKeys.KEY_NAV_CONTEXT_STACK; + + // 保存 Stack 到 SharedPreferences + public static void saveStack(Stack stack) { + String json = GsonUtils.toJson(stack); // 将 Stack 转换为 JSON 字符串 + Settings.getCommonPreferences().edit().putString(STACK_KEY, json).apply(); + } + + public static void clearStack() { + Settings.getCommonPreferences().edit().remove(STACK_KEY).apply(); + } + + // 从 SharedPreferences 中获取 Stack + public static Stack getStack() { + String json = Settings.getCommonPreferences().getString(STACK_KEY, null); + if (json == null) { + return new Stack<>(); // 返回空栈 + } + + Type type = new TypeToken>() { + }.getType(); + return GsonUtils.fromJson(json, type); // 从 JSON 恢复 Stack + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadManager.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadManager.java index eef46c498..7b049fabe 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadManager.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/CameraUploadManager.java @@ -8,6 +8,7 @@ import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.framework.util.SLogs; +import com.seafile.seadroid2.framework.worker.BackgroundJobManagerImpl; import java.util.List; @@ -122,6 +123,8 @@ public void disableCameraUpload() { ContentResolver.cancelSync(account.getAndroidAccount(), AUTHORITY); ContentResolver.setIsSyncable(account.getAndroidAccount(), AUTHORITY, 0); } + + BackgroundJobManagerImpl.getInstance().cancelAllMediaWorker(); } /** diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentViewModel.java similarity index 68% rename from app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentViewModel.java rename to app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentViewModel.java index 960c0a30f..aa5893611 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentViewModel.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.sdoc; +package com.seafile.seadroid2.ui.docs_comment; import android.content.ContentResolver; import android.net.Uri; @@ -8,7 +8,6 @@ import com.blankj.utilcode.util.CloneUtils; import com.blankj.utilcode.util.CollectionUtils; -import com.blankj.utilcode.util.RegexUtils; import com.blankj.utilcode.util.TimeUtils; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; @@ -31,6 +30,7 @@ import com.seafile.seadroid2.framework.util.StringUtils; import com.seafile.seadroid2.framework.util.Utils; import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.ui.sdoc.DocsCommentService; import com.seafile.seadroid2.view.rich_edittext.RichEditText; import java.util.Comparator; @@ -53,166 +53,17 @@ public class DocsCommentViewModel extends BaseViewModel { - private final MutableLiveData _fileProfileConfigLiveData = new MutableLiveData<>(); - private final MutableLiveData _fileRecordLiveData = new MutableLiveData<>(); private final MutableLiveData _fileCommentLiveData = new MutableLiveData<>(); - private final MutableLiveData> _sdocElementListLiveData = new MutableLiveData<>(); private final MutableLiveData _postCommentLiveData = new MutableLiveData<>(); public MutableLiveData getPostCommentLiveData() { return _postCommentLiveData; } - public MutableLiveData getFileDetailLiveData() { - return _fileProfileConfigLiveData; - } - - public MutableLiveData getSdocRecordLiveData() { - return _fileRecordLiveData; - } - public MutableLiveData getSdocCommentLiveData() { return _fileCommentLiveData; } - public MutableLiveData> getSdocElementLiveData() { - return _sdocElementListLiveData; - } - - public void loadFileConfig(String repoId, String path) { - Single userSingle = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getRelatedUsers(repoId); - Single metadataSingle = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getMetadata(repoId); - Single detailSingle = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getFileDetail(repoId, path); - - Single s = Single.zip(detailSingle, userSingle, metadataSingle, new Function3() { - @Override - public FileProfileConfigModel apply(FileDetailModel docDetailModel, UserWrapperModel userWrapperModel, MetadataConfigModel metadataConfigModel) throws Exception { - FileProfileConfigModel configModel = new FileProfileConfigModel(); - configModel.setDetail(docDetailModel); - configModel.setUsers(userWrapperModel); - configModel.setMetadata(metadataConfigModel); - return configModel; - } - }); - - addSingleDisposable(s, new Consumer() { - @Override - public void accept(FileProfileConfigModel fileProfileConfigModel) throws Exception { - getFileDetailLiveData().setValue(fileProfileConfigModel); - } - }); - } - - public void loadRecords(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; - } - - if (TextUtils.isEmpty(parent_dir)) { - parent_dir = "/"; - } - - Single single = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getRecords(repoId, parent_dir, name); - addSingleDisposable(single, new Consumer() { - @Override - public void accept(FileRecordWrapperModel fileRecordWrapperModel) throws Exception { - getSdocRecordLiveData().setValue(fileRecordWrapperModel); - } - }); - } - - public static final List _AllowedElementTypes = List.of("header1", "header2", "header3"); - - public void loadSdocElements(SDocPageOptionsModel pageOptionsModel) { - if (TextUtils.isEmpty(pageOptionsModel.seadocServerUrl)) { - return; - } - getRefreshLiveData().setValue(true); - - 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(DocsCommentService.class).getElements(pageOptionsModel.docUuid); - addSingleDisposable(single, new Consumer() { - @Override - public void accept(SDocOutlineWrapperModel 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(OutlineItemModel 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 OutlineItemModel apply(OutlineItemModel sDocModel) { - if (!TextUtils.isEmpty(sDocModel.text)) { - return sDocModel; - } - - if (CollectionUtils.isEmpty(sDocModel.children)) { - return sDocModel; - } - - String text = ""; - for (OutlineItemModel 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); - getRefreshLiveData().setValue(false); - } - }, new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { - SLogs.e(throwable); - getRefreshLiveData().setValue(false); - } - }); - } - public void loadDocComments(SDocPageOptionsModel pageOptionsModel) { if (TextUtils.isEmpty(pageOptionsModel.seadocServerUrl)) { return; @@ -374,7 +225,7 @@ private List getImageMdResult(String s) { return models; } - public void markResolve(String sdocServerUrl,String token, String sdocUid, long commentId, Consumer consumer) { + public void markResolve(String sdocServerUrl, String token, String sdocUid, long commentId, Consumer consumer) { getRefreshLiveData().setValue(true); if (!sdocServerUrl.endsWith("/")) { @@ -406,7 +257,7 @@ public void accept(Throwable throwable) { }); } - public void delete(String sdocServerUrl,String token, String sdocUid, long commentId, Consumer consumer) { + public void delete(String sdocServerUrl, String token, String sdocUid, long commentId, Consumer consumer) { getRefreshLiveData().setValue(true); if (!sdocServerUrl.endsWith("/")) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentsActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentsActivity.java index 1df2df0b9..85739856c 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentsActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/docs_comment/DocsCommentsActivity.java @@ -1,6 +1,5 @@ package com.seafile.seadroid2.ui.docs_comment; -import android.animation.ValueAnimator; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; @@ -11,15 +10,12 @@ import android.view.MenuItem; import android.view.View; import android.view.WindowManager; -import android.view.animation.Animation; -import android.widget.LinearLayout; import androidx.annotation.NonNull; import androidx.appcompat.widget.PopupMenu; import androidx.appcompat.widget.Toolbar; import androidx.lifecycle.Observer; import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import com.blankj.utilcode.util.CollectionUtils; import com.blankj.utilcode.util.KeyboardUtils; @@ -28,8 +24,6 @@ import com.chad.library.adapter4.QuickAdapterHelper; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.seafile.seadroid2.R; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.databinding.ActivityDocCommentBinding; import com.seafile.seadroid2.databinding.ToolbarActionbarBinding; import com.seafile.seadroid2.framework.data.model.docs_comment.DocsCommentModel; @@ -37,10 +31,6 @@ import com.seafile.seadroid2.framework.data.model.sdoc.SDocPageOptionsModel; import com.seafile.seadroid2.framework.util.SLogs; import com.seafile.seadroid2.ui.base.BaseMediaSelectorActivity; -import com.seafile.seadroid2.ui.bottomsheetmenu.BottomSheetHelper; -import com.seafile.seadroid2.ui.bottomsheetmenu.BottomSheetMenuFragment; -import com.seafile.seadroid2.ui.bottomsheetmenu.OnMenuClickListener; -import com.seafile.seadroid2.ui.sdoc.DocsCommentViewModel; import com.seafile.seadroid2.view.rich_edittext.RichEditText; import java.util.List; 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 8703ecb37..fba8aabb6 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 @@ -57,6 +57,7 @@ import com.seafile.seadroid2.framework.data.model.dirents.DirentFileModel; import com.seafile.seadroid2.framework.file_monitor.FileSyncService; import com.seafile.seadroid2.framework.helper.NightModeHelper; +import com.seafile.seadroid2.framework.util.GlideApp; import com.seafile.seadroid2.framework.util.PermissionUtil; import com.seafile.seadroid2.framework.util.SLogs; import com.seafile.seadroid2.framework.util.TakeCameras; @@ -505,7 +506,7 @@ private void refreshToolbarTitle() { private void bindService() { Intent syncIntent = new Intent(this, FileSyncService.class); bindService(syncIntent, syncConnection, Context.BIND_AUTO_CREATE); - startService(syncIntent); +// startService(syncIntent); //It doesn't need to run in the background } private final ServiceConnection syncConnection = new ServiceConnection() { @@ -1208,7 +1209,7 @@ private void doSelectedMultiFile(List uriList) { mainViewModel.checkLocalDirent(curAccount, this, repoModel, parent_dir, uriList, new Consumer>() { @Override - public void accept(List newUris) throws Exception { + public void accept(List newUris) { dismissProgressDialog(); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/main/MainViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/main/MainViewModel.java index 691070670..e1692f1b4 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/main/MainViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/main/MainViewModel.java @@ -28,6 +28,7 @@ import com.seafile.seadroid2.framework.datastore.DataManager; import com.seafile.seadroid2.framework.util.Utils; import com.seafile.seadroid2.framework.worker.ExistingFileStrategy; +import com.seafile.seadroid2.preferences.Settings; import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; import com.seafile.seadroid2.context.NavContext; import com.seafile.seadroid2.framework.data.ServerInfo; diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/PhotoViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/media/PhotoViewModel.java new file mode 100644 index 000000000..48a5fed35 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/PhotoViewModel.java @@ -0,0 +1,259 @@ +package com.seafile.seadroid2.ui.media; + +import android.text.TextUtils; + +import androidx.lifecycle.MutableLiveData; + +import com.blankj.utilcode.util.CollectionUtils; +import com.blankj.utilcode.util.FileUtils; +import com.seafile.seadroid2.SeafException; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.SupportAccountManager; +import com.seafile.seadroid2.enums.TransferAction; +import com.seafile.seadroid2.enums.TransferResult; +import com.seafile.seadroid2.enums.TransferStatus; +import com.seafile.seadroid2.framework.data.db.AppDatabase; +import com.seafile.seadroid2.framework.data.db.entities.DirentModel; +import com.seafile.seadroid2.framework.data.db.entities.FileTransferEntity; +import com.seafile.seadroid2.framework.datastore.DataManager; +import com.seafile.seadroid2.framework.http.HttpIO; +import com.seafile.seadroid2.framework.util.SLogs; +import com.seafile.seadroid2.framework.worker.ExistingFileStrategy; +import com.seafile.seadroid2.listener.FileTransferProgressListener; +import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; +import com.seafile.seadroid2.ui.file.FileService; + +import org.apache.commons.lang3.StringUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.net.URLEncoder; +import java.nio.file.Path; +import java.util.List; + +import io.reactivex.Single; +import io.reactivex.SingleEmitter; +import io.reactivex.SingleOnSubscribe; +import io.reactivex.SingleSource; +import io.reactivex.functions.BiFunction; +import io.reactivex.functions.Consumer; +import io.reactivex.functions.Function; +import kotlin.Pair; +import okhttp3.Call; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class PhotoViewModel extends BaseViewModel { + private final MutableLiveData _downloadedUrlLiveData = new MutableLiveData<>(); + private final MutableLiveData _originalUrlLiveData = new MutableLiveData<>(); + + private final MutableLiveData> _checkLocalLiveData = new MutableLiveData<>(); + + public MutableLiveData getDownloadedPathLiveData() { + return _downloadedUrlLiveData; + } + + public MutableLiveData getOriginalUrlLiveData() { + return _originalUrlLiveData; + } + + public MutableLiveData> getCheckLocalLiveData() { + return _checkLocalLiveData; + } + + public void checkLocal(String repoId, String fullPath) { + Single> direntSingle = AppDatabase.getInstance().direntDao().getListByFullPathAsync(repoId, fullPath); + Single> transferSingle = AppDatabase.getInstance().fileTransferDAO().getListByFullPathAsync(repoId, TransferAction.DOWNLOAD, fullPath); + Single> rSingle = Single.zip(direntSingle, transferSingle, new BiFunction, List, Pair>() { + @Override + public Pair apply(List direntModels, List fileTransferEntities) throws Exception { + if (CollectionUtils.isEmpty(direntModels)) { + return null; + } + + if (CollectionUtils.isEmpty(fileTransferEntities)) { + return new Pair<>(direntModels.get(0), null); + } + + return new Pair<>(direntModels.get(0), fileTransferEntities.get(0)); + } + }); + + addSingleDisposable(rSingle, new Consumer>() { + @Override + public void accept(Pair pair) throws Exception { + getCheckLocalLiveData().setValue(pair); + } + }); + + } + + private final int SEGMENT_SIZE = 8192; + private final FileTransferProgressListener fileTransferProgressListener = new FileTransferProgressListener(); + + public void requestOriginalUrl(DirentModel direntModel) { + Single downloadUrlSingle = HttpIO.getCurrentInstance() + .execute(FileService.class) + .getFileDownloadLinkAsync(direntModel.repo_id, direntModel.full_path); + + addSingleDisposable(downloadUrlSingle, new Consumer() { + @Override + public void accept(String dlink) throws Exception { + // + dlink = StringUtils.replace(dlink, "\"", ""); + int i = dlink.lastIndexOf('/'); + if (i == -1) { + return; + } + + dlink = dlink.substring(0, i) + "/" + URLEncoder.encode(dlink.substring(i + 1), "UTF-8"); + + getOriginalUrlLiveData().setValue(dlink); + } + }); + } + + public void download(DirentModel direntModel) { + Single downloadUrlSingle = HttpIO.getCurrentInstance() + .execute(FileService.class) + .getFileDownloadLinkAsync(direntModel.repo_id, direntModel.full_path); + + Single downloadedFilePathSingle = downloadUrlSingle.flatMap(new Function>() { + @Override + public SingleSource apply(String dlink) throws Exception { + // + dlink = StringUtils.replace(dlink, "\"", ""); + int i = dlink.lastIndexOf('/'); + if (i == -1) { + return null; + } + + dlink = dlink.substring(0, i) + "/" + URLEncoder.encode(dlink.substring(i + 1), "UTF-8"); + + return Single.just(dlink); + } + }).flatMap(new Function>() { + @Override + public SingleSource apply(String s) throws Exception { + if (TextUtils.isEmpty(s)) { + //download url is null + throw SeafException.networkException; + } + + FileTransferEntity transferEntity = FileTransferEntity.convertDirentModel2This(false, direntModel); + + return getDownloadSingle(transferEntity, s); + } + }).flatMap(new Function>() { + @Override + public SingleSource apply(FileTransferEntity transferEntity) throws Exception { + return Single.just(transferEntity.target_path); + } + }); + + addSingleDisposable(downloadedFilePathSingle, new Consumer() { + @Override + public void accept(String s) { + getDownloadedPathLiveData().setValue(s); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + SeafException seafException = getExceptionByThrowable(throwable); + getSeafExceptionLiveData().setValue(seafException); + } + }); + } + + private Single getDownloadSingle(FileTransferEntity transferEntity, String dlink) { + return Single.create(new SingleOnSubscribe() { + @Override + public void subscribe(SingleEmitter emitter) throws Exception { + + Account currentAccount = SupportAccountManager.getInstance().getCurrentAccount(); + File localFile = DataManager.getLocalRepoFile(currentAccount, transferEntity); + + transferEntity.target_path = localFile.getAbsolutePath(); + AppDatabase.getInstance().fileTransferDAO().insert(transferEntity); + + Request request = new Request.Builder() + .url(dlink) + .get() + .build(); + + Call newCall = HttpIO.getCurrentInstance().getOkHttpClient().getOkClient().newCall(request); + + try (Response response = newCall.execute()) { + if (!response.isSuccessful()) { + emitter.onError(SeafException.networkException); + return; + } + + ResponseBody responseBody = response.body(); + if (responseBody == null) { + emitter.onError(SeafException.networkException); + return; + } + + long fileSize = responseBody.contentLength(); + if (fileSize == -1) { + SLogs.d("download file error -> contentLength is -1"); + SLogs.d(localFile.getAbsolutePath()); + + fileSize = transferEntity.file_size; + } + + File tempFile = DataManager.createTempFile(); + try (InputStream inputStream = responseBody.byteStream(); + FileOutputStream fileOutputStream = new FileOutputStream(tempFile)) { + + long totalBytesRead = 0; + + int bytesRead; + byte[] buffer = new byte[SEGMENT_SIZE]; + while ((bytesRead = inputStream.read(buffer, 0, buffer.length)) != -1) { + fileOutputStream.write(buffer, 0, bytesRead); + totalBytesRead += bytesRead; + + int p = fileTransferProgressListener.onProgress(totalBytesRead, fileSize); + SLogs.e(transferEntity.file_name + ", progress: " + p); + } + int p = fileTransferProgressListener.onProgress(fileSize, fileSize); + SLogs.e(transferEntity.file_name + ", progress: " + p); + } + + //important + Path path = java.nio.file.Files.move(tempFile.toPath(), localFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + boolean isSuccess = path.toFile().exists(); + if (isSuccess) { + SLogs.e("move file success: " + path); + transferEntity.transferred_size = fileSize; + transferEntity.action_end_at = System.currentTimeMillis(); + transferEntity.file_original_modified_at = transferEntity.action_end_at; + transferEntity.transfer_result = TransferResult.TRANSMITTED; + transferEntity.transfer_status = TransferStatus.SUCCEEDED; + + transferEntity.file_md5 = FileUtils.getFileMD5ToString(transferEntity.target_path).toLowerCase(); + + AppDatabase.getInstance().fileTransferDAO().update(transferEntity); + + emitter.onSuccess(transferEntity); + } else { + SLogs.e("move file failed: " + path); + + transferEntity.transfer_result = TransferResult.FILE_ERROR; + transferEntity.transfer_status = TransferStatus.FAILED; + AppDatabase.getInstance().fileTransferDAO().update(transferEntity); + + emitter.onError(SeafException.transferFileException); + } + } catch (Exception e) { + emitter.onError(e); + } + } + }); + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewActivity.java deleted file mode 100644 index 5d67f4232..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewActivity.java +++ /dev/null @@ -1,314 +0,0 @@ -package com.seafile.seadroid2.ui.media.image_preview; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; - -import androidx.activity.OnBackPressedCallback; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.Observer; -import androidx.viewpager2.widget.ViewPager2; - -import com.blankj.utilcode.util.BarUtils; -import com.blankj.utilcode.util.CollectionUtils; -import com.blankj.utilcode.util.NetworkUtils; -import com.blankj.utilcode.util.ToastUtils; -import com.seafile.seadroid2.R; -import com.seafile.seadroid2.databinding.ActivityImagePreviewBinding; -import com.seafile.seadroid2.framework.data.db.entities.DirentModel; -import com.seafile.seadroid2.framework.data.db.entities.RepoModel; -import com.seafile.seadroid2.framework.data.db.entities.StarredModel; -import com.seafile.seadroid2.framework.data.model.activities.ActivityModel; -import com.seafile.seadroid2.framework.data.model.search.SearchModel; -import com.seafile.seadroid2.framework.util.Objs; -import com.seafile.seadroid2.ui.adapter.ViewPager2Adapter; -import com.seafile.seadroid2.ui.base.BaseActivityWithVM; -import com.seafile.seadroid2.ui.dialog_fragment.DeleteFileDialogFragment; -import com.seafile.seadroid2.ui.dialog_fragment.listener.OnRefreshDataListener; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -import io.reactivex.functions.Consumer; - -@Deprecated -public class ImagePreviewActivity extends BaseActivityWithVM { - private ActivityImagePreviewBinding binding; - private ViewPager2Adapter adapter; - private DirentModel currentDirent; - private List direntList; - private boolean isDataOperated = false; - - public static Intent startThisFromRepo(Context context, DirentModel direntModel) { - Intent intent = new Intent(context, ImagePreviewActivity.class); - intent.putExtra("dirent", direntModel); - intent.putExtra("query_db", true); - return intent; - } - - public static Intent startThisFromStarred(Context context, StarredModel starredModel) { - Intent intent = new Intent(context, ImagePreviewActivity.class); - intent.putExtra("dirent", StarredModel.convert2DirentModel(starredModel)); - return intent; - } - - public static Intent startThisFromActivity(Context context, ActivityModel starredModel) { - Intent intent = new Intent(context, ImagePreviewActivity.class); - intent.putExtra("dirent", ActivityModel.convert2DirentModel(starredModel)); - return intent; - } - - - public static void startThisFromSearch(Context context, SearchModel starredModel) { - Intent intent = new Intent(context, ImagePreviewActivity.class); - intent.putExtra("dirent", SearchModel.convert2DirentModel(starredModel)); - context.startActivity(intent); - } - - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - binding = ActivityImagePreviewBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - BarUtils.setNavBarVisibility(this, false); - BarUtils.setStatusBarVisibility(this, false); - - initData(); - - initView(); - initViewModel(); - - getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - if (isDataOperated) { - setResult(RESULT_OK); - } - finish(); - } - }); - } - - @Override - protected void onPostCreate(@Nullable Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - - loadData(); - } - - private void initData() { - - if (getIntent() == null) { - throw new IllegalArgumentException("Intent is null"); - } - - currentDirent = getIntent().getParcelableExtra("dirent"); - } - - private void initView() { - View.OnClickListener onClickListener = v -> { - int id = v.getId(); - if (id == R.id.gallery_download_photo) { - downloadFile(); - } else if (id == R.id.gallery_delete_photo) { - deleteFile(); - } else if (id == R.id.gallery_star_photo) { - starFile(); - } else if (id == R.id.gallery_share_photo) { - shareFile(); - } - }; - - binding.galleryDownloadPhoto.setOnClickListener(onClickListener); - binding.galleryDeletePhoto.setOnClickListener(onClickListener); - binding.galleryStarPhoto.setOnClickListener(onClickListener); - binding.gallerySharePhoto.setOnClickListener(onClickListener); - } - - private void initViewModel() { - getViewModel().getRefreshLiveData().observe(this, new Observer() { - @Override - public void onChanged(Boolean aBoolean) { - if (aBoolean) { - showProgressDialog(); - } else { - dismissProgressDialog(); - } - } - }); - - getViewModel().getStarLiveData().observe(this, new Observer() { - @Override - public void onChanged(Boolean aBoolean) { - isDataOperated = true; - - if (aBoolean) { - - }else{ - - } - ToastUtils.showLong(aBoolean ? R.string.star_file_succeed : R.string.star_file_failed); - } - }); - - getViewModel().getListLiveData().observe(this, new Observer>() { - @Override - public void onChanged(List direntModels) { - - if (CollectionUtils.isEmpty(direntModels)) { - direntModels = CollectionUtils.newArrayList(currentDirent); - } - - direntList = direntModels; - - notifyFragmentList(); - } - }); - } - - private void loadData() { - getViewModel().getRepoModelFromDB(currentDirent.repo_id, new Consumer() { - @Override - public void accept(RepoModel repoModel) { - if (!repoModel.hasWritePermission()) { - binding.galleryDeletePhoto.setVisibility(View.GONE); - } - - getViewModel().loadData(currentDirent.repo_id, currentDirent.parent_dir); - - } - }); - } - - private void notifyFragmentList() { - if (CollectionUtils.isEmpty(direntList)) { - return; - } - - adapter = new ViewPager2Adapter(this); - List fragments = new ArrayList<>(); - for (DirentModel direntModel : direntList) { - PhotoFragment photoFragment = PhotoFragment.newInstance(direntModel); - photoFragment.setOnPhotoTapListener((view, x, y) -> hideOrShowToolBar()); - fragments.add(photoFragment); - } - - adapter.addFragments(fragments); - - binding.pager.setAdapter(adapter); - binding.pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { - @Override - public void onPageSelected(int position) { - super.onPageSelected(position); - - String fs = String.format(Locale.ROOT, "%d/%d", (position + 1), direntList.size()); - binding.galleryPageIndex.setText(fs); - - DirentModel model = direntList.get(position); - binding.galleryPageName.setText(model.name); - } - }); - - navToSelectedPage(); - } - - private boolean showToolBar = false; - - private void hideOrShowToolBar() { - binding.galleryToolBar.setVisibility(!showToolBar ? View.VISIBLE : View.GONE); - binding.pageIndexContainer.setVisibility(!showToolBar ? View.VISIBLE : View.GONE); - showToolBar = !showToolBar; - } - - /** - * Dynamically navigate to the starting page index selected by user - * by default the starting page index is 0 - */ - private void navToSelectedPage() { - int size = direntList.size(); - int mPageIndex = -1; - for (int i = 0; i < size; i++) { - if (direntList.get(i).name.equals(currentDirent.name)) { - binding.pager.setCurrentItem(i); - binding.galleryPageIndex.setText(String.valueOf(i + 1)); - mPageIndex = i; - break; - } - } - - if (mPageIndex != -1) { - binding.galleryPageIndex.setText(String.format(Locale.ROOT, "%d/%d", (mPageIndex + 1), size)); - } - } - - private DirentModel getSelectedDirent() { - int index = binding.pager.getCurrentItem(); - return direntList.get(index); - } - - private void deleteFile() { - - int position = binding.pager.getCurrentItem(); - - DirentModel direntModel = getSelectedDirent(); - - DeleteFileDialogFragment dialogFragment = DeleteFileDialogFragment.newInstance(CollectionUtils.newArrayList(direntModel.uid)); - dialogFragment.setRefreshListener(new OnRefreshDataListener() { - @Override - public void onActionStatus(boolean isDone) { - if (isDone) { - isDataOperated = true; - - ToastUtils.showLong(R.string.delete_successful); - adapter.removeFragment(position); - adapter.notifyItemRemoved(position); - - if (adapter.getItemCount() == 0) { - setResult(RESULT_OK); - finish(); - } else { - String fs = String.format(Locale.ROOT, "%d/%d", (position + 1), adapter.getFragments().size()); - binding.galleryPageIndex.setText(fs); - } - } - } - }); - dialogFragment.show(getSupportFragmentManager(), DeleteFileDialogFragment.class.getSimpleName()); - } - - private void starFile() { - if (!NetworkUtils.isConnected()) { - ToastUtils.showLong(R.string.network_down); - return; - } - - DirentModel direntModel = getSelectedDirent(); - if (direntModel.starred) { - getViewModel().unStar(direntModel.repo_id, direntModel.full_path); - } else { - getViewModel().star(direntModel.repo_id, direntModel.full_path); - } - } - - private void shareFile() { - DirentModel direntModel = getSelectedDirent(); - Objs.showCreateShareLinkDialog(this, getSupportFragmentManager(), direntModel, false); - } - - - private void downloadFile() { - isDataOperated = true; - - DirentModel direntModel = getSelectedDirent(); - getViewModel().download(direntModel.repo_id, direntModel.full_path); - } - - - -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewViewModel.java index b3a380029..dc5d9a2d5 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/ImagePreviewViewModel.java @@ -64,16 +64,14 @@ public void getFileDetail(String repoId, String path) { getRefreshLiveData().setValue(true); Single userSingle = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getRelatedUsers(repoId); - Single metadataSingle = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getMetadata(repoId); Single detailSingle = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getFileDetail(repoId, path); - Single s = Single.zip(detailSingle, userSingle, metadataSingle, new Function3() { + Single s = Single.zip(detailSingle, userSingle, new BiFunction() { @Override - public FileProfileConfigModel apply(FileDetailModel docDetailModel, UserWrapperModel userWrapperModel, MetadataConfigModel metadataConfigModel) throws Exception { + public FileProfileConfigModel apply(FileDetailModel docDetailModel, UserWrapperModel userWrapperModel) throws Exception { FileProfileConfigModel configModel = new FileProfileConfigModel(); configModel.setDetail(docDetailModel); configModel.setUsers(userWrapperModel); - configModel.setMetadata(metadataConfigModel); return configModel; } }); @@ -86,34 +84,12 @@ public void accept(FileProfileConfigModel fileProfileConfigModel) throws Excepti } }, new Consumer() { @Override - public void accept(Throwable throwable) throws Exception { + public void accept(Throwable throwable) { getRefreshLiveData().setValue(false); } }); } - public void getRepoModelFromDB(String repoId, Consumer consumer) { - //from db - Single> singleDb = AppDatabase.getInstance().repoDao().getRepoById(repoId); - addSingleDisposable(singleDb, new Consumer>() { - @Override - public void accept(List repoModels) throws Exception { - if (consumer != null) { - if (CollectionUtils.isEmpty(repoModels)) { - //no data in sqlite, request RepoApi again - consumer.accept(null); - } else { - consumer.accept(repoModels.get(0)); - } - } - } - }, new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { - SLogs.e(throwable); - } - }); - } public void load(String repoId, String parentPath, String name, boolean isLoadOtherImagesInSameDirectory) { if (TextUtils.isEmpty(repoId) || TextUtils.isEmpty(parentPath) || TextUtils.isEmpty(name)) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/PhotoFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/PhotoFragment.java index dd865c41c..db0aeff56 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/PhotoFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview/PhotoFragment.java @@ -6,43 +6,47 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.webkit.MimeTypeMap; import android.widget.ImageView; -import android.widget.ProgressBar; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.lifecycle.Observer; -import com.blankj.utilcode.util.EncodeUtils; -import com.blankj.utilcode.util.SizeUtils; +import com.blankj.utilcode.util.EncryptUtils; +import com.blankj.utilcode.util.FileUtils; import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; +import com.bumptech.glide.load.resource.gif.GifDrawable; import com.bumptech.glide.request.RequestListener; -import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.target.Target; +import com.bumptech.glide.signature.ObjectKey; import com.github.chrisbanes.photoview.OnPhotoTapListener; import com.seafile.seadroid2.R; -import com.seafile.seadroid2.account.Account; -import com.seafile.seadroid2.account.SupportAccountManager; -import com.seafile.seadroid2.compat.ContextCompatKt; +import com.seafile.seadroid2.SeafException; +import com.seafile.seadroid2.config.OriGlideUrl; import com.seafile.seadroid2.databinding.FragmentPhotoViewBinding; import com.seafile.seadroid2.framework.data.db.entities.DirentModel; -import com.seafile.seadroid2.framework.datastore.DataManager; +import com.seafile.seadroid2.framework.data.db.entities.FileTransferEntity; import com.seafile.seadroid2.framework.util.GlideApp; -import com.seafile.seadroid2.ui.base.fragment.BaseFragment; +import com.seafile.seadroid2.framework.util.GlideRequest; +import com.seafile.seadroid2.framework.util.SLogs; +import com.seafile.seadroid2.framework.util.ThumbnailUtils; +import com.seafile.seadroid2.ui.base.fragment.BaseFragmentWithVM; +import com.seafile.seadroid2.ui.media.PhotoViewModel; -import java.io.File; +import kotlin.Pair; -public class PhotoFragment extends BaseFragment { - - private Account account; +public class PhotoFragment extends BaseFragmentWithVM { private String repoId, repoName, fullPath; private String imageUrl; private OnPhotoTapListener onPhotoTapListener; private FragmentPhotoViewBinding binding; - + private String serverUrl; public void setOnPhotoTapListener(OnPhotoTapListener onPhotoTapListener) { this.onPhotoTapListener = onPhotoTapListener; @@ -56,35 +60,25 @@ public static PhotoFragment newInstance(String url) { return fragment; } - public static PhotoFragment newInstance(String repoId, String repoName, String fullPath) { - + public static PhotoFragment newInstance(String serverUrl, String repoId, String repoName, String fullPath) { Bundle args = new Bundle(); args.putString("repoId", repoId); args.putString("repoName", repoName); args.putString("fullPath", fullPath); - + args.putString("serverUrl", serverUrl); PhotoFragment fragment = new PhotoFragment(); fragment.setArguments(args); return fragment; } - public static PhotoFragment newInstance(DirentModel direntModel) { - Bundle args = new Bundle(); - - args.putString("repoId", direntModel.repo_id); - args.putString("repoName", direntModel.repo_name); - args.putString("fullPath", direntModel.full_path); - PhotoFragment fragment = new PhotoFragment(); - fragment.setArguments(args); - return fragment; + public static PhotoFragment newInstance(String serverUrl, DirentModel direntModel) { + return newInstance(serverUrl, direntModel.repo_id, direntModel.repo_name, direntModel.full_path); } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - account = SupportAccountManager.getInstance().getCurrentAccount(); - Bundle args = getArguments(); if (args == null) { return; @@ -94,6 +88,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { repoName = args.getString("repoName"); fullPath = args.getString("fullPath"); imageUrl = args.getString("image_url"); + serverUrl = args.getString("serverUrl"); if (TextUtils.isEmpty(repoId) && TextUtils.isEmpty(imageUrl)) { throw new IllegalStateException("the args is invalid"); @@ -124,52 +119,83 @@ public void onPhotoTap(ImageView view, float x, float y) { } }); - loadImage(); - } + intViewModel(); - private void loadImage() { if (!TextUtils.isEmpty(imageUrl)) { - glideLoad(imageUrl); - return; + loadSingleUrl(imageUrl); + } else { + loadThumbnailAndRequestRawUrl(); } + } - File file = DataManager.getLocalRepoFile(account, repoId, repoName, fullPath); - if (file.exists()) { - binding.progressBar.setVisibility(View.GONE); + private void intViewModel() { + getViewModel().getSeafExceptionLiveData().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(SeafException e) { + binding.photoView.setImageResource(R.drawable.icon_image_error_filled); + binding.progressBar.setVisibility(View.GONE); + } + }); - GlideApp.with(requireContext()) - .load(file) - .into(binding.photoView); - return; - } + getViewModel().getCheckLocalLiveData().observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(Pair pair) { + DirentModel direntModel = pair.getFirst(); - String url = getUrl(); - if (url == null) { - binding.photoView.setImageResource(R.drawable.icon_image_error_filled); - return; - } + if (direntModel == null) { + binding.photoView.setImageResource(R.drawable.icon_image_error_filled); + binding.progressBar.setVisibility(View.GONE); + return; + } + + FileTransferEntity transferEntity = pair.getSecond(); + if (transferEntity != null && FileUtils.isFileExists(transferEntity.target_path)) { + //no exists local file + if (isGif(fullPath)) { + loadOriGifUrl(transferEntity.target_path); + } else { + loadOriUrl(transferEntity.target_path); + } + } else { + if (isGif(fullPath)) { + getViewModel().download(direntModel); + } else { + getViewModel().requestOriginalUrl(direntModel); + } + } + } + }); + + getViewModel().getOriginalUrlLiveData().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String oriUrl) { + loadOriUrl(oriUrl); + } + }); - glideLoad(url); + getViewModel().getDownloadedPathLiveData().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String rawPath) { + loadSingleUrl(rawPath); + } + }); } - private void glideLoad(String url) { - RequestOptions opt = new RequestOptions() - .skipMemoryCache(true) - .error(R.drawable.icon_image_error_filled) - .diskCacheStrategy(DiskCacheStrategy.NONE); - GlideApp.with(requireContext()) + private void loadSingleUrl(String url) { + GlideApp.with(this) .load(url) - .apply(opt) - .fitCenter() + .error(R.drawable.icon_image_error_filled) + .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) + .transition(DrawableTransitionOptions.withCrossFade()) .listener(new RequestListener() { @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target target, boolean isFirstResource) { binding.progressBar.setVisibility(View.GONE); return false; } @Override - public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target target, @NonNull DataSource dataSource, boolean isFirstResource) { binding.progressBar.setVisibility(View.GONE); return false; } @@ -177,20 +203,100 @@ public boolean onResourceReady(Drawable resource, Object model, Target .into(binding.photoView); } - private String getUrl() { - Account account = SupportAccountManager.getInstance().getCurrentAccount(); - if (account == null) { - return null; + private void loadThumbnailAndRequestRawUrl() { + getViewModel().checkLocal(repoId, fullPath); + } + + private void loadOriUrl(String oriUrl) { + String thumbnailUrl = convertThumbnailUrl(fullPath); + String thumbKey = EncryptUtils.encryptMD5ToString(thumbnailUrl); + // load thumbnail first + GlideRequest thumbnailRequest = GlideApp.with(this) + .load(thumbnailUrl) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .signature(new ObjectKey(thumbKey)) + .addListener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target target, boolean isFirstResource) { + return false; + } + + @Override + public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target target, @NonNull DataSource dataSource, boolean isFirstResource) { +// SLogs.e("缩略图:" + dataSource.name() + ": " + isFirstResource + ": " + thumbKey + ": " + thumbnailUrl); + return false; + } + }); + + String oriCacheKey = EncryptUtils.encryptMD5ToString(repoId + fullPath); + GlideApp.with(this) + .load(new OriGlideUrl(oriUrl, oriCacheKey)) + .thumbnail(thumbnailRequest) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .error(R.drawable.icon_image_error_filled) + .fallback(R.drawable.icon_image_error_filled) + .transition(DrawableTransitionOptions.withCrossFade()) + .listener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target target, boolean isFirstResource) { + binding.progressBar.setVisibility(View.GONE); + return false; + } + + @Override + public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target target, @NonNull DataSource dataSource, boolean isFirstResource) { + binding.progressBar.setVisibility(View.GONE); + // 图片加载成功 +// SLogs.e("原图:" + dataSource.name() + ": " + isFirstResource + ": " + oriCacheKey + ": " + oriUrl); + return false; + } + }) + .into(binding.photoView); + } + + + private void loadOriGifUrl(String rawUrl) { + GlideApp.with(this) + .asGif() + .load(rawUrl) + .diskCacheStrategy(DiskCacheStrategy.NONE)// + .error(R.drawable.icon_image_error_filled) + .fallback(R.drawable.icon_image_error_filled) + .placeholder(binding.photoView.getDrawable()) + .listener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target target, boolean isFirstResource) { + binding.progressBar.setVisibility(View.GONE); + return false; + } + + @Override + public boolean onResourceReady(@NonNull GifDrawable resource, @NonNull Object model, Target target, @NonNull DataSource dataSource, boolean isFirstResource) { + binding.progressBar.setVisibility(View.GONE); + // 图片加载成功 + SLogs.e(dataSource.name() + ": " + isFirstResource + ": " + rawUrl); + return false; + } + }) + .into(binding.photoView); + } + + private boolean isGif(String fileName) { + if (TextUtils.isEmpty(fileName)) { + return false; } -// //https://dev.seafile.com/seafhttp/repos/4809a6f3-250c-4435-bdd8-b68f34c128d1/files//6f64603fd19f9ec45d05ec379e69e22.gif/?op=download -// //https://dev.seafile.com/seahub/repo/4809a6f3-250c-4435-bdd8-b68f34c128d1/raw/6f64603fd19f9ec45d05ec379e69e22.gif -// if (direntModel.name.toLowerCase().endsWith(".gif")) { -// return String.format(Locale.ROOT, "%srepo/%s/raw/%s", account.getServer(), direntModel.repo_id, direntModel.name); -// } + String f = MimeTypeMap.getFileExtensionFromUrl(fileName); + String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(f); + return mime != null && mime.equalsIgnoreCase("image/gif"); + } -// return String.format("%srepo/%s/raw%s", account.getServer(), repoId, fileFullPath); - int size = SizeUtils.dp2px(300); - return String.format("%sapi2/repos/%s/thumbnail/?p=%s&size=%s", account.getServer(), repoId, EncodeUtils.urlEncode(fullPath), size); + private String convertThumbnailUrl(String fullPath) { + if (TextUtils.isEmpty(serverUrl)) { + return null; + } + + return ThumbnailUtils.convertThumbnailUrl(serverUrl, repoId, fullPath); } + } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselAdapter.java index 04e15a79a..9b6e2a5b4 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselAdapter.java @@ -31,19 +31,25 @@ import androidx.recyclerview.widget.RecyclerView; import com.blankj.utilcode.util.EncodeUtils; +import com.blankj.utilcode.util.EncryptUtils; import com.blankj.utilcode.util.ScreenUtils; import com.blankj.utilcode.util.SizeUtils; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.request.RequestOptions; +import com.bumptech.glide.signature.ObjectKey; import com.google.common.base.Strings; import com.seafile.seadroid2.R; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.framework.data.db.entities.DirentModel; +import com.seafile.seadroid2.framework.http.HttpIO; import com.seafile.seadroid2.framework.util.GlideApp; +import com.seafile.seadroid2.framework.util.ThumbnailUtils; public class CarouselAdapter extends ListAdapter { + boolean isLogin = SupportAccountManager.getInstance().isLogin(); + private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback<>() { @Override @@ -129,40 +135,49 @@ private void bind(CarouselItemViewHolder holder, int pos) { holder.imageView.setVisibility(View.VISIBLE); holder.itemView.getLayoutParams().width = itemWidth; - String url = getUrl(model.repo_id, model.full_path); - if (url == null) { - holder.imageView.setImageResource(R.drawable.icon_image_error_filled); + + String thumbnailUrl = convertThumbnailUrl(model.repo_id, model.full_path); + if (TextUtils.isEmpty(thumbnailUrl)) { + holder.imageView.setImageResource(R.drawable.shape_solid_grey100_radius_4); return; } - - RequestOptions opt = new RequestOptions() - .skipMemoryCache(true) - .error(R.drawable.icon_image_error_filled) - .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC); + String thumbKey = EncryptUtils.encryptMD5ToString(thumbnailUrl); GlideApp.with(holder.imageView) - .load(url) + .load(thumbnailUrl) + .signature(new ObjectKey(thumbKey)) .apply(opt) .fitCenter() .into(holder.imageView); } - private String getUrl(String repoId, String fullPath) { - Account account = SupportAccountManager.getInstance().getCurrentAccount(); - if (account == null) { + private final RequestOptions opt = new RequestOptions() + .placeholder(R.drawable.shape_solid_grey100_radius_4) + .fallback(R.drawable.shape_solid_grey100_radius_4) + .error(R.drawable.icon_format_pic) + .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC); + + private String convertThumbnailUrl(String repoId, String fullPath) { + String serverUrl = getServerUrl(); + if (TextUtils.isEmpty(serverUrl)) { return null; } -// //https://dev.seafile.com/seafhttp/repos/4809a6f3-250c-4435-bdd8-b68f34c128d1/files//6f64603fd19f9ec45d05ec379e69e22.gif/?op=download -// //https://dev.seafile.com/seahub/repo/4809a6f3-250c-4435-bdd8-b68f34c128d1/raw/6f64603fd19f9ec45d05ec379e69e22.gif -// if (direntModel.name.toLowerCase().endsWith(".gif")) { -// return String.format(Locale.ROOT, "%srepo/%s/raw/%s", account.getServer(), direntModel.repo_id, direntModel.name); -// } - -// return String.format("%srepo/%s/raw%s", account.getServer(), repoId, fileFullPath); - int size = SizeUtils.dp2px(300); - return String.format("%sapi2/repos/%s/thumbnail/?p=%s&size=%s", account.getServer(), repoId, EncodeUtils.urlEncode(fullPath), size); + return ThumbnailUtils.convertThumbnailUrl(serverUrl, repoId, fullPath); } + private String serverUrl; + + private String getServerUrl() { + if (!isLogin) { + return null; + } + + if (TextUtils.isEmpty(serverUrl)) { + serverUrl = HttpIO.getCurrentInstance().getServerUrl(); + } + + return serverUrl; + } public static class CarouselItemViewHolder extends RecyclerView.ViewHolder { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java index 0a4f0bb6f..7ea911e93 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/CarouselImagePreviewActivity.java @@ -3,6 +3,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.res.Configuration; import android.os.Bundle; import android.text.TextUtils; import android.util.Pair; @@ -21,13 +22,16 @@ import androidx.fragment.app.Fragment; import androidx.lifecycle.Observer; import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager2.widget.ViewPager2; import com.blankj.utilcode.util.BarUtils; import com.blankj.utilcode.util.CollectionUtils; import com.blankj.utilcode.util.NetworkUtils; +import com.blankj.utilcode.util.ScreenUtils; import com.blankj.utilcode.util.ToastUtils; import com.seafile.seadroid2.R; +import com.seafile.seadroid2.account.SupportAccountManager; import com.seafile.seadroid2.compat.ContextCompatKt; import com.seafile.seadroid2.context.CopyMoveContext; import com.seafile.seadroid2.databinding.ActivityCarouselImagePreviewBinding; @@ -38,7 +42,9 @@ import com.seafile.seadroid2.framework.data.model.activities.ActivityModel; import com.seafile.seadroid2.framework.data.model.sdoc.FileProfileConfigModel; import com.seafile.seadroid2.framework.data.model.search.SearchModel; +import com.seafile.seadroid2.framework.http.HttpIO; import com.seafile.seadroid2.framework.util.Objs; +import com.seafile.seadroid2.framework.util.SLogs; import com.seafile.seadroid2.framework.util.Utils; import com.seafile.seadroid2.ui.adapter.ViewPager2Adapter; import com.seafile.seadroid2.ui.base.BaseActivityWithVM; @@ -49,6 +55,7 @@ import com.seafile.seadroid2.ui.media.image_preview.ImagePreviewViewModel; import com.seafile.seadroid2.ui.media.image_preview.PhotoFragment; import com.seafile.seadroid2.ui.selector.ObjSelectorActivity; +import com.seafile.seadroid2.view.snap_recyclerview.GravitySnapHelper; import java.util.ArrayList; import java.util.HashMap; @@ -62,11 +69,14 @@ public class CarouselImagePreviewActivity extends BaseActivityWithVM direntList; private List carouselDirentList; - private boolean isLightMode = true; + private boolean isHide = false; + private boolean isNightMode = false; private boolean isDataOperated = false; private String repoId, repoName, parentDir, name; private boolean load_other_images_in_same_directory = false; + private int carouselItemWidth = 0; + private int carouselItemMargin = 0; // public static Intent startThisFromDocsComment(Context context, String url) { // Intent intent = new Intent(context, CarouselImagePreviewActivity.class); @@ -125,15 +135,22 @@ protected void onCreate(Bundle savedInstanceState) { // full screen getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + isNightMode = currentNightMode == Configuration.UI_MODE_NIGHT_YES; + //init status bar - int color = ContextCompatKt.getColorCompat(this, R.color.material_grey_100_translucent); + int color = ContextCompatKt.getColorCompat(this, R.color.bar_background_color); BarUtils.setStatusBarColor(this, color); - BarUtils.setStatusBarLightMode(this, isLightMode); + BarUtils.setStatusBarLightMode(this, !isNightMode); //init toolbar margin top ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) binding.toolbarActionbar.getLayoutParams(); layoutParams.topMargin = BarUtils.getStatusBarHeight(); + + carouselItemWidth = getResources().getDimensionPixelSize(R.dimen.carousel_item_width); + carouselItemMargin = getResources().getDimensionPixelSize(R.dimen.carousel_item_margin); + Toolbar toolbar = getActionBarToolbar(); setSupportActionBar(toolbar); if (getSupportActionBar() != null) { @@ -162,9 +179,10 @@ public void handleOnBackPressed() { initView(); - initAdapter(); - initCarouselAdapter(); - bindPager(); + initPager(); + initCarouselRecyclerView(); + +// bindPager(); initViewModel(); @@ -260,7 +278,7 @@ public void onChanged(FileProfileConfigModel configModel) { }); } - private void initAdapter() { + private void initPager() { adapter = new ViewPager2Adapter(this); binding.pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { @Override @@ -269,13 +287,20 @@ public void onPageSelected(int position) { notifyCurrentStarredStatus(); } }); + binding.pager.setOffscreenPageLimit(7); binding.pager.setAdapter(adapter); + + // + if (isNightMode) { + int color = ContextCompatKt.getColorCompat(this, R.color.bar_background_color); + binding.pager.setBackgroundColor(color); + } } private final LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); private final GravitySnapHelper gravitySnapHelper = new GravitySnapHelper(Gravity.CENTER); - private void initCarouselAdapter() { + private void initCarouselRecyclerView() { carouselAdapter = new CarouselAdapter(this, new CarouselAdapter.CarouselItemListener() { @Override public void onItemClicked(DirentModel item, int snapPosition) { @@ -288,11 +313,49 @@ public void onItemClicked(DirentModel item, int snapPosition) { binding.recyclerView.addOnScrollListener(new CenterScaleXYRecyclerViewScrollListener(this)); + int screenWidth = ScreenUtils.getAppScreenWidth();//1080/3=360 + int sidePadding = (screenWidth - carouselItemWidth) / 2 - carouselItemMargin * 2;//170-4=166 + binding.recyclerView.addItemDecoration(new LinearEdgeDecoration(sidePadding, sidePadding, RecyclerView.HORIZONTAL, false)); + gravitySnapHelper.attachToRecyclerView(binding.recyclerView); } private void bindPager() { - PagerSnapBinders.bind(binding.pager, gravitySnapHelper); + bindPager(binding.pager, gravitySnapHelper); + } + + private int whoScroll = -1; + + public void bindPager(ViewPager2 pager, GravitySnapHelper snapHelper) { + pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + if (whoScroll == 1) { + whoScroll = -1; + return; + } + + whoScroll = 0; + SLogs.e("currentPagerPosition: " + position); + snapHelper.smoothScrollToPosition(position); + } + }); + + snapHelper.setSnapListener(new GravitySnapHelper.SnapListener() { + @Override + public void onSnap(int snapPosition) { + + if (whoScroll == 0) { + whoScroll = -1; + return; + } + + whoScroll = 1; + SLogs.e("currentSnapPosition: " + snapPosition); + pager.setCurrentItem(snapPosition, false); + } + }); } private void submitData(Pair> pair) { @@ -311,10 +374,9 @@ private void submitData(Pair> pair) { direntList = pair.second; // - List fragments = new ArrayList<>(); for (DirentModel direntModel : direntList) { - PhotoFragment photoFragment = PhotoFragment.newInstance(direntModel); + PhotoFragment photoFragment = PhotoFragment.newInstance(getServerUrl(), direntModel); photoFragment.setOnPhotoTapListener((view, x, y) -> hideOrShowToolBar()); fragments.add(photoFragment); } @@ -323,9 +385,9 @@ private void submitData(Pair> pair) { adapter.notifyItemRangeInserted(0, direntList.size()); carouselDirentList = new ArrayList<>(); - carouselDirentList.add(new DirentModel()); +// carouselDirentList.add(new DirentModel()); carouselDirentList.addAll(direntList); - carouselDirentList.add(new DirentModel()); +// carouselDirentList.add(new DirentModel()); carouselAdapter.submitList(carouselDirentList); @@ -334,27 +396,44 @@ private void submitData(Pair> pair) { public void run() { navToSelectedPage(); } - }, 100); + }, 50); + } + + private String server_url; + private final boolean isLogin = SupportAccountManager.getInstance().isLogin(); + + private String getServerUrl() { + if (!TextUtils.isEmpty(server_url)) { + return server_url; + } + + if (!isLogin) { + return null; + } + + server_url = HttpIO.getCurrentInstance().getServerUrl(); + return server_url; } private void hideOrShowToolBar() { - binding.galleryToolBar.setVisibility(isLightMode ? View.GONE : View.VISIBLE); - binding.recyclerView.setVisibility(isLightMode ? View.GONE : View.VISIBLE); - binding.toolbarActionbar.setVisibility(isLightMode ? View.INVISIBLE : View.VISIBLE); + binding.galleryToolBar.setVisibility(isHide ? View.VISIBLE : View.GONE); + binding.recyclerView.setVisibility(isHide ? View.VISIBLE : View.GONE); + binding.toolbarActionbar.setVisibility(isHide ? View.VISIBLE : View.INVISIBLE); - if (isLightMode) { - binding.pager.setBackgroundColor(ContextCompatKt.getColorCompat(this, R.color.material_grey_919)); + if (isNightMode) { + //The background color has been set in the #initPager(), and no longer updated in night mode } else { - binding.pager.setBackgroundColor(ContextCompatKt.getColorCompat(this, R.color.material_grey_100)); - } + int color = ContextCompatKt.getColorCompat(this, isHide ? R.color.material_grey_100 : R.color.material_grey_911); + binding.pager.setBackgroundColor(color); - int color = ContextCompatKt.getColorCompat(this, isLightMode ? R.color.material_grey_919_ff : R.color.material_grey_100_translucent); - BarUtils.setNavBarColor(this, color); - BarUtils.setStatusBarColor(this, color); - BarUtils.setStatusBarLightMode(this, !isLightMode); - BarUtils.setNavBarLightMode(this, !isLightMode); + BarUtils.setNavBarColor(this, color); + BarUtils.setStatusBarColor(this, color); + + BarUtils.setStatusBarLightMode(this, isHide); + BarUtils.setNavBarLightMode(this, isHide); + } - isLightMode = !isLightMode; + isHide = !isHide; } /** @@ -373,8 +452,12 @@ private void navToSelectedPage() { } if (index != -1) { - binding.pager.setCurrentItem(index); + int x = (carouselItemWidth + carouselItemMargin * 2) * index; + binding.recyclerView.scrollBy(x, 0); + binding.pager.setCurrentItem(index, false); } + + bindPager(); } private void notifyCurrentStarredStatus() { @@ -384,9 +467,9 @@ private void notifyCurrentStarredStatus() { } if (direntModel.starred) { - binding.galleryStarPhoto.setImageResource(R.drawable.baseline_star_filled_24); + binding.galleryStarPhoto.setImageResource(R.drawable.baseline_starred_filled_24); } else { - binding.galleryStarPhoto.setImageResource(R.drawable.baseline_star_outline_24); + binding.galleryStarPhoto.setImageResource(R.drawable.baseline_starred_outline_24); } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/PagerSnapBinders.java b/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/PagerSnapBinders.java deleted file mode 100644 index 0d227c48d..000000000 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/PagerSnapBinders.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.seafile.seadroid2.ui.media.image_preview2; - -import androidx.viewpager2.widget.ViewPager2; - -import com.seafile.seadroid2.framework.util.SLogs; - -public class PagerSnapBinders { - private static int whoScroll = -1; - - public static void bind(ViewPager2 pager, GravitySnapHelper snapHelper) { - - pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { - @Override - public void onPageSelected(int position) { - super.onPageSelected(position); - if (whoScroll == 1) { - whoScroll = -1; - return; - } - - whoScroll = 0; - - SLogs.e("currentPagerPosition: " + position); - position++; - snapHelper.smoothScrollToPosition(position); - } - }); - - snapHelper.setSnapListener(new GravitySnapHelper.SnapListener() { - @Override - public void onSnap(int snapPosition) { - - if (whoScroll == 0) { - whoScroll = -1; - return; - } - - whoScroll = 1; - - SLogs.e("currentSnapPosition: " + snapPosition); - - snapPosition--; - pager.setCurrentItem(snapPosition, true); - } - }); - } - -} diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java index 929eee320..eb57606ed 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java @@ -16,8 +16,13 @@ import com.blankj.utilcode.util.CollectionUtils; import com.blankj.utilcode.util.EncodeUtils; +import com.blankj.utilcode.util.EncryptUtils; import com.blankj.utilcode.util.FileUtils; import com.blankj.utilcode.util.SizeUtils; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.request.RequestOptions; +import com.bumptech.glide.signature.ObjectKey; import com.seafile.seadroid2.R; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; @@ -41,6 +46,8 @@ import com.seafile.seadroid2.framework.data.model.search.SearchModel; import com.seafile.seadroid2.framework.http.HttpIO; import com.seafile.seadroid2.framework.util.GlideApp; +import com.seafile.seadroid2.framework.util.GlideOptions; +import com.seafile.seadroid2.framework.util.ThumbnailUtils; import com.seafile.seadroid2.framework.util.Utils; import com.seafile.seadroid2.ui.base.adapter.BaseMultiAdapter; import com.seafile.seadroid2.ui.repo.vh.AccountViewHolder; @@ -369,7 +376,7 @@ private void onBindDirents(DirentViewHolder holder, DirentModel model, @NonNull // holder.binding.getRoot().setBackground(AnimatedStateListDrawableCompatUtils.createDrawableCompat(getContext())); - if (repoEncrypted || !Utils.isViewableImage(model.name)) { + if (model.isDir() || repoEncrypted || (!Utils.isViewableImage(model.name) && !Utils.isVideoFile(model.name))) { holder.binding.itemIcon.setImageResource(model.getIcon()); } else { loadImage(model, holder.binding.itemIcon); @@ -473,7 +480,7 @@ private void onBindDirentsGrid(DirentGridViewHolder holder, DirentModel model, @ holder.binding.itemOutline.setVisibility(View.VISIBLE); } - if (repoEncrypted || !Utils.isViewableImage(model.name)) { + if (model.isDir() || repoEncrypted || (!Utils.isViewableImage(model.name) && !Utils.isVideoFile(model.name))) { holder.binding.itemIcon.setScaleType(ImageView.ScaleType.FIT_CENTER); holder.binding.itemIcon.setImageResource(model.getIcon()); } else { @@ -521,7 +528,7 @@ private void onBindDirentsGallery(DirentGalleryViewHolder holder, DirentModel mo } // holder.binding.getRoot().setBackground(AnimatedStateListDrawableCompatUtils.createDrawableCompat(getContext())); - if (repoEncrypted || !Utils.isViewableImage(model.name)) { + if (model.isDir() || repoEncrypted || (!Utils.isViewableImage(model.name) && !Utils.isVideoFile(model.name))) { holder.binding.itemIcon.setImageResource(model.getIcon()); } else { loadImage(model, holder.binding.itemIcon); @@ -551,7 +558,8 @@ private void onBindSearch(DirentViewHolder holder, SearchModel model) { // holder.binding.getRoot().setBackground(AnimatedStateListDrawableCompatUtils.createDrawableCompat(getContext())); - if (repoEncrypted || !Utils.isViewableImage(model.name)) { + + if (repoEncrypted || (!Utils.isViewableImage(model.name) && !Utils.isVideoFile(model.name))) { holder.binding.itemIcon.setImageResource(model.getIcon()); } else { DirentModel direntModel = new DirentModel(); @@ -570,39 +578,32 @@ private void onBindSearch(DirentViewHolder holder, SearchModel model) { } private void loadImage(DirentModel direntModel, ImageView imageView) { - - if (direntModel.name.toLowerCase().endsWith(".gif")) { - imageView.setImageResource(direntModel.getIcon()); - } else { - String url = convertThumbnailUrl(direntModel); - if (TextUtils.isEmpty(url)) { - GlideApp.with(getContext()).load(R.drawable.file_image) - .apply(GlideLoadConfig.getOptions()) - .into(imageView); - } else { - GlideApp.with(getContext()).load(url) - .apply(GlideLoadConfig.getOptions()) - .into(imageView); - } + String thumbnailUrl = convertThumbnailUrl(direntModel); + if (TextUtils.isEmpty(thumbnailUrl)) { + GlideApp.with(getContext()) + .load(direntModel.getIcon()) + .apply(GlideLoadConfig.getCacheableThumbnailOptions()) + .into(imageView); + return; } - } - private String convertThumbnailUrl(DirentModel direntModel) { - return convertThumbnailUrl(direntModel, 128); - } + String thumbKey = EncryptUtils.encryptMD5ToString(thumbnailUrl); - private String convertMiddleUrl(DirentModel direntModel) { - return convertThumbnailUrl(direntModel, 256); + GlideApp.with(getContext()) + .load(thumbnailUrl) + .signature(new ObjectKey(thumbKey)) + .apply(GlideLoadConfig.getCustomDrawableOptions(direntModel.getIcon())) + .into(imageView); } private String server_url; + private final boolean isLogin = SupportAccountManager.getInstance().isLogin(); private String getServerUrl() { if (!TextUtils.isEmpty(server_url)) { return server_url; } - boolean isLogin = SupportAccountManager.getInstance().isLogin(); if (!isLogin) { return null; } @@ -611,14 +612,12 @@ private String getServerUrl() { return server_url; } - private String convertThumbnailUrl(DirentModel direntModel, int size) { + private String convertThumbnailUrl(DirentModel direntModel) { String serverUrl = getServerUrl(); if (TextUtils.isEmpty(serverUrl)) { return null; } - - String newFilePath = EncodeUtils.urlEncode(direntModel.full_path); - return String.format(Locale.ROOT, "%sapi2/repos/%s/thumbnail/?p=%s&size=%d", serverUrl, direntModel.repo_id, newFilePath, size); + return ThumbnailUtils.convertThumbnailUrl(serverUrl, direntModel.repo_id, direntModel.full_path); } public void setOnActionMode(boolean on) { 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 5a1bdbce3..e6bec8389 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 @@ -150,7 +150,11 @@ public void onCreate(@Nullable Bundle savedInstanceState) { @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { binding = LayoutFastRvBinding.inflate(inflater, container, false); - binding.swipeRefreshLayout.setOnRefreshListener(() -> loadData(true)); + + binding.swipeRefreshLayout.setOnRefreshListener(() -> { + removeScrolledPosition(); + loadData(true); + }); return binding.getRoot(); } @@ -172,9 +176,19 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat @Override public void onFirstResume() { super.onFirstResume(); + + restoreNavContext(); + loadData(true); } + private void restoreNavContext() { + NavContext navContext = getNavContext(); + navContext.restoreNavContextFromSp(); + mainViewModel.getOnNavContextChangeListenerLiveData().setValue(true); + } + + @Override public void onOtherResume() { super.onOtherResume(); @@ -192,6 +206,15 @@ private void initRv() { .build(); binding.rv.addItemDecoration(decoration); + binding.rv.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + if (RecyclerView.SCROLL_STATE_IDLE == newState){ + saveScrollPosition(); + } + } + }); //layout manager binding.rv.setLayoutManager(getGridLayoutManager()); @@ -299,7 +322,6 @@ private void initAdapter() { binding.rv.setAdapter(adapter); } - private void initViewModel() { getViewModel().getRefreshLiveData().observe(getViewLifecycleOwner(), new Observer() { @Override @@ -713,7 +735,7 @@ private void switchRecyclerViewLayout(FileViewType newViewType) { } //If SPAN_COUNT is updated, then the data in the ScrollPosition is meaningless - removeScrollPositionExcludeRoot(); + removeScrolledPositionExcludeRoot(); lastViewType = newViewType; } @@ -811,7 +833,6 @@ public void loadData() { } public void loadData(boolean forceRefresh) { - removeCurrentScrollPosition(); if (forceRefresh) { long now = TimeUtils.getNowMills(); @@ -855,9 +876,9 @@ private void showEmptyTip() { if (FileViewType.GALLERY == type) { showErrorView(R.string.no_album_type_data); } else if (getNavContext().inRepo()) { - showErrorView(R.string.no_repo); - } else { showErrorView(R.string.dir_empty); + } else { + showErrorView(R.string.no_repo); } } @@ -877,6 +898,12 @@ private void showErrorView(int textRes) { private void showErrorView(String msg) { adapter.submitList(null); TextView tipView = TipsViews.getTipTextView(requireContext()); + tipView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + reloadData(); + } + }); tipView.setText(msg); adapter.setStateView(tipView); adapter.setStateViewEnable(true); @@ -884,8 +911,6 @@ private void showErrorView(String msg) { private void navTo(BaseModel model) { //save - saveScrollPosition(); - if (model instanceof RepoModel model1) { getNavContext().push(model1); @@ -928,7 +953,7 @@ public boolean backTo() { adapter.setOnActionMode(false); } else { // - removeCurrentScrollPosition(); + removeScrolledPosition(); // getNavContext().pop(); getViewModel().loadData(getNavContext(), false); @@ -986,8 +1011,9 @@ private void saveScrollPosition() { final int index = gridLayoutManager.findFirstVisibleItemPosition(); final ScrollState state = new ScrollState(index, top); + SLogs.d(state.toString()); - removeCurrentScrollPosition(); + removeScrolledPosition(); if (!getNavContext().inRepo()) { scrollPositions.put(KEY_REPO_SCROLL_POSITION, state); @@ -997,7 +1023,7 @@ private void saveScrollPosition() { } } - private void removeScrollPositionExcludeRoot() { + private void removeScrolledPositionExcludeRoot() { if (!scrollPositions.isEmpty()) { ScrollState rootState = scrollPositions.get(KEY_REPO_SCROLL_POSITION); scrollPositions.clear(); @@ -1005,7 +1031,7 @@ private void removeScrollPositionExcludeRoot() { } } - private void removeCurrentScrollPosition() { + private void removeScrolledPosition() { if (!getNavContext().inRepo()) { scrollPositions.remove(KEY_REPO_SCROLL_POSITION); } else { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java index e80886d16..337e89a97 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java @@ -240,6 +240,7 @@ public void accept(List direntModels) throws Exception { private void loadDirentsFromRemote(Account account, NavContext context) { if (!NetworkUtils.isConnected()) { getRefreshLiveData().setValue(false); + getSeafExceptionLiveData().setValue(SeafException.networkException); return; } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/ScrollState.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/ScrollState.java index 9951c5e6e..be6c0d0d4 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/ScrollState.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/ScrollState.java @@ -8,4 +8,12 @@ public ScrollState(int index, int top) { this.index = index; this.top = top; } + + @Override + public String toString() { + return "ScrollState{" + + "index=" + index + + ", top=" + top + + '}'; + } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentService.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentService.java index 19f3df13d..f411fe28e 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentService.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/DocsCommentService.java @@ -38,7 +38,7 @@ public interface DocsCommentService { 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); + Single getRecords(@Path("repo_id") String repoId, @Query("parent_dir") String parentDir, @Query("name") String name, @Query("file_name") String fileName); // @GET("api/v1/docs/{uuid}/comment/") 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..602210998 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocViewModel.java @@ -0,0 +1,201 @@ +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.FileDetailModel; +import com.seafile.seadroid2.framework.data.model.sdoc.FileProfileConfigModel; +import com.seafile.seadroid2.framework.data.model.sdoc.FileRecordWrapperModel; +import com.seafile.seadroid2.framework.data.model.sdoc.MetadataConfigModel; +import com.seafile.seadroid2.framework.data.model.sdoc.OutlineItemModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocOutlineWrapperModel; +import com.seafile.seadroid2.framework.data.model.sdoc.SDocPageOptionsModel; +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.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import io.reactivex.Single; +import io.reactivex.functions.BiFunction; +import io.reactivex.functions.Consumer; +import io.reactivex.functions.Function3; + +public class SDocViewModel extends BaseViewModel { + + private final MutableLiveData _fileProfileConfigLiveData = new MutableLiveData<>(); + private final MutableLiveData _fileRecordLiveData = new MutableLiveData<>(); + private final MutableLiveData> _sdocElementListLiveData = new MutableLiveData<>(); + + public MutableLiveData getFileDetailLiveData() { + return _fileProfileConfigLiveData; + } + + public MutableLiveData getSdocRecordLiveData() { + return _fileRecordLiveData; + } + + public MutableLiveData> getSdocElementLiveData() { + return _sdocElementListLiveData; + } + + public void loadFileDetail(String repoId, String path, boolean isMetadataEnable) { + + Single userSingle = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getRelatedUsers(repoId); + + //Even if isMetadataEnable is enabled, you still need to check whether the enable field of MetadataConfigModel is available + Single metadataSingle = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getMetadata(repoId); + Single detailSingle = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getFileDetail(repoId, path); + + Single s; + if (isMetadataEnable) { + s = Single.zip(detailSingle, userSingle, metadataSingle, new Function3() { + @Override + public FileProfileConfigModel apply(FileDetailModel fileDetailModel, UserWrapperModel userWrapperModel, MetadataConfigModel metadataConfigModel) throws Exception { + FileProfileConfigModel configModel = new FileProfileConfigModel(); + configModel.setDetail(fileDetailModel); + configModel.setUsers(userWrapperModel); + configModel.setMetadataConfigModel(metadataConfigModel); + return configModel; + } + }); + } else { + s = Single.zip(detailSingle, userSingle, new BiFunction() { + @Override + public FileProfileConfigModel apply(FileDetailModel fileDetailModel, UserWrapperModel userWrapperModel) throws Exception { + FileProfileConfigModel configModel = new FileProfileConfigModel(); + configModel.setDetail(fileDetailModel); + configModel.setUsers(userWrapperModel); + return configModel; + } + }); + } + + addSingleDisposable(s, new Consumer() { + @Override + public void accept(FileProfileConfigModel fileProfileConfigModel) throws Exception { + getFileDetailLiveData().setValue(fileProfileConfigModel); + } + }); + } + + public void loadRecords(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; + } + + if (TextUtils.isEmpty(parent_dir)) { + parent_dir = "/"; + } + + Single single = HttpIO.getCurrentInstance().execute(DocsCommentService.class).getRecords(repoId, parent_dir, name, name); + addSingleDisposable(single, new Consumer() { + @Override + public void accept(FileRecordWrapperModel fileRecordWrapperModel) throws Exception { + getSdocRecordLiveData().setValue(fileRecordWrapperModel); + } + }); + } + + public static final List _AllowedElementTypes = List.of("header1", "header2", "header3"); + + public void loadSdocElements(SDocPageOptionsModel pageOptionsModel) { + if (TextUtils.isEmpty(pageOptionsModel.seadocServerUrl)) { + return; + } + getRefreshLiveData().setValue(true); + + 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(DocsCommentService.class).getElements(pageOptionsModel.docUuid); + addSingleDisposable(single, new Consumer() { + @Override + public void accept(SDocOutlineWrapperModel 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(OutlineItemModel 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 OutlineItemModel apply(OutlineItemModel sDocModel) { + if (!TextUtils.isEmpty(sDocModel.text)) { + return sDocModel; + } + + if (CollectionUtils.isEmpty(sDocModel.children)) { + return sDocModel; + } + + String text = ""; + for (OutlineItemModel 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); + getRefreshLiveData().setValue(false); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + SLogs.e(throwable); + getRefreshLiveData().setValue(false); + } + }); + } +} 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 index b992c2ff6..2b3430698 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocWebViewActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/SDocWebViewActivity.java @@ -27,7 +27,6 @@ import com.seafile.seadroid2.R; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.SupportAccountManager; -import com.seafile.seadroid2.annotation.Todo; import com.seafile.seadroid2.databinding.ActivitySeaWebviewProBinding; import com.seafile.seadroid2.databinding.ToolbarActionbarProgressBarBinding; import com.seafile.seadroid2.enums.WebViewPreviewType; @@ -42,11 +41,11 @@ import com.seafile.seadroid2.ui.docs_comment.DocsCommentsActivity; import com.seafile.seadroid2.ui.file_profile.FileProfileDialog; import com.seafile.seadroid2.ui.sdoc.outline.SDocOutlineDialog; +import com.seafile.seadroid2.view.webview.OnWebPageListener; import com.seafile.seadroid2.view.webview.PreloadWebView; import com.seafile.seadroid2.view.webview.SeaWebView; -@Todo -public class SDocWebViewActivity extends BaseActivityWithVM { +public class SDocWebViewActivity extends BaseActivityWithVM { private ActivitySeaWebviewProBinding binding; private ToolbarActionbarProgressBarBinding toolBinding; @@ -56,6 +55,7 @@ public class SDocWebViewActivity extends BaseActivityWithVM() { @Override public void onChanged(FileRecordWrapperModel fileRecordWrapperModel) { @@ -196,7 +202,6 @@ public void onChanged(FileRecordWrapperModel fileRecordWrapperModel) { }); } - private void showOutlineDialog() { readSDocOutlineList(new Consumer() { @Override @@ -229,12 +234,22 @@ private void showProfileDialog() { return; } - if (configModel.metadata.enabled) { - getViewModel().loadRecords(repoId, path); - } else { - FileProfileDialog dialog = FileProfileDialog.newInstance(configModel.detail, configModel.users.user_list); - dialog.show(getSupportFragmentManager(), FileProfileDialog.class.getSimpleName()); - } + readSDocPageOptionsData(new Consumer() { + @Override + public void accept(SDocPageOptionsModel model) { + if (model.enableMetadataManagement) { + if (configModel != null && configModel.metadataConfigModel != null && configModel.metadataConfigModel.enabled) { + getViewModel().loadRecords(repoId, path); + } else if (configModel != null) { + FileProfileDialog dialog = FileProfileDialog.newInstance(configModel.detail, configModel.users.user_list); + dialog.show(getSupportFragmentManager(), FileProfileDialog.class.getSimpleName()); + } + } else { + FileProfileDialog dialog = FileProfileDialog.newInstance(configModel.detail, configModel.users.user_list); + dialog.show(getSupportFragmentManager(), FileProfileDialog.class.getSimpleName()); + } + } + }); } private void showCommentsActivity() { @@ -247,6 +262,10 @@ public void accept(SDocPageOptionsModel model) { } private void readSDocPageOptionsData(Consumer continuation) { + if (pageOptionsData != null) { + continuation.accept(pageOptionsData); + return; + } String js = "(function() {" + " if (window.app && window.app.pageOptions) {" + @@ -260,9 +279,9 @@ private void readSDocPageOptionsData(Consumer continuation public void onReceiveValue(String value) { if (!TextUtils.isEmpty(value)) { value = StringUtils.deString(value).replace("\\", ""); - SDocPageOptionsModel configModel1 = GsonUtils.fromJson(value, SDocPageOptionsModel.class); - if (configModel1 != null) { - continuation.accept(configModel1); + pageOptionsData = GsonUtils.fromJson(value, SDocPageOptionsModel.class); + if (pageOptionsData != null) { + continuation.accept(pageOptionsData); } else { SLogs.e("read sodc page options data from web, an exception occurred in the parsing data"); SLogs.e(value); @@ -400,6 +419,16 @@ private void hideProgressBar() { } } + private void canLoadPageConfigData() { + readSDocPageOptionsData(new Consumer() { + @Override + public void accept(SDocPageOptionsModel model) { + getViewModel().loadFileDetail(repoId, path, model.enableMetadataManagement); + } + }); + + } + @Override protected void onPause() { super.onPause(); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineRemoteDialog.java b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineRemoteDialog.java index 7e6cd8196..2378d046d 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineRemoteDialog.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/sdoc/outline/SDocOutlineRemoteDialog.java @@ -13,7 +13,6 @@ 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; @@ -22,12 +21,12 @@ import com.seafile.seadroid2.databinding.DialogSdocDirectoryBinding; import com.seafile.seadroid2.framework.data.model.sdoc.OutlineItemModel; import com.seafile.seadroid2.framework.data.model.sdoc.SDocPageOptionsModel; -import com.seafile.seadroid2.ui.sdoc.DocsCommentViewModel; +import com.seafile.seadroid2.ui.sdoc.SDocViewModel; import java.util.List; public class SDocOutlineRemoteDialog extends BottomSheetDialogFragment { - private DocsCommentViewModel viewModel; + private SDocViewModel viewModel; private SDocPageOptionsModel pageOptionsModel; @@ -51,7 +50,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { } pageOptionsModel = getArguments().getParcelable("pageOption"); - viewModel = new ViewModelProvider(this).get(DocsCommentViewModel.class); + viewModel = new ViewModelProvider(this).get(SDocViewModel.class); } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsFragment.java index a48934fc3..4c55a7d97 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/settings/SettingsFragment.java @@ -301,13 +301,13 @@ private void doWorkInfoLiveData(WorkInfo workInfo) { } if (String.valueOf(TransferDataSource.ALBUM_BACKUP).equals(dataType)) { - if (TransferEvent.EVENT_CANCEL_OUT_OF_QUOTA.equals(progressEvent)) { + if (TransferEvent.EVENT_CANCEL_WITH_OUT_OF_QUOTA.equals(progressEvent)) { mCameraBackupState.setSummary(R.string.above_quota); } else if (TransferEvent.EVENT_TRANSFERRING.equals(progressEvent)) { viewModel.countAlbumBackupPendingList(requireContext()); } } else if (String.valueOf(TransferDataSource.FOLDER_BACKUP).equals(dataType)) { - if (TransferEvent.EVENT_CANCEL_OUT_OF_QUOTA.equals(progressEvent)) { + if (TransferEvent.EVENT_CANCEL_WITH_OUT_OF_QUOTA.equals(progressEvent)) { mFolderBackupState.setSummary(R.string.above_quota); } else if (TransferEvent.EVENT_TRANSFERRING.equals(progressEvent)) { viewModel.countFolderBackupPendingList(requireContext()); 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 7c970136b..b34cada07 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 @@ -5,11 +5,9 @@ import android.content.DialogInterface; import android.content.Intent; -import android.os.Build; 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; @@ -37,13 +35,10 @@ import com.seafile.seadroid2.bus.TransferBusHelper; import com.seafile.seadroid2.config.Constants; import com.seafile.seadroid2.enums.NetworkMode; -import com.seafile.seadroid2.enums.NightMode; import com.seafile.seadroid2.enums.TransferDataSource; -import com.seafile.seadroid2.framework.data.ServerInfo; import com.seafile.seadroid2.framework.datastore.StorageManager; import com.seafile.seadroid2.framework.datastore.sp_livedata.AlbumBackupSharePreferenceHelper; import com.seafile.seadroid2.framework.datastore.sp_livedata.FolderBackupSharePreferenceHelper; -import com.seafile.seadroid2.framework.helper.NightModeHelper; import com.seafile.seadroid2.framework.util.PermissionUtil; import com.seafile.seadroid2.framework.util.SLogs; import com.seafile.seadroid2.framework.worker.BackgroundJobManagerImpl; @@ -53,7 +48,6 @@ import com.seafile.seadroid2.framework.worker.upload.MediaBackupScannerWorker; import com.seafile.seadroid2.framework.worker.upload.UploadFolderFileAutomaticallyWorker; import com.seafile.seadroid2.framework.worker.upload.UploadMediaFileAutomaticallyWorker; -import com.seafile.seadroid2.gesturelock.LockPatternUtils; import com.seafile.seadroid2.preferences.RenameSharePreferenceFragmentCompat; import com.seafile.seadroid2.preferences.Settings; import com.seafile.seadroid2.ui.account.AccountsActivity; @@ -67,11 +61,9 @@ import com.seafile.seadroid2.ui.folder_backup.FolderBackupConfigActivity; import com.seafile.seadroid2.ui.folder_backup.FolderBackupSelectedPathActivity; import com.seafile.seadroid2.ui.folder_backup.RepoConfig; -import com.seafile.seadroid2.ui.gesture.CreateGesturePasswordActivity; import com.seafile.seadroid2.ui.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; @@ -445,9 +437,10 @@ public void onChanged(Boolean aBoolean) { } else { mAlbumBackupSwitch.setChecked(false); AlbumBackupSharePreferenceHelper.writeRepoConfig(null); + switchAlbumBackupState(false); + dispatchAlbumBackupWork(false); } - switchAlbumBackupState(aBoolean); } }); @@ -557,14 +550,18 @@ private void checkScanWorkInfo(TransferDataSource dataSource, WorkInfo workInfo) Data outData = workInfo.getOutputData(); String outDataEvent = outData.getString(TransferWorker.KEY_DATA_EVENT); String outDataType = outData.getString(TransferWorker.KEY_DATA_TYPE); + + //scan end if (TextUtils.equals(String.valueOf(TransferDataSource.ALBUM_BACKUP), outDataType)) { if (TransferEvent.EVENT_SCAN_END.equals(outDataEvent)) { - mAlbumBackupState.setSummary(R.string.done); +// mAlbumBackupState.setSummary(R.string.done); + SLogs.e("album scan end"); return; } } else if (TextUtils.equals(String.valueOf(TransferDataSource.FOLDER_BACKUP), outDataType)) { if (TransferEvent.EVENT_SCAN_END.equals(outDataEvent)) { - mFolderBackupState.setSummary(R.string.done); +// mFolderBackupState.setSummary(R.string.done); + SLogs.e("folder scan end"); return; } } @@ -598,14 +595,18 @@ private void checkOutputData(WorkInfo workInfo) { if (TextUtils.equals(String.valueOf(TransferDataSource.ALBUM_BACKUP), outDataType)) { if (TransferEvent.EVENT_FINISH.equals(outDataEvent)) { mAlbumBackupState.setSummary(R.string.settings_cuc_finish_title); - } else if (TransferEvent.EVENT_CANCEL_OUT_OF_QUOTA.equals(outDataEvent)) { + } else if (TransferEvent.EVENT_CANCEL_WITH_OUT_OF_QUOTA.equals(outDataEvent)) { mAlbumBackupState.setSummary(R.string.above_quota); + } else if (TransferEvent.EVENT_CANCEL_WITH_BY_STOPPED.equals(outDataEvent)) { + mAlbumBackupState.setSummary(R.string.canceled); } } else if (TextUtils.equals(String.valueOf(TransferDataSource.FOLDER_BACKUP), outDataType)) { if (TransferEvent.EVENT_FINISH.equals(outDataEvent)) { mFolderBackupState.setSummary(R.string.folder_backup_waiting_state); - } else if (TransferEvent.EVENT_CANCEL_OUT_OF_QUOTA.equals(outDataEvent)) { + } else if (TransferEvent.EVENT_CANCEL_WITH_OUT_OF_QUOTA.equals(outDataEvent)) { mFolderBackupState.setSummary(R.string.above_quota); + } else if (TransferEvent.EVENT_CANCEL_WITH_BY_STOPPED.equals(outDataEvent)) { + mFolderBackupState.setSummary(R.string.canceled); } } else { checkProgressData(workInfo); @@ -696,6 +697,10 @@ private void switchAlbumBackupState(boolean isEnable) { mAlbumBackupState.setVisible(isEnable); mAlbumBackupAdvanced.setVisible(isEnable); + if (!isEnable) { + mAlbumBackupState.setSummary(null); + } + updateAlbumBackupSelectedRepoSummary(); } @@ -724,6 +729,10 @@ private void switchFolderBackupState(boolean isEnable) { mFolderBackupSelectFolder.setVisible(isEnable); mFolderBackupState.setVisible(isEnable); + if (!isEnable) { + mFolderBackupState.setSummary(null); + } + updateFolderBackupSelectedRepoAndFolderSummary(); } @@ -856,10 +865,12 @@ public void onActivityResult(ActivityResult o) { @Override public void onActivityResult(ActivityResult o) { if (o.getResultCode() == RESULT_OK) { + //The dispatch function needs to be put first dispatchAlbumBackupWork(true); - updateAlbumBackupSelectedRepoSummary(); + switchAlbumBackupState(true); } else { + //The dispatch function needs to be put first dispatchAlbumBackupWork(false); if (o.getData() != null) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/transfer_list/TransferListAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/transfer_list/TransferListAdapter.java index 5a97437db..834e088bf 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/transfer_list/TransferListAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/transfer_list/TransferListAdapter.java @@ -97,8 +97,12 @@ private void onBindHolder(TransferItemViewHolder holder, FileTransferEntity enti } //target path - String targetPath = Utils.pathJoin(entity.repo_name, entity.getParent_path()); - holder.binding.transferTargetPath.setText(targetPath); + if (!TextUtils.isEmpty(entity.repo_name)) { + String targetPath = Utils.pathJoin(entity.repo_name, entity.getParent_path()); + holder.binding.transferTargetPath.setText(targetPath); + } else { + holder.binding.transferTargetPath.setText(entity.getParent_path()); + } //file name holder.binding.transferFileName.setText(entity.file_name); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/transfer_list/TransferListFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/transfer_list/TransferListFragment.java index c8f764aeb..53080f0e6 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/transfer_list/TransferListFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/transfer_list/TransferListFragment.java @@ -176,14 +176,6 @@ public void showBottomSheet(FileTransferEntity entity) { builder.removeMenu(R.id.upload); builder.removeMenu(R.id.download); - builder.removeMenu(R.id.pause); - - //not supported yet -// if (getTransferAction() == TransferAction.DOWNLOAD) { -// builder.removeMenu(R.id.upload); -// } else if (getTransferAction() == TransferAction.UPLOAD) { -// builder.removeMenu(R.id.download); -// } builder.show(getChildFragmentManager()); } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/transfer_list/UploadListFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/transfer_list/UploadListFragment.java index b7e323623..782a97af9 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/transfer_list/UploadListFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/transfer_list/UploadListFragment.java @@ -101,7 +101,7 @@ private void doWorkInfoLiveData(TransferDataSource dataSource, WorkInfo workInfo Data progressData = workInfo.getProgress(); String progressEvent = progressData.getString(TransferWorker.KEY_DATA_EVENT); - if (TransferEvent.EVENT_CANCEL_OUT_OF_QUOTA.equals(progressEvent)) { + if (TransferEvent.EVENT_CANCEL_WITH_OUT_OF_QUOTA.equals(progressEvent)) { refreshData(); } else if (TransferEvent.EVENT_TRANSFERRING.equals(progressEvent)) { @@ -113,7 +113,6 @@ private void doWorkInfoLiveData(TransferDataSource dataSource, WorkInfo workInfo SLogs.d("upload: " + fileName + ", percent:" + percent + ", total_size:" + totalSize + ", dataSource: " + dataSource); - if (TextUtils.equals(transferId, lastTransferId)) { notifyProgressById(transferId, transferredSize, percent, progressEvent); } else { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/GravitySnapHelper.java b/app/src/main/java/com/seafile/seadroid2/view/snap_recyclerview/GravitySnapHelper.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/GravitySnapHelper.java rename to app/src/main/java/com/seafile/seadroid2/view/snap_recyclerview/GravitySnapHelper.java index 7f27ee3a7..2c7d26d06 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/media/image_preview2/GravitySnapHelper.java +++ b/app/src/main/java/com/seafile/seadroid2/view/snap_recyclerview/GravitySnapHelper.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.ui.media.image_preview2; +package com.seafile.seadroid2.view.snap_recyclerview; import android.util.DisplayMetrics; import android.view.Gravity; @@ -215,8 +215,8 @@ protected void onTargetFound(View targetView, // The associated RecyclerView has been removed so there is no action to take. return; } - int[] snapDistances = calculateDistanceToFinalSnap(recyclerView.getLayoutManager(), - targetView); + + int[] snapDistances = calculateDistanceToFinalSnap(recyclerView.getLayoutManager(), targetView); final int dx = snapDistances[0]; final int dy = snapDistances[1]; final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy))); @@ -468,18 +468,19 @@ private int getFlingDistance() { private boolean scrollTo(int position, boolean smooth) { if (recyclerView.getLayoutManager() != null) { if (smooth) { - RecyclerView.SmoothScroller smoothScroller - = createScroller(recyclerView.getLayoutManager()); + RecyclerView.SmoothScroller smoothScroller = createScroller(recyclerView.getLayoutManager()); if (smoothScroller != null) { smoothScroller.setTargetPosition(position); recyclerView.getLayoutManager().startSmoothScroll(smoothScroller); return true; } } else { - RecyclerView.ViewHolder viewHolder - = recyclerView.findViewHolderForAdapterPosition(position); +// recyclerView.getLayoutManager().scrollToPosition(position); +// return true; + RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(position); if (viewHolder != null) { - int[] distances = calculateDistanceToFinalSnap(recyclerView.getLayoutManager(), + int[] distances = calculateDistanceToFinalSnap( + recyclerView.getLayoutManager(), viewHolder.itemView); recyclerView.scrollBy(distances[0], distances[1]); return true; diff --git a/app/src/main/java/com/seafile/seadroid2/view/snap_recyclerview/GravitySnapRecyclerView.java b/app/src/main/java/com/seafile/seadroid2/view/snap_recyclerview/GravitySnapRecyclerView.java new file mode 100644 index 000000000..84dd154d1 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/snap_recyclerview/GravitySnapRecyclerView.java @@ -0,0 +1,150 @@ +package com.seafile.seadroid2.view.snap_recyclerview; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +import com.seafile.seadroid2.R; + +/** + * An {@link OrientationAwareRecyclerView} that uses a default {@link GravitySnapHelper} + */ +public class GravitySnapRecyclerView extends OrientationAwareRecyclerView { + + @NonNull + final private GravitySnapHelper snapHelper; + + private boolean isSnappingEnabled = false; + + public GravitySnapRecyclerView(@NonNull Context context) { + this(context, null); + } + + public GravitySnapRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public GravitySnapRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + TypedArray typedArray = context.obtainStyledAttributes(attrs, + R.styleable.GravitySnapRecyclerView, defStyleAttr, 0); + int snapGravity = typedArray.getInt( + R.styleable.GravitySnapRecyclerView_snapGravity, 0); + switch (snapGravity) { + case 0: + snapHelper = new GravitySnapHelper(Gravity.START); + break; + case 1: + snapHelper = new GravitySnapHelper(Gravity.TOP); + break; + case 2: + snapHelper = new GravitySnapHelper(Gravity.END); + break; + case 3: + snapHelper = new GravitySnapHelper(Gravity.BOTTOM); + break; + case 4: + snapHelper = new GravitySnapHelper(Gravity.CENTER); + break; + default: + throw new IllegalArgumentException("Invalid gravity value. Use START " + + "| END | BOTTOM | TOP | CENTER constants"); + } + + snapHelper.setSnapToPadding(typedArray.getBoolean( + R.styleable.GravitySnapRecyclerView_snapToPadding, false)); + + snapHelper.setSnapLastItem(typedArray.getBoolean( + R.styleable.GravitySnapRecyclerView_snapLastItem, false)); + + snapHelper.setMaxFlingSizeFraction(typedArray.getFloat( + R.styleable.GravitySnapRecyclerView_snapMaxFlingSizeFraction, + GravitySnapHelper.FLING_SIZE_FRACTION_DISABLE)); + + snapHelper.setScrollMsPerInch(typedArray.getFloat( + R.styleable.GravitySnapRecyclerView_snapScrollMsPerInch, 100f)); + + enableSnapping(typedArray.getBoolean( + R.styleable.GravitySnapRecyclerView_snapEnabled, true)); + + typedArray.recycle(); + } + + @Override + public void smoothScrollToPosition(int position) { + if (!isSnappingEnabled || !snapHelper.smoothScrollToPosition(position)) { + super.smoothScrollToPosition(position); + } + } + + @Override + public void scrollToPosition(int position) { + if (!isSnappingEnabled || !snapHelper.scrollToPosition(position)) { + super.scrollToPosition(position); + } + } + + @NonNull + public GravitySnapHelper getSnapHelper() { + return snapHelper; + } + + public void enableSnapping(Boolean enable) { + if (enable) { + snapHelper.attachToRecyclerView(this); + } else { + snapHelper.attachToRecyclerView(null); + } + isSnappingEnabled = enable; + } + + public boolean isSnappingEnabled() { + return isSnappingEnabled; + } + + public int getCurrentSnappedPosition() { + return snapHelper.getCurrentSnappedPosition(); + } + + public void snapToNextPosition(Boolean smooth) { + snapTo(true, smooth); + } + + public void snapToPreviousPosition(Boolean smooth) { + snapTo(false, smooth); + } + + public void setSnapListener(@Nullable GravitySnapHelper.SnapListener listener) { + snapHelper.setSnapListener(listener); + } + + private void snapTo(Boolean next, Boolean smooth) { + final RecyclerView.LayoutManager lm = getLayoutManager(); + if (lm != null) { + final View snapView = snapHelper.findSnapView(lm, false); + if (snapView != null) { + final int pos = getChildAdapterPosition(snapView); + if (next) { + if (smooth) { + smoothScrollToPosition(pos + 1); + } else { + scrollToPosition(pos + 1); + } + } else if (pos > 0) { + if (smooth) { + smoothScrollToPosition(pos - 1); + } else { + scrollToPosition(pos - 1); + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/view/snap_recyclerview/OrientationAwareRecyclerView.java b/app/src/main/java/com/seafile/seadroid2/view/snap_recyclerview/OrientationAwareRecyclerView.java new file mode 100644 index 000000000..ba993d26b --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/snap_recyclerview/OrientationAwareRecyclerView.java @@ -0,0 +1,81 @@ +package com.seafile.seadroid2.view.snap_recyclerview; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +/** + * A RecyclerView that only handles scroll events with the same orientation of its LayoutManager. + * Avoids situations where nested recyclerviews don't receive touch events properly: + */ +public class OrientationAwareRecyclerView extends RecyclerView { + + private float lastX = 0.0f; + private float lastY = 0.0f; + private boolean scrolling = false; + + public OrientationAwareRecyclerView(@NonNull Context context) { + this(context, null); + } + + public OrientationAwareRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public OrientationAwareRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + addOnScrollListener(new OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + scrolling = newState != RecyclerView.SCROLL_STATE_IDLE; + } + }); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent e) { + final LayoutManager lm = getLayoutManager(); + if (lm == null) { + return super.onInterceptTouchEvent(e); + } + + boolean allowScroll = true; + + switch (e.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + lastX = e.getX(); + lastY = e.getY(); + // If we were scrolling, stop now by faking a touch release + if (scrolling) { + MotionEvent newEvent = MotionEvent.obtain(e); + newEvent.setAction(MotionEvent.ACTION_UP); + return super.onInterceptTouchEvent(newEvent); + } + break; + } + case MotionEvent.ACTION_MOVE: { + // We're moving, so check if we're trying + // to scroll vertically or horizontally so we don't intercept the wrong event. + float currentX = e.getX(); + float currentY = e.getY(); + float dx = Math.abs(currentX - lastX); + float dy = Math.abs(currentY - lastY); + allowScroll = dy > dx ? lm.canScrollVertically() : lm.canScrollHorizontally(); + break; + } + } + + if (!allowScroll) { + return false; + } + + return super.onInterceptTouchEvent(e); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/OnWebPageListener.java b/app/src/main/java/com/seafile/seadroid2/view/webview/OnWebPageListener.java new file mode 100644 index 000000000..5348815f7 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/OnWebPageListener.java @@ -0,0 +1,7 @@ +package com.seafile.seadroid2.view.webview; + +import android.webkit.WebView; + +public interface OnWebPageListener { + void onPageFinished(WebView view, String url); +} 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 c756bed2b..55bccf3db 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 @@ -6,6 +6,7 @@ import android.util.AttributeSet; import android.webkit.CookieManager; import android.webkit.WebSettings; +import android.webkit.WebView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -92,6 +93,11 @@ private void init() { registerCommonHandler(); } + public void setOnWebPageListener(OnWebPageListener onWebPageListener) { + mWebViewClient.setOnWebPageListener(onWebPageListener); + this.setWebViewClient(mWebViewClient); + } + public void load(String targetUrl) { mWebViewClient.go(targetUrl, this); } diff --git a/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java b/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java index b6da31b3f..fe7ae53bf 100644 --- a/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java +++ b/app/src/main/java/com/seafile/seadroid2/view/webview/SeaWebViewClient.java @@ -19,10 +19,25 @@ import java.util.Map; public class SeaWebViewClient extends BridgeWebViewClient { + + private OnWebPageListener onWebPageListener; + + public void setOnWebPageListener(OnWebPageListener onWebPageListener) { + this.onWebPageListener = onWebPageListener; + } + public SeaWebViewClient(BridgeWebView webView) { super(webView); } + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + if (onWebPageListener != null) { + onWebPageListener.onPageFinished(view, url); + } + } + @Override public boolean shouldOverrideUrlLoading(WebView wb, WebResourceRequest request) { String url = request.getUrl().toString(); diff --git a/app/src/main/res/drawable/baseline_delete_24.xml b/app/src/main/res/drawable/baseline_delete_24.xml index 8603e59d2..af9a48e10 100644 --- a/app/src/main/res/drawable/baseline_delete_24.xml +++ b/app/src/main/res/drawable/baseline_delete_24.xml @@ -4,6 +4,21 @@ android:viewportWidth="24" android:viewportHeight="24"> + android:pathData="M20.792,6.462H3.208C3.093,6.462 3,6.555 3,6.669V7.985C3,8.099 3.093,8.192 3.208,8.192H20.792C20.907,8.192 21,8.099 21,7.985V6.669C21,6.555 20.907,6.462 20.792,6.462Z" + android:fillColor="#999999"/> + + + + + diff --git a/app/src/main/res/drawable/baseline_download_24.xml b/app/src/main/res/drawable/baseline_download_24.xml new file mode 100644 index 000000000..c9ca2f102 --- /dev/null +++ b/app/src/main/res/drawable/baseline_download_24.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_share_24.xml b/app/src/main/res/drawable/baseline_share_24.xml index ec5a0fac2..a3390defe 100644 --- a/app/src/main/res/drawable/baseline_share_24.xml +++ b/app/src/main/res/drawable/baseline_share_24.xml @@ -1,9 +1,21 @@ + android:width="18dp" + android:height="18dp" + android:viewportWidth="18" + android:viewportHeight="18"> + android:pathData="M17.308,2.423C17.308,1.085 16.223,0 14.884,0C13.546,0 12.461,1.085 12.461,2.423C12.461,3.761 13.546,4.846 14.884,4.846C16.223,4.846 17.308,3.761 17.308,2.423Z" + android:fillColor="#999999"/> + + + + diff --git a/app/src/main/res/drawable/baseline_star_filled_24.xml b/app/src/main/res/drawable/baseline_star_filled_24.xml deleted file mode 100644 index 40437ec7c..000000000 --- a/app/src/main/res/drawable/baseline_star_filled_24.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/baseline_star_outline_24.xml b/app/src/main/res/drawable/baseline_star_outline_24.xml deleted file mode 100644 index 2a5e48606..000000000 --- a/app/src/main/res/drawable/baseline_star_outline_24.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/baseline_starred_32.xml b/app/src/main/res/drawable/baseline_starred_32.xml deleted file mode 100644 index 6153d4543..000000000 --- a/app/src/main/res/drawable/baseline_starred_32.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/baseline_starred_filled_24.xml b/app/src/main/res/drawable/baseline_starred_filled_24.xml new file mode 100644 index 000000000..45b89e79b --- /dev/null +++ b/app/src/main/res/drawable/baseline_starred_filled_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/baseline_starred_filled_32.xml b/app/src/main/res/drawable/baseline_starred_filled_32.xml new file mode 100644 index 000000000..9efb9bdec --- /dev/null +++ b/app/src/main/res/drawable/baseline_starred_filled_32.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/baseline_starred_outline_24.xml b/app/src/main/res/drawable/baseline_starred_outline_24.xml new file mode 100644 index 000000000..b3d1e96ed --- /dev/null +++ b/app/src/main/res/drawable/baseline_starred_outline_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/shape_solid_grey100_radius_4.xml b/app/src/main/res/drawable/shape_solid_grey100_radius_4.xml new file mode 100644 index 000000000..ccd3eb776 --- /dev/null +++ b/app/src/main/res/drawable/shape_solid_grey100_radius_4.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_carousel_image_preview.xml b/app/src/main/res/layout/activity_carousel_image_preview.xml index bb2ada195..e12a5154a 100644 --- a/app/src/main/res/layout/activity_carousel_image_preview.xml +++ b/app/src/main/res/layout/activity_carousel_image_preview.xml @@ -21,7 +21,7 @@ style="@style/ToolBarStyle" android:layout_width="match_parent" android:layout_height="?actionBarSize" - android:background="@color/bar_background_translucent_color" + android:background="@color/bar_background_color" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -30,7 +30,7 @@ android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@color/bar_background_translucent_color" + android:background="@color/bar_background_color" android:clipToPadding="false" android:paddingTop="4dp" app:layout_constraintBottom_toTopOf="@+id/gallery_tool_bar" @@ -41,10 +41,11 @@ android:id="@+id/gallery_tool_bar" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@color/bar_background_translucent_color" + android:background="@color/bar_background_color" android:gravity="center_horizontal" android:orientation="horizontal" - android:paddingTop="4dp" + android:paddingTop="8dp" + android:paddingBottom="8dp" android:visibility="visible" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" @@ -56,7 +57,7 @@ android:layout_height="36dp" android:foreground="?selectableItemBackground" android:scaleType="centerInside" - android:src="@drawable/action_download" + android:src="@drawable/baseline_download_24" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/gallery_delete_photo" app:layout_constraintStart_toStartOf="parent" @@ -82,7 +83,7 @@ android:layout_height="36dp" android:foreground="?selectableItemBackground" android:scaleType="centerInside" - android:src="@drawable/baseline_star_outline_24" + android:src="@drawable/baseline_starred_outline_24" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/gallery_share_photo" app:layout_constraintStart_toEndOf="@+id/gallery_delete_photo" diff --git a/app/src/main/res/layout/activity_image_preview.xml b/app/src/main/res/layout/activity_image_preview.xml index c1f5d7f55..53a7db8c7 100644 --- a/app/src/main/res/layout/activity_image_preview.xml +++ b/app/src/main/res/layout/activity_image_preview.xml @@ -28,7 +28,7 @@ android:background="@drawable/shape_stroke_radius_solid_translucent" android:foreground="?selectableItemBackground" android:scaleType="centerInside" - android:src="@drawable/action_download" + android:src="@drawable/baseline_download_24" app:shapeAppearance="@style/ShapeCircleStyle" app:tint="@color/white" /> @@ -40,7 +40,7 @@ android:background="@drawable/shape_stroke_radius_solid_translucent" android:foreground="?selectableItemBackground" android:scaleType="centerInside" - android:src="@drawable/action_delete" + android:src="@drawable/baseline_delete_24" app:shapeAppearance="@style/ShapeCircleStyle" app:tint="@color/white" /> @@ -52,7 +52,7 @@ android:background="@drawable/shape_stroke_radius_solid_translucent" android:foreground="?selectableItemBackground" android:scaleType="centerInside" - android:src="@drawable/baseline_star_outline_24" + android:src="@drawable/baseline_starred_outline_24" app:shapeAppearance="@style/ShapeCircleStyle" app:tint="@color/white" /> @@ -63,7 +63,7 @@ android:background="@drawable/shape_stroke_radius_solid_translucent" android:foreground="?selectableItemBackground" android:scaleType="centerInside" - android:src="@drawable/action_share" + android:src="@drawable/baseline_share_24" app:shapeAppearance="@style/ShapeCircleStyle" app:tint="@color/white" /> diff --git a/app/src/main/res/layout/gallery_activity_layout.xml b/app/src/main/res/layout/gallery_activity_layout.xml deleted file mode 100644 index a5a3f937f..000000000 --- a/app/src/main/res/layout/gallery_activity_layout.xml +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/menu/bottom_navigation_menu.xml b/app/src/main/res/menu/bottom_navigation_menu.xml index 726782915..b68ecbb92 100644 --- a/app/src/main/res/menu/bottom_navigation_menu.xml +++ b/app/src/main/res/menu/bottom_navigation_menu.xml @@ -8,7 +8,7 @@ - - \ No newline at end of file diff --git a/app/src/main/res/menu/bottom_sheet_unstarred.xml b/app/src/main/res/menu/bottom_sheet_unstarred.xml index a2c840bcb..82a8e0699 100644 --- a/app/src/main/res/menu/bottom_sheet_unstarred.xml +++ b/app/src/main/res/menu/bottom_sheet_unstarred.xml @@ -11,7 +11,7 @@ \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index b436f4ac2..6ae299478 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -71,4 +71,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 70fdab007..e3c2ccd5d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -683,6 +683,8 @@ Mark as resolved Are you sure you want to delete this item? + + Canceled diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 87a53e5fc..43be69259 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -201,6 +201,10 @@ 4dp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/prefs_settings.xml b/app/src/main/res/xml/prefs_settings.xml index 1e396ef02..d67a0f306 100644 --- a/app/src/main/res/xml/prefs_settings.xml +++ b/app/src/main/res/xml/prefs_settings.xml @@ -30,7 +30,7 @@ app:isPreferenceVisible="false" app:useSimpleSummaryProvider="true" /> - - - + app:radiusPosition="top" /> + app:radiusPosition="none" /> + app:radiusPosition="bottom" /> - + - - - + - - + app:backgroundColor="@color/pref_background_color" + app:backgroundRadius="8dp" + app:dividerColor="@color/pref_divider_color" + app:dividerPosition="bottom" + app:iconSpaceReserved="false" + app:radiusPosition="top" /> + app:radiusPosition="none" /> + app:radiusPosition="none" /> - + app:radiusPosition="bottom" /> + - - + app:backgroundColor="@color/pref_background_color" + app:backgroundRadius="8dp" + app:dividerColor="@color/pref_divider_color" + app:dividerPosition="bottom" + app:iconSpaceReserved="false" + app:radiusPosition="top" /> - - + app:isPreferenceVisible="false" + app:radiusPosition="none" /> - + app:isPreferenceVisible="false" + app:radiusPosition="none" /> - - + app:isPreferenceVisible="false" + app:radiusPosition="bottom" /> + - + app:radiusPosition="top" /> + app:radiusPosition="none" /> - + app:radiusPosition="bottom" /> + - + app:radiusPosition="all" /> - + - + app:radiusPosition="top" /> + app:radiusPosition="none" /> + app:radiusPosition="bottom" /> - + - + - + app:radiusPosition="all" + app:titleTextColor="@color/fancy_orange" /> + \ No newline at end of file diff --git a/app/src/main/res/xml/prefs_settings_camera_backup_advance_2.xml b/app/src/main/res/xml/prefs_settings_camera_backup_advance_2.xml new file mode 100644 index 000000000..a8748ab98 --- /dev/null +++ b/app/src/main/res/xml/prefs_settings_camera_backup_advance_2.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + \ No newline at end of file From f05fca6484b97ad0afb348f924a2e57b4b31a39a Mon Sep 17 00:00:00 2001 From: zhwanng <48609908+zhwanng@users.noreply.github.com> Date: Mon, 6 Jan 2025 11:03:41 +0800 Subject: [PATCH 16/17] fix bugs --- .../seadroid2/annotation/NotSupport.java | 15 +++++++++ .../ui/activities/AllActivitiesFragment.java | 2 +- .../ui/activities/MineActivitiesFragment.java | 4 ++- .../seadroid2/ui/main/MainActivity.java | 31 ++++++++++++++++--- .../seadroid2/ui/repo/RepoQuickAdapter.java | 18 +++-------- .../seadroid2/ui/repo/RepoQuickFragment.java | 17 ++++++++-- .../seadroid2/ui/repo/RepoViewModel.java | 17 +++++++++- .../BackgroundListPreference.java | 6 ++-- .../BackgroundShapePreference.java | 15 +++++++-- .../BackgroundSwitchPreference.java | 6 ++-- app/src/main/res/values/attrs.xml | 1 + app/src/main/res/xml/prefs_settings_2.xml | 3 +- 12 files changed, 104 insertions(+), 31 deletions(-) create mode 100644 app/src/main/java/com/seafile/seadroid2/annotation/NotSupport.java diff --git a/app/src/main/java/com/seafile/seadroid2/annotation/NotSupport.java b/app/src/main/java/com/seafile/seadroid2/annotation/NotSupport.java new file mode 100644 index 000000000..e36d44f7d --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/annotation/NotSupport.java @@ -0,0 +1,15 @@ +package com.seafile.seadroid2.annotation; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Target; + +/** + * There are no plans to develop this feature. + */ +@Target({TYPE, METHOD, CONSTRUCTOR, FIELD}) +public @interface NotSupport { +} 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 1339ac269..923e76dcb 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 @@ -97,12 +97,12 @@ public void onFirstResume() { private void initAdapter() { adapter = new ActivityAdapter(); + TextView tipView = TipsViews.getTipTextView(requireContext()); tipView.setText(R.string.no_starred_file); tipView.setOnClickListener(v -> reload()); adapter.setStateView(tipView); adapter.setStateViewEnable(false); - adapter.setAnimationEnable(true); adapter.setOnItemClickListener((baseQuickAdapter, view, i) -> { ActivityModel activityModel = (ActivityModel) adapter.getItems().get(i); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activities/MineActivitiesFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/activities/MineActivitiesFragment.java index 69369814b..882e38022 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activities/MineActivitiesFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/activities/MineActivitiesFragment.java @@ -12,15 +12,17 @@ import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeafException; -import com.seafile.seadroid2.ui.base.fragment.BaseFragmentWithVM; +import com.seafile.seadroid2.annotation.NotSupport; import com.seafile.seadroid2.databinding.LayoutFrameSwipeRvBinding; import com.seafile.seadroid2.framework.data.model.activities.ActivityModel; +import com.seafile.seadroid2.ui.base.fragment.BaseFragmentWithVM; import com.seafile.seadroid2.view.TipsViews; import java.util.List; import kotlin.Pair; +@NotSupport @Deprecated public class MineActivitiesFragment extends BaseFragmentWithVM { 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 02586afc6..7e3ffc1e3 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 @@ -1047,15 +1047,38 @@ public void accept(Boolean aBoolean) { private int permission_media_select_type = -1; private void takePhoto() { - permission_media_select_type = 0; - cameraPermissionLauncher.launch(Manifest.permission.CAMERA); + checkCurrentPathHasWritePermission(new java.util.function.Consumer() { + @Override + public void accept(Boolean aBoolean) { + if (!aBoolean) { + ToastUtils.showLong(R.string.library_read_only); + return; + } + + permission_media_select_type = 0; + cameraPermissionLauncher.launch(Manifest.permission.CAMERA); + } + }); + } private void takeVideo() { - permission_media_select_type = 1; - cameraPermissionLauncher.launch(Manifest.permission.CAMERA); + checkCurrentPathHasWritePermission(new java.util.function.Consumer() { + @Override + public void accept(Boolean aBoolean) { + if (!aBoolean) { + ToastUtils.showLong(R.string.library_read_only); + return; + } + + permission_media_select_type = 1; + cameraPermissionLauncher.launch(Manifest.permission.CAMERA); + } + }); + } + private void takeFile(boolean isSingleSelect) { String[] mimeTypes = new String[]{"*/*"}; if (isSingleSelect) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java index 96781b726..8f6cc5518 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoQuickAdapter.java @@ -320,15 +320,7 @@ private void onBindRepos(RepoViewHolder holder, RepoModel model, @NonNull List payloads) { - int color; - if (!CollectionUtils.isEmpty(payloads)) { Bundle bundle = (Bundle) payloads.get(0); boolean isChecked = bundle.getBoolean("is_check"); @@ -520,7 +510,7 @@ private void updateItemMultiSelectViewWithPayload(ImageView imageView, boolean i color = ContextCompat.getColor(getContext(), R.color.fancy_orange); imageView.setImageResource(R.drawable.ic_checkbox_checked); } else { - color = ContextCompat.getColor(getContext(), R.color.material_grey_666); + color = ContextCompat.getColor(getContext(), R.color.bottom_sheet_pop_disable_color); imageView.setImageResource(R.drawable.ic_checkbox_unchecked); } imageView.setImageTintList(ColorStateList.valueOf(color)); 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 8dd13f19c..de8786a11 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 @@ -901,9 +901,20 @@ private void navTo(BaseModel model) { private void navToForSearch(SearchModel searchModel) { if (searchModel.isDir() && "/".equals(searchModel.fullpath)) { - RepoModel repoModel = SearchModel.convert2RepoModel(searchModel); - getNavContext().push(repoModel); - loadDataAsFirst(); + getViewModel().getRepoModelEntity(searchModel.repo_id, new Consumer() { + @Override + public void accept(RepoModel repoModel) throws Exception { + if (repoModel == null) { + ToastUtils.showLong(R.string.repo_not_found); + return; + } + + getNavContext().push(repoModel); + loadDataAsFirst(); + } + }); + + } else { DirentModel direntModel = SearchModel.convert2DirentModel(searchModel); if (direntModel.isDir()) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java index eea325693..a39f90663 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/repo/RepoViewModel.java @@ -426,6 +426,21 @@ public void accept(Throwable throwable) throws Exception { }); } + public void getRepoModelEntity(String repoId, Consumer consumer) { + Single> r = AppDatabase.getInstance().repoDao().getRepoById(repoId); + addSingleDisposable(r, new Consumer>() { + @Override + public void accept(List rs) throws Exception { + if (consumer != null) { + if (CollectionUtils.isEmpty(rs)) { + consumer.accept(null); + } else { + consumer.accept(rs.get(0)); + } + } + } + }); + } public void getRepoModelAndPermissionEntity(String repoId, Consumer> consumer) { Single> r = getRepoModelAndAllPermissionSingle(repoId); @@ -618,7 +633,7 @@ public void accept(Throwable throwable) throws Exception { public void inflateDirentMenu(Context context) { removeNonRepoPermission(); - toParseMenu(context, R.menu.bottom_sheet_op_dirent, null,null, CollectionUtils.newArrayList(R.id.unstar)); + toParseMenu(context, R.menu.bottom_sheet_op_dirent, null, null, CollectionUtils.newArrayList(R.id.unstar)); } public void inflateDirentMenuWithSelected(Context context, List selectedDirentList, List disableMenuIds, List removedMenuIds) { diff --git a/app/src/main/java/com/seafile/seadroid2/widget/prefs/background_pref/BackgroundListPreference.java b/app/src/main/java/com/seafile/seadroid2/widget/prefs/background_pref/BackgroundListPreference.java index bf26b1512..49342520f 100644 --- a/app/src/main/java/com/seafile/seadroid2/widget/prefs/background_pref/BackgroundListPreference.java +++ b/app/src/main/java/com/seafile/seadroid2/widget/prefs/background_pref/BackgroundListPreference.java @@ -74,8 +74,10 @@ public void onBindViewHolder(@NonNull PreferenceViewHolder holder) { Drawable drawable = BackgroundShapeUtils.genBackgroundDrawable(radiusPosition, backgroundColor, backgroundRadius); holder.itemView.setBackground(drawable); - TextView textView = (TextView) holder.findViewById(android.R.id.title); - textView.setTextColor(titleTextColor); + TextView titleTextView = (TextView) holder.findViewById(android.R.id.title); + if (titleTextView != null) { + titleTextView.setTextColor(titleTextColor); + } MaterialDivider topDivider = (MaterialDivider) holder.findViewById(R.id.top_divider); MaterialDivider bottomDivider = (MaterialDivider) holder.findViewById(R.id.bottom_divider); diff --git a/app/src/main/java/com/seafile/seadroid2/widget/prefs/background_pref/BackgroundShapePreference.java b/app/src/main/java/com/seafile/seadroid2/widget/prefs/background_pref/BackgroundShapePreference.java index a3fc870ac..55df06d42 100644 --- a/app/src/main/java/com/seafile/seadroid2/widget/prefs/background_pref/BackgroundShapePreference.java +++ b/app/src/main/java/com/seafile/seadroid2/widget/prefs/background_pref/BackgroundShapePreference.java @@ -46,6 +46,7 @@ public BackgroundShapePreference(@NonNull Context context) { public abstract int getLayoutId(); private int titleTextColor = 0; + private int summaryTextColor = 0; //default all private int radiusPosition = 0; private int backgroundColor = 0; @@ -60,6 +61,7 @@ private void init(Context context, AttributeSet attrs, int defStyleAttr) { R.styleable.PrefShape, defStyleAttr, 0); titleTextColor = typedArray.getColor(R.styleable.PrefShape_titleTextColor, ContextCompat.getColor(getContext(), R.color.item_title_color)); + summaryTextColor = typedArray.getColor(R.styleable.PrefShape_summaryTextColor, ContextCompat.getColor(getContext(), R.color.item_subtitle_color)); radiusPosition = typedArray.getInt(R.styleable.PrefShape_radiusPosition, 0); backgroundColor = typedArray.getColor(R.styleable.PrefShape_backgroundColor, ContextCompat.getColor(getContext(), R.color.bar_background_color)); @@ -81,8 +83,16 @@ public void onBindViewHolder(@NonNull PreferenceViewHolder holder) { Drawable drawable = BackgroundShapeUtils.genBackgroundDrawable(radiusPosition, backgroundColor, backgroundRadius); holder.itemView.setBackground(drawable); - TextView textView = (TextView) holder.findViewById(android.R.id.title); - textView.setTextColor(titleTextColor); + TextView titleTextView = (TextView) holder.findViewById(android.R.id.title); + if (titleTextView != null) { + titleTextView.setTextColor(titleTextColor); + } + + TextView summaryTextView = (TextView) holder.findViewById(android.R.id.summary); + if (summaryTextView != null) { + summaryTextView.setTextColor(summaryTextColor); + } + MaterialDivider topDivider = (MaterialDivider) holder.findViewById(R.id.top_divider); MaterialDivider bottomDivider = (MaterialDivider) holder.findViewById(R.id.bottom_divider); @@ -127,6 +137,7 @@ public void setDividerPosition(int dividerPosition) { this.dividerPosition = dividerPosition; notifyChanged(); } + public void setTitleTextColor(int titleTextColor) { this.titleTextColor = titleTextColor; notifyChanged(); diff --git a/app/src/main/java/com/seafile/seadroid2/widget/prefs/background_pref/BackgroundSwitchPreference.java b/app/src/main/java/com/seafile/seadroid2/widget/prefs/background_pref/BackgroundSwitchPreference.java index 83d10b9b3..75f844d80 100644 --- a/app/src/main/java/com/seafile/seadroid2/widget/prefs/background_pref/BackgroundSwitchPreference.java +++ b/app/src/main/java/com/seafile/seadroid2/widget/prefs/background_pref/BackgroundSwitchPreference.java @@ -74,8 +74,10 @@ public void onBindViewHolder(@NonNull PreferenceViewHolder holder) { Drawable drawable = BackgroundShapeUtils.genBackgroundDrawable(radiusPosition, backgroundColor, backgroundRadius); holder.itemView.setBackground(drawable); - TextView textView = (TextView) holder.findViewById(android.R.id.title); - textView.setTextColor(titleTextColor); + TextView titleTextView = (TextView) holder.findViewById(android.R.id.title); + if (titleTextView != null) { + titleTextView.setTextColor(titleTextColor); + } MaterialDivider topDivider = (MaterialDivider) holder.findViewById(R.id.top_divider); MaterialDivider bottomDivider = (MaterialDivider) holder.findViewById(R.id.bottom_divider); diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 005e95c89..165f4bc2f 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -89,6 +89,7 @@ + diff --git a/app/src/main/res/xml/prefs_settings_2.xml b/app/src/main/res/xml/prefs_settings_2.xml index 3ab06f4e7..b7f664252 100644 --- a/app/src/main/res/xml/prefs_settings_2.xml +++ b/app/src/main/res/xml/prefs_settings_2.xml @@ -18,7 +18,8 @@ app:dividerColor="@color/pref_divider_color" app:dividerPosition="bottom" app:iconSpaceReserved="false" - app:radiusPosition="top" /> + app:radiusPosition="top" + app:summaryTextColor="@color/fancy_orange" /> Date: Tue, 7 Jan 2025 14:24:30 +0800 Subject: [PATCH 17/17] update code --- .../seadroid2/account/AccountUtils.java | 3 ++ .../file_monitor/FileSyncService.java | 1 - .../MediaContentObserver.java | 10 +++--- .../RecursiveFileObserver.java | 2 +- .../SingleFolderPathFileObserver.java | 2 +- .../seadroid2/framework/util/Logs.java | 5 ++- .../worker/download/DownloadWorker.java | 31 ++++++++++--------- .../ui/account/AccountViewModel.java | 3 +- .../ui/camera_upload/AlbumBackupAdapter.java | 11 +++---- .../SignOutDialogFragment.java | 1 - .../snap_recyclerview/GravitySnapHelper.java | 2 -- ...ml => shape_activity_item_action_type.xml} | 2 +- app/src/main/res/layout/item_activity.xml | 2 +- 13 files changed, 37 insertions(+), 38 deletions(-) rename app/src/main/java/com/seafile/seadroid2/framework/{worker/observer => file_monitor}/MediaContentObserver.java (85%) rename app/src/main/java/com/seafile/seadroid2/framework/{worker/observer => file_monitor}/RecursiveFileObserver.java (98%) rename app/src/main/java/com/seafile/seadroid2/framework/{worker/observer => file_monitor}/SingleFolderPathFileObserver.java (95%) rename app/src/main/res/drawable/{shape_solid_grey103_r2.xml => shape_activity_item_action_type.xml} (72%) diff --git a/app/src/main/java/com/seafile/seadroid2/account/AccountUtils.java b/app/src/main/java/com/seafile/seadroid2/account/AccountUtils.java index 4df312d1a..fc8be5ad8 100644 --- a/app/src/main/java/com/seafile/seadroid2/account/AccountUtils.java +++ b/app/src/main/java/com/seafile/seadroid2/account/AccountUtils.java @@ -26,6 +26,9 @@ public static void logout(Account account) { // clear ContextStackPreferenceHelper.clearStack(); + // + CertsManager.instance().deleteCertForAccount(account); + NotificationUtils.cancelAll(); // sign out operations diff --git a/app/src/main/java/com/seafile/seadroid2/framework/file_monitor/FileSyncService.java b/app/src/main/java/com/seafile/seadroid2/framework/file_monitor/FileSyncService.java index a95003936..bc361263f 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/file_monitor/FileSyncService.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/file_monitor/FileSyncService.java @@ -18,7 +18,6 @@ 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.observer.MediaContentObserver; import com.seafile.seadroid2.ui.camera_upload.CameraUploadManager; import org.apache.commons.io.monitor.FileAlterationListener; diff --git a/app/src/main/java/com/seafile/seadroid2/framework/worker/observer/MediaContentObserver.java b/app/src/main/java/com/seafile/seadroid2/framework/file_monitor/MediaContentObserver.java similarity index 85% rename from app/src/main/java/com/seafile/seadroid2/framework/worker/observer/MediaContentObserver.java rename to app/src/main/java/com/seafile/seadroid2/framework/file_monitor/MediaContentObserver.java index 449e2d7fc..f7b861819 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/worker/observer/MediaContentObserver.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/file_monitor/MediaContentObserver.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.framework.worker.observer; +package com.seafile.seadroid2.framework.file_monitor; import android.content.ContentResolver; import android.content.Context; @@ -6,10 +6,10 @@ import android.net.Uri; import android.os.Handler; import android.provider.MediaStore; +import android.widget.Toast; -import com.seafile.seadroid2.framework.datastore.sp_livedata.AlbumBackupSharePreferenceHelper; +import com.seafile.seadroid2.SeadroidApplication; import com.seafile.seadroid2.framework.util.SLogs; -import com.seafile.seadroid2.framework.worker.BackgroundJobManagerImpl; import com.seafile.seadroid2.ui.camera_upload.CameraUploadManager; /** @@ -39,7 +39,9 @@ public MediaContentObserver(Context context, Handler handler) { public void onChange(boolean selfChange) { super.onChange(selfChange); - SLogs.d("A new file is detected and the Media task begins"); + String newVer = MediaStore.getVersion(SeadroidApplication.getAppContext()); + SLogs.e("媒体库新版本:"+newVer); + SLogs.e("A new file is detected and the Media task begins"); CameraUploadManager.getInstance().performFullSync(); } diff --git a/app/src/main/java/com/seafile/seadroid2/framework/worker/observer/RecursiveFileObserver.java b/app/src/main/java/com/seafile/seadroid2/framework/file_monitor/RecursiveFileObserver.java similarity index 98% rename from app/src/main/java/com/seafile/seadroid2/framework/worker/observer/RecursiveFileObserver.java rename to app/src/main/java/com/seafile/seadroid2/framework/file_monitor/RecursiveFileObserver.java index 749189476..744e4e447 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/worker/observer/RecursiveFileObserver.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/file_monitor/RecursiveFileObserver.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.framework.worker.observer; +package com.seafile.seadroid2.framework.file_monitor; import android.os.Build; import android.os.FileObserver; diff --git a/app/src/main/java/com/seafile/seadroid2/framework/worker/observer/SingleFolderPathFileObserver.java b/app/src/main/java/com/seafile/seadroid2/framework/file_monitor/SingleFolderPathFileObserver.java similarity index 95% rename from app/src/main/java/com/seafile/seadroid2/framework/worker/observer/SingleFolderPathFileObserver.java rename to app/src/main/java/com/seafile/seadroid2/framework/file_monitor/SingleFolderPathFileObserver.java index a0a87a6ed..04c2ac62d 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/worker/observer/SingleFolderPathFileObserver.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/file_monitor/SingleFolderPathFileObserver.java @@ -1,4 +1,4 @@ -package com.seafile.seadroid2.framework.worker.observer; +package com.seafile.seadroid2.framework.file_monitor; import org.apache.commons.io.IOCase; import org.apache.commons.io.monitor.FileAlterationObserver; diff --git a/app/src/main/java/com/seafile/seadroid2/framework/util/Logs.java b/app/src/main/java/com/seafile/seadroid2/framework/util/Logs.java index 6f0f36604..9eb6ad4d0 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/util/Logs.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/util/Logs.java @@ -38,12 +38,11 @@ public class Logs { /** * will delete log files that have not been modified for a period of time */ - private static final long MAX_TIME = 30L * 24 * 60 * 60 * 1000; + private static final long MAX_TIME = 30L * 24 * 60 * 60 * 1000;// 30 days public static void init() { LogConfiguration config = new LogConfiguration.Builder() - .logLevel(BuildConfig.DEBUG ? LogLevel.ALL - : LogLevel.ERROR) + .logLevel(BuildConfig.DEBUG ? LogLevel.ALL : LogLevel.INFO) .tag(LOG_TAG) // .enableThreadInfo() // .enableStackTrace(2) diff --git a/app/src/main/java/com/seafile/seadroid2/framework/worker/download/DownloadWorker.java b/app/src/main/java/com/seafile/seadroid2/framework/worker/download/DownloadWorker.java index c68db0955..6bb3141fe 100644 --- a/app/src/main/java/com/seafile/seadroid2/framework/worker/download/DownloadWorker.java +++ b/app/src/main/java/com/seafile/seadroid2/framework/worker/download/DownloadWorker.java @@ -47,6 +47,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URLEncoder; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -97,7 +98,7 @@ public Result doWork() { //count int pendingCount = AppDatabase.getInstance().fileTransferDAO().countPendingDownloadListSync(account.getSignature()); if (pendingCount <= 0) { - SLogs.eDebug("download list is empty."); + SLogs.i("download list is empty."); return Result.success(getFinishData(false)); } @@ -150,7 +151,7 @@ public Result doWork() { } } - SLogs.d("all task run"); + SLogs.i("all task run"); // if (isDownloaded) { @@ -174,7 +175,7 @@ private Data getFinishData(boolean isDownloaded) { private final FileTransferProgressListener.TransferProgressListener progressListener = new FileTransferProgressListener.TransferProgressListener() { @Override public void onProgressNotify(FileTransferEntity fileTransferEntity, int percent, long transferredSize, long totalSize) { - SLogs.d(fileTransferEntity.file_name + " -> progress:" + percent); + SLogs.i(fileTransferEntity.file_name + " -> progress:" + percent); int diff = AppDatabase.getInstance().fileTransferDAO().countPendingDownloadListSync(fileTransferEntity.related_account); @@ -190,7 +191,7 @@ public void onProgressNotify(FileTransferEntity fileTransferEntity, int percent, }; private void transferFile(Account account, FileTransferEntity transferEntity) throws Exception { - SLogs.d("download start:" + transferEntity.full_path); + SLogs.i("download start:" + transferEntity.full_path); //show notification int diff = AppDatabase.getInstance().fileTransferDAO().countPendingDownloadListSync(transferEntity.related_account); @@ -200,7 +201,7 @@ private void transferFile(Account account, FileTransferEntity transferEntity) th List repoModels = AppDatabase.getInstance().repoDao().getByIdSync(transferEntity.repo_id); if (CollectionUtils.isEmpty(repoModels)) { - SLogs.d("no repo for repoId: " + transferEntity.repo_id); + SLogs.i("no repo for repoId: " + transferEntity.repo_id); return; } @@ -224,13 +225,13 @@ private void downloadFile(Account account, FileTransferEntity transferEntity) th File localFile = DataManager.getLocalRepoFile(account, transferEntity); if (localFile.exists() && transferEntity.file_strategy == ExistingFileStrategy.SKIP) { - SLogs.d("skip this file, file_strategy is SKIP :" + localFile.getAbsolutePath()); + SLogs.i("skip this file, file_strategy is SKIP :" + localFile.getAbsolutePath()); return; } download(transferEntity, dlink, localFile); - SLogs.d("download finish:" + transferEntity.full_path); + SLogs.i("download finish:" + transferEntity.full_path); } private Pair getDownloadLink(FileTransferEntity transferEntity, boolean isReUsed) throws SeafException, IOException { @@ -267,8 +268,6 @@ private Pair getDownloadLink(FileTransferEntity transferEntity, } private void download(FileTransferEntity fileTransferEntity, String dlink, File localFile) throws Exception { - - fileTransferProgressListener.setFileTransferEntity(fileTransferEntity); fileTransferEntity.transfer_status = TransferStatus.IN_PROGRESS; @@ -293,8 +292,8 @@ private void download(FileTransferEntity fileTransferEntity, String dlink, File long fileSize = responseBody.contentLength(); if (fileSize == -1) { - SLogs.d("download file error -> contentLength is -1"); - SLogs.d(localFile.getAbsolutePath()); + SLogs.e("download file error -> contentLength is -1"); + SLogs.e(localFile.getAbsolutePath()); fileSize = fileTransferEntity.file_size; @@ -328,10 +327,12 @@ private void download(FileTransferEntity fileTransferEntity, String dlink, File //important tempFile.renameTo(localFile); +// Path path = java.nio.file.Files.move(tempFile.toPath(), localFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); +// boolean isSuccess = path.toFile().exists(); if (localFile.length() != fileSize) { - SLogs.d("download file error -> localFile.size != downloadedSize"); - SLogs.d(localFile.getAbsolutePath()); + SLogs.e("download file error -> localFile.size != downloadedSize"); + SLogs.e(localFile.getAbsolutePath()); updateEntityErrorState(fileTransferEntity); } else { updateEntitySuccessState(fileTransferEntity, localFile); @@ -393,7 +394,7 @@ private void downloadFileByBlock(Account account, FileTransferEntity transferEnt File localFile = DataManager.getLocalRepoFile(account, transferEntity); if (localFile.exists() && transferEntity.file_strategy == ExistingFileStrategy.SKIP) { - SLogs.d("skip this file, file_strategy is SKIP :" + localFile.getAbsolutePath()); + SLogs.i("skip this file, file_strategy is SKIP :" + localFile.getAbsolutePath()); return; } @@ -498,7 +499,7 @@ private void downloadBlock(FileBlocks fileBlocks, String blockId, String dlink, responseBody.close(); if (localFile.length() != tempFileSize) { - SLogs.d("Rename file error : " + localFile.getAbsolutePath()); + SLogs.i("Rename file error : " + localFile.getAbsolutePath()); throw SeafException.networkException; } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/account/AccountViewModel.java b/app/src/main/java/com/seafile/seadroid2/ui/account/AccountViewModel.java index 57d613dfb..db2cfc8dc 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/account/AccountViewModel.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/account/AccountViewModel.java @@ -17,6 +17,7 @@ import com.seafile.seadroid2.framework.http.HttpIO; import com.seafile.seadroid2.account.AccountUtils; import com.seafile.seadroid2.framework.util.DeviceIdManager; +import com.seafile.seadroid2.preferences.ContextStackPreferenceHelper; import com.seafile.seadroid2.ssl.CertsManager; import com.seafile.seadroid2.ui.base.viewmodel.BaseViewModel; import com.seafile.seadroid2.ui.dialog_fragment.SignOutDialogFragment; @@ -224,9 +225,9 @@ public void deleteAccount(Account account) { AccountUtils.logout(account); } else { HttpIO.removeInstanceByAccount(account); + CertsManager.instance().deleteCertForAccount(account); } - CertsManager.instance().deleteCertForAccount(account); //delete local account SupportAccountManager.getInstance().removeAccount(account.getAndroidAccount(), null, null); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/AlbumBackupAdapter.java b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/AlbumBackupAdapter.java index 27837e06f..a74964f84 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/AlbumBackupAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/camera_upload/AlbumBackupAdapter.java @@ -65,13 +65,11 @@ public void onPerformSync(android.accounts.Account account, ContentProviderClient provider, SyncResult syncResult) { - SLogs.e("onPerformSync!"); + boolean isForce = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL); + SLogs.e("albumBackupAdapter - onPerformSync, isForce -> " + isForce); Account seafileAccount = SupportAccountManager.getInstance().getSeafileAccount(account); - /** - * this should never occur, as camera upload is supposed to be disabled once the camera upload - * account signs out. - */ + // this should never occur, as camera upload is supposed to be disabled once the camera upload account signs out. if (!seafileAccount.hasValidToken()) { SLogs.e("This account has no auth token. Disable camera upload."); syncResult.stats.numAuthExceptions++; @@ -86,8 +84,7 @@ public void onPerformSync(android.accounts.Account account, return; } - //start - boolean isForce = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL); + // start BackgroundJobManagerImpl.getInstance().startMediaWorkerChain(isForce); } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SignOutDialogFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SignOutDialogFragment.java index 4be88cd17..50a170f1a 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SignOutDialogFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/dialog_fragment/SignOutDialogFragment.java @@ -35,7 +35,6 @@ public int getDialogTitleRes() { protected void onPositiveClick() { Account account = SupportAccountManager.getInstance().getCurrentAccount(); - CertsManager.instance().deleteCertForAccount(account); AccountUtils.logout(account); refreshData(); diff --git a/app/src/main/java/com/seafile/seadroid2/view/snap_recyclerview/GravitySnapHelper.java b/app/src/main/java/com/seafile/seadroid2/view/snap_recyclerview/GravitySnapHelper.java index 2c7d26d06..c5e8af2c6 100644 --- a/app/src/main/java/com/seafile/seadroid2/view/snap_recyclerview/GravitySnapHelper.java +++ b/app/src/main/java/com/seafile/seadroid2/view/snap_recyclerview/GravitySnapHelper.java @@ -475,8 +475,6 @@ private boolean scrollTo(int position, boolean smooth) { return true; } } else { -// recyclerView.getLayoutManager().scrollToPosition(position); -// return true; RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(position); if (viewHolder != null) { int[] distances = calculateDistanceToFinalSnap( diff --git a/app/src/main/res/drawable/shape_solid_grey103_r2.xml b/app/src/main/res/drawable/shape_activity_item_action_type.xml similarity index 72% rename from app/src/main/res/drawable/shape_solid_grey103_r2.xml rename to app/src/main/res/drawable/shape_activity_item_action_type.xml index 6dc53c8b2..5fc91947f 100644 --- a/app/src/main/res/drawable/shape_solid_grey103_r2.xml +++ b/app/src/main/res/drawable/shape_activity_item_action_type.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/layout/item_activity.xml b/app/src/main/res/layout/item_activity.xml index 271b26792..8c97791ba 100644 --- a/app/src/main/res/layout/item_activity.xml +++ b/app/src/main/res/layout/item_activity.xml @@ -37,7 +37,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="4dp" - android:background="@drawable/shape_solid_grey103_r2" + android:background="@drawable/shape_activity_item_action_type" android:paddingHorizontal="4dp" android:paddingVertical="2dp" android:textColor="@color/material_grey_666"