From 2b93091f5d505dcaf2c89e86462c7932c3379d6d Mon Sep 17 00:00:00 2001 From: hyb1996 <946994919@qq.com> Date: Thu, 19 Oct 2017 17:19:06 +0800 Subject: [PATCH] add: file chooser dialog --- .idea/dictionaries/Stardust.xml | 1 + .../ui/filechooser/FileChooseListView.java | 189 ++++++++++++++++++ .../filechooser/FileChooserDialogBuilder.java | 91 +++++++++ .../scriptdroid/ui/main/MainActivity.java | 3 + .../ui/main/scripts/MyScriptListFragment.java | 2 + .../ui/main/scripts/ScriptListView.java | 88 ++++---- .../com/stardust/widget/CheckBoxCompat.java | 50 +++++ .../res/layout/file_choose_list_directory.xml | 55 +++++ .../main/res/layout/file_choose_list_file.xml | 84 ++++++++ .../src/main/java/com/stardust/pio/PFile.java | 6 +- 10 files changed, 526 insertions(+), 43 deletions(-) create mode 100644 app/src/main/java/com/stardust/scriptdroid/ui/filechooser/FileChooseListView.java create mode 100644 app/src/main/java/com/stardust/scriptdroid/ui/filechooser/FileChooserDialogBuilder.java create mode 100644 app/src/main/java/com/stardust/widget/CheckBoxCompat.java create mode 100644 app/src/main/res/layout/file_choose_list_directory.xml create mode 100644 app/src/main/res/layout/file_choose_list_file.xml diff --git a/.idea/dictionaries/Stardust.xml b/.idea/dictionaries/Stardust.xml index 803965af..20a6491d 100644 --- a/.idea/dictionaries/Stardust.xml +++ b/.idea/dictionaries/Stardust.xml @@ -7,6 +7,7 @@ prefill scriptable tasker + uncheck \ No newline at end of file diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/filechooser/FileChooseListView.java b/app/src/main/java/com/stardust/scriptdroid/ui/filechooser/FileChooseListView.java new file mode 100644 index 00000000..ddf0e0aa --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ui/filechooser/FileChooseListView.java @@ -0,0 +1,189 @@ +package com.stardust.scriptdroid.ui.filechooser; + +import android.content.Context; +import android.graphics.drawable.GradientDrawable; +import android.support.annotation.Nullable; +import android.support.v4.content.res.ResourcesCompat; +import android.support.v7.widget.SimpleItemAnimator; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.TextView; + +import com.stardust.pio.PFile; +import com.stardust.pio.PFiles; +import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.model.script.ScriptFile; +import com.stardust.scriptdroid.ui.main.scripts.ScriptListView; +import com.stardust.widget.BindableViewHolder; +import com.stardust.widget.CheckBoxCompat; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnCheckedChanged; +import butterknife.OnClick; + +/** + * Created by Stardust on 2017/10/19. + */ + +public class FileChooseListView extends ScriptListView { + + private int mMaxChoice = 1; + private LinkedHashMap mSelectedFiles = new LinkedHashMap<>(); + private boolean mCanChooseDir = false; + + public FileChooseListView(Context context) { + super(context); + init(); + } + + public FileChooseListView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public void setMaxChoice(int maxChoice) { + mMaxChoice = maxChoice; + } + + public void setCanChooseDir(boolean canChooseDir) { + mCanChooseDir = canChooseDir; + } + + public List getSelectedFiles() { + ArrayList list = new ArrayList<>(mSelectedFiles.size()); + for (Map.Entry entry : mSelectedFiles.entrySet()) { + list.add(entry.getKey()); + } + return list; + } + + private void init() { + setDirectorySpanSize(2); + ((SimpleItemAnimator) getScriptListView().getItemAnimator()) + .setSupportsChangeAnimations(false); + } + + @Override + protected BindableViewHolder onCreateViewHolder(LayoutInflater inflater, ViewGroup parent, int viewType) { + if (viewType == VIEW_TYPE_FILE) { + return new ScriptFileViewHolder(inflater.inflate(R.layout.file_choose_list_file, parent, false)); + } else if (viewType == VIEW_TYPE_DIRECTORY) { + return new DirectoryViewHolder(inflater.inflate(R.layout.file_choose_list_directory, parent, false)); + } else { + return super.onCreateViewHolder(inflater, parent, viewType); + } + } + + private void check(ScriptFile file, int position) { + if (mSelectedFiles.size() == mMaxChoice) { + Map.Entry itemToUncheck = mSelectedFiles.entrySet().iterator().next(); + int positionOfItemToUncheck = itemToUncheck.getValue(); + mSelectedFiles.remove(itemToUncheck.getKey()); + getScriptListView().getAdapter().notifyItemChanged(positionOfItemToUncheck); + } + mSelectedFiles.put(file, position); + } + + + class ScriptFileViewHolder extends BindableViewHolder { + + @BindView(R.id.name) + TextView mName; + @BindView(R.id.first_char) + TextView mFirstChar; + @BindView(R.id.checkbox) + CheckBoxCompat mCheckBox; + @BindView(R.id.desc) + TextView mDesc; + GradientDrawable mFirstCharBackground; + + private ScriptFile mScriptFile; + + ScriptFileViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + mFirstCharBackground = (GradientDrawable) mFirstChar.getBackground(); + } + + @Override + public void bind(ScriptFile file, int position) { + mScriptFile = file; + mName.setText(file.getSimplifiedName()); + mDesc.setText(PFiles.getHumanReadableSize(file.length())); + mCheckBox.setChecked(mSelectedFiles.containsKey(file), false); + if (file.getType() == ScriptFile.TYPE_JAVA_SCRIPT) { + mFirstChar.setText("J"); + mFirstCharBackground.setColor(ResourcesCompat.getColor(getResources(), R.color.color_j, getContext().getTheme())); + } else { + mFirstChar.setText("R"); + mFirstCharBackground.setColor(ResourcesCompat.getColor(getResources(), R.color.color_r, getContext().getTheme())); + } + } + + @OnClick(R.id.item) + void onItemClick() { + mCheckBox.toggle(); + } + + @OnCheckedChanged(R.id.checkbox) + void onCheckedChanged() { + if (mCheckBox.isChecked()) { + check(mScriptFile, getAdapterPosition()); + } else { + mSelectedFiles.remove(mScriptFile); + } + } + + + } + + class DirectoryViewHolder extends BindableViewHolder { + + @BindView(R.id.name) + TextView mName; + + @BindView(R.id.checkbox) + CheckBoxCompat mCheckBox; + + private ScriptFile mScriptFile; + + DirectoryViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + + @Override + public void bind(ScriptFile data, int position) { + mName.setText(data.getSimplifiedName()); + mScriptFile = data; + if (mCanChooseDir) { + mCheckBox.setChecked(mSelectedFiles.containsKey(data), false); + } + } + + @OnClick(R.id.item) + void onItemClick() { + setCurrentDirectory(mScriptFile); + } + + @OnCheckedChanged(R.id.checkbox) + void onCheckedChanged() { + if (mCheckBox.isChecked()) { + check(mScriptFile, getAdapterPosition()); + } else { + mSelectedFiles.remove(mScriptFile); + } + } + + } + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/filechooser/FileChooserDialogBuilder.java b/app/src/main/java/com/stardust/scriptdroid/ui/filechooser/FileChooserDialogBuilder.java new file mode 100644 index 00000000..50c5e712 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ui/filechooser/FileChooserDialogBuilder.java @@ -0,0 +1,91 @@ +package com.stardust.scriptdroid.ui.filechooser; + +import android.content.Context; +import android.support.annotation.NonNull; + +import com.stardust.pio.PFile; +import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.model.script.ScriptFile; +import com.stardust.scriptdroid.model.script.StorageFileProvider; +import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder; + +import java.io.File; +import java.util.Collections; +import java.util.List; + +/** + * Created by Stardust on 2017/10/19. + */ + +public class FileChooserDialogBuilder extends ThemeColorMaterialDialogBuilder { + + public interface SingleChoiceCallback { + void onSelected(PFile file); + } + + public interface MultiChoiceCallback { + void onSelected(List files); + } + + private FileChooseListView mFileChooseListView; + private PFile mRootDir = StorageFileProvider.DEFAULT_DIRECTORY; + private MultiChoiceCallback mCallback; + + public FileChooserDialogBuilder(@NonNull Context context) { + super(context); + mFileChooseListView = new FileChooseListView(context); + customView(mFileChooseListView, false); + positiveText(R.string.ok); + negativeText(R.string.cancel); + onPositive((dialog, which) -> { + notifySelected(); + }); + } + + private void notifySelected() { + if (mCallback == null) + return; + List selectedFiles = mFileChooseListView.getSelectedFiles(); + if (selectedFiles.isEmpty()) { + mCallback.onSelected(Collections.singletonList(mFileChooseListView.getCurrentDirectory())); + } else { + mCallback.onSelected(selectedFiles); + } + } + + public FileChooserDialogBuilder dir(String rootDir, String initialDir) { + mRootDir = new PFile(rootDir); + mFileChooseListView.setStorageFileProvider(new StorageFileProvider(mRootDir, 10), new ScriptFile(initialDir)); + return this; + } + + public FileChooserDialogBuilder dir(String dir) { + return dir(dir, dir); + } + + + public FileChooserDialogBuilder chooseDir() { + mFileChooseListView.setCanChooseDir(true); + mFileChooseListView.setStorageFileProvider(new StorageFileProvider(mRootDir, 10, File::isDirectory)); + return this; + } + + public FileChooserDialogBuilder singleChoice(SingleChoiceCallback callback) { + mFileChooseListView.setMaxChoice(1); + mCallback = files -> callback.onSelected(files.get(0)); + return this; + } + + + public FileChooserDialogBuilder multiChoice(MultiChoiceCallback callback) { + return multiChoice(Integer.MAX_VALUE, callback); + } + + public FileChooserDialogBuilder multiChoice(int maxChoices, MultiChoiceCallback callback) { + mFileChooseListView.setMaxChoice(maxChoices); + mCallback = callback; + return this; + } + + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java index c8cc7e76..8f84a96f 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java @@ -5,6 +5,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; +import android.os.Environment; import android.support.annotation.NonNull; import android.support.annotation.RequiresApi; import android.support.design.widget.FloatingActionButton; @@ -16,6 +17,7 @@ import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.widget.Toolbar; import android.view.Gravity; import android.view.View; +import android.widget.Toast; import com.qq.e.comm.DownloadService; import com.stardust.app.FragmentPagerAdapterBuilder; @@ -27,6 +29,7 @@ import com.stardust.scriptdroid.Pref; import com.stardust.scriptdroid.R; import com.stardust.scriptdroid.autojs.AutoJs; import com.stardust.scriptdroid.ui.common.NotAskAgainDialog; +import com.stardust.scriptdroid.ui.filechooser.FileChooserDialogBuilder; import com.stardust.scriptdroid.ui.floating.FloatyWindowManger; import com.stardust.scriptdroid.model.script.StorageFileProvider; import com.stardust.scriptdroid.ui.main.community.CommunityFragment_; diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/scripts/MyScriptListFragment.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/scripts/MyScriptListFragment.java index 271af22f..80497c5e 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/scripts/MyScriptListFragment.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/scripts/MyScriptListFragment.java @@ -7,6 +7,7 @@ import android.view.View; import com.stardust.scriptdroid.R; import com.stardust.scriptdroid.model.script.ScriptFile; import com.stardust.scriptdroid.model.script.Scripts; +import com.stardust.scriptdroid.model.script.StorageFileProvider; import com.stardust.scriptdroid.tool.SimpleObserver; import com.stardust.scriptdroid.ui.common.ScriptOperations; import com.stardust.scriptdroid.ui.main.FloatingActionMenu; @@ -38,6 +39,7 @@ public class MyScriptListFragment extends ViewPagerFragment implements BackPress @AfterViews void setUpViews() { + mScriptFileList.setStorageFileProvider(StorageFileProvider.getDefault()); mScriptFileList.setOnScriptFileClickListener(new ScriptListView.OnScriptFileClickListener() { @Override public void onScriptFileClick(View view, ScriptFile file) { diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/scripts/ScriptListView.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/scripts/ScriptListView.java index f8555ae2..7a4e4f10 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/scripts/ScriptListView.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/scripts/ScriptListView.java @@ -16,7 +16,6 @@ import android.widget.ImageView; import android.widget.PopupMenu; import android.widget.TextView; -import com.stardust.pio.PFile; import com.stardust.pio.PFiles; import com.stardust.scriptdroid.R; import com.stardust.scriptdroid.model.script.ScriptFile; @@ -29,16 +28,11 @@ import com.stardust.widget.BindableViewHolder; import org.greenrobot.eventbus.Subscribe; -import java.util.concurrent.Callable; - import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.annotations.NonNull; -import io.reactivex.functions.BiConsumer; -import io.reactivex.functions.Consumer; import io.reactivex.schedulers.Schedulers; /** @@ -56,6 +50,11 @@ public class ScriptListView extends SwipeRefreshLayout implements SwipeRefreshLa void OnItemOperated(ScriptFile file); } + protected static final int VIEW_TYPE_FILE = 0; + protected static final int VIEW_TYPE_DIRECTORY = 1; + //category是类别,也即"文件", "文件夹"那两个 + protected static final int VIEW_TYPE_CATEGORY = 2; + private static final int positionOfCategoryDir = 0; private ScriptList mScriptList = new ScriptList(); private RecyclerView mScriptListView; @@ -96,7 +95,16 @@ public class ScriptListView extends SwipeRefreshLayout implements SwipeRefreshLa } public void setStorageFileProvider(StorageFileProvider storageFileProvider) { - mStorageFileProvider = storageFileProvider; + setStorageFileProvider(storageFileProvider, new ScriptFile(storageFileProvider.getInitialDirectory())); + } + + + public void setStorageFileProvider(StorageFileProvider fileProvider, ScriptFile currentDirectory) { + if (mStorageFileProvider != null) + mStorageFileProvider.unregisterDirectoryChangeListener(this); + mStorageFileProvider = fileProvider; + setCurrentDirectory(currentDirectory); + mStorageFileProvider.registerDirectoryChangeListener(this); } public void setOnItemOperatedListener(OnItemOperatedListener onItemOperatedListener) { @@ -120,9 +128,6 @@ public class ScriptListView extends SwipeRefreshLayout implements SwipeRefreshLa mScriptListView = new RecyclerView(getContext()); addView(mScriptListView); initScriptListRecyclerView(); - mStorageFileProvider = StorageFileProvider.getDefault(); - setCurrentDirectory(new ScriptFile(mStorageFileProvider.getInitialDirectory())); - mStorageFileProvider.registerDirectoryChangeListener(this); } private void initScriptListRecyclerView() { @@ -149,12 +154,13 @@ public class ScriptListView extends SwipeRefreshLayout implements SwipeRefreshLa } private void loadScriptList() { + setRefreshing(true); mScriptList.clear(); mStorageFileProvider.getDirectoryFiles(mCurrentDirectory) .subscribeOn(Schedulers.io()) - .collectInto(mScriptList, (list, file) -> { - mScriptList.add(new ScriptFile(file)); - }) + .collectInto(mScriptList, (list, file) -> + mScriptList.add(new ScriptFile(file)) + ) .observeOn(Schedulers.computation()) .doOnSuccess(ScriptList::sort) .observeOn(AndroidSchedulers.mainThread()) @@ -178,6 +184,7 @@ public class ScriptListView extends SwipeRefreshLayout implements SwipeRefreshLa loadScriptList(); } + @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { @@ -229,26 +236,20 @@ public class ScriptListView extends SwipeRefreshLayout implements SwipeRefreshLa private void sort(final int sortType, final boolean isDir) { setRefreshing(true); - Observable.fromCallable(new Callable() { - @Override - public ScriptList call() throws Exception { - if (isDir) { - mScriptList.sortDir(sortType); - } else { - mScriptList.sortFile(sortType); - } - return mScriptList; + Observable.fromCallable(() -> { + if (isDir) { + mScriptList.sortDir(sortType); + } else { + mScriptList.sortFile(sortType); } + return mScriptList; }) .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Consumer() { - @Override - public void accept(@NonNull ScriptList o) throws Exception { - mScriptListAdapter.notifyDataSetChanged(); - setRefreshing(false); - } + .subscribe(o -> { + mScriptListAdapter.notifyDataSetChanged(); + setRefreshing(false); }); } @@ -259,29 +260,32 @@ public class ScriptListView extends SwipeRefreshLayout implements SwipeRefreshLa mStorageFileProvider.unregisterDirectoryChangeListener(this); } - private class ScriptListAdapter extends RecyclerView.Adapter { - private final int VIEW_TYPE_FILE = 0; - private final int VIEW_TYPE_DIRECTORY = 1; - //category是类别,也即"文件", "文件夹"那两个 - private final int VIEW_TYPE_CATEGORY = 2; + protected BindableViewHolder onCreateViewHolder(LayoutInflater inflater, ViewGroup parent, int viewType) { + if (viewType == VIEW_TYPE_FILE) { + return new ScriptFileViewHolder(inflater.inflate(R.layout.script_file_list_file, parent, false)); + } else if (viewType == VIEW_TYPE_DIRECTORY) { + return new DirectoryViewHolder(inflater.inflate(R.layout.script_file_list_directory, parent, false)); + } else { + return new CategoryViewHolder(inflater.inflate(R.layout.script_file_list_category, parent, false)); + } + } + protected RecyclerView getScriptListView() { + return mScriptListView; + } + + private class ScriptListAdapter extends RecyclerView.Adapter> { @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public BindableViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - if (viewType == VIEW_TYPE_FILE) { - return new ScriptFileViewHolder(inflater.inflate(R.layout.script_file_list_file, parent, false)); - } else if (viewType == VIEW_TYPE_DIRECTORY) { - return new DirectoryViewHolder(inflater.inflate(R.layout.script_file_list_directory, parent, false)); - } else { - return new CategoryViewHolder(inflater.inflate(R.layout.script_file_list_category, parent, false)); - } + return ScriptListView.this.onCreateViewHolder(inflater, parent, viewType); } @SuppressWarnings("unchecked") @Override - public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + public void onBindViewHolder(BindableViewHolder holder, int position) { int positionOfCategoryFile = positionOfCategoryFile(); BindableViewHolder bindableViewHolder = (BindableViewHolder) holder; if (position == positionOfCategoryDir || position == positionOfCategoryFile) { diff --git a/app/src/main/java/com/stardust/widget/CheckBoxCompat.java b/app/src/main/java/com/stardust/widget/CheckBoxCompat.java new file mode 100644 index 00000000..7ab06388 --- /dev/null +++ b/app/src/main/java/com/stardust/widget/CheckBoxCompat.java @@ -0,0 +1,50 @@ +package com.stardust.widget; + +import android.content.Context; +import android.support.v7.widget.AppCompatCheckBox; +import android.util.AttributeSet; +import android.widget.CompoundButton; + +/** + * Created by Stardust on 2017/10/19. + */ + +public class CheckBoxCompat extends AppCompatCheckBox { + private boolean mIgnoreCheckedChange; + + public CheckBoxCompat(Context context) { + super(context); + } + + public CheckBoxCompat(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CheckBoxCompat(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + + @Override + public void setOnCheckedChangeListener(final OnCheckedChangeListener listener) { + super.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (mIgnoreCheckedChange) { + return; + } + listener.onCheckedChanged(buttonView, isChecked); + } + }); + } + + public void setChecked(boolean checked, boolean notify) { + mIgnoreCheckedChange = !notify; + setChecked(checked); + mIgnoreCheckedChange = false; + } + + public void toggle(boolean notify) { + setChecked(!isChecked(), notify); + } +} diff --git a/app/src/main/res/layout/file_choose_list_directory.xml b/app/src/main/res/layout/file_choose_list_directory.xml new file mode 100644 index 00000000..59f4574c --- /dev/null +++ b/app/src/main/res/layout/file_choose_list_directory.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/file_choose_list_file.xml b/app/src/main/res/layout/file_choose_list_file.xml new file mode 100644 index 00000000..58cdefe7 --- /dev/null +++ b/app/src/main/res/layout/file_choose_list_file.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/common/src/main/java/com/stardust/pio/PFile.java b/common/src/main/java/com/stardust/pio/PFile.java index 48642332..3b5d0e56 100644 --- a/common/src/main/java/com/stardust/pio/PFile.java +++ b/common/src/main/java/com/stardust/pio/PFile.java @@ -42,7 +42,11 @@ public class PFile extends File { private void init() { - mSimplifiedName = PFiles.getNameWithoutExtension(getName()); + if (isDirectory()) { + mSimplifiedName = getName(); + } else { + mSimplifiedName = PFiles.getNameWithoutExtension(getName()); + } mSimplifyPath = getPath(); if (mSimplifyPath.startsWith(Environment.getExternalStorageDirectory().getPath())) { mSimplifyPath = mSimplifyPath.substring(Environment.getExternalStorageDirectory().getPath().length());