Update to Material Design

and newer API using RecyclerView.

Signed-off-by: Jonas Kalderstam <jonas@kalderstam.se>
This commit is contained in:
Jonas Kalderstam 2015-04-23 00:02:03 +02:00
parent 20ce87e271
commit 33d8c38bd6
52 changed files with 1088 additions and 1260 deletions

View File

@ -5,7 +5,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.13.0'
classpath 'com.android.tools.build:gradle:1.1.0'
classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.5.1"
}
}

View File

@ -32,6 +32,6 @@ POM_LICENCE_DIST=repo
POM_DEVELOPER_ID=neckbeard
POM_DEVELOPER_NAME=Jonas Kalderstam
ANDROID_BUILD_TARGET_SDK_VERSION=19
ANDROID_BUILD_TOOLS_VERSION=19.1.0
ANDROID_BUILD_SDK_VERSION=19
ANDROID_BUILD_TARGET_SDK_VERSION=21
ANDROID_BUILD_TOOLS_VERSION=21.1.1
ANDROID_BUILD_SDK_VERSION=21

View File

@ -1,6 +1,6 @@
#Thu Sep 25 18:29:09 CEST 2014
#Thu Apr 23 00:07:46 CEST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-2.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip

View File

@ -17,14 +17,17 @@ android {
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
//minify false
//proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.1+'
compile 'com.android.support:support-v4:22.1+'
compile 'com.android.support:recyclerview-v7:+'
}
// apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/master/gradle-mvn-push.gradle'

View File

