Merge pull request #1347 from mfietz/recycler_view

RecyclerView & SnackBar
This commit is contained in:
Tom Hennen 2015-11-07 09:16:24 -05:00
commit 0aeb520f14
59 changed files with 1240 additions and 5694 deletions

View File

@ -28,12 +28,12 @@ dependencies {
compile "de.greenrobot:eventbus:$eventbusVersion"
compile "io.reactivex:rxandroid:$rxAndroidVersion"
compile "com.joanzapata.iconify:android-iconify-fontawesome:2.1.0"
compile 'com.afollestad:material-dialogs:0.7.8.1'
compile "com.afollestad:material-dialogs:0.7.8.1"
compile "com.yqritc:recyclerview-flexibledivider:1.2.6"
compile "com.github.AntennaPod:AntennaPod-AudioPlayer:v1.0.2"
compile project(":core")
compile project(":library:drag-sort-listview")
}
def getMyVersionName() {

View File

@ -97,7 +97,7 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase {
};
EventDistributor.getInstance().register(queueListener);
List<FeedItem> queue = writeTestQueue("a");
EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED_ITEMS, queue));
EventBus.getDefault().post(QueueEvent.setQueue(queue));
countDownLatch.await(5000, TimeUnit.MILLISECONDS);
assertNotNull(queue);

View File

@ -220,7 +220,7 @@ public class UITestUtils {
adapter.setQueue(queue);
adapter.close();
EventDistributor.getInstance().sendFeedUpdateBroadcast();
EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED_ITEMS, queue));
EventBus.getDefault().post(QueueEvent.setQueue(queue));
}
public PlaybackController getPlaybackController(MainActivity mainActivity) {

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -102,7 +102,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
private View navDrawer;
private ListView navList;
private NavListAdapter navAdapter;
private AdapterView.AdapterContextMenuInfo lastMenuInfo = null;
private int mPosition = -1;
private ActionBarDrawerToggle drawerToggle;
@ -139,11 +139,8 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
final FragmentManager fm = getSupportFragmentManager();
fm.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
@Override
public void onBackStackChanged() {
drawerToggle.setDrawerIndicatorEnabled(fm.getBackStackEntryCount() == 0);
}
fm.addOnBackStackChangedListener(() -> {
drawerToggle.setDrawerIndicatorEnabled(fm.getBackStackEntryCount() == 0);
});
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
@ -153,6 +150,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
navList.setAdapter(navAdapter);
navList.setOnItemClickListener(navListClickListener);
navList.setOnItemLongClickListener(newListLongClickListener);
registerForContextMenu(navList);
navAdapter.registerDataSetObserver(new DataSetObserver() {
@Override
@ -161,12 +159,9 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
}
});
findViewById(R.id.nav_settings).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
drawerLayout.closeDrawer(navDrawer);
startActivity(new Intent(MainActivity.this, PreferenceController.getPreferenceActivity()));
}
findViewById(R.id.nav_settings).setOnClickListener(v -> {
drawerLayout.closeDrawer(navDrawer);
startActivity(new Intent(MainActivity.this, PreferenceController.getPreferenceActivity()));
});
FragmentTransaction transaction = fm.beginTransaction();
@ -218,12 +213,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
private void checkFirstLaunch() {
SharedPreferences prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE);
if (prefs.getBoolean(PREF_IS_FIRST_LAUNCH, true)) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
drawerLayout.openDrawer(navDrawer);
}
}, 1500);
new Handler().postDelayed(() -> drawerLayout.openDrawer(navDrawer), 1500);
SharedPreferences.Editor edit = prefs.edit();
edit.putBoolean(PREF_IS_FIRST_LAUNCH, false);
@ -245,21 +235,15 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle(R.string.drawer_preferences);
builder.setMultiChoiceItems(navLabels, checked, new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
if (isChecked) {
hiddenDrawerItems.remove(NAV_DRAWER_TAGS[which]);
} else {
hiddenDrawerItems.add(NAV_DRAWER_TAGS[which]);
}
builder.setMultiChoiceItems(navLabels, checked, (dialog, which, isChecked) -> {
if (isChecked) {
hiddenDrawerItems.remove(NAV_DRAWER_TAGS[which]);
} else {
hiddenDrawerItems.add(NAV_DRAWER_TAGS[which]);
}
});
builder.setPositiveButton(R.string.confirm_label, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
UserPreferences.setHiddenDrawerItems(hiddenDrawerItems);
}
builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
UserPreferences.setHiddenDrawerItems(hiddenDrawerItems);
});
builder.setNegativeButton(R.string.cancel_label, null);
builder.create().show();
@ -422,6 +406,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
showDrawerPreferencesDialog();
return true;
} else {
mPosition = position;
return false;
}
}
@ -521,6 +506,68 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
if(v.getId() != R.id.nav_list) {
return;
}
AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo;
int position = adapterInfo.position;
if(position < navAdapter.getSubscriptionOffset()) {
return;
}
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.nav_feed_context, menu);
Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset());
menu.setHeaderTitle(feed.getTitle());
// episodes are not loaded, so we cannot check if the podcast has new or unplayed ones!
}
@Override
public boolean onContextItemSelected(MenuItem item) {
final int position = mPosition;
mPosition = -1; // reset
if(position < 0) {
return false;
}
Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset());
switch(item.getItemId()) {
case R.id.mark_all_seen_item:
DBWriter.markFeedSeen(feed.getId());
return true;
case R.id.mark_all_read_item:
DBWriter.markFeedRead(feed.getId());
return true;
case R.id.remove_item:
final FeedRemover remover = new FeedRemover(this, feed) {
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
if(getSelectedNavListIndex() == position) {
loadFragment(EpisodesFragment.TAG, null);
}
}
};
ConfirmationDialog conDialog = new ConfirmationDialog(this,
R.string.remove_feed_label,
R.string.feed_delete_confirmation_msg) {
@Override
public void onConfirmButtonPressed(
DialogInterface dialog) {
dialog.dismiss();
remover.executeAsync();
}
};
conDialog.createNewDialog().show();
return true;
default:
return super.onContextItemSelected(item);
}
}
private DBReader.NavDrawerData navDrawerData;
private AsyncTask<Void, Void, DBReader.NavDrawerData> loadTask;
private int selectedNavListIndex = 0;

View File

@ -7,6 +7,8 @@ import android.widget.ImageButton;
import org.apache.commons.lang3.Validate;
import java.lang.ref.WeakReference;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
@ -27,7 +29,7 @@ public class ActionButtonUtils {
public ActionButtonUtils(Context context) {
Validate.notNull(context);
this.context = context;
this.context = context.getApplicationContext();
drawables = context.obtainStyledAttributes(new int[] {
R.attr.av_play,
R.attr.navigation_cancel,

View File

@ -5,10 +5,13 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
@ -22,6 +25,7 @@ import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.GlideDrawableImageViewTarget;
import com.joanzapata.iconify.Iconify;
import com.nineoldandroids.view.ViewHelper;
import java.lang.ref.WeakReference;
@ -51,6 +55,8 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR
private final boolean showOnlyNewEpisodes;
private final WeakReference<MainActivity> mainActivityRef;
private int position = -1;
public AllEpisodesRecycleAdapter(Context context,
MainActivity mainActivity,
ItemAccess itemAccess,
@ -96,6 +102,10 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR
public void onBindViewHolder(final Holder holder, int position) {
final FeedItem item = itemAccess.getItem(position);
if (item == null) return;
holder.itemView.setOnLongClickListener(v -> {
this.position = position;
return false;
});
holder.item = item;
holder.position = position;
holder.placeholder.setVisibility(View.VISIBLE);
@ -189,6 +199,12 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR
return itemAccess.getItem(position);
}
public int getPosition() {
int pos = position;
position = -1; // reset
return pos;
}
private class CoverTarget extends GlideDrawableImageViewTarget {
private final WeakReference<Uri> fallback;
@ -235,63 +251,10 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR
}
};
private Menu popupMenu;
private final FeedItemMenuHandler.MenuInterface contextMenuInterface = new FeedItemMenuHandler.MenuInterface() {
@Override
public void setItemVisibility(int id, boolean visible) {
if(popupMenu == null) {
return;
}
MenuItem item = popupMenu.findItem(id);
if (item != null) {
item.setVisible(visible);
}
}
};
private final boolean showContextMenu(View view) {
// Create a PopupMenu, giving it the clicked view for an anchor
MainActivity mainActivity = this.mainActivityRef.get();
if (mainActivity == null) {
Log.d(TAG, "mainActivity is null");
return false;
}
PopupMenu popup = new PopupMenu(mainActivity, view);
Menu menu = popup.getMenu();
// Inflate our menu resource into the PopupMenu's Menu
popup.getMenuInflater().inflate(R.menu.allepisodes_context, popup.getMenu());
Holder holder = (Holder) view.getTag();
FeedItem item = holder.item;
if (item == null) {
return false;
}
popupMenu = menu;
FeedItemMenuHandler.onPrepareMenu(context, contextMenuInterface, item, true, null);
// Set a listener so we are notified if a menu item is clicked
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
try {
FeedItemMenuHandler.onMenuItemClicked(context, menuItem.getItemId(), item);
return true;
} catch (DownloadRequestException e) {
e.printStackTrace();
}
return false;
}
});
popup.show();
return true;
}
public class Holder extends RecyclerView.ViewHolder
implements View.OnClickListener, View.OnLongClickListener{
implements View.OnClickListener,
View.OnCreateContextMenuListener,
ItemTouchHelperViewHolder {
TextView placeholder;
TextView title;
TextView pubDate;
@ -308,7 +271,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR
public Holder(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
itemView.setOnCreateContextMenuListener(this);
}
@Override
@ -319,14 +282,42 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR
}
}
public FeedItem getFeedItem() { return item; }
public int getItemPosition() { return position; }
@Override
public void onItemSelected() {
ViewHelper.setAlpha(itemView, 0.5f);
}
@Override
public boolean onLongClick(View view) {
return showContextMenu(view);
public void onItemClear() {
ViewHelper.setAlpha(itemView, 1.0f);
}
public FeedItem getFeedItem() { return item; }
@Override
public void onCreateContextMenu(final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
FeedItem item = itemAccess.getItem(getAdapterPosition());
MenuInflater inflater = mainActivityRef.get().getMenuInflater();
inflater.inflate(R.menu.allepisodes_context, menu);
if (item != null) {
menu.setHeaderTitle(item.getTitle());
}
FeedItemMenuHandler.MenuInterface contextMenuInterface = (id, visible) -> {
if (menu == null) {
return;
}
MenuItem item1 = menu.findItem(id);
if (item1 != null) {
item1.setVisible(visible);
}
};
FeedItemMenuHandler.onPrepareMenu(mainActivityRef.get(), contextMenuInterface, item, true,
null);
}
}
public interface ItemAccess {
@ -340,4 +331,26 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR
boolean isInQueue(FeedItem item);
}
/**
* Notifies a View Holder of relevant callbacks from
* {@link ItemTouchHelper.Callback}.
*/
public interface ItemTouchHelperViewHolder {
/**
* Called when the {@link ItemTouchHelper} first registers an
* item as being moved or swiped.
* Implementations should update the item view to indicate
* it's active state.
*/
void onItemSelected();
/**
* Called when the {@link ItemTouchHelper} has completed the
* move or swipe, and the active item state should be cleared.
*/
void onItemClear();
}
}

View File

