add: file chooser dialog

This commit is contained in:
hyb1996
2017-10-19 17:19:06 +08:00
parent 4b43434b25
commit 2b93091f5d
10 changed files with 526 additions and 43 deletions

View File

@@ -7,6 +7,7 @@
<w>prefill</w>
<w>scriptable</w>
<w>tasker</w>
<w>uncheck</w>
</words>
</dictionary>
</component>

View File

@@ -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<PFile, Integer> 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<PFile> getSelectedFiles() {
ArrayList<PFile> list = new ArrayList<>(mSelectedFiles.size());
for (Map.Entry<PFile, Integer> 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<PFile, Integer> 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<ScriptFile> {
@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<ScriptFile> {
@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);
}
}
}
}

View File

@@ -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<PFile> 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<PFile> 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;
}
}

View File

@@ -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_;

View File

@@ -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) {

View File

@@ -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<ScriptList>() {
@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<ScriptList>() {
@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<RecyclerView.ViewHolder> {
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<BindableViewHolder<?>> {
@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) {

View File

@@ -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);
}
}

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:clickable="true"
android:foreground="?selectableItemBackground"
app:cardBackgroundColor="#ffffff"
app:cardElevation="1dp"
app:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:paddingBottom="16dp"
android:paddingTop="16dp">
<com.stardust.widget.CheckBoxCompat
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_margin="8dp"
android:gravity="center"/>
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="8dp"
android:layout_marginRight="16dp"
android:src="@drawable/ic_folder_yellow_100px"
android:tint="#ffda93"/>
<TextView
android:id="@+id/name"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:paddingRight="16dp"
android:textColor="#484C4F"
android:textSize="14sp"
tools:text="正在运行的服务"/>
</LinearLayout>
</android.support.v7.widget.CardView>

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#EDEEEF"/>
<LinearLayout
android:id="@+id/item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:clickable="true"
android:foreground="?selectableItemBackground"
android:orientation="horizontal"
android:padding="8dp">
<com.stardust.widget.CheckBoxCompat
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center"/>
<TextView
android:id="@+id/first_char"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="8dp"
android:background="@drawable/circle_light_green"
android:gravity="center"
android:textColor="@android:color/white"
android:textSize="24sp"
tools:text="J"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="16dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:textColor="#484C4F"
android:textSize="14sp"
tools:text="正在运行的服务"/>
<TextView
android:id="@+id/desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:textColor="#9DA0A2"
android:textSize="11sp"
tools:text="18 KB"/>
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#EDEEEF"/>
</LinearLayout>

View File

@@ -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());