@ -20,16 +20,13 @@ package com.nononsenseapps.filepicker;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.FragmentManager;
import android.content.ClipData;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Window;
import android.view.WindowManager;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.List;
@ -62,7 +59,7 @@ import java.util.List;
*
* @param <T>
*/
public abstract class AbstractFilePickerActivity<T> extends Activity
public abstract class AbstractFilePickerActivity<T> extends AppCompatActivity
implements AbstractFilePickerFragment.OnFilePickedListener {
public static final String EXTRA_START_PATH =
"nononsense.intent" + ".START_PATH";
@ -85,9 +82,6 @@ public abstract class AbstractFilePickerActivity<T> extends Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_ACTION_BAR);
setupFauxDialog();
setupActionBar();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_filepicker);
@ -102,7 +96,7 @@ public abstract class AbstractFilePickerActivity<T> extends Activity
intent.getBooleanExtra(EXTRA_ALLOW_MULTIPLE, allowMultiple);
}
FragmentManager fm = getFragmentManager();
FragmentManager fm = getSupportFragmentManager();
AbstractFilePickerFragment<T> fragment =
(AbstractFilePickerFragment<T>) fm.findFragmentByTag(TAG);
@ -120,64 +114,10 @@ public abstract class AbstractFilePickerActivity<T> extends Activity
setResult(Activity.RESULT_CANCELED);
}
protected void setupFauxDialog() {
// Check if this should be a dialog
TypedValue tv = new TypedValue();
if (!getTheme().resolveAttribute(R.attr.isDialog, tv, true) ||
tv.data == 0) {
return;
}
// Should be a dialog; set up the window parameters.
DisplayMetrics dm = getResources().getDisplayMetrics();
WindowManager.LayoutParams params = getWindow().getAttributes();
params.width = getResources()
.getDimensionPixelSize(R.dimen.configure_dialog_width);
params.height = Math.min(getResources()
.getDimensionPixelSize(R.dimen.configure_dialog_max_height),
dm.heightPixels * 3 / 4);
params.alpha = 1.0f;
params.dimAmount = 0.5f;
getWindow().setAttributes(params);
}
protected void setupActionBar() {
getActionBar().setTitle(getWindowTitle());
}
protected abstract AbstractFilePickerFragment<T> getFragment(
final String startPath, final int mode, final boolean allowMultiple,
final boolean allowCreateDir);
/**
* @return the title to apply to the window
*/
protected String getWindowTitle() {
final int res;
switch (mode) {
case AbstractFilePickerFragment.MODE_DIR:
res = R.plurals.select_dir;
break;
case AbstractFilePickerFragment.MODE_FILE_AND_DIR:
res = R.plurals.select_dir_or_file;
break;
case AbstractFilePickerFragment.MODE_FILE:
default:
res = R.plurals.select_file;
break;
}
final int count;
if (allowMultiple) {
count = 99;
} else {
count = 1;
}
return getResources().getQuantityString(res, count);
}
@Override
public void onSaveInstanceState(Bundle b) {
super.onSaveInstanceState(b);

View File

@ -18,23 +18,28 @@
package com.nononsenseapps.filepicker;
import android.app.Activity;
import android.app.ListFragment;
import android.app.LoaderManager;
import android.content.Loader;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.util.SortedList;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.CheckedTextView;
import android.widget.ListView;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
/**
@ -45,10 +50,9 @@ import java.util.List;
* OnFilePickedListener}
* interface.
*/
public abstract class AbstractFilePickerFragment<T> extends ListFragment
implements LoaderManager.LoaderCallbacks<List<T>>,
NewItemFragment.OnNewFolderListener,
AdapterView.OnItemLongClickListener {
public abstract class AbstractFilePickerFragment<T> extends Fragment
implements LoaderManager.LoaderCallbacks<SortedList<T>>,
NewItemFragment.OnNewFolderListener, LogicHandler<T> {
// The different preset modes of operation. This impacts the behaviour
// and possible actions in the UI.
@ -66,21 +70,34 @@ public abstract class AbstractFilePickerFragment<T> extends ListFragment
public static final String KEY_ALLOW_MULTIPLE = "KEY_ALLOW_MULTIPLE";
// Used for saving state.
protected static final String KEY_CURRENT_PATH = "KEY_CURRENT PATH";
protected final DefaultHashMap<Integer, Boolean> checkedItems;
protected final HashSet<T> checkedItems;
protected final HashSet<CheckableViewHolder> checkedVisibleViewHolders;
protected T currentPath = null;
protected boolean allowCreateDir = false;
protected boolean allowMultiple = false;
protected Comparator<T> comparator = null;
private OnFilePickedListener listener;
private BindableArrayAdapter<T> adapter;
private FileItemAdapter<T> mAdapter = null;
private TextView currentDirView;
private Toolbar mToolbar;
private RecyclerView mRecyclerView;
private LinearLayoutManager mLayoutManager;
private SortedList<T> mFiles = null;
protected FileItemAdapter<T> getAdapter() {
return mAdapter;
}
protected FileItemAdapter<T> getDummyAdapter() {
return new FileItemAdapter<>(this);
}
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public AbstractFilePickerFragment() {
checkedItems = new DefaultHashMap<Integer, Boolean>(false);
checkedItems = new HashSet<>();
checkedVisibleViewHolders = new HashSet<>();
}
/**
@ -92,7 +109,7 @@ public abstract class AbstractFilePickerFragment<T> extends ListFragment
* @param allowDirCreate
*/
public void setArgs(final String startPath, final int mode,
final boolean allowMultiple, final boolean allowDirCreate) {
final boolean allowMultiple, final boolean allowDirCreate) {
Bundle b = new Bundle();
if (startPath != null) {
b.putString(KEY_START_PATH, startPath);
@ -105,12 +122,26 @@ public abstract class AbstractFilePickerFragment<T> extends ListFragment
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_filepicker, null);
ListView lv = (ListView) view.findViewById(android.R.id.list);
mToolbar = (Toolbar) view.findViewById(R.id.picker_toolbar);
((AppCompatActivity) getActivity()).setSupportActionBar(mToolbar);
lv.setOnItemLongClickListener(this);
mRecyclerView = (RecyclerView) view.findViewById(android.R.id.list);
// improve performance if you know that changes in content
// do not change the size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
// I want some dividers
//mRecyclerView.addItemDecoration(new DividerColor
// (getActivity(), DividerColor.VERTICAL_LIST, 0, 1));
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
// Set adapter
mAdapter = new FileItemAdapter<>(this);
mRecyclerView.setAdapter(mAdapter);
view.findViewById(R.id.button_cancel)
.setOnClickListener(new View.OnClickListener() {
@ -131,13 +162,7 @@ public abstract class AbstractFilePickerFragment<T> extends ListFragment
}
// Some invalid cases first
if (allowMultiple && checkedItems.isEmpty()) {
Toast.makeText(getActivity(),
R.string.select_something_first,
Toast.LENGTH_SHORT).show();
return;
}
if (mode == MODE_FILE && isDir(currentPath)) {
if ((allowMultiple || mode == MODE_FILE) && checkedItems.isEmpty()) {
Toast.makeText(getActivity(),
R.string.select_something_first,
Toast.LENGTH_SHORT).show();
@ -145,37 +170,22 @@ public abstract class AbstractFilePickerFragment<T> extends ListFragment
}
if (allowMultiple) {
listener.onFilesPicked(toUri(getCheckedItems()));
} else {
listener.onFilesPicked(toUri(checkedItems));
} else if (mode == MODE_FILE) {
listener.onFilePicked(toUri(getFirstCheckedItem()));
} else if (mode == MODE_DIR) {
listener.onFilePicked(toUri(currentPath));
} else {
// single FILE OR DIR
if (checkedItems.isEmpty()) {
listener.onFilePicked(toUri(currentPath));
} else {
listener.onFilePicked(toUri(getFirstCheckedItem()));
}
}
}
});
view.findViewById(R.id.button_go_parent)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
// Go to parent
currentPath = getParent(currentPath);
refresh();
}
});
final View createDirView = view.findViewById(R.id.button_create_dir);
// Only show the create dir button if configured to
createDirView.setVisibility((allowCreateDir && (mode == MODE_DIR)) ?
View.VISIBLE :
View.INVISIBLE);
createDirView.setEnabled((allowCreateDir && (mode == MODE_DIR)));
createDirView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
NewFolderFragment.showDialog(getFragmentManager(),
AbstractFilePickerFragment.this);
}
});
currentDirView = (TextView) view.findViewById(R.id.current_dir);
// Restore state
if (currentPath != null) {
@ -185,14 +195,14 @@ public abstract class AbstractFilePickerFragment<T> extends ListFragment
return view;
}
/**
* Return true if the path is a directory and not a file.
*
* @param path
*/
protected abstract boolean isDir(final T path);
public T getFirstCheckedItem() {
for (T file : checkedItems) {
return file;
}
return null;
}
protected List<Uri> toUri(List<T> files) {
protected List<Uri> toUri(Iterable<T> files) {
ArrayList<Uri> uris = new ArrayList<Uri>();
for (T file : files) {
uris.add(toUri(file));
@ -200,61 +210,11 @@ public abstract class AbstractFilePickerFragment<T> extends ListFragment
return uris;
}
/**
* @return the selected files. Can be empty.
*/
protected List<T> getCheckedItems() {
final BindableArrayAdapter<T> adapter =
(BindableArrayAdapter<T>) getListAdapter();
final ArrayList<T> files = new ArrayList<T>();
for (int pos : checkedItems.keySet()) {
if (checkedItems.get(pos)) {
files.add(adapter.getItem(pos));
}
}
return files;
}
/**
* Convert the path to a URI for the return intent
*
* @param path
* @return
*/
protected abstract Uri toUri(final T path);
/**
* Return the path to the parent directory. Should return the root if
* from is root.
*
* @param from
*/
protected abstract T getParent(final T from);
/**
* @param path
* @return the full path to the file
*/
protected abstract String getFullPath(final T path);
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
currentPath = (T) getListAdapter().getItem(position);
if (isDir(currentPath)) {
refresh();
} else if (isCheckable(currentPath)) {
toggleItemCheck(
(CheckedTextView) v.findViewById(android.R.id.text1),
position, currentPath);
}
}
protected boolean isCheckable(final T data) {
public boolean isCheckable(final T data) {
final boolean checkable;
if (isDir(data)) {
checkable = ((mode == MODE_DIR && allowMultiple) ||
(mode == MODE_FILE_AND_DIR && allowMultiple));
(mode == MODE_FILE_AND_DIR && allowMultiple));
} else {
// File
checkable = (mode != MODE_DIR);
@ -262,22 +222,6 @@ public abstract class AbstractFilePickerFragment<T> extends ListFragment
return checkable;
}
protected void toggleItemCheck(final CheckedTextView view,
final int position, final T data) {
if (!isCheckable(data)) {
return;
}
final boolean oldVal = checkedItems.get(position);
if (!allowMultiple) {
checkedItems.clear();
}
checkedItems.put(position, !oldVal);
// Redraw the items
getListView().invalidateViews();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
@ -285,7 +229,7 @@ public abstract class AbstractFilePickerFragment<T> extends ListFragment
listener = (OnFilePickedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() +
" must implement OnFilePickedListener");
" must implement OnFilePickedListener");
}
}
@ -328,6 +272,29 @@ public abstract class AbstractFilePickerFragment<T> extends ListFragment
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.picker_actions, menu);
MenuItem item = menu.findItem(R.id.action_createdir);
item.setVisible(allowCreateDir && (mode == MODE_DIR));
}
@Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
if (R.id.action_createdir == menuItem.getItemId()) {
Activity activity = getActivity();
if (activity instanceof AppCompatActivity) {
NewFolderFragment.showDialog(((AppCompatActivity) activity).getSupportFragmentManager(),
AbstractFilePickerFragment.this);
}
return true;
} else {
return false;
}
}
@Override
public void onSaveInstanceState(Bundle b) {
super.onSaveInstanceState(b);
@ -343,18 +310,6 @@ public abstract class AbstractFilePickerFragment<T> extends ListFragment
listener = null;
}
/**
* Convert the path to the type used.
*
* @param path
*/
protected abstract T getPath(final String path);
/**
* Get the root path (lowest allowed).
*/
protected abstract T getRoot();
/**
* Refreshes the list. Call this when current path changes.
*/
@ -363,36 +318,6 @@ public abstract class AbstractFilePickerFragment<T> extends ListFragment
.restartLoader(0, null, AbstractFilePickerFragment.this);
}
/**
* Callback method to be invoked when an item in this view has been
* clicked and held.
* <p/>
* Implementers can call getItemAtPosition(position) if they need to access
* the data associated with the selected item.
*
* @param parent The AbsListView where the click happened
* @param view The view within the AbsListView that was clicked
* @param position The position of the view in the list
* @param id The row id of the item that was clicked
* @return true if the callback consumed the long click, false otherwise
*/
@Override
public boolean onItemLongClick(final AdapterView<?> parent, final View view,
final int position, final long id) {
final T data = (T) getListAdapter().getItem(position);
if (!isCheckable(data)) {
return false;
}
// Special case for single choice to handle directories
if (!allowMultiple) {
return false;
}
toggleItemCheck((CheckedTextView) view.findViewById(android.R.id.text1),
position, data);
return true;
}
/**
* Instantiate and return a new Loader for the given ID.
*
@ -401,16 +326,10 @@ public abstract class AbstractFilePickerFragment<T> extends ListFragment
* @return Return a new Loader instance that is ready to start loading.
*/
@Override
public Loader<List<T>> onCreateLoader(final int id, final Bundle args) {
public Loader<SortedList<T>> onCreateLoader(final int id, final Bundle args) {
return getLoader();
}
/**
* Get a loader that lists the files in the current path,
* and monitors changes.
*/
protected abstract Loader<List<T>> getLoader();
/**
* Called when a previously created loader has finished its load.
*
@ -418,125 +337,15 @@ public abstract class AbstractFilePickerFragment<T> extends ListFragment
* @param data The data generated by the Loader.
*/
@Override
public void onLoadFinished(final Loader<List<T>> loader,
final List<T> data) {
if (adapter == null) {
// View type not really used, overridden in ViewBinder
adapter = new BindableArrayAdapter<T>(getActivity(),
R.layout.filepicker_listitem_checkable);
adapter.setViewBinder(getViewBinder());
} else {
adapter.clear();
}
if (comparator == null) {
comparator = getComparator();
}
public void onLoadFinished(final Loader<SortedList<T>> loader,
final SortedList<T> data) {
checkedItems.clear();
adapter.addAll(data);
adapter.sort(comparator);
setListAdapter(adapter);
adapter.notifyDataSetChanged();
checkedVisibleViewHolders.clear();
mFiles = data;
mAdapter.setList(data);
currentDirView.setText(getFullPath(currentPath));
}
/**
* @return a ViewBinder to handle list items, or null.
*/
protected BindableArrayAdapter.ViewBinder<T> getViewBinder() {
class ViewHolder {
protected View icon;
protected TextView text;
protected CheckedTextView checkbox;
}
return new BindableArrayAdapter.ViewBinder<T>() {
/**
* Called if convertView is null. If this returns null,
* the specified resource is used. Use this to return multiple views
* depending on type.
*
* @param position
* @param defResource
* @param inflater
* @param parent
* @return
*/
@Override
public View inflateView(final int position, final int defResource,
final LayoutInflater inflater, final ViewGroup parent) {
final boolean checkable =
isCheckable((T) getListAdapter().getItem(position));
final View view = inflater.inflate(checkable ?
R.layout.filepicker_listitem_checkable :
R.layout.filepicker_listitem_dir,
parent, false);
ViewHolder viewHolder = new ViewHolder();
viewHolder.icon = view.findViewById(R.id.item_icon);
viewHolder.text =
(TextView) view.findViewById(android.R.id.text1);
if (checkable) {
viewHolder.checkbox = (CheckedTextView) view
.findViewById(android.R.id.text1);
} else {
viewHolder.checkbox = null;
}
view.setTag(viewHolder);
return view;
}
/**
* Used to determine the view's type. Returning false will use same
* type for all rows.
*
* @param position
* @param data
* @return
*/
@Override
public boolean isDir(final int position, final T data) {
return AbstractFilePickerFragment.this.isDir(data);
}
/**
* Fill the content in the row
* @param view
* @param position
* @param data
*/
@Override
public void setViewValue(final View view, final int position,
final T data) {
if (view.getTag() == null) {
return;
}
((ViewHolder) view.getTag()).text.setText(getName(data));
((ViewHolder) view.getTag()).icon.setVisibility(
isDir(position, data) ? View.VISIBLE : View.GONE);
if (((ViewHolder) view.getTag()).checkbox != null) {
((ViewHolder) view.getTag()).checkbox
.setChecked(checkedItems.get(position));
}
}
};
}
/**
* @return a comparator that can sort the items alphabetically
*/
protected abstract Comparator<T> getComparator();
/**
* @param path
* @return the name of this file/folder
*/
protected abstract String getName(final T path);
/**
* Called when a previously created loader is being reset, and thus
* making its data unavailable. The application should at this point
@ -545,9 +354,9 @@ public abstract class AbstractFilePickerFragment<T> extends ListFragment
* @param loader The Loader that is being reset.
*/
@Override
public void onLoaderReset(final Loader<List<T>> loader) {
setListAdapter(null);
adapter = null;
public void onLoaderReset(final Loader<SortedList<T>> loader) {
mAdapter.setList(null);
mFiles = null;
}
/**
@ -569,16 +378,202 @@ public abstract class AbstractFilePickerFragment<T> extends ListFragment
public void onCancelled();
}
public class DefaultHashMap<K, V> extends HashMap<K, V> {
protected final V defaultValue;
public DefaultHashMap(final V defaultValue) {
this.defaultValue = defaultValue;
}
@Override
public V get(Object k) {
return containsKey(k) ? super.get(k) : defaultValue;
/**
* @param position 0 - n, where the header has been subtracted
* @param data
* @return an integer greater than 0
*/
@Override
public int getItemViewType(int position, T data) {
if (isCheckable(data)) {
return LogicHandler.VIEWTYPE_CHECKABLE;
} else {
return LogicHandler.VIEWTYPE_DIR;
}
}
@Override
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder) {
viewHolder.text.setText("..");
}
/**
* @param parent
* @param viewType
* @return a view holder for a file or directory
*/
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v;
switch (viewType) {
case LogicHandler.VIEWTYPE_HEADER:
v = LayoutInflater.from(getActivity()).inflate(R.layout.filepicker_listitem_dir,
parent, false);
return new HeaderViewHolder(v);
case LogicHandler.VIEWTYPE_CHECKABLE:
v = LayoutInflater.from(getActivity()).inflate(R.layout.filepicker_listitem_checkable,
parent, false);
return new CheckableViewHolder(v);
case LogicHandler.VIEWTYPE_DIR:
default:
v = LayoutInflater.from(getActivity()).inflate(R.layout.filepicker_listitem_dir,
parent, false);
return new DirViewHolder(v);
}
}
/**
* @param vh to bind data from either a file or directory
* @param position 0 - n, where the header has been subtracted
* @param data
*/
@Override
public void onBindViewHolder(DirViewHolder vh, int position, T data) {
vh.file = data;
vh.icon.setVisibility(isDir(data) ? View.VISIBLE : View.GONE);
vh.text.setText(getName(data));
if (isCheckable(data)) {
if (checkedItems.contains(data)) {
checkedVisibleViewHolders.add((CheckableViewHolder) vh);
((CheckableViewHolder) vh).checkbox.setChecked(true);
} else {
checkedVisibleViewHolders.remove(vh);
((CheckableViewHolder) vh).checkbox.setChecked(false);
}
}
}
public class HeaderViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
final TextView text;
public HeaderViewHolder(View v) {
super(v);
v.setOnClickListener(this);
text = (TextView) v.findViewById(android.R.id.text1);
}
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
@Override
public void onClick(View v) {
currentPath = getParent(currentPath);
checkedItems.clear();
checkedVisibleViewHolders.clear();
refresh();
}
}
public class DirViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
public View icon;
public TextView text;
public T file;
public DirViewHolder(View v) {
super(v);
v.setOnClickListener(this);
v.setOnLongClickListener(this);
icon = v.findViewById(R.id.item_icon);
text = (TextView) v.findViewById(android.R.id.text1);
}
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
@Override
public void onClick(View v) {
if (isDir(file)) {
currentPath = file;
checkedItems.clear();
checkedVisibleViewHolders.clear();
refresh();
}
}
/**
* Called when a view has been clicked and held.
*
* @param v The view that was clicked and held.
* @return true if the callback consumed the long click, false otherwise.
*/
@Override
public boolean onLongClick(View v) {
return false;
}
}
public class CheckableViewHolder extends DirViewHolder {
public CheckBox checkbox;
public CheckableViewHolder(View v) {
super(v);
checkbox = (CheckBox) v.findViewById(R.id.checkbox);
checkbox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onLongClick(v);
}
});
}
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
@Override
public void onClick(View v) {
if (isDir(file)) {
currentPath = file;
checkedItems.clear();
checkedVisibleViewHolders.clear();
refresh();
} else {
onLongClick(v);
}
}
/**
* Called when a view has been clicked and held.
*
* @param v The view that was clicked and held.
* @return true if the callback consumed the long click, false otherwise.
*/
@Override
public boolean onLongClick(View v) {
if (checkedItems.contains(file)) {
checkbox.setChecked(false);
checkedItems.remove(file);
checkedVisibleViewHolders.remove(this);
} else {
if (!allowMultiple) {
clearSelections();
}
checkbox.setChecked(true);
checkedItems.add(file);
checkedVisibleViewHolders.add(this);
}
return true;
}
}
/**
* Animate de-selection of visible views and clear
* selected set.
*/
public void clearSelections() {
for (CheckableViewHolder vh : checkedVisibleViewHolders) {
vh.checkbox.setChecked(false);
}
checkedVisibleViewHolders.clear();
checkedItems.clear();
}
}

