Merge pull request #1347 from mfietz/recycler_view
RecyclerView & SnackBar
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -20,5 +20,4 @@ public class FeedMediaEvent {
|
|||
return new FeedMediaEvent(Action.UPDATE, media);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
Before Width: | Height: | Size: 268 B |
Before Width: | Height: | Size: 244 B |
After Width: | Height: | Size: 246 B |
After Width: | Height: | Size: 242 B |
Before Width: | Height: | Size: 217 B |
Before Width: | Height: | Size: 188 B |
After Width: | Height: | Size: 197 B |
After Width: | Height: | Size: 193 B |
Before Width: | Height: | Size: 318 B |
Before Width: | Height: | Size: 297 B |
After Width: | Height: | Size: 291 B |
After Width: | Height: | Size: 286 B |
Before Width: | Height: | Size: 410 B |
Before Width: | Height: | Size: 380 B |
After Width: | Height: | Size: 389 B |
After Width: | Height: | Size: 386 B |
After Width: | Height: | Size: 501 B |
After Width: | Height: | Size: 498 B |
|
@ -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>
|
||||
|
|
|
@ -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
|
|
@ -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"
|
||||
}
|
|
@ -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 *;
|
||||
#}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
|
@ -1,2 +1,2 @@
|
|||
include ':app', ':core'
|
||||
include ':library:drag-sort-listview'
|
||||
include ':app'
|
||||
include ':core'
|
||||
|
|