@ -1,250 +0,0 @@
package de.danoeh.antennapod.adapter;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.GlideDrawableImageViewTarget;
import com.joanzapata.iconify.Iconify;
import java.lang.ref.WeakReference;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.NetworkUtils;
/**
* List adapter for the queue.
*/
public class QueueListAdapter extends BaseAdapter {
private static final String TAG = QueueListAdapter.class.getSimpleName();
private final Context context;
private final ItemAccess itemAccess;
private final ActionButtonCallback actionButtonCallback;
private final ActionButtonUtils actionButtonUtils;
private boolean locked;
public QueueListAdapter(Context context, ItemAccess itemAccess, ActionButtonCallback actionButtonCallback) {
super();
this.context = context;
this.itemAccess = itemAccess;
this.actionButtonUtils = new ActionButtonUtils(context);
this.actionButtonCallback = actionButtonCallback;
locked = UserPreferences.isQueueLocked();
}
public void setLocked(boolean locked) {
this.locked = locked;
notifyDataSetChanged();
}
@Override
public int getCount() {
return itemAccess.getCount();
}
@Override
public Object getItem(int position) {
return itemAccess.getItem(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Holder holder;
final FeedItem item = (FeedItem) getItem(position);
if (item == null) return null;
if (convertView == null) {
holder = new Holder();
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.queue_listitem,
parent, false);
holder.dragHandle = (ImageView) convertView.findViewById(R.id.drag_handle);
holder.placeholder = (TextView) convertView.findViewById(R.id.txtvPlaceholder);
holder.cover = (ImageView) convertView.findViewById(R.id.imgvCover);
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
holder.pubDate = (TextView) convertView.findViewById(R.id.txtvPubDate);
holder.progressLeft = (TextView) convertView.findViewById(R.id.txtvProgressLeft);
holder.progressRight = (TextView) convertView
.findViewById(R.id.txtvProgressRight);
holder.butSecondary = (ImageButton) convertView
.findViewById(R.id.butSecondaryAction);
holder.progress = (ProgressBar) convertView
.findViewById(R.id.progressBar);
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
if(locked) {
holder.dragHandle.setVisibility(View.GONE);
} else {
holder.dragHandle.setVisibility(View.VISIBLE);
}
holder.placeholder.setText(item.getFeed().getTitle());
holder.title.setText(item.getTitle());
FeedMedia media = item.getMedia();
holder.title.setText(item.getTitle());
String pubDate = DateUtils.formatDateTime(context, item.getPubDate().getTime(), DateUtils.FORMAT_ABBREV_ALL);
holder.pubDate.setText(pubDate.replace(" ", "\n"));
if (media != null) {
final boolean isDownloadingMedia = DownloadRequester.getInstance().isDownloadingFile(media);
FeedItem.State state = item.getState();
if (isDownloadingMedia) {
holder.progressLeft.setText(Converter.byteToString(itemAccess.getItemDownloadedBytes(item)));
if(itemAccess.getItemDownloadSize(item) > 0) {
holder.progressRight.setText(Converter.byteToString(itemAccess.getItemDownloadSize(item)));
} else {
holder.progressRight.setText(Converter.byteToString(media.getSize()));
}
holder.progress.setProgress(itemAccess.getItemDownloadProgressPercent(item));
holder.progress.setVisibility(View.VISIBLE);
} else if (state == FeedItem.State.PLAYING
|| state == FeedItem.State.IN_PROGRESS) {
if (media.getDuration() > 0) {
int progress = (int) (100.0 * media.getPosition() / media.getDuration());
holder.progress.setProgress(progress);
holder.progress.setVisibility(View.VISIBLE);
holder.progressLeft.setText(Converter
.getDurationStringLong(media.getPosition()));
holder.progressRight.setText(Converter.getDurationStringLong(media.getDuration()));
}
} else {
if(media.getSize() > 0) {
holder.progressLeft.setText(Converter.byteToString(media.getSize()));
} else if(false == media.checkedOnSizeButUnknown()) {
holder.progressLeft.setText("{fa-spinner}");
Iconify.addIcons(holder.progressLeft);
NetworkUtils.getFeedMediaSizeObservable(media)
.subscribe(
size -> {
if (size > 0) {
holder.progressLeft.setText(Converter.byteToString(size));
} else {
holder.progressLeft.setText("");
}
}, error -> {
holder.progressLeft.setText("");
Log.e(TAG, Log.getStackTraceString(error));
});
} else {
holder.progressLeft.setText("");
}
holder.progressRight.setText(Converter.getDurationStringLong(media.getDuration()));
holder.progress.setVisibility(View.GONE);
}
}
actionButtonUtils.configureActionButton(holder.butSecondary, item);
holder.butSecondary.setFocusable(false);
holder.butSecondary.setTag(item);
holder.butSecondary.setOnClickListener(secondaryActionListener);
Glide.with(context)
.load(item.getImageUri())
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
.fitCenter()
.dontAnimate()
.into(new CoverTarget(item.getFeed().getImageUri(), holder.placeholder, holder.cover));
return convertView;
}
private class CoverTarget extends GlideDrawableImageViewTarget {
private final WeakReference<Uri> fallback;
private final WeakReference<TextView> placeholder;
private final WeakReference<ImageView> cover;
public CoverTarget(Uri fallbackUri, TextView txtvPlaceholder, ImageView imgvCover) {
super(imgvCover);
fallback = new WeakReference<>(fallbackUri);
placeholder = new WeakReference<>(txtvPlaceholder);
cover = new WeakReference<>(imgvCover);
}
@Override
public void onLoadFailed(Exception e, Drawable errorDrawable) {
Uri fallbackUri = fallback.get();
TextView txtvPlaceholder = placeholder.get();
ImageView imgvCover = cover.get();
if(fallbackUri != null && txtvPlaceholder != null && imgvCover != null) {
Glide.with(context)
.load(fallbackUri)
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
.fitCenter()
.dontAnimate()
.into(new CoverTarget(null, txtvPlaceholder, imgvCover));
}
}
@Override
public void onResourceReady(GlideDrawable drawable, GlideAnimation anim) {
super.onResourceReady(drawable, anim);
TextView txtvPlaceholder = placeholder.get();
if(txtvPlaceholder != null) {
txtvPlaceholder.setVisibility(View.INVISIBLE);
}
}
}
private View.OnClickListener secondaryActionListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
FeedItem item = (FeedItem) v.getTag();
actionButtonCallback.onActionButtonPressed(item);
}
};
static class Holder {
ImageView dragHandle;
ImageView cover;
TextView placeholder;
TextView title;
TextView pubDate;
TextView progressLeft;
TextView progressRight;
ProgressBar progress;
ImageButton butSecondary;
}
public interface ItemAccess {
FeedItem getItem(int position);
int getCount();
long getItemDownloadedBytes(FeedItem item);
long getItemDownloadSize(FeedItem item);
int getItemDownloadProgressPercent(FeedItem item);
}
}

View File

@ -0,0 +1,346 @@
package de.danoeh.antennapod.adapter;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.v4.view.MotionEventCompat;
import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.GlideDrawableImageViewTarget;
import com.joanzapata.iconify.Iconify;
import com.nineoldandroids.view.ViewHelper;
import java.lang.ref.WeakReference;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.fragment.ItemFragment;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
/**
* List adapter for the queue.
*/
public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdapter.ViewHolder> {
private static final String TAG = QueueRecyclerAdapter.class.getSimpleName();
private WeakReference<MainActivity> mainActivity;
private final ItemAccess itemAccess;
private final ActionButtonCallback actionButtonCallback;
private final ActionButtonUtils actionButtonUtils;
private final ItemTouchHelper itemTouchHelper;
private boolean locked;
private int position = -1;
public QueueRecyclerAdapter(MainActivity mainActivity,
ItemAccess itemAccess,
ActionButtonCallback actionButtonCallback,
ItemTouchHelper itemTouchHelper) {
super();
this.mainActivity = new WeakReference<>(mainActivity);
this.itemAccess = itemAccess;
this.actionButtonUtils = new ActionButtonUtils(mainActivity);
this.actionButtonCallback = actionButtonCallback;
this.itemTouchHelper = itemTouchHelper;
locked = UserPreferences.isQueueLocked();
}
public void setLocked(boolean locked) {
this.locked = locked;
notifyDataSetChanged();
}
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.queue_listitem, parent, false);
return new ViewHolder(view);
}
public void onBindViewHolder(ViewHolder holder, int pos) {
FeedItem item = itemAccess.getItem(pos);
holder.bind(item);
holder.itemView.setOnLongClickListener(v -> {
position = pos;
return false;
});
}
public int getItemCount() {
return itemAccess.getCount();
}
public int getPosition() {
return position;
}
public class ViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener,
View.OnCreateContextMenuListener,
ItemTouchHelperViewHolder {
private final ImageView dragHandle;
private final TextView placeholder;
private final ImageView cover;
private final TextView title;
private final TextView pubDate;
private final TextView progressLeft;
private final TextView progressRight;
private final ProgressBar progressBar;
private final ImageButton butSecondary;
private FeedItem item;
public ViewHolder(View v) {
super(v);
dragHandle = (ImageView) v.findViewById(R.id.drag_handle);
placeholder = (TextView) v.findViewById(R.id.txtvPlaceholder);
cover = (ImageView) v.findViewById(R.id.imgvCover);
title = (TextView) v.findViewById(R.id.txtvTitle);
pubDate = (TextView) v.findViewById(R.id.txtvPubDate);
progressLeft = (TextView) v.findViewById(R.id.txtvProgressLeft);
progressRight = (TextView) v.findViewById(R.id.txtvProgressRight);
butSecondary = (ImageButton) v.findViewById(R.id.butSecondaryAction);
progressBar = (ProgressBar) v.findViewById(R.id.progressBar);
v.setTag(this);
v.setOnClickListener(this);
v.setOnCreateContextMenuListener(this);
dragHandle.setOnTouchListener((v1, event) -> {
if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
Log.d(TAG, "startDrag()");
itemTouchHelper.startDrag(ViewHolder.this);
}
return false;
});
}
@Override
public void onClick(View v) {
MainActivity activity = mainActivity.get();
if (activity != null) {
activity.loadChildFragment(ItemFragment.newInstance(item.getId()));
}
}
@Override
public void onCreateContextMenu(final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
FeedItem item = itemAccess.getItem(getAdapterPosition());
MenuInflater inflater = mainActivity.get().getMenuInflater();
inflater.inflate(R.menu.queue_context, menu);
if (item != null) {
menu.setHeaderTitle(item.getTitle());
}
FeedItemMenuHandler.MenuInterface contextMenuInterface = (id, visible) -> {
if (menu == null) {
return;
}
MenuItem item1 = menu.findItem(id);
if (item1 != null) {
item1.setVisible(visible);
}
};
FeedItemMenuHandler.onPrepareMenu(mainActivity.get(), contextMenuInterface, item, true,
itemAccess.getQueueIds());
}
@Override
public void onItemSelected() {
ViewHelper.setAlpha(itemView, 0.5f);
}
@Override
public void onItemClear() {
ViewHelper.setAlpha(itemView, 1.0f);
}
public void bind(FeedItem item) {
this.item = item;
if(locked) {
dragHandle.setVisibility(View.GONE);
} else {
dragHandle.setVisibility(View.VISIBLE);
}
placeholder.setText(item.getFeed().getTitle());
title.setText(item.getTitle());
FeedMedia media = item.getMedia();
title.setText(item.getTitle());
String pubDateStr = DateUtils.formatDateTime(mainActivity.get(),
item.getPubDate().getTime(), DateUtils.FORMAT_ABBREV_ALL);
pubDate.setText(pubDateStr.replace(" ", "\n"));
if (media != null) {
final boolean isDownloadingMedia = DownloadRequester.getInstance().isDownloadingFile(media);
FeedItem.State state = item.getState();
if (isDownloadingMedia) {
progressLeft.setText(Converter.byteToString(itemAccess.getItemDownloadedBytes(item)));
if(itemAccess.getItemDownloadSize(item) > 0) {
progressRight.setText(Converter.byteToString(itemAccess.getItemDownloadSize(item)));
} else {
progressRight.setText(Converter.byteToString(media.getSize()));
}
progressBar.setProgress(itemAccess.getItemDownloadProgressPercent(item));
progressBar.setVisibility(View.VISIBLE);
} else if (state == FeedItem.State.PLAYING
|| state == FeedItem.State.IN_PROGRESS) {
if (media.getDuration() > 0) {
int progress = (int) (100.0 * media.getPosition() / media.getDuration());
progressBar.setProgress(progress);
progressBar.setVisibility(View.VISIBLE);
progressLeft.setText(Converter
.getDurationStringLong(media.getPosition()));
progressRight.setText(Converter.getDurationStringLong(media.getDuration()));
}
} else {
if(media.getSize() > 0) {
progressLeft.setText(Converter.byteToString(media.getSize()));
} else if(false == media.checkedOnSizeButUnknown()) {
progressLeft.setText("{fa-spinner}");
Iconify.addIcons(progressLeft);
NetworkUtils.getFeedMediaSizeObservable(media)
.subscribe(
size -> {
if (size > 0) {
progressLeft.setText(Converter.byteToString(size));
} else {
progressLeft.setText("");
}
}, error -> {
progressLeft.setText("");
Log.e(TAG, Log.getStackTraceString(error));
});
} else {
progressLeft.setText("");
}
progressRight.setText(Converter.getDurationStringLong(media.getDuration()));
progressBar.setVisibility(View.GONE);
}
}
actionButtonUtils.configureActionButton(butSecondary, item);
butSecondary.setFocusable(false);
butSecondary.setTag(item);
butSecondary.setOnClickListener(secondaryActionListener);
Glide.with(mainActivity.get())
.load(item.getImageUri())
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
.fitCenter()
.dontAnimate()
.into(new CoverTarget(item.getFeed().getImageUri(), placeholder, cover));
}
}
private class CoverTarget extends GlideDrawableImageViewTarget {
private final WeakReference<Uri> fallback;
private final WeakReference<TextView> placeholder;
private final WeakReference<ImageView> cover;
public CoverTarget(Uri fallbackUri, TextView txtvPlaceholder, ImageView imgvCover) {
super(imgvCover);
fallback = new WeakReference<>(fallbackUri);
placeholder = new WeakReference<>(txtvPlaceholder);
cover = new WeakReference<>(imgvCover);
}
@Override
public void onLoadFailed(Exception e, Drawable errorDrawable) {
Uri fallbackUri = fallback.get();
TextView txtvPlaceholder = placeholder.get();
ImageView imgvCover = cover.get();
if(fallbackUri != null && txtvPlaceholder != null && imgvCover != null) {
Glide.with(mainActivity.get())
.load(fallbackUri)
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
.fitCenter()
.dontAnimate()
.into(new CoverTarget(null, txtvPlaceholder, imgvCover));
}
}
@Override
public void onResourceReady(GlideDrawable drawable, GlideAnimation anim) {
super.onResourceReady(drawable, anim);
TextView txtvPlaceholder = placeholder.get();
if(txtvPlaceholder != null) {
txtvPlaceholder.setVisibility(View.INVISIBLE);
}
}
}
private View.OnClickListener secondaryActionListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
FeedItem item = (FeedItem) v.getTag();
actionButtonCallback.onActionButtonPressed(item);
}
};
public interface ItemAccess {
FeedItem getItem(int position);
int getCount();
long getItemDownloadedBytes(FeedItem item);
long getItemDownloadSize(FeedItem item);
int getItemDownloadProgressPercent(FeedItem item);
LongList getQueueIds();
}
/**
* Notifies a View Holder of relevant callbacks from
* {@link ItemTouchHelper.Callback}.
*/
public interface ItemTouchHelperViewHolder {
/**
* Called when the {@link ItemTouchHelper} first registers an
* item as being moved or swiped.
* Implementations should update the item view to indicate
* it's active state.
*/
void onItemSelected();
/**
* Called when the {@link ItemTouchHelper} has completed the
* move or swipe, and the active item state should be cleared.
*/
void onItemClear();
}
}

View File

