diff --git a/Chan/res/values/strings.xml b/Chan/res/values/strings.xml index e8876632..b2144f34 100644 --- a/Chan/res/values/strings.xml +++ b/Chan/res/values/strings.xml @@ -46,7 +46,7 @@ Open drawer Close drawer Pinned threads - Enter title + Edit title reply replies diff --git a/Chan/src/org/floens/chan/ChanApplication.java b/Chan/src/org/floens/chan/ChanApplication.java index 36116b76..c4da0ed0 100644 --- a/Chan/src/org/floens/chan/ChanApplication.java +++ b/Chan/src/org/floens/chan/ChanApplication.java @@ -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)); } } diff --git a/Chan/src/org/floens/chan/activity/BaseActivity.java b/Chan/src/org/floens/chan/activity/BaseActivity.java index 5d069a1a..0474bf7b 100644 --- a/Chan/src/org/floens/chan/activity/BaseActivity.java +++ b/Chan/src/org/floens/chan/activity/BaseActivity.java @@ -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); diff --git a/Chan/src/org/floens/chan/activity/BoardActivity.java b/Chan/src/org/floens/chan/activity/BoardActivity.java index 1d2a85d1..1556aeec 100644 --- a/Chan/src/org/floens/chan/activity/BoardActivity.java +++ b/Chan/src/org/floens/chan/activity/BoardActivity.java @@ -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( - 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 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; diff --git a/Chan/src/org/floens/chan/adapter/PinnedAdapter.java b/Chan/src/org/floens/chan/adapter/PinnedAdapter.java index 8444b383..4e75f0c0 100644 --- a/Chan/src/org/floens/chan/adapter/PinnedAdapter.java +++ b/Chan/src/org/floens/chan/adapter/PinnedAdapter.java @@ -19,40 +19,38 @@ import android.widget.TextView; public class PinnedAdapter extends ArrayAdapter { private final HashMap idMap; private int idCounter; - private View.OnTouchListener listener; - + public PinnedAdapter(Context context, int resId) { super(context, resId, new ArrayList()); - + idMap = new HashMap(); } - - 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 { // 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 { @Override public long getItemId(int position) { if (position < 0 || position >= getCount()) return -1; - + Pin item = getItem(position); if (item == null) { return -1; diff --git a/Chan/src/org/floens/chan/adapter/PostAdapter.java b/Chan/src/org/floens/chan/adapter/PostAdapter.java index 4483fdaa..baf99c75 100644 --- a/Chan/src/org/floens/chan/adapter/PostAdapter.java +++ b/Chan/src/org/floens/chan/adapter/PostAdapter.java @@ -28,6 +28,7 @@ public class PostAdapter extends BaseAdapter { private boolean endOfLine; private int count = 0; private final List postList = new ArrayList(); + 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; diff --git a/Chan/src/org/floens/chan/loader/Loader.java b/Chan/src/org/floens/chan/loader/Loader.java index 5fb384f6..61cc59aa 100644 --- a/Chan/src/org/floens/chan/loader/Loader.java +++ b/Chan/src/org/floens/chan/loader/Loader.java @@ -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 listeners = new ArrayList(); private final Loadable loadable; private final SparseArray postsById = new SparseArray(); - + 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>() { @Override public void onResponse(List list) { @@ -185,47 +191,49 @@ public class Loader { onError(error); } }); - + ChanApplication.getVolleyRequestQueue().add(request); - + return request; } - + private void onData(List 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 result, boolean append); public void onError(VolleyError error); diff --git a/Chan/src/org/floens/chan/manager/PinnedManager.java b/Chan/src/org/floens/chan/manager/PinnedManager.java index 38e66253..19b11beb 100644 --- a/Chan/src/org/floens/chan/manager/PinnedManager.java +++ b/Chan/src/org/floens/chan/manager/PinnedManager.java @@ -11,27 +11,27 @@ import android.content.Context; public class PinnedManager { private static PinnedManager instance; - + private final List listeners = new ArrayList(); private final List 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 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(); } diff --git a/Chan/src/org/floens/chan/manager/ThreadManager.java b/Chan/src/org/floens/chan/manager/ThreadManager.java index de91b906..212ec13b 100644 --- a/Chan/src/org/floens/chan/manager/ThreadManager.java +++ b/Chan/src/org/floens/chan/manager/ThreadManager.java @@ -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; } diff --git a/Chan/src/org/floens/chan/model/Pin.java b/Chan/src/org/floens/chan/model/Pin.java index c9257ce2..c2608053 100644 --- a/Chan/src/org/floens/chan/model/Pin.java +++ b/Chan/src/org/floens/chan/model/Pin.java @@ -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(); } } diff --git a/Chan/src/org/floens/chan/service/PinnedService.java b/Chan/src/org/floens/chan/service/PinnedService.java index 34bf7959..9ab02d81 100644 --- a/Chan/src/org/floens/chan/service/PinnedService.java +++ b/Chan/src/org/floens/chan/service/PinnedService.java @@ -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 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) { diff --git a/Chan/src/org/floens/chan/watch/PinWatcher.java b/Chan/src/org/floens/chan/watch/PinWatcher.java index 875c9b23..71523501 100644 --- a/Chan/src/org/floens/chan/watch/PinWatcher.java +++ b/Chan/src/org/floens/chan/watch/PinWatcher.java @@ -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 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(); } - - } diff --git a/Chan/src/org/floens/chan/watch/WatchNotifier.java b/Chan/src/org/floens/chan/watch/WatchNotifier.java new file mode 100644 index 00000000..d87b0b51 --- /dev/null +++ b/Chan/src/org/floens/chan/watch/WatchNotifier.java @@ -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()); + } +}