View File

@ -1,207 +0,0 @@
/*
* Copyright (c) 2014 Jonas Kalderstam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nononsenseapps.filepicker;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import java.util.List;
public class BindableArrayAdapter<T> extends ArrayAdapter<T> {
private final LayoutInflater mInflater;
private final int mResource;
private ViewBinder<T> viewBinder = null;
/**
* Constructor
*
* @param context The current context.
* @param resource The resource ID for a layout file containing a TextView to use when
*/
public BindableArrayAdapter(final Context context, final int resource) {
super(context, resource);
mResource = resource;
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* Constructor
*
* @param context The current context.
* @param resource The resource ID for a layout file containing a layout to use when
* instantiating views.
* @param textViewResourceId The id of the TextView within the layout resource to be populated
*/
public BindableArrayAdapter(final Context context, final int resource, final int textViewResourceId) {
super(context, resource, textViewResourceId);
mResource = resource;
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* Constructor
*
* @param context The current context.
* @param resource The resource ID for a layout file containing a TextView to use when
* instantiating views.
* @param objects The objects to represent in the ListView.
*/
public BindableArrayAdapter(final Context context, final int resource, final T[] objects) {
super(context, resource, objects);
mResource = resource;
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* Constructor
*
* @param context The current context.
* @param resource The resource ID for a layout file containing a layout to use when
* instantiating views.
* @param textViewResourceId The id of the TextView within the layout resource to be populated
* @param objects The objects to represent in the ListView.
*/
public BindableArrayAdapter(final Context context, final int resource, final int textViewResourceId, final T[] objects) {
super(context, resource, textViewResourceId, objects);
mResource = resource;
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* Constructor
*
* @param context The current context.
* @param resource The resource ID for a layout file containing a TextView to use when
* instantiating views.
* @param objects The objects to represent in the ListView.
*/
public BindableArrayAdapter(final Context context, final int resource, final List<T> objects) {
super(context, resource, objects);
mResource = resource;
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* Constructor
*
* @param context The current context.
* @param resource The resource ID for a layout file containing a layout to use when
* instantiating views.
* @param textViewResourceId The id of the TextView within the layout resource to be populated
* @param objects The objects to represent in the ListView.
*/
public BindableArrayAdapter(final Context context, final int resource, final int textViewResourceId, final List<T> objects) {
super(context, resource, textViewResourceId, objects);
mResource = resource;
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getItemViewType(int position) {
if (viewBinder != null) {
if (viewBinder.isDir(position, getItem(position))) {
return 1;
}
}
return 0;
}
@Override
public int getViewTypeCount() {
// Files and dirs, so 2
return 2;
}
/**
* Set the view binder to use to bind data to the list item views.
* @param binder
*/
public void setViewBinder(ViewBinder binder) {
this.viewBinder = binder;
}
/**
* Handle more complex views than standard implementation.
*
* @param position
* @param convertView
* @param parent
* @return
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (viewBinder == null) {
return super.getView(position, convertView, parent);
}
else {
View view;
if (convertView == null && viewBinder != null) {
view = viewBinder.inflateView(position, mResource, mInflater,
parent);
}
else if (convertView == null) {
view = mInflater.inflate(mResource, parent, false);
} else {
view = convertView;
}
viewBinder.setViewValue(view, position, getItem(position));
return view;
}
}
public interface ViewBinder<T> {
/**
* Called if convertView is null. If this returns null,
* the specified resource is used. Use this to return multiple views
* depending on type.
* @param position
* @param resource
* @param inflater
* @param parent
* @return
*/
public View inflateView(final int position,
final int resource,
final LayoutInflater inflater,
final ViewGroup parent);
/**
* Used to determine the view's type. Returning false will use same
* type for all rows.
* @param position
* @param data
* @return
*/
public boolean isDir(final int position, final T data);
/**
* Fill the content in the row
* @param view
* @param position
* @param data
*/
public void setViewValue(final View view, final int position, final T
data);
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) 2015 Jonas Kalderstam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nononsenseapps.filepicker;
import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
/**
* A simple adapter which also inserts a header item ".." to handle going up to the parent folder.
* @param <T> the type which is used, for example a normal java File object.
*/
public class FileItemAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final LogicHandler<T> mLogic;
private SortedList<T> mList = null;
public FileItemAdapter(LogicHandler<T> logic) {
this.mLogic = logic;
}
public void setList(SortedList<T> list) {
mList = list;
notifyDataSetChanged();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return mLogic.onCreateViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int headerPosition) {
if (headerPosition == 0) {
mLogic.onBindHeaderViewHolder((AbstractFilePickerFragment<T>.HeaderViewHolder) viewHolder);
} else {
int pos = headerPosition - 1;
mLogic.onBindViewHolder((AbstractFilePickerFragment<T>.DirViewHolder) viewHolder, pos, mList.get(pos));
}
}
@Override
public int getItemViewType(int headerPosition) {
if (0 == headerPosition) {
return LogicHandler.VIEWTYPE_HEADER;
} else {
int pos = headerPosition - 1;
return mLogic.getItemViewType(pos, mList.get(pos));
}
}
@Override
public int getItemCount() {
if (mList == null) {
return 0;
}
// header + count
return 1 + mList.size();
}
}

View File

@ -17,21 +17,21 @@
package com.nononsenseapps.filepicker;
import android.content.AsyncTaskLoader;
import android.content.Loader;
import android.net.Uri;
import android.os.Environment;
import android.os.FileObserver;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.support.v7.util.SortedList;
import android.support.v7.widget.util.SortedListAdapterCallback;
import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class FilePickerFragment extends AbstractFilePickerFragment<File> {
public FilePickerFragment() {}
public FilePickerFragment() {
}
/**
* Return true if the path is a directory and not a file.
@ -39,10 +39,19 @@ public class FilePickerFragment extends AbstractFilePickerFragment<File> {
* @param path
*/
@Override
protected boolean isDir(final File path) {
public boolean isDir(final File path) {
return path.isDirectory();
}
/**
* @param path
* @return filename of path
*/
@Override
public String getName(File path) {
return path.getName();
}
/**
* Return the path to the parent directory. Should return the root if
* from is root.
@ -50,7 +59,7 @@ public class FilePickerFragment extends AbstractFilePickerFragment<File> {
* @param from
*/
@Override
protected File getParent(final File from) {
public File getParent(final File from) {
if (from.getParentFile() != null) {
if (from.isFile()) {
return getParent(from.getParentFile());
@ -68,7 +77,7 @@ public class FilePickerFragment extends AbstractFilePickerFragment<File> {
* @param path
*/
@Override
protected File getPath(final String path) {
public File getPath(final String path) {
return new File(path);
}
@ -77,72 +86,71 @@ public class FilePickerFragment extends AbstractFilePickerFragment<File> {
* @return the full path to the file
*/
@Override
protected String getFullPath(final File path) {
public String getFullPath(final File path) {
return path.getPath();
}
/**
* @param path
* @return the name of this file/folder
*/
@Override
protected String getName(final File path) {
return path.getName();
}
/**
* Get the root path (lowest allowed).
*/
@Override
protected File getRoot() {
public File getRoot() {
return Environment.getExternalStorageDirectory();
}
/**
* Convert the path to a URI for the return intent
*
* @param file
* @return
*/
@Override
protected Uri toUri(final File file) {
public Uri toUri(final File file) {
return Uri.fromFile(file);
}
/**
* @return a comparator that can sort the items alphabetically
*/
@Override
protected Comparator<File> getComparator() {
return new Comparator<File>() {
@Override
public int compare(final File lhs, final File rhs) {
if (lhs.isDirectory() && !rhs.isDirectory()) {
return -1;
} else if (rhs.isDirectory() && !lhs.isDirectory()) {
return 1;
} else {
return lhs.getName().toLowerCase().compareTo(rhs.getName()
.toLowerCase());
}
}
};
}
/**
* Get a loader that lists the Files in the current path,
* and monitors changes.
*/
@Override
protected Loader<List<File>> getLoader() {
return new AsyncTaskLoader<List<File>>(getActivity()) {
public Loader<SortedList<File>> getLoader() {
return new AsyncTaskLoader<SortedList<File>>(getActivity()) {
FileObserver fileObserver;
@Override
public List<File> loadInBackground() {
ArrayList<File> files = new ArrayList<File>();
public SortedList<File> loadInBackground() {
File[] listFiles = currentPath.listFiles();
if(listFiles != null) {
final int initCap = listFiles == null ? 0 : listFiles.length;
SortedList<File> files = new SortedList<>(File.class, new SortedListAdapterCallback<File>(getDummyAdapter()) {
@Override
public int compare(File lhs, File rhs) {
if (lhs.isDirectory() && !rhs.isDirectory()) {
return -1;
} else if (rhs.isDirectory() && !lhs.isDirectory()) {
return 1;
} else {
return lhs.getName().toLowerCase().compareTo(rhs.getName()
.toLowerCase());
}
}
@Override
public boolean areContentsTheSame(File file, File file2) {
return file.getAbsolutePath().equals(file2.getAbsolutePath()) && (file.isFile() == file2.isFile());
}
@Override
public boolean areItemsTheSame(File file, File file2) {
return areContentsTheSame(file, file2);
}
}, initCap);
files.beginBatchedUpdates();
if (listFiles != null) {
for (java.io.File f : listFiles) {
if ((mode == MODE_FILE || mode == MODE_FILE_AND_DIR)
|| f.isDirectory()) {
@ -150,6 +158,9 @@ public class FilePickerFragment extends AbstractFilePickerFragment<File> {
}
}
}
files.endBatchedUpdates();
return files;
}
@ -217,4 +228,6 @@ public class FilePickerFragment extends AbstractFilePickerFragment<File> {
Toast.LENGTH_SHORT).show();
}
}
}

View File

@ -0,0 +1,125 @@
/*
* Copyright (c) 2015 Jonas Kalderstam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nononsenseapps.filepicker;
import android.net.Uri;
import android.support.v4.content.Loader;
import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
/**
* An interface for the methods required to handle backend-specific stuff.
*/
public interface LogicHandler<T> {
/**
* Return true if the path is a directory and not a file.
*
* @param path
*/
public boolean isDir(final T path);
/**
*
* @param path
* @return filename of path
*/
public String getName(final T path);
/**
* Convert the path to a URI for the return intent
*
* @param path
* @return
*/
public Uri toUri(final T path);
/**
* Return the path to the parent directory. Should return the root if
* from is root.
*
* @param from
*/
public T getParent(final T from);
/**
* @param path
* @return the full path to the file
*/
public String getFullPath(final T path);
/**
* Convert the path to the type used.
*
* @param path
*/
public T getPath(final String path);
/**
* Get the root path (lowest allowed).
*/
public T getRoot();
/**
* Get a loader that lists the files in the current path,
* and monitors changes.
*/
public Loader<SortedList<T>> getLoader();
/**
* @return a comparator that can sort the items alphabetically
*/
//public Comparator<T> getComparator();
public final static int VIEWTYPE_HEADER = 0;
public final static int VIEWTYPE_DIR = 1;
public final static int VIEWTYPE_CHECKABLE = 2;
/**
* Bind the header ".." which goes to parent folder.
* @param viewHolder
*/
public void onBindHeaderViewHolder(AbstractFilePickerFragment<T>.HeaderViewHolder viewHolder);
/**
* Header is subtracted from the position
*
* @param parent
* @param viewType
* @return a view holder for a file or directory
*/
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);
/**
*
* @param viewHolder to bind data from either a file or directory
* @param position 0 - n, where the header has been subtracted
* @param data
*/
public void onBindViewHolder(AbstractFilePickerFragment<T>.DirViewHolder viewHolder, int position, T data);
/**
*
* @param position 0 - n, where the header has been subtracted
* @param data
* @return an integer greater than 0
*/
public int getItemViewType(int position, T data);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014 Jonas Kalderstam
* Copyright (c) 2015 Jonas Kalderstam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,11 +18,13 @@
package com.nononsenseapps.filepicker;
import android.app.FragmentManager;
import android.support.v4.app.FragmentManager;
import android.text.TextUtils;
public class NewFolderFragment extends NewItemFragment {
private static final String TAG = "new_folder_fragment";
public static void showDialog(final FragmentManager fm, final OnNewFolderListener listener) {
NewItemFragment d = new NewFolderFragment();
d.setListener(listener);
@ -31,12 +33,9 @@ public class NewFolderFragment extends NewItemFragment {
@Override
protected boolean validateName(final String itemName) {
return itemName != null && !itemName.isEmpty()
&& !itemName.contains("/");
}
@Override
protected int getDialogTitle() {
return R.string.new_folder;
return !TextUtils.isEmpty(itemName)
&& !itemName.contains("/")
&& !itemName.equals(".")
&& !itemName.equals("..");
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014 Jonas Kalderstam
* Copyright (c) 2015 Jonas Kalderstam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,21 +18,20 @@
package com.nononsenseapps.filepicker;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.DialogFragment;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
public abstract class NewItemFragment extends DialogFragment {
private String itemName = null;
private View okButton = null;
private OnNewFolderListener listener = null;
public NewItemFragment() {
@ -50,67 +49,70 @@ public abstract class NewItemFragment extends DialogFragment {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
getDialog().setTitle(getDialogTitle());
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(R.layout.dialog_folder_name)
.setTitle(R.string.new_folder)
.setNegativeButton(android.R.string.cancel,
null)
.setPositiveButton(android.R.string.ok,
null);
@SuppressLint("InflateParams") final View view =
inflater.inflate(R.layout.dialog_new_item, null);
final AlertDialog dialog = builder.create();
okButton = view.findViewById(R.id.button_ok);
okButton.setOnClickListener(new View.OnClickListener() {
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onClick(final View v) {
if (listener != null) {
listener.onNewFolder(itemName);
}
dismiss();
}
});
public void onShow(DialogInterface dialog1) {
final AlertDialog dialog = (AlertDialog) dialog1;
final EditText editText = (EditText) dialog.findViewById(R.id.edit_text);
Button cancel = dialog.getButton(AlertDialog.BUTTON_NEGATIVE);
cancel.setOnClickListener(new View.OnClickListener() {
view.findViewById(R.id.button_cancel)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
dismiss();
public void onClick(View view) {
dialog.cancel();
}
});
final EditText editText = (EditText) view.findViewById(R.id.edit_text);
if (itemName == null) {
okButton.setEnabled(false);
} else {
editText.setText(itemName);
validateItemName();
}
final Button ok = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
// Start disabled
ok.setEnabled(false);
ok.setOnClickListener(new View.OnClickListener() {
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(final CharSequence s, final int start,
final int count, final int after) {
}
@Override
public void onClick(View view) {
String itemName = editText.getText().toString();
if (validateName(itemName)) {
if (listener != null) {
listener.onNewFolder(itemName);
}
dialog.dismiss();
}
}
});
@Override
public void onTextChanged(final CharSequence s, final int start,
final int before, final int count) {
}
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(final CharSequence s, final int start,
final int count, final int after) {
}
@Override
public void afterTextChanged(final Editable s) {
itemName = s.toString();
validateItemName();
@Override
public void onTextChanged(final CharSequence s, final int start,
final int before, final int count) {
}
@Override
public void afterTextChanged(final Editable s) {
ok.setEnabled(validateName(s.toString()));
}
});
}
});
return view;
}
protected abstract int getDialogTitle();
private void validateItemName() {
if (okButton != null) {
okButton.setEnabled(validateName(itemName));
}
return dialog;
}
protected abstract boolean validateName(final String itemName);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 955 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2014 Jonas Kalderstam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@color/action_select" />
<item android:state_selected="true" android:drawable="@color/action_select" />
<item android:state_activated="true" android:drawable="@color/action_select" />
<item android:drawable="@android:color/transparent" />
</selector>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2015 Jonas Kalderstam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="48dp"
android:fontFamily="light"
android:padding="4dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/name"
android:singleLine="true"
android:maxLines="1"
android:gravity="center_vertical"
android:imeOptions="actionDone"
style="?android:textAppearanceMedium"
android:inputType="textAutoComplete|textAutoCorrect"
/>
</FrameLayout>

View File

@ -1,78 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2014 Jonas Kalderstam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="48dp"
android:fontFamily="light"
android:padding="4dp"
android:hint="@string/name"
android:singleLine="true"
android:maxLines="1"
android:gravity="center_vertical"
android:imeOptions="actionDone"
style="?android:textAppearanceMedium"
android:inputType="textAutoComplete|textAutoCorrect"
/>
<LinearLayout
android:id="@+id/button_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/dividerHorizontal"/>
<LinearLayout
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/button_cancel"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="light"
android:text="@android:string/cancel"/>
<Button
android:id="@+id/button_ok"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="light"
android:text="@android:string/ok"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -28,25 +28,33 @@
<ImageView
android:id="@+id/item_icon"
android:src="@drawable/ic_collections_collection_light"
android:src="@drawable/ic_file_folder"
android:visibility="visible"
android:layout_width="wrap_content"
android:layout_height="match_parent"
tools:ignore="ContentDescription"/>
tools:ignore="ContentDescription"
android:tint="?attr/colorAccent"
android:adjustViewBounds="true"/>
<CheckedTextView
<TextView
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="?android:textAppearanceLarge"
android:checked="false"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:fontFamily="light"
android:padding="8dp"
android:gravity="center_vertical"
android:textColor="?android:attr/textColorPrimary"
android:id="@android:id/text1"
android:maxLines="1"
android:ellipsize="end"
android:singleLine="true"/>
android:singleLine="true"
android:text="@string/name"/>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/checkbox"
android:paddingEnd="8dp"
android:paddingRight="8dp"
/>
</LinearLayout>

View File

@ -23,28 +23,29 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="?android:listPreferredItemHeight"
android:orientation="horizontal"
android:background="@drawable/selectable_background_filepickertheme">
android:background="@drawable/selectable_background_filepickertheme"
>
<ImageView
android:id="@+id/item_icon"
android:src="@drawable/ic_collections_collection_light"
android:src="@drawable/ic_file_folder"
android:visibility="visible"
android:layout_width="wrap_content"
android:layout_height="match_parent"
tools:ignore="ContentDescription"/>
tools:ignore="ContentDescription"
android:tint="?attr/colorAccent"
android:adjustViewBounds="true"/>
<TextView
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="?android:textAppearanceLarge"
android:fontFamily="light"
android:padding="8dp"
android:gravity="center_vertical"
android:textColor="?android:attr/textColorPrimary"
android:id="@android:id/text1"
android:maxLines="1"
android:ellipsize="end"
android:singleLine="true"/>
android:singleLine="true"
android:text="@string/name"/>
</LinearLayout>

View File

@ -18,103 +18,60 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FilePickerActivity">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FilePickerActivity">
<LinearLayout
android:id="@+id/action_container"
<android.support.v7.widget.Toolbar
android:id="@+id/picker_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:orientation="horizontal"
android:layout_width="match_parent"
android:background="@color/dark_grey"
android:layout_height="?android:listPreferredItemHeight">
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar">
<ImageButton
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:src="@drawable/ic_navigation_back"
android:background="@drawable/selectable_action"
android:clickable="true"
android:scaleType="fitCenter"
android:contentDescription="@string/go_back"
android:id="@+id/button_go_parent"/>
<ImageButton
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/selectable_action"
android:id="@+id/button_create_dir"
android:src="@drawable/ic_collections_add_collection"
android:clickable="true"
android:contentDescription="@string/new_folder"
android:scaleType="fitCenter"/>
</LinearLayout>
<TextView
android:id="@+id/current_dir"
android:layout_below="@id/action_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/grey"
android:fontFamily="light"
android:maxLines="1"
android:singleLine="true"
android:scrollHorizontally="false"
android:ellipsize="start"
style="?android:textAppearanceMedium"
android:padding="4dp"
android:gravity="center_vertical"
android:textColor="?android:attr/textColorPrimary"
/>
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/current_dir"
android:id="@android:id/list"
android:layout_above="@+id/button_container"
tools:listitem="@layout/filepicker_listitem_dir"/>
<LinearLayout android:id="@+id/button_container"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/dividerHorizontal"/>
<LinearLayout
style="?android:attr/buttonBarStyle"
<TextView
android:id="@+id/current_dir"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:ellipsize="start"
android:singleLine="true"
android:textColor="@android:color/white"
android:textAppearance="@style/TextAppearance.AppCompat.Widget.ActionBar.Title" />
</android.support.v7.widget.Toolbar>
<android.support.v7.widget.RecyclerView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/button_container"
android:layout_below="@+id/picker_toolbar"
tools:listitem="@layout/filepicker_listitem_dir" />
<LinearLayout
android:id="@+id/button_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal">
<Button
android:id="@+id/button_cancel"
style="?android:attr/buttonBarButtonStyle"
style="?android:attr/borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="48dp"
android:layout_weight="1"
android:fontFamily="light"
android:text="@android:string/cancel"/>
android:text="@android:string/cancel" />
<Button
android:id="@+id/button_ok"
style="?android:attr/buttonBarButtonStyle"
style="?android:attr/borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="48dp"
android:layout_weight="1"
android:fontFamily="light"
android:text="@android:string/ok"/>
</LinearLayout>
android:text="@android:string/ok" />
</LinearLayout>
</RelativeLayout>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_createdir"
android:title="@string/new_folder"
app:showAsAction="always"
android:orderInCategory="1"
android:icon="@drawable/ic_collections_add_collection"
/>
</menu>

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2014 Jonas Kalderstam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<dimen name="configure_dialog_width">480dp</dimen>
<dimen name="configure_dialog_max_height">600dp</dimen>
</resources>

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2014 Jonas Kalderstam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<style name="FilePicker.Theme.Base"
parent="android:Theme.Holo.Light.Dialog">
<item name="isDialog">true</item>
<item name="android:windowIsFloating">false</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowSoftInputMode">stateAlwaysHidden</item>
<item name="android:windowIsTranslucent">true</item>
</style>
</resources>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2014 Jonas Kalderstam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<declare-styleable name="FilePickerTheme">
<attr name="isDialog" format="boolean" />
</declare-styleable>
</resources>

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2014 Jonas Kalderstam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<color name="grey">#e6e6e6</color>
<color name="dark_grey">#4d4d4d</color>
<color name="action_select">#6d6d6d</color>
</resources>

View File

@ -20,24 +20,5 @@
<string name="new_folder">New folder</string>
<string name="create_folder_error">Failed to create folder</string>
<string name="name">Name</string>
<string name="go_back">Go back</string>
<string name="select_something_first">Please select something first</string>
<plurals
name="select_dir">
<item quantity="one">Select directory</item>
<item quantity="other">Select directories</item>
</plurals>
<plurals
name="select_file">
<item quantity="one">Select file</item>
<item quantity="other">Select files</item>
</plurals>
<plurals
name="select_dir_or_file">
<item quantity="one">Select directory or file</item>
<item quantity="other">Select directories or files</item>
</plurals>
</resources>

View File

@ -1,50 +0,0 @@
<!--
Copyright (c) 2014 Jonas Kalderstam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- This base is overridden for larger displays -->
<style name="FilePicker.Theme.Base" parent="android:Theme.Holo.Light">
<item name="isDialog">false</item>
</style>
<style name="FilePicker.Theme" parent="FilePicker.Theme.Base">
<item name="android:actionBarStyle">@style/FilePicker.ActionBar</item>
<item name="android:actionOverflowButtonStyle">@style/FilePicker.ActionButton.Overflow</item>
<item name="android:actionBarItemBackground">@drawable/ab_solid_filepickertheme</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowShowWallpaper">true</item>
<!--<item name="android:windowBackground">@android:color
/transparent</item>-->
<!--<item name="android:actionBarWidgetTheme">@android:style/Theme.Holo</item>-->
<item name="android:textAppearance">?android:attr/textAppearanceLarge</item>
</style>
<style name="FilePicker.ActionBar" parent="android:Widget.Holo.Light.ActionBar.Solid.Inverse">
<item name="android:background">@drawable/ab_solid_filepickertheme</item>
<item name="android:displayOptions">disableHome|showTitle</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowShowWallpaper">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
<style name="FilePicker.ActionButton.Overflow" parent="android:Widget.Holo.ActionButton.Overflow">
<!--<item name="android:src">@drawable/ic_action_overflow</item>-->
</style>
</resources>

View File

@ -30,8 +30,8 @@ android {
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
//runProguard false
//proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}

View File

@ -33,31 +33,44 @@
<!-- Sample app -->
<activity
android:name="com.nononsenseapps.filepicker.sample.NoNonsenseFilePicker"
android:label="@string/title_activity_no_nonsense_file_picker"
android:theme ="@style/SampleTheme">
android:label="@string/title_activity_no_nonsense_file_picker">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- SD-card picker -->
<!-- SD-card pickers -->
<activity
android:name="com.nononsenseapps.filepicker.FilePickerActivity"
android:label="@string/app_name"
android:theme="@style/FilePicker.Theme">
android:theme="@style/SampleTheme">
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- Dropbox picker -->
<activity
android:name="com.nononsenseapps.filepicker.sample.FilePickerActivity2"
android:label="@string/app_name"
android:theme="@style/SampleThemeLight">
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- Dropbox pickers -->
<activity
android:name="com.nononsenseapps.filepicker.sample.dropbox.DropboxFilePickerActivity"
android:label="@string/app_name"
android:theme="@style/FilePicker.Theme">
android:theme="@style/SampleTheme">
</activity>
<activity
android:name="com.nononsenseapps.filepicker.sample.dropbox.DropboxFilePickerActivity2"
android:label="@string/app_name"
android:theme="@style/SampleThemeLight">
</activity>
<!-- Dropbox authenticator -->
<activity
@ -66,7 +79,7 @@
android:configChanges="orientation|keyboard">
<intent-filter>
<!-- Change this to be db- followed by your app key -->
<data android:scheme="db-YOUR_APP_KEY_HERE" />
<data android:scheme="db-sm57t7s6lmgj745" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2015 Jonas Kalderstam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nononsenseapps.filepicker.sample;
import com.nononsenseapps.filepicker.FilePickerActivity;
/**
* This is a copy of the included activity for the sole purpose
* of being to show you a second example theme
*/
public class FilePickerActivity2 extends FilePickerActivity {
}

View File

@ -17,7 +17,6 @@
package com.nononsenseapps.filepicker.sample;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ClipData;
import android.content.Intent;
@ -36,6 +35,7 @@ import com.dropbox.client2.android.AndroidAuthSession;
import com.nononsenseapps.filepicker.AbstractFilePickerFragment;
import com.nononsenseapps.filepicker.FilePickerActivity;
import com.nononsenseapps.filepicker.sample.dropbox.DropboxFilePickerActivity;
import com.nononsenseapps.filepicker.sample.dropbox.DropboxFilePickerActivity2;
import com.nononsenseapps.filepicker.sample.dropbox.DropboxSyncHelper;
import java.util.ArrayList;
@ -57,17 +57,26 @@ public class NoNonsenseFilePicker extends Activity {
(CheckBox) findViewById(R.id.checkAllowCreateDir);
final CheckBox checkAllowMultiple =
(CheckBox) findViewById(R.id.checkAllowMultiple);
final CheckBox checkLightTheme =
(CheckBox) findViewById(R.id.checkLightTheme);
final RadioGroup radioGroup =
(RadioGroup) findViewById(R.id.radioGroup);
textView = (TextView) findViewById(R.id.text);
findViewById(R.id.button)
findViewById(R.id.button_sd)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
//Intent i = new Intent(NoNonsenseFilePicker.this,
// FilePickerActivity.class);
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
Intent i;
if (checkLightTheme.isChecked()) {
i = new Intent(NoNonsenseFilePicker.this,
FilePickerActivity2.class);
} else {
i = new Intent(NoNonsenseFilePicker.this,
FilePickerActivity.class);
}
i.setAction(Intent.ACTION_GET_CONTENT);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE,
checkAllowMultiple.isChecked());
@ -113,8 +122,14 @@ public class NoNonsenseFilePicker extends Activity {
mDBApi.getSession().startOAuth2Authentication(
NoNonsenseFilePicker.this);
} else { // User is authorized, open file picker
Intent i = new Intent(NoNonsenseFilePicker.this,
DropboxFilePickerActivity.class);
Intent i;
if (checkLightTheme.isChecked()) {
i = new Intent(NoNonsenseFilePicker.this,
DropboxFilePickerActivity2.class);
} else {
i = new Intent(NoNonsenseFilePicker.this,
DropboxFilePickerActivity.class);
}
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE,
checkAllowMultiple.isChecked());
@ -182,12 +197,11 @@ public class NoNonsenseFilePicker extends Activity {
return id == R.id.action_settings || super.onOptionsItemSelected(item);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
Intent data) {
if ((CODE_SD == requestCode || CODE_DB == requestCode) &&
resultCode == Activity.RESULT_OK) {
resultCode == Activity.RESULT_OK) {
if (data.getBooleanExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE,
false)) {
@ -216,7 +230,6 @@ public class NoNonsenseFilePicker extends Activity {
}
textView.setText(sb.toString());
}
} else {
textView.setText(data.getData().toString());
}

View File

@ -26,7 +26,7 @@ import com.nononsenseapps.filepicker.AbstractFilePickerFragment;
public class DropboxFilePickerActivity
extends AbstractFilePickerActivity<DropboxAPI.Entry> {
extends AbstractFilePickerActivity<DropboxAPI.Entry> {
// In the class declaration section:
private DropboxAPI<AndroidAuthSession> mDBApi;

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2015 Jonas Kalderstam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nononsenseapps.filepicker.sample.dropbox;
/**
* This is a copy of the included activity for the sole purpose
* of being to show you a second example theme
*/
public class DropboxFilePickerActivity2 extends DropboxFilePickerActivity {
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014 Jonas Kalderstam
* Copyright (c) 2015 Jonas Kalderstam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,10 +18,13 @@
package com.nononsenseapps.filepicker.sample.dropbox;
import android.annotation.SuppressLint;
import android.content.AsyncTaskLoader;
import android.content.Loader;
import android.net.Uri;
import android.os.AsyncTask;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.support.v7.util.SortedList;
import android.support.v7.widget.util.SortedListAdapterCallback;
import android.text.TextUtils;
import android.widget.Toast;
import com.dropbox.client2.DropboxAPI;
@ -31,9 +34,6 @@ import com.nononsenseapps.filepicker.AbstractFilePickerFragment;
import com.nononsenseapps.filepicker.sample.R;
import java.io.File;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@SuppressLint("ValidFragment")
public class DropboxFilePickerFragment
@ -54,6 +54,7 @@ public class DropboxFilePickerFragment
this.dbApi = api;
}
@Override
public void onNewFolder(final String name) {
File folder = new File(currentPath.path, name);
@ -64,37 +65,19 @@ public class DropboxFilePickerFragment
folderCreator.execute(folder.getPath());
}
private class FolderCreator extends AsyncTask<String, Void, Void> {
@Override
protected Void doInBackground(final String... paths) {
for (String path : paths) {
try {
dbApi.createFolder(path);
currentPath = dbApi.metadata(path, 1, null, false, null);
refresh();
} catch (DropboxException e) {
Toast.makeText(getActivity(), R.string.create_folder_error,
Toast.LENGTH_SHORT).show();
}
}
return null;
}
}
@Override
protected boolean isDir(final DropboxAPI.Entry file) {
public boolean isDir(final DropboxAPI.Entry file) {
return file.isDir;
}
@Override
protected DropboxAPI.Entry getParent(final DropboxAPI.Entry from) {
public DropboxAPI.Entry getParent(final DropboxAPI.Entry from) {
// Take care of a slight limitation in Dropbox code:
if (from.path.length() > 1 && from.path.endsWith("/")) {
from.path = from.path.substring(0, from.path.length() - 1);
}
String parent = from.parentPath();
if (parent.isEmpty()) {
if (TextUtils.isEmpty(parent)) {
parent = "/";
}
@ -103,7 +86,7 @@ public class DropboxFilePickerFragment
}
@Override
protected DropboxAPI.Entry getPath(final String path) {
public DropboxAPI.Entry getPath(final String path) {
final DropboxAPI.Entry entry = new DropboxAPI.Entry();
entry.path = path;
entry.isDir = true;
@ -112,51 +95,56 @@ public class DropboxFilePickerFragment
}
@Override
protected String getFullPath(final DropboxAPI.Entry file) {
public String getFullPath(final DropboxAPI.Entry file) {
return file.path;
}
@Override
protected String getName(final DropboxAPI.Entry file) {
public String getName(final DropboxAPI.Entry file) {
return file.fileName();
}
@Override
protected DropboxAPI.Entry getRoot() {
public DropboxAPI.Entry getRoot() {
return getPath("/");
}
@Override
protected Uri toUri(final DropboxAPI.Entry file) {
return new Uri.Builder().scheme("dropbox").path(file.path).build();
public Uri toUri(final DropboxAPI.Entry file) {
return new Uri.Builder().scheme("dropbox").authority("").path(file.path).build();
}
@Override
protected Comparator<DropboxAPI.Entry> getComparator() {
return new Comparator<DropboxAPI.Entry>() {
@Override
public int compare(final DropboxAPI.Entry lhs,
final DropboxAPI.Entry rhs) {
if (isDir(lhs) && !isDir(rhs)) {
return -1;
} else if (isDir(rhs) && !isDir(lhs)) {
return 1;
} else {
return lhs.fileName().toLowerCase()
.compareTo(rhs.fileName().toLowerCase());
}
}
};
}
@Override
protected Loader<List<DropboxAPI.Entry>> getLoader() {
return new AsyncTaskLoader<List<DropboxAPI.Entry>>(getActivity()) {
public Loader<SortedList<DropboxAPI.Entry>> getLoader() {
return new AsyncTaskLoader<SortedList<DropboxAPI.Entry>>(getActivity()) {
@Override
public List<DropboxAPI.Entry> loadInBackground() {
ArrayList<DropboxAPI.Entry> files =
new ArrayList<DropboxAPI.Entry>();
public SortedList<DropboxAPI.Entry> loadInBackground() {
SortedList<DropboxAPI.Entry> files = new SortedList<>(DropboxAPI.Entry.class,
new SortedListAdapterCallback<DropboxAPI.Entry>(getAdapter()) {
@Override
public int compare(DropboxAPI.Entry lhs, DropboxAPI.Entry rhs) {
if (isDir(lhs) && !isDir(rhs)) {
return -1;
} else if (isDir(rhs) && !isDir(lhs)) {
return 1;
} else {
return lhs.fileName().toLowerCase()
.compareTo(rhs.fileName().toLowerCase());
}
}
@Override
public boolean areContentsTheSame(DropboxAPI.Entry lhs, DropboxAPI.Entry rhs) {
return lhs.fileName().equals(rhs.fileName()) && (lhs.isDir == rhs.isDir);
}
@Override
public boolean areItemsTheSame(DropboxAPI.Entry lhs, DropboxAPI.Entry rhs) {
return areContentsTheSame(lhs, rhs);
}
}, 0);
try {
if (!dbApi.metadata(currentPath.path, 1, null, false,
@ -167,13 +155,18 @@ public class DropboxFilePickerFragment
DropboxAPI.Entry dirEntry =
dbApi.metadata(currentPath.path, 0, null, true,
null);
files.beginBatchedUpdates();
for (DropboxAPI.Entry entry : dirEntry.contents) {
if ((mode == MODE_FILE || mode == MODE_FILE_AND_DIR) ||
entry.isDir) {
entry.isDir) {
files.add(entry);
}
}
} catch (DropboxException e) {
files.endBatchedUpdates();
} catch (DropboxException ignored) {
}
return files;
@ -203,5 +196,21 @@ public class DropboxFilePickerFragment
};
}
private class FolderCreator extends AsyncTask<String, Void, Void> {
@Override
protected Void doInBackground(final String... paths) {
for (String path : paths) {
try {
dbApi.createFolder(path);
currentPath = dbApi.metadata(path, 1, null, false, null);
refresh();
} catch (DropboxException e) {
Toast.makeText(getActivity(), R.string.create_folder_error,
Toast.LENGTH_SHORT).show();
}
}
return null;
}
}
}

View File

@ -37,8 +37,8 @@ import com.dropbox.client2.session.AppKeyPair;
*/
public class DropboxSyncHelper {
// Change these two lines to your app's stuff
final static public String APP_KEY = "INSERT_APP_KEY";
final static public String APP_SECRET = "INSERT_APP_SECRET";
final static public String APP_KEY = "sm57t7s6lmgj745";
final static public String APP_SECRET = "eie6mq0lvcw9t7x";
public static final String PREF_DROPBOX_TOKEN = "dropboxtoken";

View File

@ -14,128 +14,136 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/scrollView">
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context="com.nononsenseapps.filepicker.sample.NoNonsenseFilePicker">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/scrollView"
android:layout_gravity="center_horizontal">
</ScrollView>
<LinearLayout
<FrameLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
tools:context="com.nononsenseapps.filepicker.sample.NoNonsenseFilePicker">
<LinearLayout
android:orientation="vertical"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<RadioGroup
android:id="@+id/radioGroup"
<LinearLayout
android:orientation="vertical"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RadioGroup
android:id="@+id/radioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Select file"
android:id="@+id/radioFile"
style="?android:textAppearanceMedium"
android:fontFamily="light"
android:checked="true"
android:gravity="center_vertical"
android:layout_gravity="left"/>
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Select directory"
android:id="@+id/radioDir"
style="?android:textAppearanceMedium"
android:fontFamily="light"
android:gravity="center_vertical"
android:layout_gravity="left"/>
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Select any"
android:id="@+id/radioFilesAndDirs"
style="?android:textAppearanceMedium"
android:fontFamily="light"
android:gravity="center_vertical"
android:layout_gravity="left"/>
</RadioGroup>
<CheckBox
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="Multiple items"
android:gravity="center_vertical"
android:id="@+id/checkAllowMultiple"
style="?android:textAppearanceMedium"
android:fontFamily="light"
android:layout_gravity="left"/>
<CheckBox
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="Allow creation of directories\n(if selecting directories)"
android:id="@+id/checkAllowCreateDir"
android:gravity="center_vertical"
style="?android:textAppearanceMedium"
android:fontFamily="light"
android:layout_gravity="left"/>
<CheckBox
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="Use light theme"
android:id="@+id/checkLightTheme"
android:gravity="center_vertical"
style="?android:textAppearanceMedium"
android:fontFamily="light"
android:layout_gravity="left"/>
</LinearLayout>
<Button
android:layout_width="match_parent"
android:layout_height="64dp"
android:text="Pick SD-card"
android:layout_marginTop="16dp"
android:id="@+id/button_sd"
android:gravity="center"
style="?android:attr/borderlessButtonStyle"
android:fontFamily="light"
android:layout_gravity="center"/>
<Button
android:layout_width="match_parent"
android:layout_height="64dp"
android:text="Pick Dropbox"
android:layout_marginTop="8dp"
android:id="@+id/button_dropbox"
android:gravity="center"
style="?android:attr/borderlessButtonStyle"
android:fontFamily="light"
android:layout_gravity="center"/>
<TextView
android:id="@+id/text"
android:padding="16dp"
android:layout_marginTop="16dp"
android:text="Result will be displayed here"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Select file"
android:id="@+id/radioFile"
style="?android:textAppearanceMedium"
android:fontFamily="light"
android:checked="true"
android:gravity="center_vertical"
android:layout_gravity="left"/>
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Select directory"
android:id="@+id/radioDir"
style="?android:textAppearanceMedium"
android:fontFamily="light"
android:gravity="center_vertical"
android:layout_gravity="left"/>
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Select any"
android:id="@+id/radioFilesAndDirs"
style="?android:textAppearanceMedium"
android:fontFamily="light"
android:gravity="center_vertical"
android:layout_gravity="left"/>
</RadioGroup>
<CheckBox
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="Multiple items"
android:gravity="center_vertical"
android:id="@+id/checkAllowMultiple"
style="?android:textAppearanceMedium"
android:fontFamily="light"
android:layout_gravity="left"/>
<CheckBox
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="Allow creation of directories\n(if selecting directories)"
android:id="@+id/checkAllowCreateDir"
android:gravity="center_vertical"
style="?android:textAppearanceMedium"
android:fontFamily="light"
android:layout_gravity="left"/>
android:layout_gravity="center_horizontal"/>
</LinearLayout>
<Button
android:layout_width="wrap_content"
android:layout_height="48dp"
android:text="Pick SD-card"
android:layout_marginTop="8dp"
android:id="@+id/button"
android:gravity="center_vertical"
style="?android:textAppearanceMedium"
android:fontFamily="light"
android:layout_gravity="center"/>
<Button
android:layout_width="wrap_content"
android:layout_height="48dp"
android:text="Pick Dropbox"
android:layout_marginTop="8dp"
android:id="@+id/button_dropbox"
android:gravity="center_vertical"
style="?android:textAppearanceMedium"
android:fontFamily="light"
android:layout_gravity="center"/>
<TextView
android:id="@+id/text"
android:padding="16dp"
android:text="Result will be displayed here"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:textAppearanceMedium"
android:fontFamily="light"
android:layout_gravity="center_horizontal"/>
</LinearLayout>
</FrameLayout>
</FrameLayout>
</ScrollView>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="primary">#607D8B</color>
<color name="primary_dark">#455A64</color>
<color name="accent">#536DFE</color>
<color name="primary2">#F44336</color>
<color name="primary_dark2">#D32F2F</color>
<color name="accent2">#FFAB00</color>
</resources>

View File

@ -1,5 +1,5 @@
<!--
Copyright (c) 2014 Jonas Kalderstam
Copyright (c) 2015 Jonas Kalderstam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -17,6 +17,40 @@
<resources>
<style name="SampleTheme" parent="android:Theme.Holo.Light.DarkActionBar" />
<style name="SampleTheme" parent="Theme.AppCompat.DialogWhenLarge">
<item name="colorPrimary">@color/primary2</item>
<item name="colorPrimaryDark">@color/primary_dark2</item>
<item name="colorAccent">@color/accent2</item>
<item name="alertDialogTheme">@style/SampleAlertDialogTheme</item>
<!-- These are important. Handled by toolbar -->
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="SampleThemeLight" parent="Theme.AppCompat.Light.DialogWhenLarge">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primary_dark</item>
<item name="colorAccent">@color/accent</item>
<item name="alertDialogTheme">@style/SampleAlertDialogThemeLight</item>
<!-- These are important. Handled by toolbar -->
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="SampleAlertDialogTheme" parent="Theme.AppCompat.Dialog.Alert">
<item name="colorPrimary">@color/primary2</item>
<item name="colorPrimaryDark">@color/primary_dark2</item>
<item name="colorAccent">@color/accent2</item>
</style>
<style name="SampleAlertDialogThemeLight" parent="Theme.AppCompat.Light.Dialog.Alert">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primary_dark</item>
<item name="colorAccent">@color/accent</item>
</style>
</resources>