@ -79,16 +79,14 @@ public class EpisodesApplyActionFragment extends Fragment {
mListView = (ListView) view.findViewById(android.R.id.list);
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> ListView, View view, int position, long rowId) {
long id = episodes.get(position).getId();
if (checkedIds.contains(id)) {
checkedIds.remove(id);
} else {
checkedIds.add(id);
}
refreshCheckboxes();
mListView.setOnItemClickListener((ListView, view1, position, rowId) -> {
long id = episodes.get(position).getId();
if (checkedIds.contains(id)) {
checkedIds.remove(id);
} else {
checkedIds.add(id);
}
refreshCheckboxes();
});
for(FeedItem episode : episodes) {
@ -101,40 +99,15 @@ public class EpisodesApplyActionFragment extends Fragment {
checkAll();
btnAddToQueue = (Button) view.findViewById(R.id.btnAddToQueue);
btnAddToQueue.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
queueChecked();
}
});
btnAddToQueue.setOnClickListener(v -> queueChecked());
btnMarkAsPlayed = (Button) view.findViewById(R.id.btnMarkAsPlayed);
btnMarkAsPlayed.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
markedCheckedPlayed();
}
});
btnMarkAsPlayed.setOnClickListener(v -> markedCheckedPlayed());
btnMarkAsUnplayed = (Button) view.findViewById(R.id.btnMarkAsUnplayed);
btnMarkAsUnplayed.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
markedCheckedUnplayed();
}
});
btnMarkAsUnplayed.setOnClickListener(v -> markedCheckedUnplayed());
btnDownload = (Button) view.findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
downloadChecked();
}
});
btnDownload.setOnClickListener(v -> downloadChecked());
btnDelete = (Button) view.findViewById(R.id.btnDelete);
btnDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
deleteChecked();
}
});
btnDelete.setOnClickListener(v -> deleteChecked());
return view;
}
@ -153,16 +126,13 @@ public class EpisodesApplyActionFragment extends Fragment {
FontAwesomeIcons.fa_sort).color(textColor).actionBarSize());
mSelectToggle = menu.findItem(R.id.select_toggle);
mSelectToggle.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (checkedIds.size() == episodes.size()) {
checkNone();
} else {
checkAll();
}
return true;
mSelectToggle.setOnMenuItemClickListener(item -> {
if (checkedIds.size() == episodes.size()) {
checkNone();
} else {
checkAll();
}
return true;
});
menu.findItem(R.id.select_options).setIcon(new IconDrawable(getActivity(),
@ -241,14 +211,11 @@ public class EpisodesApplyActionFragment extends Fragment {
}
private void sortByTitle(final boolean reverse) {
Collections.sort(episodes, new Comparator<FeedItem>() {
@Override
public int compare(FeedItem lhs, FeedItem rhs) {
if (reverse) {
return -1 * lhs.getTitle().compareTo(rhs.getTitle());
} else {
return lhs.getTitle().compareTo(rhs.getTitle());
}
Collections.sort(episodes, (lhs, rhs) -> {
if (reverse) {
return -1 * lhs.getTitle().compareTo(rhs.getTitle());
} else {
return lhs.getTitle().compareTo(rhs.getTitle());
}
});
refreshTitles();
@ -256,20 +223,17 @@ public class EpisodesApplyActionFragment extends Fragment {
}
private void sortByDate(final boolean reverse) {
Collections.sort(episodes, new Comparator<FeedItem>() {
@Override
public int compare(FeedItem lhs, FeedItem rhs) {
if (lhs.getPubDate() == null) {
return -1;
} else if (rhs.getPubDate() == null) {
return 1;
}
int code = lhs.getPubDate().compareTo(rhs.getPubDate());
if (reverse) {
return -1 * code;
} else {
return code;
}
Collections.sort(episodes, (lhs, rhs) -> {
if (lhs.getPubDate() == null) {
return -1;
} else if (rhs.getPubDate() == null) {
return 1;
}
int code = lhs.getPubDate().compareTo(rhs.getPubDate());
if (reverse) {
return -1 * code;
} else {
return code;
}
});
refreshTitles();
@ -277,22 +241,19 @@ public class EpisodesApplyActionFragment extends Fragment {
}
private void sortByDuration(final boolean reverse) {
Collections.sort(episodes, new Comparator<FeedItem>() {
@Override
public int compare(FeedItem lhs, FeedItem rhs) {
int ordering;
if (false == lhs.hasMedia()) {
ordering = 1;
} else if (false == rhs.hasMedia()) {
ordering = -1;
} else {
ordering = lhs.getMedia().getDuration() - rhs.getMedia().getDuration();
}
if(reverse) {
return -1 * ordering;
Collections.sort(episodes, (lhs, rhs) -> {
int ordering;
if (false == lhs.hasMedia()) {
ordering = 1;
} else if (false == rhs.hasMedia()) {
ordering = -1;
} else {
return ordering;
ordering = lhs.getMedia().getDuration() - rhs.getMedia().getDuration();
}
if(reverse) {
return -1 * ordering;
} else {
return ordering;
}
});
refreshTitles();
@ -361,7 +322,7 @@ public class EpisodesApplyActionFragment extends Fragment {
}
private void queueChecked() {
DBWriter.addQueueItem(getActivity(), episodes.toArray(new FeedItem[0]));
DBWriter.addQueueItem(getActivity(), true, checkedIds.toArray());
close();
}

View File

@ -21,6 +21,8 @@ import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.Toast;
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
@ -39,9 +41,10 @@ import de.danoeh.antennapod.core.service.download.Downloader;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.view.DividerItemDecoration;
import rx.Observable;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
@ -93,12 +96,6 @@ public class AllEpisodesFragment extends Fragment {
setHasOptionsMenu(true);
}
@Override
public void onResume() {
super.onResume();
loadItems();
}
@Override
public void onStart() {
super.onStart();
@ -113,10 +110,18 @@ public class AllEpisodesFragment extends Fragment {
}
}
@Override
public void onResume() {
super.onResume();
loadItems();
registerForContextMenu(recyclerView);
}
@Override
public void onPause() {
super.onPause();
saveScrollPosition();
unregisterForContextMenu(recyclerView);
}
@Override
@ -260,6 +265,31 @@ public class AllEpisodesFragment extends Fragment {
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if(!isVisible()) {
return false;
}
int pos = listAdapter.getPosition();
if(pos < 0) {
return false;
}
FeedItem selectedItem = itemAccess.getItem(pos);
if (selectedItem == null) {
Log.i(TAG, "Selected item at position " + pos + " was null, ignoring selection");
return super.onContextItemSelected(item);
}
try {
return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem);
} catch (DownloadRequestException e) {
e.printStackTrace();
Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show();
return true;
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return onCreateViewHelper(inflater, container, savedInstanceState,
@ -278,8 +308,7 @@ public class AllEpisodesFragment extends Fragment {
layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
RecyclerView.ItemDecoration itemDecoration = new DividerItemDecoration(getActivity(), null);
recyclerView.addItemDecoration(itemDecoration);
recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build());
progLoading = (ProgressBar) root.findViewById(R.id.progLoading);

View File

@ -14,9 +14,10 @@ import java.util.List;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.AllEpisodesRecycleAdapter;
import de.danoeh.antennapod.adapter.QueueRecyclerAdapter;
import de.danoeh.antennapod.core.event.QueueEvent;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.event.QueueEvent;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
@ -104,6 +105,32 @@ public class NewEpisodesFragment extends AllEpisodesFragment {
snackbar.show();
h.postDelayed(r, (int)Math.ceil(snackbar.getDuration() * 1.05f));
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder,
int actionState) {
// We only want the active item
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
if (viewHolder instanceof AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder) {
AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder itemViewHolder =
(AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder) viewHolder;
itemViewHolder.onItemSelected();
}
}
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void clearView(RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
if (viewHolder instanceof AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder) {
AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder itemViewHolder =
(AllEpisodesRecycleAdapter.ItemTouchHelperViewHolder) viewHolder;
itemViewHolder.onItemClear();
}
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);

View File

@ -1,43 +1,45 @@
package de.danoeh.antennapod.fragment;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.Log;
import android.view.ContextMenu;
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.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.mobeta.android.dslv.DragSortListView;
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.DefaultActionButtonCallback;
import de.danoeh.antennapod.adapter.QueueListAdapter;
import de.danoeh.antennapod.adapter.QueueRecyclerAdapter;
import de.danoeh.antennapod.core.asynctask.DownloadObserver;
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.event.QueueEvent;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.event.QueueEvent;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadService;
import de.danoeh.antennapod.core.service.download.Downloader;
@ -47,10 +49,9 @@ import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.QueueSorter;
import de.danoeh.antennapod.core.util.gui.FeedItemUndoToken;
import de.danoeh.antennapod.core.util.gui.UndoBarController;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.greenrobot.event.EventBus;
@ -71,28 +72,19 @@ public class QueueFragment extends Fragment {
EventDistributor.PLAYER_STATUS_UPDATE;
private TextView infoBar;
private DragSortListView listView;
private QueueListAdapter listAdapter;
private RecyclerView recyclerView;
private QueueRecyclerAdapter recyclerAdapter;
private TextView txtvEmpty;
private ProgressBar progLoading;
private ContextMenu contextMenu;
private AdapterView.AdapterContextMenuInfo lastMenuInfo = null;
private UndoBarController<FeedItemUndoToken> undoBarController;
private List<FeedItem> queue;
private List<Downloader> downloaderList;
private boolean itemsLoaded = false;
private boolean viewsCreated = false;
private boolean isUpdatingFeeds = false;
private static final String PREFS = "QueueFragment";
private static final String PREF_KEY_LIST_TOP = "list_top";
private static final String PREF_KEY_LIST_SELECTION = "list_selection";
private AtomicReference<Activity> activity = new AtomicReference<Activity>();
private static final String PREF_SCROLL_POSITION = "scroll_position";
private static final String PREF_SCROLL_OFFSET = "scroll_offset";
private DownloadObserver downloadObserver = null;
@ -102,6 +94,8 @@ public class QueueFragment extends Fragment {
private boolean blockDownloadObserverUpdate = false;
private Subscription subscription;
private LinearLayoutManager layoutManager;
private ItemTouchHelper itemTouchHelper;
@Override
@ -111,91 +105,118 @@ public class QueueFragment extends Fragment {
setHasOptionsMenu(true);
}
@Override
public void onResume() {
super.onResume();
loadItems();
}
@Override
public void onStart() {
super.onStart();
EventDistributor.getInstance().register(contentUpdate);
EventBus.getDefault().register(this);
this.activity.set((MainActivity) getActivity());
if (downloadObserver != null) {
downloadObserver.setActivity(getActivity());
downloadObserver.onResume();
}
if (viewsCreated && itemsLoaded) {
if (queue != null) {
onFragmentLoaded();
}
}
@Override
public void onResume() {
super.onResume();
recyclerView.setAdapter(recyclerAdapter);
loadItems();
EventDistributor.getInstance().register(contentUpdate);
EventBus.getDefault().register(this);
}
@Override
public void onPause() {
super.onPause();
saveScrollPosition();
}
@Override
public void onStop() {
super.onStop();
EventDistributor.getInstance().unregister(contentUpdate);
EventBus.getDefault().unregister(this);
if(subscription != null) {
subscription.unsubscribe();
}
if(undoBarController.isShowing()) {
undoBarController.close();
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
this.activity.set((MainActivity) activity);
}
public void onEventMainThread(QueueEvent event) {
Log.d(TAG, "onEvent(" + event + ")");
if(event.action == QueueEvent.Action.REMOVED) {
undoBarController.showUndoBar(false, getString(R.string.removed_from_queue),
new FeedItemUndoToken(event.item, event.position));
switch(event.action) {
case ADDED:
queue.add(event.position, event.item);
recyclerAdapter.notifyItemInserted(event.position);
break;
case SET_QUEUE:
queue = event.items;
recyclerAdapter.notifyDataSetChanged();
break;
case REMOVED:
int position = FeedItemUtil.indexOfItemWithId(queue, event.item.getId());
queue.remove(position);
recyclerAdapter.notifyItemRemoved(position);
break;
case CLEARED:
queue.clear();
recyclerAdapter.notifyDataSetChanged();
break;
case SORTED:
queue = event.items;
recyclerAdapter.notifyDataSetChanged();
break;
case MOVED:
int from = FeedItemUtil.indexOfItemWithId(queue, event.item.getId());
int to = event.position;
Collections.swap(queue, from, to);
recyclerAdapter.notifyItemMoved(from, to);
break;
}
onFragmentLoaded();
}
public void onEventMainThread(FeedItemEvent event) {
Log.d(TAG, "onEvent(" + event + ")");
for(int i=0, size = event.items.size(); i < size; i++) {
FeedItem item = event.items.get(i);
int pos = FeedItemUtil.indexOfItemWithId(queue, item.getId());
if(pos >= 0) {
queue.remove(pos);
queue.add(pos, item);
recyclerAdapter.notifyItemChanged(pos);
}
}
loadItems();
}
private void saveScrollPosition() {
int firstItem = layoutManager.findFirstVisibleItemPosition();
View firstItemView = layoutManager.findViewByPosition(firstItem);
float topOffset;
if(firstItemView == null) {
topOffset = 0;
} else {
topOffset = firstItemView.getTop();
}
SharedPreferences prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
View v = listView.getChildAt(0);
int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop());
editor.putInt(PREF_KEY_LIST_SELECTION, listView.getFirstVisiblePosition());
editor.putInt(PREF_KEY_LIST_TOP, top);
editor.putInt(PREF_SCROLL_POSITION, firstItem);
editor.putFloat(PREF_SCROLL_OFFSET, topOffset);
editor.commit();
}
private void restoreScrollPosition() {
SharedPreferences prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
int listSelection = prefs.getInt(PREF_KEY_LIST_SELECTION, 0);
int top = prefs.getInt(PREF_KEY_LIST_TOP, 0);
if(listSelection > 0 || top > 0) {
listView.setSelectionFromTop(listSelection, top);
int position = prefs.getInt(PREF_SCROLL_POSITION, 0);
float offset = prefs.getFloat(PREF_SCROLL_OFFSET, 0.0f);
if (position > 0 || offset > 0) {
layoutManager.scrollToPositionWithOffset(position, (int) offset);
// restore once, then forget
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(PREF_KEY_LIST_SELECTION, 0);
editor.putInt(PREF_KEY_LIST_TOP, 0);
editor.putInt(PREF_SCROLL_POSITION, 0);
editor.putFloat(PREF_SCROLL_OFFSET, 0.0f);
editor.commit();
}
}
private void resetViewState() {
unregisterForContextMenu(listView);
listAdapter = null;
activity.set(null);
undoBarController = null;
viewsCreated = false;
recyclerAdapter = null;
blockDownloadObserverUpdate = false;
if (downloadObserver != null) {
downloadObserver.onPause();
@ -208,17 +229,14 @@ public class QueueFragment extends Fragment {
resetViewState();
}
private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = new MenuItemUtils.UpdateRefreshMenuItemChecker() {
@Override
public boolean isRefreshing() {
return DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds();
}
private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = () -> {
return DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds();
};
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
if (itemsLoaded) {
if (queue != null) {
inflater.inflate(R.menu.queue, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
@ -251,14 +269,9 @@ public class QueueFragment extends Fragment {
switch (item.getItemId()) {
case R.id.queue_lock:
boolean locked = !UserPreferences.isQueueLocked();
if(locked) {
listView.setDragEnabled(false);
} else {
listView.setDragEnabled(true);
}
UserPreferences.setQueueLocked(locked);
getActivity().supportInvalidateOptionsMenu();
listAdapter.setLocked(locked);
recyclerAdapter.setLocked(locked);
return true;
case R.id.refresh_item:
List<Feed> feeds = ((MainActivity) getActivity()).getFeeds();
@ -305,54 +318,19 @@ public class QueueFragment extends Fragment {
} else {
return true;
}
}
private final FeedItemMenuHandler.MenuInterface contextMenuInterface = new FeedItemMenuHandler.MenuInterface() {
@Override
public void setItemVisibility(int id, boolean visible) {
if(contextMenu == null) {
return;
}
MenuItem item = contextMenu.findItem(id);
if (item != null) {
item.setVisible(visible);
}
}
};
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo;
FeedItem item = itemAccess.getItem(adapterInfo.position);
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.queue_context, menu);
if (item != null) {
menu.setHeaderTitle(item.getTitle());
}
contextMenu = menu;
lastMenuInfo = (AdapterView.AdapterContextMenuInfo) menuInfo;
LongList queueIds = new LongList(queue.size());
for(FeedItem queueItem : queue) {
queueIds.add(queueItem.getId());
}
FeedItemMenuHandler.onPrepareMenu(getActivity(), contextMenuInterface, item, true, queueIds);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
if(menuInfo == null) {
menuInfo = lastMenuInfo;
if(!isVisible()) {
return false;
}
FeedItem selectedItem = itemAccess.getItem(menuInfo.position);
int pos = recyclerAdapter.getPosition();
FeedItem selectedItem = itemAccess.getItem(pos);
if (selectedItem == null) {
Log.i(TAG, "Selected item at position " + menuInfo.position + " was null, ignoring selection");
Log.i(TAG, "Selected item at position " + pos + " was null, ignoring selection");
return super.onContextItemSelected(item);
}
@ -373,112 +351,111 @@ public class QueueFragment extends Fragment {
View root = inflater.inflate(R.layout.queue_fragment, container, false);
infoBar = (TextView) root.findViewById(R.id.info_bar);
listView = (DragSortListView) root.findViewById(android.R.id.list);
txtvEmpty = (TextView) root.findViewById(android.R.id.empty);
progLoading = (ProgressBar) root.findViewById(R.id.progLoading);
listView.setEmptyView(txtvEmpty);
recyclerView = (RecyclerView) root.findViewById(R.id.recyclerView);
layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build());
recyclerView.setHasFixedSize(true);
registerForContextMenu(recyclerView);
if(UserPreferences.isQueueLocked()) {
listView.setDragEnabled(false);
} else {
listView.setDragEnabled(true);
}
itemTouchHelper = new ItemTouchHelper(
new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.RIGHT) {
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
FeedItem item = (FeedItem) listAdapter.getItem(position - listView.getHeaderViewsCount());
if (item != null) {
((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(item.getId()));
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int from = viewHolder.getAdapterPosition();
int to = target.getAdapterPosition();
Log.d(TAG, "move(" + from + ", " + to + ")");
Collections.swap(queue, from, to);
recyclerAdapter.notifyItemMoved(from, to);
DBWriter.moveQueueItem(from, to, false);
return true;
}
}
});
listView.setDragSortListener(new DragSortListView.DragSortListener() {
@Override
public void drag(int from, int to) {
Log.d(TAG, "drag");
blockDownloadObserverUpdate = true;
}
@Override
public void drop(int from, int to) {
Log.d(TAG, "drop");
blockDownloadObserverUpdate = false;
if(subscription != null) {
subscription.unsubscribe();
}
final FeedItem item = queue.remove(from);
queue.add(to, item);
listAdapter.notifyDataSetChanged();
DBWriter.moveQueueItem(from, to, true);
}
@Override
public void remove(int which) {
Log.d(TAG, "remove(" + which + ")");
if(subscription != null) {
subscription.unsubscribe();
}
FeedItem item = (FeedItem) listView.getAdapter().getItem(which);
DBWriter.removeQueueItem(getActivity(), item, true);
}
});
undoBarController = new UndoBarController<FeedItemUndoToken>(root.findViewById(R.id.undobar),
new UndoBarController.UndoListener<FeedItemUndoToken>() {
private final Context context = getActivity();
@Override
public void onUndo(FeedItemUndoToken token) {
if (token != null) {
long itemId = token.getFeedItemId();
int position = token.getPosition();
DBWriter.addQueueItemAt(context, itemId, position, false);
}
}
@Override
public void onHide(FeedItemUndoToken token) {
if (token != null && context != null) {
long itemId = token.getFeedItemId();
FeedItem item = DBReader.getFeedItem(itemId);
if(item != null) {
FeedMedia media = item.getMedia();
if (media != null && media.hasAlmostEnded() && item.getFeed().getPreferences().getCurrentAutoDelete()) {
DBWriter.deleteFeedMediaOfItem(context, media.getId());
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
if(subscription != null) {
subscription.unsubscribe();
}
final int position = viewHolder.getAdapterPosition();
Log.d(TAG, "remove(" + position + ")");
final FeedItem item = queue.get(position);
final boolean isRead = item.isPlayed();
DBWriter.markItemPlayed(FeedItem.PLAYED, item.getId());
DBWriter.removeQueueItem(getActivity(), item, true);
Snackbar snackbar = Snackbar.make(root, getString(R.string.marked_as_read_label), Snackbar.LENGTH_LONG);
snackbar.setAction(getString(R.string.undo), v -> {
DBWriter.addQueueItemAt(getActivity(), item.getId(), position, false);
if(false == isRead) {
DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId());
}
});
snackbar.show();
}
@Override
public boolean isLongPressDragEnabled() {
return false == UserPreferences.isQueueLocked();
}
@Override
public boolean isItemViewSwipeEnabled() {
return false == UserPreferences.isQueueLocked();
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder,
int actionState) {
// We only want the active item
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
if (viewHolder instanceof QueueRecyclerAdapter.ItemTouchHelperViewHolder) {
QueueRecyclerAdapter.ItemTouchHelperViewHolder itemViewHolder =
(QueueRecyclerAdapter.ItemTouchHelperViewHolder) viewHolder;
itemViewHolder.onItemSelected();
}
}
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void clearView(RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
if (viewHolder instanceof QueueRecyclerAdapter.ItemTouchHelperViewHolder) {
QueueRecyclerAdapter.ItemTouchHelperViewHolder itemViewHolder =
(QueueRecyclerAdapter.ItemTouchHelperViewHolder) viewHolder;
itemViewHolder.onItemClear();
}
}
}
);
itemTouchHelper.attachToRecyclerView(recyclerView);
});
registerForContextMenu(listView);
if (!itemsLoaded) {
progLoading.setVisibility(View.VISIBLE);
txtvEmpty.setVisibility(View.GONE);
}
viewsCreated = true;
if (itemsLoaded && activity.get() != null) {
onFragmentLoaded();
}
txtvEmpty = (TextView) root.findViewById(android.R.id.empty);
txtvEmpty.setVisibility(View.GONE);
progLoading = (ProgressBar) root.findViewById(R.id.progLoading);
progLoading.setVisibility(View.VISIBLE);
return root;
}
private void onFragmentLoaded() {
if (listAdapter == null) {
listAdapter = new QueueListAdapter(activity.get(), itemAccess, new DefaultActionButtonCallback(activity.get()));
listView.setAdapter(listAdapter);
downloadObserver = new DownloadObserver(activity.get(), new Handler(), downloadObserverCallback);
if (recyclerAdapter == null) {
MainActivity activity = (MainActivity) getActivity();
recyclerAdapter = new QueueRecyclerAdapter(activity, itemAccess,
new DefaultActionButtonCallback(activity), itemTouchHelper);
recyclerView.setAdapter(recyclerAdapter);
downloadObserver = new DownloadObserver(activity, new Handler(), downloadObserverCallback);
downloadObserver.onResume();
}
listAdapter.notifyDataSetChanged();
if(queue == null || queue.size() == 0) {
recyclerView.setVisibility(View.GONE);
txtvEmpty.setVisibility(View.VISIBLE);
} else {
txtvEmpty.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}
restoreScrollPosition();
@ -486,7 +463,10 @@ public class QueueFragment extends Fragment {
// needs data that may have just been loaded.
getActivity().supportInvalidateOptionsMenu();
// refresh information bar
refreshInfoBar();
}
private void refreshInfoBar() {
String info = queue.size() + getString(R.string.episodes_suffix);
if(queue.size() > 0) {
long duration = 0;
@ -505,21 +485,21 @@ public class QueueFragment extends Fragment {
@Override
public void onContentChanged(List<Downloader> downloaderList) {
QueueFragment.this.downloaderList = downloaderList;
if (listAdapter != null && !blockDownloadObserverUpdate) {
listAdapter.notifyDataSetChanged();
if (recyclerAdapter != null && !blockDownloadObserverUpdate) {
recyclerAdapter.notifyDataSetChanged();
}
}
};
private QueueListAdapter.ItemAccess itemAccess = new QueueListAdapter.ItemAccess() {
private QueueRecyclerAdapter.ItemAccess itemAccess = new QueueRecyclerAdapter.ItemAccess() {
@Override
public int getCount() {
return (itemsLoaded) ? queue.size() : 0;
return queue != null ? queue.size() : 0;
}
@Override
public FeedItem getItem(int position) {
return (itemsLoaded) ? queue.get(position) : null;
return queue != null ? queue.get(position) : null;
}
@Override
@ -561,6 +541,11 @@ public class QueueFragment extends Fragment {
}
return 0;
}
@Override
public LongList getQueueIds() {
return queue != null ? LongList.of(FeedItemUtil.getIds(queue)) : new LongList(0);
}
};
private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
@ -579,23 +564,19 @@ public class QueueFragment extends Fragment {
if(subscription != null) {
subscription.unsubscribe();
}
if (viewsCreated && !itemsLoaded) {
listView.setVisibility(View.GONE);
if (queue == null) {
recyclerView.setVisibility(View.GONE);
txtvEmpty.setVisibility(View.GONE);
progLoading.setVisibility(View.VISIBLE);
}
subscription = Observable.defer(() -> Observable.just(DBReader.getQueue()))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
listView.setVisibility(View.VISIBLE);
progLoading.setVisibility(View.GONE);
if(result != null) {
queue = result;
itemsLoaded = true;
if (viewsCreated && activity.get() != null) {
onFragmentLoaded();
}
.subscribe(items -> {
if(items != null) {
progLoading.setVisibility(View.GONE);
queue = items;
onFragmentLoaded();
}
}, error -> {
Log.e(TAG, Log.getStackTraceString(error));

View File

@ -1,124 +0,0 @@
package de.danoeh.antennapod.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
private boolean mShowFirstDivider = false;
private boolean mShowLastDivider = false;
public DividerItemDecoration(Context context, AttributeSet attrs) {
final TypedArray a = context.obtainStyledAttributes(attrs, new int[] { android.R.attr.listDivider });
mDivider = a.getDrawable(0);
a.recycle();
}
public DividerItemDecoration(Context context, AttributeSet attrs, boolean showFirstDivider,
boolean showLastDivider) {
this(context, attrs);
mShowFirstDivider = showFirstDivider;
mShowLastDivider = showLastDivider;
}
public DividerItemDecoration(Drawable divider) {
mDivider = divider;
}
public DividerItemDecoration(Drawable divider, boolean showFirstDivider,
boolean showLastDivider) {
this(divider);
mShowFirstDivider = showFirstDivider;
mShowLastDivider = showLastDivider;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (mDivider == null) {
return;
}
if (parent.getChildPosition(view) < 1) {
return;
}
if (getOrientation(parent) == LinearLayoutManager.VERTICAL) {
outRect.top = mDivider.getIntrinsicHeight();
} else {
outRect.left = mDivider.getIntrinsicWidth();
}
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mDivider == null) {
super.onDrawOver(c, parent, state);
return;
}
// Initialization needed to avoid compiler warning
int left = 0, right = 0, top = 0, bottom = 0, size;
int orientation = getOrientation(parent);
int childCount = parent.getChildCount();
if (orientation == LinearLayoutManager.VERTICAL) {
size = mDivider.getIntrinsicHeight();
left = parent.getPaddingLeft();
right = parent.getWidth() - parent.getPaddingRight();
} else { //horizontal
size = mDivider.getIntrinsicWidth();
top = parent.getPaddingTop();
bottom = parent.getHeight() - parent.getPaddingBottom();
}
for (int i = mShowFirstDivider ? 0 : 1; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
if (orientation == LinearLayoutManager.VERTICAL) {
top = child.getTop() - params.topMargin;
bottom = top + size;
} else { //horizontal
left = child.getLeft() - params.leftMargin;
right = left + size;
}
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
// show last divider
if (mShowLastDivider && childCount > 0) {
View child = parent.getChildAt(childCount - 1);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
if (orientation == LinearLayoutManager.VERTICAL) {
top = child.getBottom() + params.bottomMargin;
bottom = top + size;
} else { // horizontal
left = child.getRight() + params.rightMargin;
right = left + size;
}
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
private int getOrientation(RecyclerView parent) {
if (parent.getLayoutManager() instanceof LinearLayoutManager) {
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
return layoutManager.getOrientation();
} else {
throw new IllegalStateException(
"DividerItemDecoration can only be used with a LinearLayoutManager.");
}
}
}

View File

@ -5,6 +5,7 @@
android:layout_width="match_parent"
android:layout_height="@dimen/listitem_threeline_height"
android:orientation="horizontal"
android:background="?attr/selectableItemBackground"
tools:background="@android:color/darker_gray">
<RelativeLayout

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dslv="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -12,7 +11,7 @@
android:layout_alignParentTop="true"
android:gravity="center"
android:textSize="12sp"
android:text="42 episodes \u2022 5 hours 17 minutes"/>
android:text=""/>
<View
android:id="@+id/divider"
@ -21,26 +20,12 @@
android:layout_below="@id/info_bar"
android:background="?android:attr/listDivider"/>
<com.mobeta.android.dslv.DragSortListView
android:id="@android:id/list"
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:scrollbarStyle="outsideOverlay"
android:layout_below="@+id/divider"
dslv:collapsed_height="2dp"
dslv:drag_enabled="true"
dslv:drag_handle_id="@id/drag_handle"
dslv:drag_scroll_start="0.33"
dslv:float_alpha="0.6"
dslv:float_background_color="?attr/dragview_float_background"
dslv:max_drag_scroll_speed="0.5"
dslv:remove_enabled="true"
dslv:remove_mode="flingRemove"
dslv:slide_shuffle_speed="0.3"
dslv:sort_enabled="true"
dslv:track_drag_sort="true"
dslv:use_default_controller="true" />
android:layout_below="@id/divider"
android:scrollbars="vertical"/>
<TextView
android:id="@id/android:empty"
@ -58,19 +43,4 @@
android:indeterminateOnly="true"
android:visibility="gone" />
<LinearLayout
android:id="@+id/undobar"
android:layout_alignParentBottom="true"
style="@style/UndoBar">
<TextView
android:id="@+id/undobar_message"
style="@style/UndoBarMessage"/>
<Button
android:id="@+id/undobar_button"
style="@style/UndoBarButton"/>
</LinearLayout>
</RelativeLayout>

View File

@ -6,24 +6,28 @@
android:layout_width="match_parent"
android:layout_height="@dimen/listitem_threeline_height"
android:orientation="horizontal"
android:paddingLeft="16dp"
android:paddingLeft="8dp"
android:gravity="center_vertical"
android:background="?attr/selectableItemBackground"
tools:background="@android:color/darker_gray" >
<ImageView
android:id="@+id/drag_handle"
android:layout_width="100dp"
android:layout_height="match_parent"
android:layout_marginLeft="-8dp"
android:layout_marginRight="-64dp"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginLeft="-16dp"
android:layout_marginRight="-20dp"
android:gravity="center"
android:contentDescription="@string/drag_handle_content_description"
android:scaleType="fitXY"
android:src="?attr/dragview_background"
tools:src="@drawable/ic_drag_handle"
tools:src="@drawable/ic_drag_vertical_grey600_48dp"
tools:background="@android:color/holo_green_dark" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:layout_marginLeft="8dp">
<TextView
android:id="@+id/txtvPlaceholder"
android:layout_width="@dimen/thumbnail_length_queue_item"

View File

@ -94,6 +94,9 @@ by Square, licensed under the Apache 2.0 license <a href="LICENSE_OKIO.txt">(Vie
<h2>Presto Client <a href="http://www.aocate.com/presto/">(Link)</a></h2>
licensed under the Apache 2.0 license <a href="LICENSE_PRESTO.txt">(View)</a>
<h2>RecyclerView-FlexibleDivider <a href="https://github.com/yqritc/RecyclerView-FlexibleDivider">(Link)</a></h2>
licensed under the Apache 2.0 license <a href="LICENSE_FLEXIBLE_DIVIDER.txt">(View)</a>
<h2>RxAndroid <a href="https://github.com/ReactiveX/RxAndroid">(Link)</a></h2>
licensed under the Apache 2.0 license <a href="LICENSE_RX_ANDROID.txt">(View)</a>

View File

@ -15,6 +15,7 @@ import de.danoeh.antennapod.core.service.download.Downloader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Provides access to the DownloadService's list of items that are currently being downloaded.
@ -39,6 +40,8 @@ public class DownloadObserver {
private Thread refresherThread;
private AtomicBoolean refresherThreadRunning = new AtomicBoolean(false);
private AtomicInteger users = new AtomicInteger(0);
/**
* Creates a new download observer.
@ -59,13 +62,18 @@ public class DownloadObserver {
}
public void onResume() {
if (BuildConfig.DEBUG) Log.d(TAG, "DownloadObserver resumed");
activity.registerReceiver(contentChangedReceiver, new IntentFilter(DownloadService.ACTION_DOWNLOADS_CONTENT_CHANGED));
connectToDownloadService();
Log.d(TAG, "DownloadObserver resumed");
if(users.getAndIncrement() == 0) {
activity.registerReceiver(contentChangedReceiver, new IntentFilter(DownloadService.ACTION_DOWNLOADS_CONTENT_CHANGED));
connectToDownloadService();
}
}
public void onPause() {
if (BuildConfig.DEBUG) Log.d(TAG, "DownloadObserver paused");
Log.d(TAG, "DownloadObserver paused");
if(users.decrementAndGet() > 0) {
return;
}
try {
activity.unregisterReceiver(contentChangedReceiver);
} catch (IllegalArgumentException e) {

View File

@ -0,0 +1,43 @@
package de.danoeh.antennapod.core.event;
import android.support.annotation.NonNull;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.util.List;
import de.danoeh.antennapod.core.feed.FeedItem;
public class FeedItemEvent {
public enum Action {
UPDATE, DELETE_MEDIA
}
@NonNull public final Action action;
@NonNull public final List<FeedItem> items;
private FeedItemEvent(Action action, List<FeedItem> items) {
this.action = action;
this.items = items;
}
public static FeedItemEvent deletedMedia(List<FeedItem> items) {
return new FeedItemEvent(Action.DELETE_MEDIA, items);
}
public static FeedItemEvent updated(List<FeedItem> items) {
return new FeedItemEvent(Action.UPDATE, items);
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("action", action)
.append("items", items)
.toString();
}
}

View File

@ -20,5 +20,4 @@ public class FeedMediaEvent {
return new FeedMediaEvent(Action.UPDATE, media);
}
}

View File

@ -1,5 +1,7 @@
package de.danoeh.antennapod.core.event;
import android.support.annotation.Nullable;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@ -10,7 +12,7 @@ import de.danoeh.antennapod.core.feed.FeedItem;
public class QueueEvent {
public enum Action {
ADDED, ADDED_ITEMS, REMOVED, IRREVERSIBLE_REMOVED, CLEARED, DELETED_MEDIA, SORTED, MOVED
ADDED, ADDED_ITEMS, SET_QUEUE, REMOVED, IRREVERSIBLE_REMOVED, CLEARED, DELETED_MEDIA, SORTED, MOVED
}
public final Action action;
@ -18,29 +20,45 @@ public class QueueEvent {
public final int position;
public final List<FeedItem> items;
public QueueEvent(Action action) {
this(action, null, null, -1);
}
public QueueEvent(Action action, FeedItem item) {
this(action, item, null, -1);
}
public QueueEvent(Action action, FeedItem item, int position) {
this(action, item, null, position);
}
public QueueEvent(Action action, List<FeedItem> items) {
this(action, null, items, -1);
}
private QueueEvent(Action action, FeedItem item, List<FeedItem> items, int position) {
private QueueEvent(Action action,
@Nullable FeedItem item,
@Nullable List<FeedItem> items,
int position) {
this.action = action;
this.item = item;
this.items = items;
this.position = position;
}
public static QueueEvent added(FeedItem item, int position) {
return new QueueEvent(Action.ADDED, item, null, position);
}
public static QueueEvent setQueue(List<FeedItem> queue) {
return new QueueEvent(Action.SET_QUEUE, null, queue, -1);
}
public static QueueEvent removed(FeedItem item) {
return new QueueEvent(Action.REMOVED, item, null, -1);
}
public static QueueEvent irreversibleRemoved(FeedItem item) {
return new QueueEvent(Action.IRREVERSIBLE_REMOVED, item, null, -1);
}
public static QueueEvent cleared() {
return new QueueEvent(Action.CLEARED, null, null, -1);
}
public static QueueEvent sorted(List<FeedItem> sortedQueue) {
return new QueueEvent(Action.SORTED, null, sortedQueue, -1);
}
public static QueueEvent moved(FeedItem item, int newPosition) {
return new QueueEvent(Action.MOVED, item, null, newPosition);
}
public boolean contains(long id) {
if(item != null) {
return item.getId() == id;

View File

@ -14,6 +14,7 @@ import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
@ -27,6 +28,8 @@ import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.core.event.FavoritesEvent;
import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.event.QueueEvent;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedEvent;
@ -34,7 +37,6 @@ import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.event.QueueEvent;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
@ -131,7 +133,7 @@ public class DBWriter {
}
}
Log.d(TAG, "Deleting File. Result: " + result);
EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.DELETED_MEDIA, media.getItem()));
EventBus.getDefault().post(FeedItemEvent.deletedMedia(Arrays.asList(media.getItem())));
EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
}
});
@ -210,8 +212,9 @@ public class DBWriter {
adapter.open();
if (removed.size() > 0) {
adapter.setQueue(queue);
EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.IRREVERSIBLE_REMOVED,
removed));
for(FeedItem item : removed) {
EventBus.getDefault().post(QueueEvent.irreversibleRemoved(item));
}
}
adapter.removeFeed(feed);
adapter.close();
@ -319,7 +322,7 @@ public class DBWriter {
queue.add(index, item);
adapter.setQueue(queue);
item.addTag(FeedItem.TAG_QUEUE);
EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED, item, index));
EventBus.getDefault().post(QueueEvent.added(item, index));
if (item.isNew()) {
DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId());
}
@ -365,17 +368,21 @@ public class DBWriter {
if (queue != null) {
boolean queueModified = false;
LongList markAsUnplayedIds = new LongList();
List<QueueEvent> events = new ArrayList<QueueEvent>();
for (int i = 0; i < itemIds.length; i++) {
if (!itemListContains(queue, itemIds[i])) {
final FeedItem item = DBReader.getFeedItem(itemIds[i]);
if (item != null) {
// add item to either front ot back of queue
boolean addToFront = UserPreferences.enqueueAtFront();
if (addToFront) {
queue.add(0 + i, item);
events.add(QueueEvent.added(item, 0 + i));
} else {
queue.add(item);
events.add(QueueEvent.added(item, queue.size()-1));
}
queueModified = true;
if (item.isNew()) {
@ -386,7 +393,9 @@ public class DBWriter {
}
if (queueModified) {
adapter.setQueue(queue);
EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED_ITEMS, queue));
for(QueueEvent event : events) {
EventBus.getDefault().post(event);
}
if (markAsUnplayedIds.size() > 0) {
DBWriter.markItemPlayed(FeedItem.UNPLAYED, markAsUnplayedIds.toArray());
}
@ -411,7 +420,7 @@ public class DBWriter {
adapter.clearQueue();
adapter.close();
EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.CLEARED));
EventBus.getDefault().post(QueueEvent.cleared());
});
}
@ -435,7 +444,7 @@ public class DBWriter {
queue.remove(position);
adapter.setQueue(queue);
item.removeTag(FeedItem.TAG_QUEUE);
EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.REMOVED, item, position));
EventBus.getDefault().post(QueueEvent.removed(item));
} else {
Log.w(TAG, "Queue was not modified by call to removeQueueItem");
}
@ -512,9 +521,9 @@ public class DBWriter {
return dbExec.submit(() -> {
LongList queueIdList = DBReader.getQueueIDList();
int index = queueIdList.indexOf(itemId);
if(index >= 0) {
if (index >= 0) {
moveQueueItemHelper(index, queueIdList.size() - 1,
broadcastUpdate);
broadcastUpdate);
} else {
Log.e(TAG, "moveQueueItemToBottom: item not found");
}
@ -561,7 +570,7 @@ public class DBWriter {
adapter.setQueue(queue);
if (broadcastUpdate) {
EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.MOVED, item, to));
EventBus.getDefault().post(QueueEvent.moved(item, to));
}
}
} else {
@ -955,7 +964,7 @@ public class DBWriter {
Collections.sort(queue, comparator);
adapter.setQueue(queue);
if (broadcastUpdate) {
EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.SORTED));
EventBus.getDefault().post(QueueEvent.sorted(queue));
}
} else {
Log.e(TAG, "sortQueue: Could not load queue");

View File

@ -0,0 +1,68 @@
package de.danoeh.antennapod.core.util;
import java.util.List;
import de.danoeh.antennapod.core.feed.FeedItem;
public class FeedItemUtil {
public static int indexOfItemWithDownloadUrl(List<FeedItem> items, String downloadUrl) {
if(items == null) {
return -1;
}
for(int i=0; i < items.size(); i++) {
FeedItem item = items.get(i);
if(item.hasMedia() && item.getMedia().getDownload_url().equals(downloadUrl)) {
return i;
}
}
return -1;
}
public static int indexOfItemWithId(List<FeedItem> items, long id) {
for(int i=0; i < items.size(); i++) {
FeedItem item = items.get(i);
if(item != null && item.getId() == id) {
return i;
}
}
return -1;
}
public static long[] getIds(FeedItem... items) {
if(items == null || items.length == 0) {
return new long[0];
}
long[] result = new long[items.length];
for(int i=0; i < items.length; i++) {
result[i] = items[i].getId();
}
return result;
}
public static long[] getIds(List<FeedItem> items) {
if(items == null || items.size() == 0) {
return new long[0];
}
long[] result = new long[items.size()];
for(int i=0; i < items.size(); i++) {
result[i] = items.get(i).getId();
}
return result;
}
public static boolean containsAnyId(List<FeedItem> items, long[] ids) {
if(items == null || items.size() == 0) {
return false;
}
for(FeedItem item : items) {
for(long id : ids) {
if(item.getId() == id) {
return true;
}
}
}
return false;
}
}

View File

@ -30,6 +30,17 @@ public final class LongList {
size = 0;
}
public static LongList of(long... values) {
if(values == null || values.length == 0) {
return new LongList(0);
}
LongList result = new LongList(values.length);
for(long value : values) {
result.add(value);
}
return result;
}
@Override
public int hashCode() {
int hashCode = 1;

View File

@ -1,127 +0,0 @@
package de.danoeh.antennapod.core.util.gui;
import android.os.Handler;
import android.view.View;
import android.widget.TextView;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.view.ViewHelper;
import com.nineoldandroids.view.ViewPropertyAnimator;
import de.danoeh.antennapod.core.R;
import static com.nineoldandroids.view.ViewPropertyAnimator.animate;
public class UndoBarController<T> {
private View mBarView;
private TextView mMessageView;
private ViewPropertyAnimator mBarAnimator;
private Handler mHideHandler = new Handler();
private UndoListener<T> mUndoListener;
// State objects
private T mUndoToken;
private CharSequence mUndoMessage;
public interface UndoListener<T> {
/**
* This callback function is called when the undo button is pressed
*
* @param token
*/
void onUndo(T token);
/**
*
* This callback function is called when the bar fades out without button press
*
* @param token
*/
void onHide(T token);
}
public UndoBarController(View undoBarView, UndoListener<T> undoListener) {
mBarView = undoBarView;
mBarAnimator = animate(mBarView);
mUndoListener = undoListener;
mMessageView = (TextView) mBarView.findViewById(R.id.undobar_message);
mBarView.findViewById(R.id.undobar_button)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
hideUndoBar(false);
mUndoListener.onUndo(mUndoToken);
}
});
hideUndoBar(true);
}
public void showUndoBar(boolean immediate, CharSequence message, T undoToken) {
mUndoToken = undoToken;
mUndoMessage = message;
mMessageView.setText(mUndoMessage);
mHideHandler.removeCallbacks(mHideRunnable);
mHideHandler.postDelayed(mHideRunnable,
mBarView.getResources().getInteger(R.integer.undobar_hide_delay));
mBarView.setVisibility(View.VISIBLE);
if (immediate) {
ViewHelper.setAlpha(mBarView, 1);
} else {
mBarAnimator.cancel();
mBarAnimator
.alpha(1)
.setDuration(
mBarView.getResources()
.getInteger(android.R.integer.config_shortAnimTime))
.setListener(null);
}
}
public boolean isShowing() {
return mBarView.getVisibility() == View.VISIBLE;
}
public void close() {
hideUndoBar(true);
if(mUndoListener != null) {
mUndoListener.onHide(mUndoToken);
}
}
public void hideUndoBar(boolean immediate) {
mHideHandler.removeCallbacks(mHideRunnable);
if (immediate) {
mBarView.setVisibility(View.GONE);
ViewHelper.setAlpha(mBarView, 0);
mUndoMessage = null;
} else {
mBarAnimator.cancel();
mBarAnimator
.alpha(0)
.setDuration(mBarView.getResources()
.getInteger(android.R.integer.config_shortAnimTime))
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mBarView.setVisibility(View.GONE);
mUndoMessage = null;
mUndoToken = null;
}
});
}
}
private Runnable mHideRunnable = new Runnable() {
@Override
public void run() {
hideUndoBar(false);
mUndoListener.onHide(mUndoToken);
}
};
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

View File

@ -33,7 +33,7 @@
<item name="attr/non_transparent_background">@color/white</item>
<item name="attr/overlay_background">@color/overlay_light</item>
<item name="attr/overlay_drawable">@drawable/overlay_drawable</item>
<item name="attr/dragview_background">@drawable/ic_drag_handle</item>
<item name="attr/dragview_background">@drawable/ic_drag_vertical_grey600_48dp</item>
<item name="attr/dragview_float_background">@color/white</item>
<item name="attr/nav_drawer_background">@color/white</item>
<item name="attr/ic_action_overflow">@drawable/ic_more_vert_grey600_24dp</item>
@ -80,7 +80,7 @@
<item name="attr/non_transparent_background">@color/black</item>
<item name="attr/overlay_background">@color/overlay_dark</item>
<item name="attr/overlay_drawable">@drawable/overlay_drawable_dark</item>
<item name="attr/dragview_background">@drawable/ic_drag_handle_dark</item>
<item name="attr/dragview_background">@drawable/ic_drag_vertical_white_48dp</item>
<item name="attr/dragview_float_background">@color/black</item>
<item name="attr/nav_drawer_background">#3B3B3B</item>
<item name="attr/ic_action_overflow">@drawable/ic_more_vert_white_24dp</item>
@ -129,7 +129,7 @@
<item name="attr/non_transparent_background">@color/white</item>
<item name="attr/overlay_background">@color/overlay_light</item>
<item name="attr/overlay_drawable">@drawable/overlay_drawable</item>
<item name="attr/dragview_background">@drawable/ic_drag_handle</item>
<item name="attr/dragview_background">@drawable/ic_drag_vertical_grey600_48dp</item>
<item name="attr/dragview_float_background">@color/white</item>
<item name="attr/nav_drawer_background">@color/white</item>
<item name="attr/ic_action_overflow">@drawable/ic_more_vert_grey600_24dp</item>
@ -177,7 +177,7 @@
<item name="attr/non_transparent_background">@color/black</item>
<item name="attr/overlay_background">@color/overlay_dark</item>
<item name="attr/overlay_drawable">@drawable/overlay_drawable_dark</item>
<item name="attr/dragview_background">@drawable/ic_drag_handle_dark</item>
<item name="attr/dragview_background">@drawable/ic_drag_vertical_white_48dp</item>
<item name="attr/dragview_float_background">@color/black</item>
<item name="attr/nav_drawer_background">#3B3B3B</item>
<item name="attr/ic_action_overflow">@drawable/ic_more_vert_white_24dp</item>

View File

@ -1,22 +0,0 @@
# built application files
*.apk
*.ap_
# files for the dex VM
*.dex
# Java class files
*.class
# generated files
bin/
gen/
target/
# Local configuration file (sdk path, etc)
local.properties
.gitattributes
# Eclipse project files
.classpath
.project

View File

@ -1,30 +0,0 @@
apply plugin: "com.android.library"
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 4
versionName "0.6.1"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
}
dependencies {
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:support-v4:$supportVersion"
}

View File

@ -1,20 +0,0 @@
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mobeta.android.dslv"
android:versionCode="4"
android:versionName="0.6.1">
<uses-sdk android:targetSdkVersion="7"
android:minSdkVersion="7" />
</manifest>

View File

@ -1,471 +0,0 @@
package com.mobeta.android.dslv;
import android.graphics.Point;
import android.view.GestureDetector;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.AdapterView;
/**
* Class that starts and stops item drags on a {@link DragSortListView}
* based on touch gestures. This class also inherits from
* {@link SimpleFloatViewManager}, which provides basic float View
* creation.
*
* An instance of this class is meant to be passed to the methods
* {@link DragSortListView#setTouchListener()} and
* {@link DragSortListView#setFloatViewManager()} of your
* {@link DragSortListView} instance.
*/
public class DragSortController extends SimpleFloatViewManager implements View.OnTouchListener, GestureDetector.OnGestureListener {
/**
* Drag init mode enum.
*/
public static final int ON_DOWN = 0;
public static final int ON_DRAG = 1;
public static final int ON_LONG_PRESS = 2;
private int mDragInitMode = ON_DOWN;
private boolean mSortEnabled = true;
/**
* Remove mode enum.
*/
public static final int CLICK_REMOVE = 0;
public static final int FLING_REMOVE = 1;
/**
* The current remove mode.
*/
private int mRemoveMode;
private boolean mRemoveEnabled = false;
private boolean mIsRemoving = false;
private GestureDetector mDetector;
private GestureDetector mFlingRemoveDetector;
private int mTouchSlop;
public static final int MISS = -1;
private int mHitPos = MISS;
private int mFlingHitPos = MISS;
private int mClickRemoveHitPos = MISS;
private int[] mTempLoc = new int[2];
private int mItemX;
private int mItemY;
private int mCurrX;
private int mCurrY;
private boolean mDragging = false;
private float mFlingSpeed = 500f;
private int mDragHandleId;
private int mClickRemoveId;
private int mFlingHandleId;
private boolean mCanDrag;
private DragSortListView mDslv;
private int mPositionX;
/**
* Calls {@link #DragSortController(DragSortListView, int)} with a
* 0 drag handle id, FLING_RIGHT_REMOVE remove mode,
* and ON_DOWN drag init. By default, sorting is enabled, and
* removal is disabled.
*
* @param dslv The DSLV instance
*/
public DragSortController(DragSortListView dslv) {
this(dslv, 0, ON_DOWN, FLING_REMOVE);
}
public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, int removeMode) {
this(dslv, dragHandleId, dragInitMode, removeMode, 0);
}
public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, int removeMode, int clickRemoveId) {
this(dslv, dragHandleId, dragInitMode, removeMode, clickRemoveId, 0);
}
/**
* By default, sorting is enabled, and removal is disabled.
*
* @param dslv The DSLV instance
* @param dragHandleId The resource id of the View that represents
* the drag handle in a list item.
*/
public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode,
int removeMode, int clickRemoveId, int flingHandleId) {
super(dslv);
mDslv = dslv;
mDetector = new GestureDetector(dslv.getContext(), this);
mFlingRemoveDetector = new GestureDetector(dslv.getContext(), mFlingRemoveListener);
mFlingRemoveDetector.setIsLongpressEnabled(false);
mTouchSlop = ViewConfiguration.get(dslv.getContext()).getScaledTouchSlop();
mDragHandleId = dragHandleId;
mClickRemoveId = clickRemoveId;
mFlingHandleId = flingHandleId;
setRemoveMode(removeMode);
setDragInitMode(dragInitMode);
}
public int getDragInitMode() {
return mDragInitMode;
}
/**
* Set how a drag is initiated. Needs to be one of
* {@link ON_DOWN}, {@link ON_DRAG}, or {@link ON_LONG_PRESS}.
*
* @param mode The drag init mode.
*/
public void setDragInitMode(int mode) {
mDragInitMode = mode;
}
/**
* Enable/Disable list item sorting. Disabling is useful if only item
* removal is desired. Prevents drags in the vertical direction.
*
* @param enabled Set <code>true</code> to enable list
* item sorting.
*/
public void setSortEnabled(boolean enabled) {
mSortEnabled = enabled;
}
public boolean isSortEnabled() {
return mSortEnabled;
}
/**
* One of {@link CLICK_REMOVE}, {@link FLING_RIGHT_REMOVE},
* {@link FLING_LEFT_REMOVE},
* {@link SLIDE_RIGHT_REMOVE}, or {@link SLIDE_LEFT_REMOVE}.
*/
public void setRemoveMode(int mode) {
mRemoveMode = mode;
}
public int getRemoveMode() {
return mRemoveMode;
}
/**
* Enable/Disable item removal without affecting remove mode.
*/
public void setRemoveEnabled(boolean enabled) {
mRemoveEnabled = enabled;
}
public boolean isRemoveEnabled() {
return mRemoveEnabled;
}
/**
* Set the resource id for the View that represents the drag
* handle in a list item.
*
* @param id An android resource id.
*/
public void setDragHandleId(int id) {
mDragHandleId = id;
}
/**
* Set the resource id for the View that represents the fling
* handle in a list item.
*
* @param id An android resource id.
*/
public void setFlingHandleId(int id) {
mFlingHandleId = id;
}
/**
* Set the resource id for the View that represents click
* removal button.
*
* @param id An android resource id.
*/
public void setClickRemoveId(int id) {
mClickRemoveId = id;
}
/**
* Sets flags to restrict certain motions of the floating View
* based on DragSortController settings (such as remove mode).
* Starts the drag on the DragSortListView.
*
* @param position The list item position (includes headers).
* @param deltaX Touch x-coord minus left edge of floating View.
* @param deltaY Touch y-coord minus top edge of floating View.
*
* @return True if drag started, false otherwise.
*/
public boolean startDrag(int position, int deltaX, int deltaY) {
int dragFlags = 0;
if (mSortEnabled && !mIsRemoving) {
dragFlags |= DragSortListView.DRAG_POS_Y | DragSortListView.DRAG_NEG_Y;
}
if (mRemoveEnabled && mIsRemoving) {
dragFlags |= DragSortListView.DRAG_POS_X;
dragFlags |= DragSortListView.DRAG_NEG_X;
}
mDragging = mDslv.startDrag(position - mDslv.getHeaderViewsCount(), dragFlags, deltaX,
deltaY);
return mDragging;
}
@Override
public boolean onTouch(View v, MotionEvent ev) {
if (!mDslv.isDragEnabled() || mDslv.listViewIntercepted()) {
return false;
}
mDetector.onTouchEvent(ev);
if (mRemoveEnabled && mDragging && mRemoveMode == FLING_REMOVE) {
mFlingRemoveDetector.onTouchEvent(ev);
}
int action = ev.getAction() & MotionEvent.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
mCurrX = (int) ev.getX();
mCurrY = (int) ev.getY();
break;
case MotionEvent.ACTION_UP:
if (mRemoveEnabled && mIsRemoving) {
int x = mPositionX >= 0 ? mPositionX : -mPositionX;
int removePoint = mDslv.getWidth() / 2;
if (x > removePoint) {
mDslv.stopDragWithVelocity(true, 0);
}
}
case MotionEvent.ACTION_CANCEL:
mIsRemoving = false;
mDragging = false;
break;
}
return false;
}
/**
* Overrides to provide fading when slide removal is enabled.
*/
@Override
public void onDragFloatView(View floatView, Point position, Point touch) {
if (mRemoveEnabled && mIsRemoving) {
mPositionX = position.x;
}
}
/**
* Get the position to start dragging based on the ACTION_DOWN
* MotionEvent. This function simply calls
* {@link #dragHandleHitPosition(MotionEvent)}. Override
* to change drag handle behavior;
* this function is called internally when an ACTION_DOWN
* event is detected.
*
* @param ev The ACTION_DOWN MotionEvent.
*
* @return The list position to drag if a drag-init gesture is
* detected; MISS if unsuccessful.
*/
public int startDragPosition(MotionEvent ev) {
return dragHandleHitPosition(ev);
}
public int startFlingPosition(MotionEvent ev) {
return mRemoveMode == FLING_REMOVE ? flingHandleHitPosition(ev) : MISS;
}
/**
* Checks for the touch of an item's drag handle (specified by
* {@link #setDragHandleId(int)}), and returns that item's position
* if a drag handle touch was detected.
*
* @param ev The ACTION_DOWN MotionEvent.
* @return The list position of the item whose drag handle was
* touched; MISS if unsuccessful.
*/
public int dragHandleHitPosition(MotionEvent ev) {
return viewIdHitPosition(ev, mDragHandleId);
}
public int flingHandleHitPosition(MotionEvent ev) {
return viewIdHitPosition(ev, mFlingHandleId);
}
public int viewIdHitPosition(MotionEvent ev, int id) {
final int x = (int) ev.getX();
final int y = (int) ev.getY();
int touchPos = mDslv.pointToPosition(x, y); // includes headers/footers
final int numHeaders = mDslv.getHeaderViewsCount();
final int numFooters = mDslv.getFooterViewsCount();
final int count = mDslv.getCount();
// Log.d("mobeta", "touch down on position " + itemnum);
// We're only interested if the touch was on an
// item that's not a header or footer.
if (touchPos != AdapterView.INVALID_POSITION && touchPos >= numHeaders
&& touchPos < (count - numFooters)) {
final View item = mDslv.getChildAt(touchPos - mDslv.getFirstVisiblePosition());
final int rawX = (int) ev.getRawX();
final int rawY = (int) ev.getRawY();
View dragBox = id == 0 ? item : (View) item.findViewById(id);
if (dragBox != null) {
dragBox.getLocationOnScreen(mTempLoc);
if (rawX > mTempLoc[0] && rawY > mTempLoc[1] &&
rawX < mTempLoc[0] + dragBox.getWidth() &&
rawY < mTempLoc[1] + dragBox.getHeight()) {
mItemX = item.getLeft();
mItemY = item.getTop();
return touchPos;
}
}
}
return MISS;
}
@Override
public boolean onDown(MotionEvent ev) {
if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) {
mClickRemoveHitPos = viewIdHitPosition(ev, mClickRemoveId);
}
mHitPos = startDragPosition(ev);
if (mHitPos != MISS && mDragInitMode == ON_DOWN) {
startDrag(mHitPos, (int) ev.getX() - mItemX, (int) ev.getY() - mItemY);
}
mIsRemoving = false;
mCanDrag = true;
mPositionX = 0;
mFlingHitPos = startFlingPosition(ev);
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// it can happen where the motion events are null
if (e1 == null || e2 == null) {
// we can't really do anything
return false;
}
final int x1 = (int) e1.getX();
final int y1 = (int) e1.getY();
final int x2 = (int) e2.getX();
final int y2 = (int) e2.getY();
final int deltaX = x2 - mItemX;
final int deltaY = y2 - mItemY;
if (mCanDrag && !mDragging && (mHitPos != MISS || mFlingHitPos != MISS)) {
if (mHitPos != MISS) {
if (mDragInitMode == ON_DRAG && Math.abs(y2 - y1) > mTouchSlop && mSortEnabled) {
startDrag(mHitPos, deltaX, deltaY);
}
else if (mDragInitMode != ON_DOWN && Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled)
{
mIsRemoving = true;
startDrag(mFlingHitPos, deltaX, deltaY);
}
} else if (mFlingHitPos != MISS) {
if (Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled) {
mIsRemoving = true;
startDrag(mFlingHitPos, deltaX, deltaY);
} else if (Math.abs(y2 - y1) > mTouchSlop) {
mCanDrag = false; // if started to scroll the list then
// don't allow sorting nor fling-removing
}
}
}
// return whatever
return false;
}
@Override
public void onLongPress(MotionEvent e) {
// Log.d("mobeta", "lift listener long pressed");
if (mHitPos != MISS && mDragInitMode == ON_LONG_PRESS) {
mDslv.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
startDrag(mHitPos, mCurrX - mItemX, mCurrY - mItemY);
}
}
// complete the OnGestureListener interface
@Override
public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
// complete the OnGestureListener interface
@Override
public boolean onSingleTapUp(MotionEvent ev) {
if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) {
if (mClickRemoveHitPos != MISS) {
mDslv.removeItem(mClickRemoveHitPos - mDslv.getHeaderViewsCount());
}
}
return true;
}
// complete the OnGestureListener interface
@Override
public void onShowPress(MotionEvent ev) {
// do nothing
}
private GestureDetector.OnGestureListener mFlingRemoveListener =
new GestureDetector.SimpleOnGestureListener() {
@Override
public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
ViewConfiguration vc = ViewConfiguration.get(mDslv.getContext());
int minSwipeVelocity = vc.getScaledMinimumFlingVelocity();
int maxSwipeVelocity = vc.getScaledMaximumFlingVelocity();
if (mRemoveEnabled && mIsRemoving) {
int w = mDslv.getWidth();
if(mPositionX >= w/2) {
mDslv.stopDragWithVelocity(true, velocityX);
} else if(mPositionX >= w/5 && minSwipeVelocity <= velocityX && velocityX <= maxSwipeVelocity) {
mDslv.stopDragWithVelocity(true, velocityX);
}
mIsRemoving = false;
}
return false;
}
};
}

View File

@ -1,241 +0,0 @@
package com.mobeta.android.dslv;
import java.util.ArrayList;
import android.content.Context;
import android.database.Cursor;
import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewGroup;
import android.support.v4.widget.CursorAdapter;
/**
* A subclass of {@link android.widget.CursorAdapter} that provides
* reordering of the elements in the Cursor based on completed
* drag-sort operations. The reordering is a simple mapping of
* list positions into Cursor positions (the Cursor is unchanged).
* To persist changes made by drag-sorts, one can retrieve the
* mapping with the {@link #getCursorPositions()} method, which
* returns the reordered list of Cursor positions.
*
* An instance of this class is passed
* to {@link DragSortListView#setAdapter(ListAdapter)} and, since
* this class implements the {@link DragSortListView.DragSortListener}
* interface, it is automatically set as the DragSortListener for
* the DragSortListView instance.
*/
public abstract class DragSortCursorAdapter extends CursorAdapter implements DragSortListView.DragSortListener {
public static final int REMOVED = -1;
/**
* Key is ListView position, value is Cursor position
*/
private SparseIntArray mListMapping = new SparseIntArray();
private ArrayList<Integer> mRemovedCursorPositions = new ArrayList<Integer>();
public DragSortCursorAdapter(Context context, Cursor c) {
super(context, c);
}
public DragSortCursorAdapter(Context context, Cursor c, boolean autoRequery) {
super(context, c, autoRequery);
}
public DragSortCursorAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
}
/**
* Swaps Cursor and clears list-Cursor mapping.
*
* @see android.widget.CursorAdapter#swapCursor(android.database.Cursor)
*/
@Override
public Cursor swapCursor(Cursor newCursor) {
Cursor old = super.swapCursor(newCursor);
resetMappings();
return old;
}
/**
* Changes Cursor and clears list-Cursor mapping.
*
* @see android.widget.CursorAdapter#changeCursor(android.database.Cursor)
*/
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
resetMappings();
}
/**
* Resets list-cursor mapping.
*/
public void reset() {
resetMappings();
notifyDataSetChanged();
}
private void resetMappings() {
mListMapping.clear();
mRemovedCursorPositions.clear();
}
@Override
public Object getItem(int position) {
return super.getItem(mListMapping.get(position, position));
}
@Override
public long getItemId(int position) {
return super.getItemId(mListMapping.get(position, position));
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return super.getDropDownView(mListMapping.get(position, position), convertView, parent);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return super.getView(mListMapping.get(position, position), convertView, parent);
}
/**
* On drop, this updates the mapping between Cursor positions
* and ListView positions. The Cursor is unchanged. Retrieve
* the current mapping with {@link getCursorPositions()}.
*
* @see DragSortListView.DropListener#drop(int, int)
*/
@Override
public void drop(int from, int to) {
if (from != to) {
int cursorFrom = mListMapping.get(from, from);
if (from > to) {
for (int i = from; i > to; --i) {
mListMapping.put(i, mListMapping.get(i - 1, i - 1));
}
} else {
for (int i = from; i < to; ++i) {
mListMapping.put(i, mListMapping.get(i + 1, i + 1));
}
}
mListMapping.put(to, cursorFrom);
cleanMapping();
notifyDataSetChanged();
}
}
/**
* On remove, this updates the mapping between Cursor positions
* and ListView positions. The Cursor is unchanged. Retrieve
* the current mapping with {@link getCursorPositions()}.
*
* @see DragSortListView.RemoveListener#remove(int)
*/
@Override
public void remove(int which) {
int cursorPos = mListMapping.get(which, which);
if (!mRemovedCursorPositions.contains(cursorPos)) {
mRemovedCursorPositions.add(cursorPos);
}
int newCount = getCount();
for (int i = which; i < newCount; ++i) {
mListMapping.put(i, mListMapping.get(i + 1, i + 1));
}
mListMapping.delete(newCount);
cleanMapping();
notifyDataSetChanged();
}
/**
* Does nothing. Just completes DragSortListener interface.
*/
@Override
public void drag(int from, int to) {
// do nothing
}
/**
* Remove unnecessary mappings from sparse array.
*/
private void cleanMapping() {
ArrayList<Integer> toRemove = new ArrayList<Integer>();
int size = mListMapping.size();
for (int i = 0; i < size; ++i) {
if (mListMapping.keyAt(i) == mListMapping.valueAt(i)) {
toRemove.add(mListMapping.keyAt(i));
}
}
size = toRemove.size();
for (int i = 0; i < size; ++i) {
mListMapping.delete(toRemove.get(i));
}
}
@Override
public int getCount() {
return super.getCount() - mRemovedCursorPositions.size();
}
/**
* Get the Cursor position mapped to by the provided list position
* (given all previously handled drag-sort
* operations).
*
* @param position List position
*
* @return The mapped-to Cursor position
*/
public int getCursorPosition(int position) {
return mListMapping.get(position, position);
}
/**
* Get the current order of Cursor positions presented by the
* list.
*/
public ArrayList<Integer> getCursorPositions() {
ArrayList<Integer> result = new ArrayList<Integer>();
for (int i = 0; i < getCount(); ++i) {
result.add(mListMapping.get(i, i));
}
return result;
}
/**
* Get the list position mapped to by the provided Cursor position.
* If the provided Cursor position has been removed by a drag-sort,
* this returns {@link #REMOVED}.
*
* @param cursorPosition A Cursor position
* @return The mapped-to list position or REMOVED
*/
public int getListPosition(int cursorPosition) {
if (mRemovedCursorPositions.contains(cursorPosition)) {
return REMOVED;
}
int index = mListMapping.indexOfValue(cursorPosition);
if (index < 0) {
return cursorPosition;
} else {
return mListMapping.keyAt(index);
}
}
}

View File

@ -1,100 +0,0 @@
package com.mobeta.android.dslv;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.util.Log;
/**
* Lightweight ViewGroup that wraps list items obtained from user's
* ListAdapter. ItemView expects a single child that has a definite
* height (i.e. the child's layout height is not MATCH_PARENT).
* The width of
* ItemView will always match the width of its child (that is,
* the width MeasureSpec given to ItemView is passed directly
* to the child, and the ItemView measured width is set to the
* child's measured width). The height of ItemView can be anything;
* the
*
*
* The purpose of this class is to optimize slide
* shuffle animations.
*/
public class DragSortItemView extends ViewGroup {
private int mGravity = Gravity.TOP;
public DragSortItemView(Context context) {
super(context);
// always init with standard ListView layout params
setLayoutParams(new AbsListView.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
//setClipChildren(true);
}
public void setGravity(int gravity) {
mGravity = gravity;
}
public int getGravity() {
return mGravity;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final View child = getChildAt(0);
if (child == null) {
return;
}
if (mGravity == Gravity.TOP) {
child.layout(0, 0, getMeasuredWidth(), child.getMeasuredHeight());
} else {
child.layout(0, getMeasuredHeight() - child.getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight());
}
}
/**
*
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = MeasureSpec.getSize(heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final View child = getChildAt(0);
if (child == null) {
setMeasuredDimension(0, width);
return;
}
if (child.isLayoutRequested()) {
// Always let child be as tall as it wants.
measureChild(child, widthMeasureSpec,
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
}
if (heightMode == MeasureSpec.UNSPECIFIED) {
ViewGroup.LayoutParams lp = getLayoutParams();
if (lp.height > 0) {
height = lp.height;
} else {
height = child.getMeasuredHeight();
}
}
setMeasuredDimension(width, height);
}
}

View File

@ -1,55 +0,0 @@
package com.mobeta.android.dslv;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.Checkable;
import android.util.Log;
/**
* Lightweight ViewGroup that wraps list items obtained from user's
* ListAdapter. ItemView expects a single child that has a definite
* height (i.e. the child's layout height is not MATCH_PARENT).
* The width of
* ItemView will always match the width of its child (that is,
* the width MeasureSpec given to ItemView is passed directly
* to the child, and the ItemView measured width is set to the
* child's measured width). The height of ItemView can be anything;
* the
*
*
* The purpose of this class is to optimize slide
* shuffle animations.
*/
public class DragSortItemViewCheckable extends DragSortItemView implements Checkable {
public DragSortItemViewCheckable(Context context) {
super(context);
}
@Override
public boolean isChecked() {
View child = getChildAt(0);
if (child instanceof Checkable)
return ((Checkable) child).isChecked();
else
return false;
}
@Override
public void setChecked(boolean checked) {
View child = getChildAt(0);
if (child instanceof Checkable)
((Checkable) child).setChecked(checked);
}
@Override
public void toggle() {
View child = getChildAt(0);
if (child instanceof Checkable)
((Checkable) child).toggle();
}
}

View File

@ -1,133 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mobeta.android.dslv;
import android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
// taken from v4 rev. 10 ResourceCursorAdapter.java
/**
* Static library support version of the framework's {@link android.widget.ResourceCursorAdapter}.
* Used to write apps that run on platforms prior to Android 3.0. When running
* on Android 3.0 or above, this implementation is still used; it does not try
* to switch to the framework's implementation. See the framework SDK
* documentation for a class overview.
*/
public abstract class ResourceDragSortCursorAdapter extends DragSortCursorAdapter {
private int mLayout;
private int mDropDownLayout;
private LayoutInflater mInflater;
/**
* Constructor the enables auto-requery.
*
* @deprecated This option is discouraged, as it results in Cursor queries
* being performed on the application's UI thread and thus can cause poor
* responsiveness or even Application Not Responding errors. As an alternative,
* use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
*
* @param context The context where the ListView associated with this adapter is running
* @param layout resource identifier of a layout file that defines the views
* for this list item. Unless you override them later, this will
* define both the item views and the drop down views.
*/
@Deprecated
public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c) {
super(context, c);
mLayout = mDropDownLayout = layout;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* Constructor with default behavior as per
* {@link CursorAdapter#CursorAdapter(Context, Cursor, boolean)}; it is recommended
* you not use this, but instead {@link #ResourceCursorAdapter(Context, int, Cursor, int)}.
* When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
* will always be set.
*
* @param context The context where the ListView associated with this adapter is running
* @param layout resource identifier of a layout file that defines the views
* for this list item. Unless you override them later, this will
* define both the item views and the drop down views.
* @param c The cursor from which to get the data.
* @param autoRequery If true the adapter will call requery() on the
* cursor whenever it changes so the most recent
* data is always displayed. Using true here is discouraged.
*/
public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, boolean autoRequery) {
super(context, c, autoRequery);
mLayout = mDropDownLayout = layout;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* Standard constructor.
*
* @param context The context where the ListView associated with this adapter is running
* @param layout Resource identifier of a layout file that defines the views
* for this list item. Unless you override them later, this will
* define both the item views and the drop down views.
* @param c The cursor from which to get the data.
* @param flags Flags used to determine the behavior of the adapter,
* as per {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}.
*/
public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, int flags) {
super(context, c, flags);
mLayout = mDropDownLayout = layout;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* Inflates view(s) from the specified XML file.
*
* @see android.widget.CursorAdapter#newView(android.content.Context,
* android.database.Cursor, ViewGroup)
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(mLayout, parent, false);
}
@Override
public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(mDropDownLayout, parent, false);
}
/**
* <p>Sets the layout resource of the item views.</p>
*
* @param layout the layout resources used to create item views
*/
public void setViewResource(int layout) {
mLayout = layout;
}
/**
* <p>Sets the layout resource of the drop down views.</p>
*
* @param dropDownLayout the layout resources used to create drop down views
*/
public void setDropDownViewResource(int dropDownLayout) {
mDropDownLayout = dropDownLayout;
}
}

View File

@ -1,422 +0,0 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mobeta.android.dslv;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.view.View;
import android.widget.TextView;
import android.widget.ImageView;
// taken from sdk/sources/android-16/android/widget/SimpleCursorAdapter.java
/**
* An easy adapter to map columns from a cursor to TextViews or ImageViews
* defined in an XML file. You can specify which columns you want, which
* views you want to display the columns, and the XML file that defines
* the appearance of these views.
*
* Binding occurs in two phases. First, if a
* {@link android.widget.SimpleCursorAdapter.ViewBinder} is available,
* {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
* is invoked. If the returned value is true, binding has occured. If the
* returned value is false and the view to bind is a TextView,
* {@link #setViewText(TextView, String)} is invoked. If the returned value
* is false and the view to bind is an ImageView,
* {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
* binding can be found, an {@link IllegalStateException} is thrown.
*
* If this adapter is used with filtering, for instance in an
* {@link android.widget.AutoCompleteTextView}, you can use the
* {@link android.widget.SimpleCursorAdapter.CursorToStringConverter} and the
* {@link android.widget.FilterQueryProvider} interfaces
* to get control over the filtering process. You can refer to
* {@link #convertToString(android.database.Cursor)} and
* {@link #runQueryOnBackgroundThread(CharSequence)} for more information.
*/
public class SimpleDragSortCursorAdapter extends ResourceDragSortCursorAdapter {
/**
* A list of columns containing the data to bind to the UI.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected int[] mFrom;
/**
* A list of View ids representing the views to which the data must be bound.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected int[] mTo;
private int mStringConversionColumn = -1;
private CursorToStringConverter mCursorToStringConverter;
private ViewBinder mViewBinder;
String[] mOriginalFrom;
/**
* Constructor the enables auto-requery.
*
* @deprecated This option is discouraged, as it results in Cursor queries
* being performed on the application's UI thread and thus can cause poor
* responsiveness or even Application Not Responding errors. As an alternative,
* use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
*/
@Deprecated
public SimpleDragSortCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
super(context, layout, c);
mTo = to;
mOriginalFrom = from;
findColumns(c, from);
}
/**
* Standard constructor.
*
* @param context The context where the ListView associated with this
* SimpleListItemFactory is running
* @param layout resource identifier of a layout file that defines the views
* for this list item. The layout file should include at least
* those named views defined in "to"
* @param c The database cursor. Can be null if the cursor is not available yet.
* @param from A list of column names representing the data to bind to the UI. Can be null
* if the cursor is not available yet.
* @param to The views that should display column in the "from" parameter.
* These should all be TextViews. The first N views in this list
* are given the values of the first N columns in the from
* parameter. Can be null if the cursor is not available yet.
* @param flags Flags used to determine the behavior of the adapter,
* as per {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}.
*/
public SimpleDragSortCursorAdapter(Context context, int layout,
Cursor c, String[] from, int[] to, int flags) {
super(context, layout, c, flags);
mTo = to;
mOriginalFrom = from;
findColumns(c, from);
}
/**
* Binds all of the field names passed into the "to" parameter of the
* constructor with their corresponding cursor columns as specified in the
* "from" parameter.
*
* Binding occurs in two phases. First, if a
* {@link android.widget.SimpleCursorAdapter.ViewBinder} is available,
* {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
* is invoked. If the returned value is true, binding has occured. If the
* returned value is false and the view to bind is a TextView,
* {@link #setViewText(TextView, String)} is invoked. If the returned value is
* false and the view to bind is an ImageView,
* {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
* binding can be found, an {@link IllegalStateException} is thrown.
*
* @throws IllegalStateException if binding cannot occur
*
* @see android.widget.CursorAdapter#bindView(android.view.View,
* android.content.Context, android.database.Cursor)
* @see #getViewBinder()
* @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
* @see #setViewImage(ImageView, String)
* @see #setViewText(TextView, String)
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
final ViewBinder binder = mViewBinder;
final int count = mTo.length;
final int[] from = mFrom;
final int[] to = mTo;
for (int i = 0; i < count; i++) {
final View v = view.findViewById(to[i]);
if (v != null) {
boolean bound = false;
if (binder != null) {
bound = binder.setViewValue(v, cursor, from[i]);
}
if (!bound) {
String text = cursor.getString(from[i]);
if (text == null) {
text = "";
}
if (v instanceof TextView) {
setViewText((TextView) v, text);
} else if (v instanceof ImageView) {
setViewImage((ImageView) v, text);
} else {
throw new IllegalStateException(v.getClass().getName() + " is not a " +
" view that can be bounds by this SimpleCursorAdapter");
}
}
}
}
}
/**
* Returns the {@link ViewBinder} used to bind data to views.
*
* @return a ViewBinder or null if the binder does not exist
*
* @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
* @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
*/
public ViewBinder getViewBinder() {
return mViewBinder;
}
/**
* Sets the binder used to bind data to views.
*
* @param viewBinder the binder used to bind data to views, can be null to
* remove the existing binder
*
* @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
* @see #getViewBinder()
*/
public void setViewBinder(ViewBinder viewBinder) {
mViewBinder = viewBinder;
}
/**
* Called by bindView() to set the image for an ImageView but only if
* there is no existing ViewBinder or if the existing ViewBinder cannot
* handle binding to an ImageView.
*
* By default, the value will be treated as an image resource. If the
* value cannot be used as an image resource, the value is used as an
* image Uri.
*
* Intended to be overridden by Adapters that need to filter strings
* retrieved from the database.
*
* @param v ImageView to receive an image
* @param value the value retrieved from the cursor
*/
public void setViewImage(ImageView v, String value) {
try {
v.setImageResource(Integer.parseInt(value));
} catch (NumberFormatException nfe) {
v.setImageURI(Uri.parse(value));
}
}
/**
* Called by bindView() to set the text for a TextView but only if
* there is no existing ViewBinder or if the existing ViewBinder cannot
* handle binding to a TextView.
*
* Intended to be overridden by Adapters that need to filter strings
* retrieved from the database.
*
* @param v TextView to receive text
* @param text the text to be set for the TextView
*/
public void setViewText(TextView v, String text) {
v.setText(text);
}
/**
* Return the index of the column used to get a String representation
* of the Cursor.
*
* @return a valid index in the current Cursor or -1
*
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
* @see #setStringConversionColumn(int)
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
* @see #getCursorToStringConverter()
*/
public int getStringConversionColumn() {
return mStringConversionColumn;
}
/**
* Defines the index of the column in the Cursor used to get a String
* representation of that Cursor. The column is used to convert the
* Cursor to a String only when the current CursorToStringConverter
* is null.
*
* @param stringConversionColumn a valid index in the current Cursor or -1 to use the default
* conversion mechanism
*
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
* @see #getStringConversionColumn()
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
* @see #getCursorToStringConverter()
*/
public void setStringConversionColumn(int stringConversionColumn) {
mStringConversionColumn = stringConversionColumn;
}
/**
* Returns the converter used to convert the filtering Cursor
* into a String.
*
* @return null if the converter does not exist or an instance of
* {@link android.widget.SimpleCursorAdapter.CursorToStringConverter}
*
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
* @see #getStringConversionColumn()
* @see #setStringConversionColumn(int)
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
*/
public CursorToStringConverter getCursorToStringConverter() {
return mCursorToStringConverter;
}
/**
* Sets the converter used to convert the filtering Cursor
* into a String.
*
* @param cursorToStringConverter the Cursor to String converter, or
* null to remove the converter
*
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
* @see #getStringConversionColumn()
* @see #setStringConversionColumn(int)
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
*/
public void setCursorToStringConverter(CursorToStringConverter cursorToStringConverter) {
mCursorToStringConverter = cursorToStringConverter;
}
/**
* Returns a CharSequence representation of the specified Cursor as defined
* by the current CursorToStringConverter. If no CursorToStringConverter
* has been set, the String conversion column is used instead. If the
* conversion column is -1, the returned String is empty if the cursor
* is null or Cursor.toString().
*
* @param cursor the Cursor to convert to a CharSequence
*
* @return a non-null CharSequence representing the cursor
*/
@Override
public CharSequence convertToString(Cursor cursor) {
if (mCursorToStringConverter != null) {
return mCursorToStringConverter.convertToString(cursor);
} else if (mStringConversionColumn > -1) {
return cursor.getString(mStringConversionColumn);
}
return super.convertToString(cursor);
}
/**
* Create a map from an array of strings to an array of column-id integers in cursor c.
* If c is null, the array will be discarded.
*
* @param c the cursor to find the columns from
* @param from the Strings naming the columns of interest
*/
private void findColumns(Cursor c, String[] from) {
if (c != null) {
int i;
int count = from.length;
if (mFrom == null || mFrom.length != count) {
mFrom = new int[count];
}
for (i = 0; i < count; i++) {
mFrom[i] = c.getColumnIndexOrThrow(from[i]);
}
} else {
mFrom = null;
}
}
@Override
public Cursor swapCursor(Cursor c) {
// super.swapCursor() will notify observers before we have
// a valid mapping, make sure we have a mapping before this
// happens
findColumns(c, mOriginalFrom);
return super.swapCursor(c);
}
/**
* Change the cursor and change the column-to-view mappings at the same time.
*
* @param c The database cursor. Can be null if the cursor is not available yet.
* @param from A list of column names representing the data to bind to the UI. Can be null
* if the cursor is not available yet.
* @param to The views that should display column in the "from" parameter.
* These should all be TextViews. The first N views in this list
* are given the values of the first N columns in the from
* parameter. Can be null if the cursor is not available yet.
*/
public void changeCursorAndColumns(Cursor c, String[] from, int[] to) {
mOriginalFrom = from;
mTo = to;
// super.changeCursor() will notify observers before we have
// a valid mapping, make sure we have a mapping before this
// happens
findColumns(c, mOriginalFrom);
super.changeCursor(c);
}
/**
* This class can be used by external clients of SimpleCursorAdapter
* to bind values fom the Cursor to views.
*
* You should use this class to bind values from the Cursor to views
* that are not directly supported by SimpleCursorAdapter or to
* change the way binding occurs for views supported by
* SimpleCursorAdapter.
*
* @see SimpleCursorAdapter#bindView(android.view.View, android.content.Context, android.database.Cursor)
* @see SimpleCursorAdapter#setViewImage(ImageView, String)
* @see SimpleCursorAdapter#setViewText(TextView, String)
*/
public static interface ViewBinder {
/**
* Binds the Cursor column defined by the specified index to the specified view.
*
* When binding is handled by this ViewBinder, this method must return true.
* If this method returns false, SimpleCursorAdapter will attempts to handle
* the binding on its own.
*
* @param view the view to bind the data to
* @param cursor the cursor to get the data from
* @param columnIndex the column at which the data can be found in the cursor
*
* @return true if the data was bound to the view, false otherwise
*/
boolean setViewValue(View view, Cursor cursor, int columnIndex);
}
/**
* This class can be used by external clients of SimpleCursorAdapter
* to define how the Cursor should be converted to a String.
*
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
*/
public static interface CursorToStringConverter {
/**
* Returns a CharSequence representing the specified Cursor.
*
* @param cursor the cursor for which a CharSequence representation
* is requested
*
* @return a non-null CharSequence representing the cursor
*/
CharSequence convertToString(Cursor cursor);
}
}

View File

@ -1,89 +0,0 @@
package com.mobeta.android.dslv;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Color;
import android.widget.ListView;
import android.widget.ImageView;
import android.view.View;
import android.view.ViewGroup;
import android.util.Log;
/**
* Simple implementation of the FloatViewManager class. Uses list
* items as they appear in the ListView to create the floating View.
*/
public class SimpleFloatViewManager implements DragSortListView.FloatViewManager {
private Bitmap mFloatBitmap;
private ImageView mImageView;
private int mFloatBGColor = Color.BLACK;
private ListView mListView;
public SimpleFloatViewManager(ListView lv) {
mListView = lv;
}
public void setBackgroundColor(int color) {
mFloatBGColor = color;
}
/**
* This simple implementation creates a Bitmap copy of the
* list item currently shown at ListView <code>position</code>.
*/
@Override
public View onCreateFloatView(int position) {
// Guaranteed that this will not be null? I think so. Nope, got
// a NullPointerException once...
View v = mListView.getChildAt(position + mListView.getHeaderViewsCount() - mListView.getFirstVisiblePosition());
if (v == null) {
return null;
}
v.setPressed(false);
// Create a copy of the drawing cache so that it does not get
// recycled by the framework when the list tries to clean up memory
//v.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
v.setDrawingCacheEnabled(true);
mFloatBitmap = Bitmap.createBitmap(v.getDrawingCache());
v.setDrawingCacheEnabled(false);
if (mImageView == null) {
mImageView = new ImageView(mListView.getContext());
}
mImageView.setBackgroundColor(mFloatBGColor);
mImageView.setPadding(0, 0, 0, 0);
mImageView.setImageBitmap(mFloatBitmap);
mImageView.setLayoutParams(new ViewGroup.LayoutParams(v.getWidth(), v.getHeight()));
return mImageView;
}
/**
* This does nothing
*/
@Override
public void onDragFloatView(View floatView, Point position, Point touch) {
// do nothing
}
/**
* Removes the Bitmap from the ImageView created in
* onCreateFloatView() and tells the system to recycle it.
*/
@Override
public void onDestroyFloatView(View floatView) {
((ImageView) floatView).setImageDrawable(null);
mFloatBitmap.recycle();
mFloatBitmap = null;
}
}

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<declare-styleable name="DragSortListView">
<attr name="collapsed_height" format="dimension" />
<attr name="drag_scroll_start" format="float" />
<attr name="max_drag_scroll_speed" format="float" />
<attr name="float_background_color" format="color" />
<attr name="remove_mode">
<enum name="clickRemove" value="0" />
<enum name="flingRemove" value="1" />
</attr>
<attr name="track_drag_sort" format="boolean"/>
<attr name="float_alpha" format="float"/>
<attr name="slide_shuffle_speed" format="float"/>
<attr name="remove_animation_duration" format="integer"/>
<attr name="drop_animation_duration" format="integer"/>
<attr name="drag_enabled" format="boolean" />
<attr name="sort_enabled" format="boolean" />
<attr name="remove_enabled" format="boolean" />
<attr name="drag_start_mode">
<enum name="onDown" value="0" />
<enum name="onMove" value="1" />
<enum name="onLongPress" value="2"/>
</attr>
<attr name="drag_handle_id" format="integer" />
<attr name="fling_handle_id" format="integer" />
<attr name="click_remove_id" format="integer" />
<attr name="use_default_controller" format="boolean" />
</declare-styleable>
</resources>

View File

@ -1,2 +1,2 @@
include ':app', ':core'
include ':library:drag-sort-listview'
include ':app'
include ':core'