Skip to content

Commit

Permalink
FS-4097 single selection support (#180)
Browse files Browse the repository at this point in the history
* Add Kotlin support in tests

* FS-4097 add flag for enabling single/multi selection

* Update dependencies + gradle version

* Remove local.properties from source control
  • Loading branch information
scana authored Sep 27, 2018
1 parent 104632b commit 43c23bf
Show file tree
Hide file tree
Showing 16 changed files with 216 additions and 62 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
.idea
*.iml
/build
local.properties
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
ext.kotlin_version = '1.2.70'
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
Expand Down
8 changes: 5 additions & 3 deletions filestack/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ plugins {
}

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

group = 'com.filestack'
version = file(new File('../VERSION')).text.trim()
Expand Down Expand Up @@ -33,18 +34,19 @@ android {
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
api 'com.filestack:filestack-java:0.8.2'

implementation 'com.android.support:appcompat-v7:27.1.1'
api 'com.filestack:filestack-java:0.7.0'
implementation 'com.android.support:design:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
implementation 'com.squareup.picasso:picasso:2.5.2'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'

androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
testImplementation 'junit:junit:4.12'
testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

task sourcesJar(type: Jar) {
Expand Down
8 changes: 6 additions & 2 deletions filestack/src/main/java/com/filestack/android/FsActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ public class FsActivity extends AppCompatActivity implements
private boolean shouldCheckAuth;
private NavigationView nav;

private boolean allowMultipleFiles;

// Activity lifecycle overrides (in sequential order)

@Override
Expand Down Expand Up @@ -112,6 +114,8 @@ protected void onCreate(Bundle savedInstanceState) {
sources = Util.getDefaultSources();
}

allowMultipleFiles = intent.getBooleanExtra(FsConstants.EXTRA_ALLOW_MULTIPLE_FILES, true);

// Check if MIME filtering conflicts with camera source
String[] mimeTypes = intent.getStringArrayExtra(FsConstants.EXTRA_MIME_TYPES);
if (mimeTypes != null && sources.contains(Sources.CAMERA)) {
Expand Down Expand Up @@ -277,7 +281,7 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) {
shouldCheckAuth = false;
break;
case Sources.DEVICE:
fragment = new LocalFilesFragment();
fragment = LocalFilesFragment.newInstance(allowMultipleFiles);
// Needed to prevent UI bug when selecting local source after cloud source
shouldCheckAuth = false;
break;
Expand Down Expand Up @@ -322,7 +326,7 @@ public void onSuccess(CloudResponse contents) {
transaction.commit();
} else {
shouldCheckAuth = false;
CloudListFragment cloudListFragment = CloudListFragment.create(selectedSource);
CloudListFragment cloudListFragment = CloudListFragment.create(selectedSource, allowMultipleFiles);
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.content, cloudListFragment);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ public class FsConstants {
public static final String EXTRA_STORE_OPTS = "storeOpts";
/** Expects string array. Sets MIME types that are allowed to be selected. */
public static final String EXTRA_MIME_TYPES = "mimeTypes";
/**
* Expects boolean. If true, multiple files can be selected for upload.
* Settings this value to false restricts the selection to only one file.
* Defaults to true.
*/
public static final String EXTRA_ALLOW_MULTIPLE_FILES = "multipleFiles";

/** Action for upload broadcast intent filter. */
public static final String BROADCAST_UPLOAD = "com.filestack.android.BROADCAST_UPLOAD";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;

import com.filestack.CloudItem;
import com.filestack.CloudResponse;
import com.filestack.android.R;
import com.filestack.android.Selection;

import java.util.ArrayList;
Expand Down Expand Up @@ -40,7 +42,7 @@
* @see <a href="https://developer.android.com/guide/topics/ui/layout/recyclerview">
* https://developer.android.com/guide/topics/ui/layout/recyclerview</a>
*/
class CloudListAdapter extends RecyclerView.Adapter implements
class CloudListAdapter extends RecyclerView.Adapter<CloudListViewHolder> implements
SingleObserver<CloudResponse>, View.OnClickListener, BackButtonListener {

private static final double LOAD_TRIGGER = 0.50;
Expand All @@ -56,12 +58,14 @@ class CloudListAdapter extends RecyclerView.Adapter implements
private RecyclerView recyclerView;
private String currentPath;
private String[] mimeTypes;
private Selector selector;

CloudListAdapter(String sourceId, String[] mimeTypes, Bundle saveInstanceState) {
CloudListAdapter(String sourceId, String[] mimeTypes, Bundle saveInstanceState,
Selector selector) {
this.sourceId = sourceId;
this.mimeTypes = mimeTypes;
this.selector = selector;
setHasStableIds(true);

if (saveInstanceState != null) {
currentPath = saveInstanceState.getString(STATE_CURRENT_PATH);
folders = (HashMap) saveInstanceState.getSerializable(STATE_FOLDERS);
Expand All @@ -73,26 +77,23 @@ class CloudListAdapter extends RecyclerView.Adapter implements
}
}

// RecyclerView.Adapter overrides (in sequential order)

@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
this.recyclerView = recyclerView;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
public CloudListViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
View listItemView = inflater.inflate(viewType, viewGroup, false);
return new CloudListViewHolder(listItemView);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
public void onBindViewHolder(CloudListViewHolder holder, int i) {
ArrayList<CloudItem> items = folders.get(currentPath);
CloudItem item = items.get(i);
CloudListViewHolder holder = (CloudListViewHolder) viewHolder;

holder.setId(i);
holder.setName(item.getName());
Expand All @@ -102,12 +103,11 @@ public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
holder.setIcon(item.getThumbnail());
holder.setOnClickListener(this);
holder.setEnabled(item.isFolder() || Util.mimeAllowed(mimeTypes, item.getMimetype()));

SelectionSaver selectionSaver = Util.getSelectionSaver();
Selection selection = new Selection(sourceId, item.getPath(), item.getMimetype(),
item.getName());
holder.setSelected(selectionSaver.isSelected(selection));

int tintColor = holder.itemView.getResources().getColor(R.color.primary_dark);
holder.setSelectionTint(tintColor);

Selection selection = SelectionFactory.from(sourceId, item);
holder.setSelected(selector.isSelected(selection));
String nextToken = nextTokens.get(currentPath);
if (!isLoading) {
if (nextToken != null && i >= (LOAD_TRIGGER * items.size())) {
Expand Down Expand Up @@ -191,10 +191,10 @@ public void onClick(View view) {
return;
}

SelectionSaver selectionSaver = Util.getSelectionSaver();
SelectionFactory.from(sourceId, item);
Selection selection = new Selection(sourceId, item.getPath(), item.getMimetype(),
item.getName());
boolean selected = selectionSaver.toggleItem(selection);
boolean selected = selector.toggle(selection);
CloudListViewHolder holder = (CloudListViewHolder) recyclerView.findViewHolderForItemId(id);

holder.setSelected(selected);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,21 @@
* https://developer.android.com/guide/topics/ui/layout/recyclerview</a>
*/
public class CloudListFragment extends Fragment implements BackButtonListener {
private final static String ARG_SOURCE = "source";
private final static String STATE_IS_LIST_MODE = "isListMode";
private static final String ARG_SOURCE = "source";
private static final String ARG_ALLOW_MULTIPLE_FILES = "multipleFiles";
private static final String STATE_IS_LIST_MODE = "isListMode";

private boolean isListMode = true;
private CloudListAdapter adapter;
private SpacingDecoration spacer;
private RecyclerView recyclerView;
private SourceInfo sourceInfo;

public static CloudListFragment create(String source) {
public static CloudListFragment create(String source, boolean allowMultipleFiles) {
CloudListFragment fragment = new CloudListFragment();
Bundle args = new Bundle();
args.putString(ARG_SOURCE, source);
args.putBoolean(ARG_ALLOW_MULTIPLE_FILES, allowMultipleFiles);
fragment.setArguments(args);
return fragment;
}
Expand All @@ -62,7 +64,12 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
recyclerView = baseView.findViewById(R.id.recycler);
Intent intent = requireActivity().getIntent();
String[] mimeTypes = intent.getStringArrayExtra(FsConstants.EXTRA_MIME_TYPES);
adapter = new CloudListAdapter(sourceInfo.getId(), mimeTypes, savedInstanceState);
boolean allowMultipleFiles = getArguments().getBoolean(ARG_ALLOW_MULTIPLE_FILES);
final Selector selector = allowMultipleFiles ?
new Selector.Multi(Util.getSelectionSaver()) :
new Selector.Single(Util.getSelectionSaver());

adapter = new CloudListAdapter(sourceInfo.getId(), mimeTypes, savedInstanceState, selector);
recyclerView.setAdapter(adapter);

if (savedInstanceState != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.filestack.android.internal;

import android.content.Context;
import android.content.res.ColorStateList;
import android.support.annotation.ColorInt;
import android.support.v4.widget.ImageViewCompat;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ImageView;
Expand All @@ -18,15 +21,13 @@
* https://developer.android.com/guide/topics/ui/layout/recyclerview</a>
*/
class CloudListViewHolder extends RecyclerView.ViewHolder {
private View itemView;
private TextView nameView;
private TextView infoView;
private ImageView iconView;
private ImageView checkboxView;

CloudListViewHolder(View listItemView) {
super(listItemView);
this.itemView = listItemView;
this.nameView = listItemView.findViewById(R.id.name);
this.infoView = listItemView.findViewById(R.id.info);
this.iconView = listItemView.findViewById(R.id.icon);
Expand Down Expand Up @@ -60,12 +61,12 @@ public void setIcon(String url) {
}
}

void setOnClickListener(View.OnClickListener listener) {
itemView.setOnClickListener(listener);
public void setSelectionTint(@ColorInt int color) {
ImageViewCompat.setImageTintList(checkboxView, ColorStateList.valueOf(color));
}

void setOnLongClickListener(View.OnLongClickListener listener) {
itemView.setOnLongClickListener(listener);
void setOnClickListener(View.OnClickListener listener) {
itemView.setOnClickListener(listener);
}

void setSelected(boolean selected) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,18 @@
* https://developer.android.com/guide/topics/providers/document-provider</a>
*/
public class LocalFilesFragment extends Fragment implements View.OnClickListener {
private static final String ARG_ALLOW_MULTIPLE_FILES = "multipleFiles";
private static final int READ_REQUEST_CODE = RESULT_FIRST_USER;
private static final String TAG = "LocalFilesFragment";

public static Fragment newInstance(boolean allowMultipleFiles) {
Fragment fragment = new LocalFilesFragment();
Bundle args = new Bundle();
args.putBoolean(ARG_ALLOW_MULTIPLE_FILES, allowMultipleFiles);
fragment.setArguments(args);
return fragment;
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
Expand All @@ -45,7 +54,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle st
public void onClick(View view) {
Intent docIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
docIntent.addCategory(Intent.CATEGORY_OPENABLE);
docIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
boolean allowMultipleFiles = getArguments().getBoolean(ARG_ALLOW_MULTIPLE_FILES, true);
docIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultipleFiles);
docIntent.setType("*/*");

Intent launchIntent = getActivity().getIntent();
Expand Down Expand Up @@ -82,7 +92,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent resultData)
}
}

// Get metadata for specified URI and return it loaded into Selection instance
// Get metadata for specified URI and return it loaded into Selector instance
public Selection processUri(Uri uri) {
ContentResolver resolver = getActivity().getContentResolver();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.filestack.android.internal;

import android.net.Uri;

import com.filestack.CloudItem;
import com.filestack.Sources;
import com.filestack.android.Selection;

class SelectionFactory {

public static Selection from(String sourceId, CloudItem cloudItem) {
return new Selection(sourceId, cloudItem.getPath(), cloudItem.getMimetype(),
cloudItem.getName());
}

public static Selection from(Uri uri, int size, String mimeType, String name) {
return new Selection(Sources.DEVICE, uri, size, mimeType, name);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.filestack.android.internal;

import com.filestack.android.Selection;

public interface Selector {
boolean toggle(Selection selection);
boolean isSelected(Selection selection);

class Single implements Selector {

private final SelectionSaver selectionSaver;

public Single(SelectionSaver selectionSaver) {
this.selectionSaver = selectionSaver;
}

@Override
public boolean toggle(Selection selection) {
if (selectionSaver.isEmpty()) {
return selectionSaver.toggleItem(selection);
} else if (selectionSaver.getItems().get(0).equals(selection)) {
return selectionSaver.toggleItem(selection);
}
return false;
}

@Override
public boolean isSelected(Selection selection) {
return selectionSaver.isSelected(selection);
}
}

class Multi implements Selector {

private SelectionSaver selectionSaver;

public Multi(SelectionSaver selectionSaver) {
this.selectionSaver = selectionSaver;
}

@Override
public boolean toggle(Selection selection) {
return selectionSaver.toggleItem(selection);
}

@Override
public boolean isSelected(Selection selection) {
return selectionSaver.isSelected(selection);
}
}
}
Loading

0 comments on commit 43c23bf

Please sign in to comment.