/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2015 Umeng, Inc
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.umeng.comm.ui.presenter.impl;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.text.TextUtils;
import com.umeng.comm.core.beans.CommConfig;
import com.umeng.comm.core.beans.FeedItem;
import com.umeng.comm.core.constants.Constants;
import com.umeng.comm.core.constants.ErrorCode;
import com.umeng.comm.core.listeners.Listeners.FetchListener;
import com.umeng.comm.core.listeners.Listeners.OnResultListener;
import com.umeng.comm.core.listeners.Listeners.SimpleFetchListener;
import com.umeng.comm.core.nets.responses.FeedsResponse;
import com.umeng.comm.core.nets.uitls.NetworkUtils;
import com.umeng.comm.ui.mvpview.MvpFeedView;
import com.umeng.comm.ui.utils.Filter;
/**
* Feed列表相关的Presenter,该Presenter从网络、数据库中读取Feed,如果数据是从网络上获取的,那么需要将数据存储到数据库中。
* 在获取数据后会通过MvpFeedView的{@link MvpFeedView#getBindDataSource()}
* 函数获取到列表的数据集,然后对新获取的数据进行去重、排序,再将新获取到的数据添加到列表的数据集合中,最后调用
* {@link MvpFeedView#notifyDataSetChanged()} 函数更新对应的列表视图。
*
* @author mrsimple
*/
public class FeedListPresenter extends BaseFeedPresenter {
protected MvpFeedView mFeedView;
protected String mNextPageUrl;
private Filter<FeedItem> mFeedFilter;
private boolean isRegisterLoginBroadcast = false;
private boolean isAttached = false;
private OnResultListener mOnResultListener; // feed更新时回调,用于提示
/**
* 是否需要清空数据库缓存的标志位
*/
protected AtomicBoolean isNeedRemoveOldFeeds = new AtomicBoolean(true);
public FeedListPresenter(MvpFeedView view) {
this(view, false);
}
public FeedListPresenter(MvpFeedView view, boolean isRegisterLoginBroadcast) {
mFeedView = view;
this.isRegisterLoginBroadcast = isRegisterLoginBroadcast;
}
/**
* 设置回调</br>
*
* @param listener
*/
public void setOnResultListener(OnResultListener listener) {
this.mOnResultListener = listener;
}
@Override
public void loadDataFromServer() {
mCommunitySDK.fetchLastestFeeds(mRefreshListener);
}
/**
* 下拉数据的Listener
*/
protected FetchListener<FeedsResponse> mRefreshListener = new SimpleFetchListener<FeedsResponse>() {
@Override
public void onStart() {
mFeedView.onRefreshStart();
}
// [注意]:mFeedView.onRefreshEnd方法不可提前统一调用,该方法会被判断是否显示空视图的逻辑
@Override
public void onComplete(FeedsResponse response) {
// 根据response进行Toast
if (NetworkUtils.handleResponseAll(response)) {
mFeedView.onRefreshEnd();
return;
}
final List<FeedItem> newFeedItems = response.result;
// 对于下拉刷新,仅在下一个地址为空(首次刷新)时设置下一页地址
if (TextUtils.isEmpty(mNextPageUrl) && isNeedRemoveOldFeeds.get()) {
mNextPageUrl = response.nextPageUrl;
}
beforeDeliveryFeeds(response);
// 更新数据
int news = addFeedItemsToHeader(newFeedItems);
if (mOnResultListener != null) {
mOnResultListener.onResult(news);
}
// 保存加载的数据。如果该数据存在于DB中,则替换成最新的,否则Insert一条新纪录
saveDataToDB(newFeedItems);
mFeedView.onRefreshEnd();
}
};
protected void beforeDeliveryFeeds(FeedsResponse response) {
// 第一次从网络上下拉到数据,那么则清除从数据库中加载进来的数据,避免下一页地址出问题.
clearFeedDBCache();
}
/**
* 第一次从网上下拉到数据时清空数据库中的缓存,然后会调用{@see saveFeedsToDB}将新的数据存储到缓存中
*
* @param helper
*/
private void clearFeedDBCache() {
if (isNeedRemoveOldFeeds.get()) {
// 清空原来的缓存数据
// mFeedView.clearListView(); //
// 此时暂时不清空Adapter数据,确保手机显示的数据是集为:cache+server端数据
// 清空数据库中的缓存数据
mDatabaseAPI.getFeedDBAPI().deleteAllFeedsFromDB();
if (!TextUtils.isEmpty(CommConfig.getConfig().loginedUser.id)) {
isNeedRemoveOldFeeds.set(false);
}
}
}
public void fetchNextPageData() {
if (TextUtils.isEmpty(mNextPageUrl)) {
mFeedView.onRefreshEnd();
return;
}
mCommunitySDK.fetchNextPageData(mNextPageUrl,
FeedsResponse.class, new SimpleFetchListener<FeedsResponse>() {
@Override
public void onComplete(FeedsResponse response) {
mFeedView.onRefreshEnd();
// 根据response进行Toast
if (NetworkUtils.handleResponseAll(response)) {
if (response.errCode == ErrorCode.NO_ERROR) { // 如果返回的数据是空,则需要置下一页地址为空
mNextPageUrl = "";
}
return;
}
mNextPageUrl = response.nextPageUrl;
// 去掉重复的feed
final List<FeedItem> feedItems = getNewFeedItems(response.result);
if (feedItems != null && feedItems.size() > 0) {
// 追加数据
appendFeedItems(feedItems,false);
saveDataToDB(feedItems);
}
}
});
}
/**
* 去重并更新adapter的数据,追加到后面。</br>
*
* @param feedItems
*/
protected List<FeedItem> appendFeedItems(List<FeedItem> feedItems,boolean fromDB) {
List<FeedItem> newFeeds = null;
synchronized (this) {
newFeeds = getNewFeedItems(feedItems);
if (newFeeds != null && newFeeds.size() > 0) {
// 添加到listview中
mFeedView.getBindDataSource().addAll(newFeeds);
sortFeedItems(mFeedView.getBindDataSource());
mFeedView.notifyDataSetChanged();
}
}
return newFeeds;
}
/**
* 过滤数据</br>
*
* @return
*/
protected List<FeedItem> filteFeeds(List<FeedItem> list) {
List<FeedItem> destList = mFeedFilter != null ? mFeedFilter.doFilte(list) : list;
// 移除status>=2的feed,具体值得的含义参考文档说明
Iterator<FeedItem> iterator = destList.iterator();
while (iterator.hasNext()) {
FeedItem item = iterator.next();
if (item.status >= FeedItem.STATUS_SPAM) {
iterator.remove();
}
}
return destList;
}
/**
* 移除存在的feed,获取最新的feed</br>
*
* @param feedItems 最新的feed(待被移除)
* @return
*/
private List<FeedItem> getNewFeedItems(List<FeedItem> feedItems) {
// 去掉在本地已经存在的feeds[此时应该先从adapter中移除数据,确保加载下来的是最新的数据]
// feedItems.removeAll(mFeedView.getAdapterDataSet());
mFeedView.getBindDataSource().removeAll(feedItems);
// 过滤数据
return filteFeeds(feedItems);
}
/**
* 去重并更新adapter的数据,将数据插到前边。</br>
*
* @param feedItems
*/
protected int addFeedItemsToHeader(List<FeedItem> feedItems) {
feedItems = filteFeeds(feedItems);
List<FeedItem> olds = mFeedView.getBindDataSource();
int size = olds.size();
olds.removeAll(feedItems);
olds.addAll(0, feedItems);
int news = olds.size() - size;
sortFeedItems(olds);
mFeedView.notifyDataSetChanged();
return news;
}
public void sortFeedItems(List<FeedItem> items) {
// 所有feed都按照feed的时间降序排列。【该代码避免用户首次登录时,推荐的feed时间较新,但是管理员的帖子较旧的情况】
Collections.sort(items, mComparator);
}
/**
* 获取所有的feed
*/
@Override
public void loadDataFromDB() {
mDatabaseAPI.getFeedDBAPI().loadFeedsFromDB(mDbFetchListener);
}
/**
* 获取某个用户的feed</br>
*
* @param uid
*/
// 注意这里不能重构
public void loadFeedsFromDB(String uid) {
mDatabaseAPI.getFeedDBAPI().loadFeedsFromDB(uid, mDbFetchListener);
}
@Override
public void attach(Context context) {
super.attach(context);
if (isRegisterLoginBroadcast && !isAttached) {
registerLoginSuccessBroadcast();
isAttached = true;
}
}
/**
* 用于数据库的SimpleFetchListener
*/
protected SimpleFetchListener<List<FeedItem>> mDbFetchListener =
new SimpleFetchListener<List<FeedItem>>() {
@Override
public void onComplete(List<FeedItem> response) {
appendFeedItems(response,true);
}
};
protected Comparator<FeedItem> mComparator = new Comparator<FeedItem>() {
@Override
public int compare(FeedItem lhs, FeedItem rhs) {
int isTop = rhs.isTop - lhs.isTop;
return isTop != 0 ? isTop : rhs.publishTime.compareTo(lhs.publishTime);
}
};
/**
* 注册登录成功时的广播</br>
*/
private void registerLoginSuccessBroadcast() {
IntentFilter filter = new IntentFilter();
filter.addAction(Constants.ACTION_LOGIN_SUCCESS);
// LocalBroadcastManager.getInstance(mContext).registerReceiver(mLoginReceiver,
// filter);
mContext.registerReceiver(mLoginReceiver, filter);
}
private BroadcastReceiver mLoginReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
fetchDataFromServerByLogin();
}
};
/**
* 从server加载数据。【注意:该接口仅仅在登录成功的时候调用,需要对此种情况有特殊的逻辑处理】</br>
*/
protected void fetchDataFromServerByLogin() {
mCommunitySDK.fetchLastestFeeds(mLoginRefreshListener);
}
protected FetchListener<FeedsResponse> mLoginRefreshListener = new FetchListener<FeedsResponse>() {
@Override
public void onStart() {
}
@Override
public void onComplete(FeedsResponse response) {
// 首先清理未登录时存储的数据
mDatabaseAPI.getFeedDBAPI().deleteAllFeedsFromDB();
// 清理Adapter中的数据
mFeedView.getBindDataSource().clear();
if (NetworkUtils.handleResponseAll(response)) {
mFeedView.onRefreshEnd();
return;
}
// 更新下一页地址
mNextPageUrl = response.nextPageUrl;
if (!TextUtils.isEmpty(mNextPageUrl)) {
isNeedRemoveOldFeeds.set(false);
}
beforeDeliveryFeeds(response);
mFeedView.getBindDataSource().addAll(response.result);
mFeedView.notifyDataSetChanged();
// 保存加载的数据。如果该数据存在于DB中,则替换成最新的,否则Insert一条新纪录
saveDataToDB(response.result);
mFeedView.onRefreshEnd();
}
};
}