Pin watching now works in the background.

captchafix
Florens Douwes 11 years ago
parent f756a6e692
commit 39d974f91c
  1. 2
      Chan/res/values/strings.xml
  2. 4
      Chan/src/org/floens/chan/ChanApplication.java
  3. 87
      Chan/src/org/floens/chan/activity/BaseActivity.java
  4. 164
      Chan/src/org/floens/chan/activity/BoardActivity.java
  5. 56
      Chan/src/org/floens/chan/adapter/PinnedAdapter.java
  6. 6
      Chan/src/org/floens/chan/adapter/PostAdapter.java
  7. 106
      Chan/src/org/floens/chan/loader/Loader.java
  8. 47
      Chan/src/org/floens/chan/manager/PinnedManager.java
  9. 12
      Chan/src/org/floens/chan/manager/ThreadManager.java
  10. 38
      Chan/src/org/floens/chan/model/Pin.java
  11. 67
      Chan/src/org/floens/chan/service/PinnedService.java
  12. 56
      Chan/src/org/floens/chan/watch/PinWatcher.java
  13. 33
      Chan/src/org/floens/chan/watch/WatchNotifier.java

@ -46,7 +46,7 @@
<string name="drawer_open">Open drawer</string>
<string name="drawer_close">Close drawer</string>
<string name="drawer_pinned">Pinned threads</string>
<string name="drawer_pinned_change_title">Enter title</string>
<string name="drawer_pinned_change_title">Edit title</string>
<string name="one_reply">reply</string>
<string name="multiple_replies">replies</string>

@ -4,9 +4,11 @@ import org.floens.chan.database.DatabaseManager;
import org.floens.chan.manager.BoardManager;
import org.floens.chan.manager.PinnedManager;
import org.floens.chan.manager.ReplyManager;
import org.floens.chan.service.PinnedService;
import org.floens.chan.utils.IconCache;
import android.app.Application;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.StrictMode;
import android.preference.PreferenceManager;
@ -70,7 +72,7 @@ public class ChanApplication extends Application {
new PinnedManager(this);
new ReplyManager(this);
// startService(new Intent(this, PinnedService.class));
startService(new Intent(this, PinnedService.class));
}
}

@ -7,6 +7,7 @@ import org.floens.chan.animation.SwipeDismissListViewTouchListener.DismissCallba
import org.floens.chan.manager.PinnedManager;
import org.floens.chan.model.Pin;
import org.floens.chan.model.Post;
import org.floens.chan.utils.Logger;
import android.app.Activity;
import android.app.AlertDialog;
@ -38,22 +39,22 @@ import android.widget.ShareActionProvider;
public abstract class BaseActivity extends Activity implements PanelSlideListener, PinnedManager.PinListener {
private final static int ACTION_OPEN_URL = 1;
protected PinnedAdapter pinnedAdapter;
protected DrawerLayout pinDrawer;
protected ListView pinDrawerView;
protected ActionBarDrawerToggle pinDrawerListener;
protected SlidingPaneLayout threadPane;
private ShareActionProvider shareActionProvider;
/**
* Called when a post has been clicked in the pinned drawer
* @param post
*/
abstract public void openPin(Pin post);
/**
* Called when a post has been clicked in the listview
* @param post
@ -63,25 +64,25 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_base);
pinDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
initDrawer();
threadPane = (SlidingPaneLayout) findViewById(R.id.pane_container);
initPane();
PinnedManager.getInstance().addPinListener(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
PinnedManager.getInstance().removePinListener(this);
}
private void initPane() {
threadPane.setPanelSlideListener(this);
threadPane.setParallaxDistance(200);
@ -89,21 +90,21 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene
threadPane.setSliderFadeColor(0xcce5e5e5);
threadPane.openPane();
}
protected void initDrawer() {
if (pinDrawerListener == null) {
return;
}
pinDrawer.setDrawerListener(pinDrawerListener);
pinDrawer.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
pinDrawerView = (ListView)findViewById(R.id.left_drawer);
pinnedAdapter = new PinnedAdapter(getActionBar().getThemedContext(), 0); // Get the dark theme, not the light one
pinnedAdapter.reload();
pinDrawerView.setAdapter(pinnedAdapter);
pinDrawerView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
@ -112,19 +113,19 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene
openPin(pin);
}
});
pinDrawerView.setOnItemLongClickListener(new OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
Pin post = pinnedAdapter.getItem(position);
if (post == null || post.type == Pin.Type.HEADER) return false;
changePinTitle(post);
return true;
}
});
SwipeDismissListViewTouchListener touchListener = new SwipeDismissListViewTouchListener(pinDrawerView,
new DismissCallbacks() {
@Override
@ -139,24 +140,26 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene
return pinnedAdapter.getItem(position).type != Pin.Type.HEADER;
}
});
pinDrawerView.setOnTouchListener(touchListener);
pinDrawerView.setOnScrollListener(touchListener.makeScrollListener());
}
@Override
public void onPinsChanged() {
pinnedAdapter.reload();
pinDrawerView.invalidate();
Logger.test("onPinsChanged");
}
public void addPin(Pin pin) {
PinnedManager.getInstance().add(pin);
}
public void removePin(Pin pin) {
PinnedManager.getInstance().remove(pin);
}
public void updatePin(Pin pin) {
PinnedManager.getInstance().update(pin);
}
@ -166,13 +169,13 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene
text.setSingleLine();
text.setText(pin.loadable.title);
text.setSelectAllOnFocus(true);
AlertDialog dialog = new AlertDialog.Builder(this)
.setPositiveButton(R.string.change, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface d, int which) {
String value = text.getText().toString();
if (!TextUtils.isEmpty(value)) {
pin.loadable.title = value;
updatePin(pin);
@ -187,9 +190,9 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene
.setTitle(R.string.drawer_pinned_change_title)
.setView(text)
.create();
text.requestFocus();
dialog.setOnShowListener(new OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
@ -197,7 +200,7 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene
imm.showSoftInput(text, 0);
}
});
dialog.show();
}
@ -208,18 +211,18 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene
startActivity(new Intent(this, SettingsActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.base, menu);
shareActionProvider = (ShareActionProvider) menu.findItem(R.id.action_share).getActionProvider();
return true;
}
@Override
public void onPanelClosed(View view) {
}
@ -231,7 +234,7 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene
@Override
public void onPanelSlide(View view, float offset) {
}
/**
* Set the url that Android Beam and the share action will send.
* @param url
@ -247,11 +250,11 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene
e.printStackTrace();
return;
}
NdefMessage message = new NdefMessage(new NdefRecord[] {record});
adapter.setNdefPushMessage(message, this);
}
if (shareActionProvider != null) {
Intent share = new Intent(android.content.Intent.ACTION_SEND);
share.putExtra(android.content.Intent.EXTRA_TEXT, url);
@ -259,7 +262,7 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene
shareActionProvider.setShareIntent(share);
}
}
/**
* Let the user choose between all activities that can open the url.
* This is done to prevent "open in browser" opening the url in our own app.
@ -267,20 +270,20 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene
*/
public void showUrlOpenPicker(String url) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
pickIntent.putExtra(Intent.EXTRA_INTENT, intent);
startActivityForResult(pickIntent, ACTION_OPEN_URL);
}
/**
* Used for showUrlOpenPicker
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == ACTION_OPEN_URL && resultCode == RESULT_OK && data != null) {
data.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(data);

@ -34,44 +34,44 @@ public class BoardActivity extends BaseActivity implements ActionBar.OnNavigatio
private ThreadFragment boardFragment;
private ThreadFragment threadFragment;
private boolean boardSetByIntent = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
boardLoadable.mode = Loadable.Mode.BOARD;
threadLoadable.mode = Loadable.Mode.THREAD;
boardFragment = ThreadFragment.newInstance(this);
threadFragment = ThreadFragment.newInstance(this);
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.left_pane, boardFragment);
ft.replace(R.id.right_pane, threadFragment);
ft.commitAllowingStateLoss();
updateActionBarState();
final ActionBar actionBar = getActionBar();
actionBar.setListNavigationCallbacks(
new ArrayAdapter<String>(
actionBar.getThemedContext(),
actionBar.getThemedContext(),
R.layout.board_select_spinner,
android.R.id.text1,
BoardManager.getInstance().getMyBoardsKeys()
), this);
Intent startIntent = getIntent();
Uri startUri = startIntent.getData();
if (savedInstanceState != null) {
boardLoadable.readFromBundle(this, "board", savedInstanceState);
boardLoadable.no = 0;
boardLoadable.listViewIndex = 0;
boardLoadable.listViewTop = 0;
threadLoadable.readFromBundle(this, "thread", savedInstanceState);
setNavigationFromBoardValue(boardLoadable.board);
startLoadingThread(threadLoadable);
} else if (startUri != null) {
@ -80,41 +80,41 @@ public class BoardActivity extends BaseActivity implements ActionBar.OnNavigatio
getActionBar().setSelectedNavigationItem(0);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
boardLoadable.writeToBundle(this, "board", outState);
threadLoadable.writeToBundle(this, "thread", outState);
}
@Override
protected void onStart() {
super.onStart();
PinnedService.onActivityStart();
}
@Override
protected void onStop() {
super.onStop();
PinnedService.onActivityStop();
}
@Override
protected void onPause() {
super.onPause();
PinnedManager.getInstance().updateAll();
}
@Override
protected void initDrawer() {
pinDrawerListener = new ActionBarDrawerToggle(this, pinDrawer,
pinDrawerListener = new ActionBarDrawerToggle(this, pinDrawer,
R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) {};
super.initDrawer();
}
@ -124,16 +124,16 @@ public class BoardActivity extends BaseActivity implements ActionBar.OnNavigatio
boardLoadable = new Loadable(BoardManager.getInstance().getMyBoardsValues().get(position));
startLoadingBoard(boardLoadable);
}
boardSetByIntent = false;
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
return true;
}
@ -147,7 +147,7 @@ public class BoardActivity extends BaseActivity implements ActionBar.OnNavigatio
@Override
public void openPin(Pin pin) {
startLoadingThread(pin.loadable);
pinDrawer.closeDrawer(pinDrawerView);
}
@ -161,7 +161,7 @@ public class BoardActivity extends BaseActivity implements ActionBar.OnNavigatio
super.onPostCreate(savedInstanceState);
pinDrawerListener.syncState();
}
@Override
public void onBackPressed() {
if (threadPane.isOpen()) {
@ -170,10 +170,16 @@ public class BoardActivity extends BaseActivity implements ActionBar.OnNavigatio
threadPane.openPane();
}
}
@Override
public void updatePin(Pin pin) {
super.updatePin(pin);
updateActionBarState();
}
private void updateActionBarState() {
final ActionBar actionBar = getActionBar();
if (threadPane.isSlideable()) {
if (threadPane.isOpen()) {
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
@ -185,51 +191,51 @@ public class BoardActivity extends BaseActivity implements ActionBar.OnNavigatio
actionBar.setTitle(threadLoadable.title);
pinDrawerListener.setDrawerIndicatorEnabled(false);
}
actionBar.setDisplayHomeAsUpEnabled(true);
} else {
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
pinDrawerListener.setDrawerIndicatorEnabled(true);
actionBar.setTitle(threadLoadable.title);
actionBar.setDisplayHomeAsUpEnabled(true);
}
actionBar.setDisplayShowTitleEnabled(true);
invalidateOptionsMenu();
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
boolean open = threadPane.isOpen();
boolean slidable = threadPane.isSlideable();
setMenuItemEnabled(menu.findItem(R.id.action_reload_board), slidable && open);
setMenuItemEnabled(menu.findItem(R.id.action_reload_thread), slidable && !open);
setMenuItemEnabled(menu.findItem(R.id.action_reload_tablet), !slidable);
setMenuItemEnabled(menu.findItem(R.id.action_pin), !slidable || !open);
setMenuItemEnabled(menu.findItem(R.id.action_reply), slidable);
setMenuItemEnabled(menu.findItem(R.id.action_reply_tablet), !slidable);
return super.onPrepareOptionsMenu(menu);
}
private void setMenuItemEnabled(MenuItem item, boolean enabled) {
if (item != null) {
item.setVisible(enabled);
item.setEnabled(enabled);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (pinDrawerListener.onOptionsItemSelected(item)) {
return true;
}
switch(item.getItemId()) {
case R.id.action_reload_board:
case R.id.action_reload_tablet_board:
@ -248,22 +254,22 @@ public class BoardActivity extends BaseActivity implements ActionBar.OnNavigatio
return true;
case R.id.action_reply_board:
boardFragment.openReply();
return true;
case R.id.action_reply_thread:
threadFragment.openReply();
return true;
case R.id.action_pin:
if (threadFragment.hasLoader()) {
Pin pin = new Pin();
pin.loadable = threadLoadable;
addPin(pin);
pinDrawer.openDrawer(pinDrawerView);
}
return true;
case R.id.action_open_browser:
if (threadPane.isOpen()) {
@ -273,14 +279,14 @@ public class BoardActivity extends BaseActivity implements ActionBar.OnNavigatio
showUrlOpenPicker(ChanUrls.getThreadUrlDesktop(threadLoadable.board, threadLoadable.no));
}
}
return true;
case android.R.id.home:
threadPane.openPane();
return true;
}
return super.onOptionsItemSelected(item);
}
@ -293,63 +299,63 @@ public class BoardActivity extends BaseActivity implements ActionBar.OnNavigatio
public void onPanelOpened(View view) {
updateActionBarState();
}
private void startLoadingBoard(Loadable loadable) {
if (loadable.mode == Loadable.Mode.INVALID) return;
this.boardLoadable = loadable;
boardLoadable = loadable;
boardFragment.bindLoadable(loadable);
boardFragment.requestData();
setShareUrl(ChanUrls.getBoardUrlDesktop(loadable.board));
updateActionBarState();
}
private void startLoadingThread(Loadable loadable) {
if (loadable.mode == Loadable.Mode.INVALID) return;
Pin pin = PinnedManager.getInstance().findPinByLoadable(loadable);
if (pin != null) {
// Use the loadable from the pin.
// This way we can store the listview position in the pin loadable,
// This way we can store the listview position in the pin loadable,
// and not in a separate loadable instance.
loadable = pin.loadable;
loadable = pin.loadable;
}
threadLoadable = loadable;
threadFragment.bindLoadable(loadable);
threadFragment.requestData();
setShareUrl(ChanUrls.getThreadUrlDesktop(loadable.board, loadable.no));
if (TextUtils.isEmpty(loadable.title)) {
loadable.title = "/" + loadable.board + "/" + loadable.no;
}
threadPane.closePane();
updateActionBarState();
}
/**
* Handle opening from an external url.
* @param startUri
*/
private void handleIntentURI(Uri startUri) {
List<String> parts = startUri.getPathSegments();
if (parts.size() == 1) {
// Board mode
String rawBoard = parts.get(0);
String rawBoard = parts.get(0);
if (BoardManager.getInstance().getBoardExists(rawBoard)) {
boardSetByIntent = true;
startLoadingBoard(new Loadable(rawBoard));
ActionBar actionBar = getActionBar();
if (!setNavigationFromBoardValue(rawBoard)) {
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
@ -357,7 +363,7 @@ public class BoardActivity extends BaseActivity implements ActionBar.OnNavigatio
String value = BoardManager.getInstance().getBoardKey(rawBoard);
actionBar.setTitle(value == null ? ("/" + rawBoard + "/") : value);
}
} else {
handleIntentURIFallback(startUri.toString());
}
@ -366,14 +372,14 @@ public class BoardActivity extends BaseActivity implements ActionBar.OnNavigatio
// First load a board and then start another activity opening the thread
String rawBoard = parts.get(0);
int no = -1;
try {
no = Integer.parseInt(parts.get(2));
} catch (NumberFormatException e) {}
if (no >= 0 && BoardManager.getInstance().getBoardExists(rawBoard)) {
boardSetByIntent = true;
startLoadingBoard(new Loadable(rawBoard));
startLoadingThread(new Loadable(rawBoard, no));
} else {
@ -384,7 +390,7 @@ public class BoardActivity extends BaseActivity implements ActionBar.OnNavigatio
showUrlOpenPicker(startUri.toString());
}
}
private void handleIntentURIFallback(final String url) {
new AlertDialog.Builder(this)
.setTitle(R.string.open_unknown_title)
@ -407,7 +413,7 @@ public class BoardActivity extends BaseActivity implements ActionBar.OnNavigatio
.create()
.show();
}
/**
* Set the visual selector to the board. If the user has not set the board as a favorite,
* return false.
@ -423,7 +429,7 @@ public class BoardActivity extends BaseActivity implements ActionBar.OnNavigatio
break;
}
}
if (foundIndex >= 0) {
getActionBar().setSelectedNavigationItem(foundIndex);
return true;

@ -19,40 +19,38 @@ import android.widget.TextView;
public class PinnedAdapter extends ArrayAdapter<Pin> {
private final HashMap<Pin, Integer> idMap;
private int idCounter;
private View.OnTouchListener listener;
public PinnedAdapter(Context context, int resId) {
super(context, resId, new ArrayList<Pin>());
idMap = new HashMap<Pin, Integer>();
}
public void setTouchListener(View.OnTouchListener listener) {
this.listener = listener;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout view = null;
Pin item = getItem(position);
if (item.type == Pin.Type.HEADER) {
view = (LinearLayout) inflater.inflate(R.layout.pin_item_header, null);
((TextView) view.findViewById(R.id.drawer_item_header)).setText(R.string.drawer_pinned);
} else {
view = (LinearLayout) inflater.inflate(R.layout.pin_item, null);
((TextView) view.findViewById(R.id.drawer_item_text)).setText(item.loadable.title);
int count = Math.max(0, item.watchNewCount - item.watchLastCount);
String total = Integer.toString(Math.min(count, 999));
int count = item.getNewPostCount();
String total = Integer.toString(count);
if (count > 999) {
total = "1k+";
}
((TextView) view.findViewById(R.id.drawer_item_count)).setText(total);
FrameLayout frameLayout = (FrameLayout) view.findViewById(R.id.drawer_item_count_container);
// if (Math.random() < 0.5d) {
frameLayout.setBackgroundResource(R.drawable.pin_icon_blue);
@ -60,38 +58,36 @@ public class PinnedAdapter extends ArrayAdapter<Pin> {
// frameLayout.setBackgroundResource(R.drawable.pin_icon_red);
// }
}
if (listener != null) {
view.setOnTouchListener(listener);
}
return view;
}
public void reload() {
clear();
Pin header = new Pin();
header.type = Pin.Type.HEADER;
add(header);
addAll(PinnedManager.getInstance().getPins());
notifyDataSetChanged();
}
@Override
public void remove(Pin item) {
super.remove(item);
idMap.remove(item);
notifyDataSetChanged();
}
@Override
public void add(Pin item) {
idMap.put(item, ++idCounter);
super.add(item);
notifyDataSetChanged();
}
@Override
public boolean hasStableIds() {
return true;
@ -100,7 +96,7 @@ public class PinnedAdapter extends ArrayAdapter<Pin> {
@Override
public long getItemId(int position) {
if (position < 0 || position >= getCount()) return -1;
Pin item = getItem(position);
if (item == null) {
return -1;

@ -28,6 +28,7 @@ public class PostAdapter extends BaseAdapter {
private boolean endOfLine;
private int count = 0;
private final List<Post> postList = new ArrayList<Post>();
private long lastViewedTime = 0;
public PostAdapter(Context activity, ThreadManager threadManager, ListView listView) {
context = activity;
@ -62,6 +63,11 @@ public class PostAdapter extends BaseAdapter {
}
if (position >= count) {
if (System.currentTimeMillis() - lastViewedTime > 10000L) {
lastViewedTime = System.currentTimeMillis();
threadManager.bottomPostViewed();
}
return createThreadEndView();
} else {
PostView postView = null;

@ -9,6 +9,7 @@ import org.floens.chan.model.Post;
import org.floens.chan.utils.Logger;
import android.os.Handler;
import android.os.Looper;
import android.util.SparseArray;
import com.android.volley.Response;
@ -17,26 +18,26 @@ import com.android.volley.VolleyError;
public class Loader {
private static final String TAG = "Loader";
private static final Handler handler = new Handler();
private static final int[] watchTimeouts = {10, 15, 20, 30, 60, 90, 120, 180, 240, 300};
private static final Handler handler = new Handler(Looper.getMainLooper());
private static final int[] watchTimeouts = {5, 10, 15, 20, 30, 60, 90, 120, 180, 240, 300};
private final List<LoaderListener> listeners = new ArrayList<LoaderListener>();
private final Loadable loadable;
private final SparseArray<Post> postsById = new SparseArray<Post>();
private boolean destroyed = false;
private ChanReaderRequest request;
private int currentTimeout;
private int lastPostCount;
private long lastLoadTime;
private Runnable pendingRunnable;
public Loader(Loadable loadable) {
this.loadable = loadable;
}
/**
* Add a LoaderListener
* @param l the listener to add
@ -44,7 +45,7 @@ public class Loader {
public void addListener(LoaderListener l) {
listeners.add(l);
}
/**
* Remove a LoaderListener
* @param l the listener to remove
@ -60,81 +61,87 @@ public class Loader {
return false;
}
}
public void requestData() {
if (request != null) {
request.cancel();
}
if (loadable.isBoardMode()) {
loadable.no = 0;
loadable.listViewIndex = 0;
loadable.listViewTop = 0;
}
currentTimeout = 0;
request = getData(loadable);
}
public void requestNextData() {
if (loadable.isBoardMode()) {
loadable.no++;
if (request != null) {
request.cancel();
}
request = getData(loadable);
} else if (loadable.isThreadMode()) {
if (request != null) {
return;
}
clearTimer();
request = getData(loadable);
}
}
public void requestNextDataResetTimer() {
currentTimeout = 0;
requestNextData();
}
/**
* @return Returns if this loader is currently loading
*/
public boolean isLoading() {
return request != null;
}
public Post findPostById(int id) {
return postsById.get(id);
}
public Loadable getLoadable() {
return loadable;
}
public void onStart() {
if (loadable.isThreadMode()) {
requestNextDataResetTimer();
}
}
public void onStop() {
clearTimer();
}
public long getTimeUntilReload() {
long waitTime = watchTimeouts[currentTimeout] * 1000L;
return lastLoadTime + waitTime - System.currentTimeMillis();
if (request != null) {
return 0L;
} else {
long waitTime = watchTimeouts[currentTimeout] * 1000L;
return lastLoadTime + waitTime - System.currentTimeMillis();
}
}
private void setTimer(int postCount) {
if (pendingRunnable != null) {
clearTimer();
}
if (pendingRunnable == null) {
pendingRunnable = new Runnable() {
@Override
@ -142,36 +149,35 @@ public class Loader {
pendingRunnableCallback();
};
};
lastPostCount = postCount;
if (postCount > lastPostCount) {
currentTimeout = 0;
} else {
currentTimeout = Math.min(watchTimeouts.length - 1, currentTimeout + 1);
}
lastPostCount = postCount;
handler.postDelayed(pendingRunnable, watchTimeouts[currentTimeout] * 1000L);
}
}
private void clearTimer() {
if (pendingRunnable != null) {
handler.removeCallbacks(pendingRunnable);
pendingRunnable = null;
lastLoadTime = 0;
}
}
private void pendingRunnableCallback() {
Logger.d(TAG, "pending callback");
pendingRunnable = null;
requestNextData();
}
private ChanReaderRequest getData(Loadable loadable) {
Logger.i(TAG, "Requested " + loadable.board + ", " + loadable.no);
ChanReaderRequest request = ChanReaderRequest.newInstance(loadable, new Response.Listener<List<Post>>() {
@Override
public void onResponse(List<Post> list) {
@ -185,47 +191,49 @@ public class Loader {
onError(error);
}
});
ChanApplication.getVolleyRequestQueue().add(request);
return request;
}
private void onData(List<Post> result) {
if (destroyed) return;
Logger.test("ondata in loader");
postsById.clear();
for (Post post : result) {
postsById.append(post.no, post);
}
for (LoaderListener l : listeners) {
l.onData(result, loadable.isBoardMode());
}
lastLoadTime = System.currentTimeMillis();
if (loadable.isThreadMode()) {
setTimer(result.size());
}
}
private void onError(VolleyError error) {
if (destroyed) return;
Logger.e(TAG, "Error loading " + error.getMessage(), error);
// 404 with more pages already loaded means endofline
if ((error instanceof ServerError) && loadable.isBoardMode() && loadable.no > 0) {
error = new EndOfLineException();
}
for (LoaderListener l : listeners) {
l.onError(error);
}
clearTimer();
}
public static interface LoaderListener {
public void onData(List<Post> result, boolean append);
public void onError(VolleyError error);

@ -11,27 +11,27 @@ import android.content.Context;
public class PinnedManager {
private static PinnedManager instance;
private final List<PinListener> listeners = new ArrayList<PinListener>();
private final List<Pin> pins;
public PinnedManager(Context context) {
instance = this;
pins = DatabaseManager.getInstance().getPinned();
}
public static PinnedManager getInstance() {
return instance;
}
public void addPinListener(PinListener l) {
listeners.add(l);
}
public void removePinListener(PinListener l) {
listeners.remove(l);
}
/**
* Look for a pin that has an loadable that is equal to the supplied loadable.
* @param other
@ -43,14 +43,14 @@ public class PinnedManager {
return pin;
}
}
return null;
}
public List<Pin> getPins() {
return pins;
}
/**
* Add a pin
* @param pin
@ -63,15 +63,15 @@ public class PinnedManager {
return false;
}
}
pins.add(pin);
DatabaseManager.getInstance().addPin(pin);
onPinsChanged();
return true;
}
/**
* Remove a pin
* @param pin
@ -79,22 +79,23 @@ public class PinnedManager {
public void remove(Pin pin) {
pins.remove(pin);
DatabaseManager.getInstance().removePin(pin);
pin.destroy();
onPinsChanged();
}
/**
* Update the pin in the database
* @param pin
*/
public void update(Pin pin) {
DatabaseManager.getInstance().updatePin(pin);
onPinsChanged();
}
/**
* Updates all the pins to the database.
* Updates all the pins to the database.
* This will run in a new thread because it can be an expensive operation.
* (this will be an huge headache later on when we get concurrent problems)
*/
@ -106,19 +107,19 @@ public class PinnedManager {
}
}).start();
}
public void onPinViewed(Pin pin) {
pin.watchLastCount = pin.watchNewCount;
pin.onViewed();
onPinsChanged();
}
public void onPinsChanged() {
for (PinListener l : listeners) {
l.onPinsChanged();
}
}
public static interface PinListener {
public void onPinsChanged();
}

@ -13,6 +13,7 @@ import org.floens.chan.loader.LoaderPool;
import org.floens.chan.manager.ReplyManager.DeleteListener;
import org.floens.chan.manager.ReplyManager.DeleteResponse;
import org.floens.chan.model.Loadable;
import org.floens.chan.model.Pin;
import org.floens.chan.model.Post;
import org.floens.chan.model.PostLinkable;
import org.floens.chan.model.SavedReply;
@ -93,6 +94,15 @@ public class ThreadManager implements Loader.LoaderListener {
highlightedPost = null;
}
public void bottomPostViewed() {
if (loader != null && loader.getLoadable().isThreadMode()) {
Pin pin = PinnedManager.getInstance().findPinByLoadable(loader.getLoadable());
if (pin != null) {
PinnedManager.getInstance().onPinViewed(pin);
}
}
}
public void requestData() {
if (loader != null) {
loader.requestData();
@ -398,7 +408,7 @@ public class ThreadManager implements Loader.LoaderListener {
FragmentTransaction ft = activity.getFragmentManager().beginTransaction();
ft.add(popup, "postPopup");
ft.commit();
ft.commitAllowingStateLoss();
currentPopupFragment = popup;
}

@ -10,33 +10,51 @@ public class Pin {
// Database stuff
@DatabaseField(generatedId = true)
private int id;
@DatabaseField(canBeNull = false, foreign = true)
public Loadable loadable = new Loadable("", -1);
// ListView Stuff
/** Header is used to display a static header in the drawer listview. */
public Type type = Type.THREAD;
public static enum Type {
HEADER,
HEADER,
THREAD
};
// PinnedService stuff
public PinWatcher pinWatcher;
@DatabaseField
public int watchLastCount;
@DatabaseField
public int watchNewCount;
public void updateWatch() {
if (pinWatcher == null) {
// pinWatcher = new PinWatcher(this);
pinWatcher = new PinWatcher(this);
}
pinWatcher.update();
}
public int getNewPostCount() {
if (pinWatcher != null) {
return pinWatcher.getNewPostCount();
} else {
return 0;
}
}
public void onViewed() {
watchLastCount = watchNewCount;
}
public void destroy() {
if (pinWatcher != null) {
pinWatcher.destroy();
}
// pinWatcher.update();
}
}

@ -2,13 +2,11 @@ package org.floens.chan.service;
import java.util.List;
import org.floens.chan.R;
import org.floens.chan.manager.PinnedManager;
import org.floens.chan.model.Pin;
import org.floens.chan.utils.Logger;
import org.floens.chan.watch.WatchNotifier;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
@ -23,6 +21,7 @@ public class PinnedService extends Service {
private Thread loadThread;
private boolean running = true;
private final WatchNotifier watchNotifier;
public static void onActivityStart() {
Logger.test("onActivityStart");
@ -34,6 +33,10 @@ public class PinnedService extends Service {
activityInForeground = false;
}
public PinnedService() {
watchNotifier = new WatchNotifier(this);
}
@Override
public void onCreate() {
super.onCreate();
@ -49,37 +52,39 @@ public class PinnedService extends Service {
}
private void start() {
loadThread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
while (running) {
doUpdates();
long timeout = activityInForeground ? FOREGROUND_INTERVAL : BACKGROUND_INTERVAL;
try {
Thread.sleep(timeout);
} catch (InterruptedException e) {
e.printStackTrace();
running = true;
if (loadThread == null) {
loadThread = new Thread(new Runnable() {
@Override
public void run() {
while (running) {
update();
long timeout = activityInForeground ? FOREGROUND_INTERVAL : BACKGROUND_INTERVAL;
try {
Thread.sleep(timeout);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
loadThread = null;
}
}
});
});
loadThread.start();
loadThread.start();
}
}
private void doUpdates() {
private void update() {
List<Pin> pins = PinnedManager.getInstance().getPins();
for (Pin pin : pins) {
pin.updateWatch();
}
watchNotifier.update();
}
public static void callOnPinsChanged() {
@ -91,18 +96,6 @@ public class PinnedService extends Service {
});
}
@SuppressWarnings("deprecation")
private void showNotification(String text) {
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Notification.Builder builder = new Notification.Builder(this);
builder.setTicker(text);
builder.setContentTitle(text);
builder.setContentText(text);
builder.setSmallIcon(R.drawable.ic_stat_notify);
nm.notify(1, builder.getNotification());
}
@Override
public IBinder onBind(Intent intent) {

@ -3,6 +3,7 @@ package org.floens.chan.watch;
import java.util.List;
import org.floens.chan.loader.Loader;
import org.floens.chan.loader.LoaderPool;
import org.floens.chan.model.Pin;
import org.floens.chan.model.Post;
import org.floens.chan.service.PinnedService;
@ -12,44 +13,63 @@ import com.android.volley.VolleyError;
public class PinWatcher implements Loader.LoaderListener {
private static final String TAG = "PinWatcher";
private final Pin pin;
private Loader loader;
private final Loader loader;
private long startTime;
private boolean isError = false;
public PinWatcher(Pin pin) {
this.pin = pin;
// loader = new Loader(this);
loader = LoaderPool.getInstance().obtain(pin.loadable, this);
}
public void destroy() {
LoaderPool.getInstance().release(loader, this);
}
public void update() {
Logger.test("PinWatcher update");
if (!isError) {
if (loader.getTimeUntilReload() < -1000000L) {
Logger.test("Here: " + loader.getTimeUntilReload());
}
if (loader.getTimeUntilReload() < 0L) {
loader.requestNextDataResetTimer();
}
}
}
public int getNewPostCount() {
if (pin.watchLastCount <= 0) {
return 0;
} else {
return Math.max(0, pin.watchNewCount - pin.watchLastCount);
}
}
@Override
public void onError(VolleyError error) {
Logger.test("PinWatcher onError: ", error);
isError = true;
}
@Override
public void onData(List<Post> result, boolean append) {
int count = result.size();
Logger.test("PinWatcher onData");
Logger.test("Post size: " + count);
Logger.test("PinWatcher onData, Post size: " + count);
if (pin.watchLastCount <= 0) {
pin.watchLastCount = pin.watchNewCount;
}
pin.watchNewCount = count;
Logger.test("Load time: " + (System.currentTimeMillis() - startTime) + "ms");
PinnedService.callOnPinsChanged();
}
}

@ -0,0 +1,33 @@
package org.floens.chan.watch;
import org.floens.chan.R;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
public class WatchNotifier {
private final int NOTIFICATION_ID = 1;
private final Context context;
public WatchNotifier(Context context) {
this.context = context;
}
public void update() {
showNotification("Update!");
}
private void showNotification(String text) {
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification.Builder builder = new Notification.Builder(context);
builder.setTicker(text);
builder.setContentTitle(text);
builder.setContentText(text);
builder.setSmallIcon(R.drawable.ic_stat_notify);
nm.notify(NOTIFICATION_ID, builder.getNotification());
}
}
Loading…
Cancel
Save