Remove old classes and xml files

Removing all old activities/fragments/views that are not used in the new UI system.
Make all icons material.
Make lint happy(ier)
Add license headers where missing.
filtering
Floens 10 years ago
parent e63c68e361
commit 22db727dbf
  1. 59
      Clover/app/src/main/AndroidManifest.xml
  2. 469
      Clover/app/src/main/java/com/mobeta/android/dslv/DragSortController.java
  3. 239
      Clover/app/src/main/java/com/mobeta/android/dslv/DragSortCursorAdapter.java
  4. 92
      Clover/app/src/main/java/com/mobeta/android/dslv/DragSortItemView.java
  5. 46
      Clover/app/src/main/java/com/mobeta/android/dslv/DragSortItemViewCheckable.java
  6. 2974
      Clover/app/src/main/java/com/mobeta/android/dslv/DragSortListView.java
  7. 155
      Clover/app/src/main/java/com/mobeta/android/dslv/ResourceDragSortCursorAdapter.java
  8. 448
      Clover/app/src/main/java/com/mobeta/android/dslv/SimpleDragSortCursorAdapter.java
  9. 87
      Clover/app/src/main/java/com/mobeta/android/dslv/SimpleFloatViewManager.java
  10. 220
      Clover/app/src/main/java/org/floens/chan/Chan.java
  11. 204
      Clover/app/src/main/java/org/floens/chan/ChanApplication.java
  12. 6
      Clover/app/src/main/java/org/floens/chan/chan/ImageSearch.java
  13. 17
      Clover/app/src/main/java/org/floens/chan/controller/ControllerLogic.java
  14. 17
      Clover/app/src/main/java/org/floens/chan/controller/FadeInTransition.java
  15. 17
      Clover/app/src/main/java/org/floens/chan/controller/FadeOutTransition.java
  16. 9
      Clover/app/src/main/java/org/floens/chan/core/cache/FileCache.java
  17. 62
      Clover/app/src/main/java/org/floens/chan/core/loader/ChanLoader.java
  18. 12
      Clover/app/src/main/java/org/floens/chan/core/loader/ChanParser.java
  19. 37
      Clover/app/src/main/java/org/floens/chan/core/loader/EndOfLineException.java
  20. 10
      Clover/app/src/main/java/org/floens/chan/core/manager/BoardManager.java
  21. 604
      Clover/app/src/main/java/org/floens/chan/core/manager/ThreadManager.java
  22. 26
      Clover/app/src/main/java/org/floens/chan/core/manager/WatchManager.java
  23. 40
      Clover/app/src/main/java/org/floens/chan/core/model/Loadable.java
  24. 6
      Clover/app/src/main/java/org/floens/chan/core/model/Post.java
  25. 17
      Clover/app/src/main/java/org/floens/chan/core/model/PostImage.java
  26. 47
      Clover/app/src/main/java/org/floens/chan/core/net/ByteArrayRequest.java
  27. 50
      Clover/app/src/main/java/org/floens/chan/core/net/ChanReaderRequest.java
  28. 58
      Clover/app/src/main/java/org/floens/chan/core/net/FileRequest.java
  29. 11
      Clover/app/src/main/java/org/floens/chan/core/net/JsonReaderRequest.java
  30. 17
      Clover/app/src/main/java/org/floens/chan/core/presenter/ImageViewerPresenter.java
  31. 31
      Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java
  32. 8
      Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java
  33. 17
      Clover/app/src/main/java/org/floens/chan/core/reply/HttpCall.java
  34. 17
      Clover/app/src/main/java/org/floens/chan/core/reply/ReplyHttpCall.java
  35. 189
      Clover/app/src/main/java/org/floens/chan/core/reply/ReplyManager.java
  36. 17
      Clover/app/src/main/java/org/floens/chan/core/settings/BooleanSetting.java
  37. 201
      Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java
  38. 17
      Clover/app/src/main/java/org/floens/chan/core/settings/Setting.java
  39. 17
      Clover/app/src/main/java/org/floens/chan/core/settings/StringSetting.java
  40. 6
      Clover/app/src/main/java/org/floens/chan/core/watch/PinWatcher.java
  41. 6
      Clover/app/src/main/java/org/floens/chan/test/TestActivity.java
  42. 48
      Clover/app/src/main/java/org/floens/chan/ui/ThemeActivity.java
  43. 41
      Clover/app/src/main/java/org/floens/chan/ui/activity/AboutActivity.java
  44. 134
      Clover/app/src/main/java/org/floens/chan/ui/activity/AdvancedSettingsActivity.java
  45. 384
      Clover/app/src/main/java/org/floens/chan/ui/activity/BaseActivity.java
  46. 451
      Clover/app/src/main/java/org/floens/chan/ui/activity/BoardEditor.java
  47. 759
      Clover/app/src/main/java/org/floens/chan/ui/activity/ChanActivity.java
  48. 109
      Clover/app/src/main/java/org/floens/chan/ui/activity/DeveloperActivity.java
  49. 4
      Clover/app/src/main/java/org/floens/chan/ui/activity/ImagePickActivity.java
  50. 271
      Clover/app/src/main/java/org/floens/chan/ui/activity/ImageViewActivity.java
  51. 38
      Clover/app/src/main/java/org/floens/chan/ui/activity/LicenseActivity.java
  52. 188
      Clover/app/src/main/java/org/floens/chan/ui/activity/PassSettingsActivity.java
  53. 81
      Clover/app/src/main/java/org/floens/chan/ui/activity/ReplyActivity.java
  54. 90
      Clover/app/src/main/java/org/floens/chan/ui/activity/SettingsActivity.java
  55. 25
      Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java
  56. 198
      Clover/app/src/main/java/org/floens/chan/ui/activity/WatchSettingsActivity.java
  57. 67
      Clover/app/src/main/java/org/floens/chan/ui/adapter/ImageViewAdapter.java
  58. 17
      Clover/app/src/main/java/org/floens/chan/ui/adapter/ImageViewerAdapter.java
  59. 36
      Clover/app/src/main/java/org/floens/chan/ui/adapter/PinAdapter.java
  60. 231
      Clover/app/src/main/java/org/floens/chan/ui/adapter/PinnedAdapter.java
  61. 140
      Clover/app/src/main/java/org/floens/chan/ui/animation/ScrollerRunnable.java
  62. 409
      Clover/app/src/main/java/org/floens/chan/ui/animation/SwipeDismissListViewTouchListener.java
  63. 55
      Clover/app/src/main/java/org/floens/chan/ui/animation/ViewFlipperAnimations.java
  64. 36
      Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java
  65. 19
      Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java
  66. 24
      Clover/app/src/main/java/org/floens/chan/ui/controller/AdvancedSettingsController.java
  67. 23
      Clover/app/src/main/java/org/floens/chan/ui/controller/BoardEditController.java
  68. 12
      Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java
  69. 27
      Clover/app/src/main/java/org/floens/chan/ui/controller/DeveloperSettingsController.java
  70. 25
      Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java
  71. 17
      Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerNavigationController.java
  72. 17
      Clover/app/src/main/java/org/floens/chan/ui/controller/LicensesController.java
  73. 21
      Clover/app/src/main/java/org/floens/chan/ui/controller/PassSettingsController.java
  74. 35
      Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java
  75. 6
      Clover/app/src/main/java/org/floens/chan/ui/controller/RootNavigationController.java
  76. 21
      Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java
  77. 10
      Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java
  78. 17
      Clover/app/src/main/java/org/floens/chan/ui/controller/WatchSettingsController.java
  79. 17
      Clover/app/src/main/java/org/floens/chan/ui/drawable/DropdownArrowDrawable.java
  80. 18
      Clover/app/src/main/java/org/floens/chan/ui/drawable/ThumbDrawable.java
  81. 6
      Clover/app/src/main/java/org/floens/chan/ui/fragment/FolderPickFragment.java
  82. 354
      Clover/app/src/main/java/org/floens/chan/ui/fragment/ImageViewFragment.java
  83. 175
      Clover/app/src/main/java/org/floens/chan/ui/fragment/PostRepliesFragment.java
  84. 609
      Clover/app/src/main/java/org/floens/chan/ui/fragment/ReplyFragment.java
  85. 201
      Clover/app/src/main/java/org/floens/chan/ui/fragment/SettingsFragment.java
  86. 648
      Clover/app/src/main/java/org/floens/chan/ui/fragment/ThreadFragment.java
  87. 17
      Clover/app/src/main/java/org/floens/chan/ui/helper/PostHelper.java
  88. 17
      Clover/app/src/main/java/org/floens/chan/ui/helper/SwipeItemAnimator.java
  89. 19
      Clover/app/src/main/java/org/floens/chan/ui/helper/SwipeListener.java
  90. 19
      Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java
  91. 6
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java
  92. 17
      Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java
  93. 17
      Clover/app/src/main/java/org/floens/chan/ui/settings/BooleanSettingView.java
  94. 17
      Clover/app/src/main/java/org/floens/chan/ui/settings/LinkSettingView.java
  95. 17
      Clover/app/src/main/java/org/floens/chan/ui/settings/ListSettingView.java
  96. 17
      Clover/app/src/main/java/org/floens/chan/ui/settings/SettingView.java
  97. 17
      Clover/app/src/main/java/org/floens/chan/ui/settings/SettingsController.java
  98. 17
      Clover/app/src/main/java/org/floens/chan/ui/settings/SettingsGroup.java
  99. 17
      Clover/app/src/main/java/org/floens/chan/ui/settings/StringSettingView.java
  100. 2
      Clover/app/src/main/java/org/floens/chan/ui/toolbar/NavigationItem.java
  101. Some files were not shown because too many files have changed in this diff Show More

@ -24,26 +24,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<uses-permission android:name="android.permission.NFC" />
<application
android:name="org.floens.chan.ChanApplication"
android:name=".ChanApplication"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
android:theme="@style/Chan.Theme">
<activity
android:name=".ui.activity.BoardActivity"
android:configChanges="keyboardHidden|orientation|screenSize">
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".test.TestActivity">
</activity>
<activity
android:name=".ui.activity.ChanActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
@ -76,48 +72,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:scheme="https" />
</intent-filter>
</activity>
<activity
android:name="org.floens.chan.ui.activity.ReplyActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="adjustResize" />
<activity
android:name="org.floens.chan.ui.activity.SettingsActivity"
android:label="@string/action_settings" />
<activity
android:name="org.floens.chan.ui.activity.WatchSettingsActivity"
android:label="@string/preference_watch_settings" />
<activity
android:name="org.floens.chan.ui.activity.PassSettingsActivity"
android:label="@string/preference_pass_settings" />
<activity
android:name="org.floens.chan.ui.activity.AboutActivity"
android:label="@string/preference_about" />
<activity
android:name="org.floens.chan.ui.activity.LicenseActivity"
android:label="@string/preference_about"
android:parentActivityName=".ui.activity.ChanActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.floens.chan.ui.activity.BoardActivity" />
</activity>
<activity
android:name="org.floens.chan.ui.activity.BoardEditor"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/board_edit" />
<activity
android:name="org.floens.chan.ui.activity.ImageViewActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:launchMode="singleTop"
android:theme="@style/Chan.ImageView" />
<activity android:name="org.floens.chan.ui.activity.ImagePickActivity" />
<activity android:name="org.floens.chan.ui.activity.DeveloperActivity" />
<activity
android:name="org.floens.chan.ui.activity.AdvancedSettingsActivity"
android:label="@string/settings_advanced_label" />
<activity android:name=".test.TestActivity" />
<activity android:name=".ui.activity.ImagePickActivity" />
<service
android:name=".ui.service.WatchNotifier"
android:exported="false" />
</application>
</manifest>

@ -1,469 +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) {
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) {
// Log.d("mobeta", "on fling remove called");
if (mRemoveEnabled && mIsRemoving) {
int w = mDslv.getWidth();
int minPos = w / 5;
if (velocityX > mFlingSpeed) {
if (mPositionX > -minPos) {
mDslv.stopDragWithVelocity(true, velocityX);
}
} else if (velocityX < -mFlingSpeed) {
if (mPositionX < minPos) {
mDslv.stopDragWithVelocity(true, velocityX);
}
}
mIsRemoving = false;
}
return false;
}
};
}

@ -1,239 +0,0 @@
package com.mobeta.android.dslv;
import java.util.ArrayList;
import android.content.Context;
import android.database.Cursor;
import android.support.v4.widget.CursorAdapter;
import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListAdapter;
/**
* 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 final SparseIntArray mListMapping = new SparseIntArray();
private final ArrayList<Integer> mRemovedCursorPositions = new ArrayList<Integer>();
@SuppressWarnings("deprecation")
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,92 +0,0 @@
package com.mobeta.android.dslv;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
/**
* 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.MATCH_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,46 +0,0 @@
package com.mobeta.android.dslv;
import android.content.Context;
import android.view.View;
import android.widget.Checkable;
/**
* 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,155 +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.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
// 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,448 +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.ImageView;
import android.widget.TextView;
// 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 setFilter 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 setFilter 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,87 +0,0 @@
package com.mobeta.android.dslv;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Point;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ListView;
/**
* 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;
}
}

@ -0,0 +1,220 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.StrictMode;
import android.view.ViewConfiguration;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.RefWatcher;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.cache.FileCache;
import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.manager.WatchManager;
import org.floens.chan.core.net.BitmapLruImageCache;
import org.floens.chan.core.reply.ReplyManager;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.database.DatabaseManager;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.Logger;
import java.io.File;
import java.lang.reflect.Field;
import java.util.Locale;
import de.greenrobot.event.EventBus;
public class Chan extends Application {
private static final String TAG = "ChanApplication";
private static final long FILE_CACHE_DISK_SIZE = 50 * 1024 * 1024;
private static final String FILE_CACHE_NAME = "filecache";
private static final int VOLLEY_LRU_CACHE_SIZE = 8 * 1024 * 1024;
private static final int VOLLEY_CACHE_SIZE = 10 * 1024 * 1024;
public static Context con;
private static Chan instance;
private static RequestQueue volleyRequestQueue;
private static com.android.volley.toolbox.ImageLoader imageLoader;
private static BoardManager boardManager;
private static WatchManager watchManager;
private static ReplyManager replyManager;
private static DatabaseManager databaseManager;
private static FileCache fileCache;
private static RefWatcher refWatcher;
private String userAgent;
private int activityForegroundCounter = 0;
public Chan() {
instance = this;
con = this;
}
public static Chan getInstance() {
return instance;
}
public static RequestQueue getVolleyRequestQueue() {
return volleyRequestQueue;
}
public static ImageLoader getVolleyImageLoader() {
return imageLoader;
}
public static BoardManager getBoardManager() {
return boardManager;
}
public static WatchManager getWatchManager() {
return watchManager;
}
public static ReplyManager getReplyManager() {
return replyManager;
}
public static DatabaseManager getDatabaseManager() {
return databaseManager;
}
public static FileCache getFileCache() {
return fileCache;
}
public static RefWatcher getRefWatcher() {
return refWatcher;
}
@Override
public void onCreate() {
super.onCreate();
// Force the overflow button to show, even on devices that have a
// physical button.
try {
ViewConfiguration config = ViewConfiguration.get(this);
Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
if (menuKeyField != null) {
menuKeyField.setAccessible(true);
menuKeyField.setBoolean(config, false);
}
} catch (Exception e) {
}
if (ChanBuild.DEVELOPER_MODE) {
refWatcher = LeakCanary.install(this);
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
}
AndroidUtils.init();
ChanUrls.loadScheme(ChanSettings.networkHttps.get());
// User agent is <appname>/<version>
String version = "";
try {
version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
version = version.toLowerCase(Locale.ENGLISH).replace(" ", "_");
userAgent = getString(R.string.app_name) + "/" + version;
cleanupOutdated();
File cacheDir = getExternalCacheDir() != null ? getExternalCacheDir() : getCacheDir();
replyManager = new ReplyManager(this);
volleyRequestQueue = Volley.newRequestQueue(this, getUserAgent(), null, new File(cacheDir, Volley.DEFAULT_CACHE_DIR), VOLLEY_CACHE_SIZE);
imageLoader = new ImageLoader(volleyRequestQueue, new BitmapLruImageCache(VOLLEY_LRU_CACHE_SIZE));
fileCache = new FileCache(new File(cacheDir, FILE_CACHE_NAME), FILE_CACHE_DISK_SIZE, getUserAgent());
databaseManager = new DatabaseManager(this);
boardManager = new BoardManager();
watchManager = new WatchManager(this);
}
public String getUserAgent() {
return userAgent;
}
public void activityEnteredForeground() {
boolean lastForeground = getApplicationInForeground();
activityForegroundCounter++;
if (getApplicationInForeground() != lastForeground) {
EventBus.getDefault().post(new ForegroundChangedMessage(getApplicationInForeground()));
}
}
public void activityEnteredBackground() {
boolean lastForeground = getApplicationInForeground();
activityForegroundCounter--;
if (activityForegroundCounter < 0) {
Logger.wtf(TAG, "activityForegroundCounter below 0");
}
if (getApplicationInForeground() != lastForeground) {
EventBus.getDefault().post(new ForegroundChangedMessage(getApplicationInForeground()));
}
}
public boolean getApplicationInForeground() {
return activityForegroundCounter > 0;
}
private void cleanupOutdated() {
File ionCacheFolder = new File(getCacheDir() + "/ion");
if (ionCacheFolder.exists() && ionCacheFolder.isDirectory()) {
Logger.i(TAG, "Clearing old ion folder");
for (File file : ionCacheFolder.listFiles()) {
if (!file.delete()) {
Logger.i(TAG, "Could not delete old ion file " + file.getName());
}
}
if (!ionCacheFolder.delete()) {
Logger.i(TAG, "Could not delete old ion folder");
} else {
Logger.i(TAG, "Deleted old ion folder");
}
}
}
public static class ForegroundChangedMessage {
public boolean inForeground;
public ForegroundChangedMessage(boolean inForeground) {
this.inForeground = inForeground;
}
}
}

@ -17,207 +17,5 @@
*/
package org.floens.chan;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.StrictMode;
import android.view.ViewConfiguration;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.RefWatcher;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.reply.ReplyManager;
import org.floens.chan.core.manager.WatchManager;
import org.floens.chan.core.net.BitmapLruImageCache;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.database.DatabaseManager;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.FileCache;
import org.floens.chan.utils.IconCache;
import org.floens.chan.utils.Logger;
import java.io.File;
import java.lang.reflect.Field;
import java.util.Locale;
import de.greenrobot.event.EventBus;
public class ChanApplication extends Application {
private static final String TAG = "ChanApplication";
private static final long FILE_CACHE_DISK_SIZE = 50 * 1024 * 1024;
private static final String FILE_CACHE_NAME = "filecache";
private static final int VOLLEY_LRU_CACHE_SIZE = 8 * 1024 * 1024;
private static final int VOLLEY_CACHE_SIZE = 10 * 1024 * 1024;
public static Context con;
private static ChanApplication instance;
private static RequestQueue volleyRequestQueue;
private static com.android.volley.toolbox.ImageLoader imageLoader;
private static BoardManager boardManager;
private static WatchManager watchManager;
private static ReplyManager replyManager;
private static DatabaseManager databaseManager;
private static FileCache fileCache;
private static RefWatcher refWatcher;
private String userAgent;
private int activityForegroundCounter = 0;
public ChanApplication() {
instance = this;
con = this;
}
public static ChanApplication getInstance() {
return instance;
}
public static RequestQueue getVolleyRequestQueue() {
return volleyRequestQueue;
}
public static ImageLoader getVolleyImageLoader() {
return imageLoader;
}
public static BoardManager getBoardManager() {
return boardManager;
}
public static WatchManager getWatchManager() {
return watchManager;
}
public static ReplyManager getReplyManager() {
return replyManager;
}
public static DatabaseManager getDatabaseManager() {
return databaseManager;
}
public static FileCache getFileCache() {
return fileCache;
}
public static RefWatcher getRefWatcher() {
return refWatcher;
}
@Override
public void onCreate() {
super.onCreate();
// Force the overflow button to show, even on devices that have a
// physical button.
try {
ViewConfiguration config = ViewConfiguration.get(this);
Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
if (menuKeyField != null) {
menuKeyField.setAccessible(true);
menuKeyField.setBoolean(config, false);
}
} catch (Exception e) {
}
if (ChanBuild.DEVELOPER_MODE) {
refWatcher = LeakCanary.install(this);
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
}
AndroidUtils.init();
ChanUrls.loadScheme(ChanSettings.getNetworkHttps());
// User agent is <appname>/<version>
String version = "";
try {
version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
version = version.toLowerCase(Locale.ENGLISH).replace(" ", "_");
userAgent = getString(R.string.app_name) + "/" + version;
IconCache.createIcons(this);
cleanupOutdated();
File cacheDir = getExternalCacheDir() != null ? getExternalCacheDir() : getCacheDir();
replyManager = new ReplyManager(this);
volleyRequestQueue = Volley.newRequestQueue(this, getUserAgent(), null, new File(cacheDir, Volley.DEFAULT_CACHE_DIR), VOLLEY_CACHE_SIZE);
imageLoader = new ImageLoader(volleyRequestQueue, new BitmapLruImageCache(VOLLEY_LRU_CACHE_SIZE));
fileCache = new FileCache(new File(cacheDir, FILE_CACHE_NAME), FILE_CACHE_DISK_SIZE, getUserAgent());
databaseManager = new DatabaseManager(this);
boardManager = new BoardManager();
watchManager = new WatchManager(this);
}
public String getUserAgent() {
return userAgent;
}
public void activityEnteredForeground() {
boolean lastForeground = getApplicationInForeground();
activityForegroundCounter++;
if (getApplicationInForeground() != lastForeground) {
EventBus.getDefault().post(new ForegroundChangedMessage(getApplicationInForeground()));
}
}
public void activityEnteredBackground() {
boolean lastForeground = getApplicationInForeground();
activityForegroundCounter--;
if (activityForegroundCounter < 0) {
Logger.wtf(TAG, "activityForegroundCounter below 0");
}
if (getApplicationInForeground() != lastForeground) {
EventBus.getDefault().post(new ForegroundChangedMessage(getApplicationInForeground()));
}
}
public boolean getApplicationInForeground() {
return activityForegroundCounter > 0;
}
private void cleanupOutdated() {
File ionCacheFolder = new File(getCacheDir() + "/ion");
if (ionCacheFolder.exists() && ionCacheFolder.isDirectory()) {
Logger.i(TAG, "Clearing old ion folder");
for (File file : ionCacheFolder.listFiles()) {
if (!file.delete()) {
Logger.i(TAG, "Could not delete old ion file " + file.getName());
}
}
if (!ionCacheFolder.delete()) {
Logger.i(TAG, "Could not delete old ion folder");
} else {
Logger.i(TAG, "Deleted old ion folder");
}
}
}
public static class ForegroundChangedMessage {
public boolean inForeground;
public ForegroundChangedMessage(boolean inForeground) {
this.inForeground = inForeground;
}
}
public class ChanApplication extends Chan {
}

@ -17,8 +17,6 @@
*/
package org.floens.chan.chan;
import org.floens.chan.R;
import java.util.ArrayList;
import java.util.List;
@ -34,7 +32,7 @@ public abstract class ImageSearch {
static {
engines.add(new ImageSearch() {
public int getId() {
return R.id.action_0;
return 0;
}
public String getName() {
@ -48,7 +46,7 @@ public abstract class ImageSearch {
engines.add(new ImageSearch() {
public int getId() {
return R.id.action_1;
return 1;
}
public String getName() {

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.controller;
import android.view.ViewGroup;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.controller;
import android.animation.Animator;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.controller;
import android.animation.Animator;

@ -15,10 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.utils;
import android.os.Handler;
import android.os.Looper;
package org.floens.chan.core.cache;
import com.squareup.okhttp.Call;
import com.squareup.okhttp.OkHttpClient;
@ -28,6 +25,10 @@ import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;
import com.squareup.okhttp.internal.Util;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.Logger;
import org.floens.chan.utils.Time;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.File;

@ -18,13 +18,11 @@
package org.floens.chan.core.loader;
import android.text.TextUtils;
import android.util.SparseArray;
import com.android.volley.Response;
import com.android.volley.ServerError;
import com.android.volley.VolleyError;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.core.model.ChanThread;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Post;
@ -48,7 +46,6 @@ public class ChanLoader {
private final List<ChanLoaderCallback> listeners = new ArrayList<>();
private final Loadable loadable;
private final SparseArray<Post> postsById = new SparseArray<>();
private ChanThread thread;
private boolean destroyed = false;
@ -62,6 +59,10 @@ public class ChanLoader {
public ChanLoader(Loadable loadable) {
this.loadable = loadable;
if (loadable.mode == Loadable.Mode.BOARD) {
loadable.mode = Loadable.Mode.CATALOG;
}
}
/**
@ -129,7 +130,7 @@ public class ChanLoader {
request.cancel();
}
if (loadable.isBoardMode() || loadable.isCatalogMode()) {
if (loadable.isCatalogMode()) {
loadable.no = 0;
loadable.listViewIndex = 0;
loadable.listViewTop = 0;
@ -147,16 +148,7 @@ public class ChanLoader {
public void requestMoreData() {
clearTimer();
if (loadable.isBoardMode()) {
if (request != null) {
// finish the last board load first
return;
}
loadable.no++;
request = getData();
} else if (loadable.isThreadMode()) {
if (loadable.isThreadMode()) {
if (request != null) {
return;
}
@ -180,18 +172,12 @@ public class ChanLoader {
return request != null;
}
public Post findPostById(int id) {
return postsById.get(id);
}
public Loadable getLoadable() {
return loadable;
}
/**
* Get the time in milliseconds until another loadMore is recommended
*
* @return
*/
public long getTimeUntilLoadMore() {
if (request != null) {
@ -272,7 +258,7 @@ public class ChanLoader {
}
);
ChanApplication.getVolleyRequestQueue().add(request);
Chan.getVolleyRequestQueue().add(request);
return request;
}
@ -285,31 +271,8 @@ public class ChanLoader {
thread = new ChanThread(loadable, new ArrayList<Post>());
}
if (loadable.isThreadMode() || loadable.isCatalogMode()) {
thread.posts.clear();
thread.posts.addAll(result);
postsById.clear();
for (Post post : result) {
postsById.append(post.no, post);
}
} else if (loadable.isBoardMode()) {
// Only add new posts
boolean flag;
for (Post post : result) {
flag = true;
for (Post cached : thread.posts) {
if (post.no == cached.no) {
flag = false;
break;
}
}
if (flag) {
thread.posts.add(post);
postsById.append(post.no, post);
}
}
}
thread.posts.clear();
thread.posts.addAll(result);
if (loadable.isThreadMode() && thread.posts.size() > 0) {
thread.op = thread.posts.get(0);
@ -346,11 +309,6 @@ public class ChanLoader {
Logger.e(TAG, "Loading error");
// 404 with more pages already loaded means endofline
if ((error instanceof ServerError) && loadable.isBoardMode() && loadable.no > 0) {
error = new EndOfLineException();
}
clearTimer();
for (ChanLoaderCallback l : listeners) {

@ -30,11 +30,11 @@ import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.PostLinkable;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.ThemeHelper;
import org.jsoup.Jsoup;
@ -90,11 +90,11 @@ public class ChanParser {
}
private void parseSpans(Post post) {
boolean anonymize = ChanSettings.getAnonymize();
boolean anonymizeIds = ChanSettings.getAnonymizeIds();
boolean anonymize = ChanSettings.anonymize.get();
boolean anonymizeIds = ChanSettings.anonymizeIds.get();
if (anonymize) {
post.name = ChanApplication.getInstance().getString(R.string.default_name);
post.name = "Anonymous";
post.tripcode = "";
}
@ -376,7 +376,7 @@ public class ChanParser {
// Append You when it's a reply to an saved reply
// todo synchronized
if (ChanApplication.getDatabaseManager().isSavedReply(post.board, id)) {
if (Chan.getDatabaseManager().isSavedReply(post.board, id)) {
key += " (You)";
}
}

@ -1,37 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.loader;
import com.android.volley.NetworkResponse;
import com.android.volley.VolleyError;
@SuppressWarnings("serial")
public class EndOfLineException extends VolleyError {
public EndOfLineException(NetworkResponse networkResponse) {
super(networkResponse);
}
public EndOfLineException() {
super();
}
@Override
public String getMessage() {
return "End of the line";
}
}

@ -20,7 +20,7 @@ package org.floens.chan.core.manager;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.model.Board;
import org.floens.chan.core.net.BoardsRequest;
@ -84,7 +84,7 @@ public class BoardManager {
}
public void updateSavedBoards() {
ChanApplication.getDatabaseManager().updateBoards(allBoards);
Chan.getDatabaseManager().updateBoards(allBoards);
notifyChanged();
}
@ -103,12 +103,12 @@ public class BoardManager {
private void storeBoards() {
updateByValueMap();
ChanApplication.getDatabaseManager().setBoards(allBoards);
Chan.getDatabaseManager().setBoards(allBoards);
notifyChanged();
}
private void loadBoards() {
allBoards = ChanApplication.getDatabaseManager().getBoards();
allBoards = Chan.getDatabaseManager().getBoards();
if (allBoards.size() == 0) {
Logger.d(TAG, "Loading default boards");
allBoards = getDefaultBoards();
@ -149,7 +149,7 @@ public class BoardManager {
}
private void loadFromServer() {
ChanApplication.getVolleyRequestQueue().add(
Chan.getVolleyRequestQueue().add(
new BoardsRequest(ChanUrls.getBoardsUrl(), new Response.Listener<List<Board>>() {
@Override
public void onResponse(List<Board> data) {

@ -1,604 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.manager;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.FragmentTransaction;
import android.app.ProgressDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.Toast;
import com.android.volley.VolleyError;
import org.floens.chan.ChanApplication;
import org.floens.chan.R;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.loader.ChanLoader;
import org.floens.chan.core.loader.LoaderPool;
import org.floens.chan.core.reply.ReplyManager.DeleteListener;
import org.floens.chan.core.reply.ReplyManager.DeleteResponse;
import org.floens.chan.core.model.ChanThread;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Pin;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.PostLinkable;
import org.floens.chan.core.model.SavedReply;
import org.floens.chan.ui.activity.ReplyActivity;
import org.floens.chan.ui.fragment.PostRepliesFragment;
import org.floens.chan.ui.fragment.ReplyFragment;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.Logger;
import java.util.ArrayList;
import java.util.List;
import static org.floens.chan.utils.AndroidUtils.dp;
/**
* All PostView's need to have this referenced. This manages some things like
* pages, starting and stopping of loading, handling linkables, replies popups
* etc. onDestroy, onStart and onStop must be called from the activity/fragment
*/
public class ThreadManager implements ChanLoader.ChanLoaderCallback {
public static enum ViewMode {
LIST, GRID
}
private static final String TAG = "ThreadManager";
private final Activity activity;
private final ThreadManagerListener threadManagerListener;
private final List<RepliesPopup> popupQueue = new ArrayList<>();
private PostRepliesFragment currentPopupFragment;
private int highlightedPost = -1;
private int lastPost = -1;
private String highlightedId = null;
private ChanLoader chanLoader;
public ThreadManager(Activity activity, final ThreadManagerListener listener) {
this.activity = activity;
threadManagerListener = listener;
}
public void onDestroy() {
unbindLoader();
}
public void onStart() {
if (chanLoader != null) {
if (isWatching()) {
chanLoader.setAutoLoadMore(true);
chanLoader.requestMoreDataAndResetTimer();
}
}
}
public void onStop() {
if (chanLoader != null) {
chanLoader.setAutoLoadMore(false);
}
}
public void bindLoader(Loadable loadable) {
if (chanLoader != null) {
unbindLoader();
}
chanLoader = LoaderPool.getInstance().obtain(loadable, this);
if (isWatching()) {
chanLoader.setAutoLoadMore(true);
}
}
public void unbindLoader() {
if (chanLoader != null) {
chanLoader.setAutoLoadMore(false);
LoaderPool.getInstance().release(chanLoader, this);
chanLoader = null;
} else {
Logger.e(TAG, "Loader already unbinded");
}
highlightedPost = -1;
lastPost = -1;
highlightedId = null;
}
public void bottomPostViewed() {
if (chanLoader.getLoadable().isThreadMode() && chanLoader.getThread() != null && chanLoader.getThread().posts.size() > 0) {
chanLoader.getLoadable().lastViewed = chanLoader.getThread().posts.get(chanLoader.getThread().posts.size() - 1).no;
}
Pin pin = ChanApplication.getWatchManager().findPinByLoadable(chanLoader.getLoadable());
if (pin != null) {
pin.onBottomPostViewed();
ChanApplication.getWatchManager().onPinsChanged();
}
}
public boolean isWatching() {
if (!chanLoader.getLoadable().isThreadMode()) {
return false;
} else if (!ChanSettings.getThreadAutoRefresh()) {
return false;
} else if (chanLoader.getThread() != null && chanLoader.getThread().closed) {
return false;
} else {
return true;
}
}
public void requestData() {
if (chanLoader != null) {
chanLoader.requestData();
} else {
Logger.e(TAG, "Loader null in requestData");
}
}
/**
* Called by postadapter and threadwatchcounterview.onclick
*/
public void requestNextData() {
if (chanLoader != null) {
chanLoader.requestMoreData();
} else {
Logger.e(TAG, "Loader null in requestData");
}
}
@Override
public void onChanLoaderError(VolleyError error) {
threadManagerListener.onThreadLoadError(error);
}
@Override
public void onChanLoaderData(ChanThread thread) {
if (!isWatching()) {
chanLoader.setAutoLoadMore(false);
}
if (thread.posts.size() > 0) {
lastPost = thread.posts.get(thread.posts.size() - 1).no;
}
threadManagerListener.onThreadLoaded(thread);
}
public boolean hasLoader() {
return chanLoader != null;
}
public Post findPostById(int id) {
if (chanLoader == null)
return null;
return chanLoader.findPostById(id);
}
public Loadable getLoadable() {
if (chanLoader == null)
return null;
return chanLoader.getLoadable();
}
public ChanLoader getChanLoader() {
return chanLoader;
}
public void onThumbnailClicked(Post post) {
threadManagerListener.onThumbnailClicked(post);
}
public void onPostClicked(Post post) {
if (chanLoader != null) {
threadManagerListener.onPostClicked(post);
}
}
public void showPostOptions(final Post post, PopupMenu popupMenu) {
Menu menu = popupMenu.getMenu();
if (chanLoader.getLoadable().isBoardMode() || chanLoader.getLoadable().isCatalogMode()) {
menu.add(Menu.NONE, 9, Menu.NONE, activity.getString(R.string.action_pin));
}
// if (chanLoader.getLoadable().isThreadMode()) {
// menu.add(Menu.NONE, 10, Menu.NONE, activity.getString(R.string.post_quick_reply));
// }
String[] baseOptions = activity.getResources().getStringArray(R.array.post_options);
for (int i = 0; i < baseOptions.length; i++) {
menu.add(Menu.NONE, i, Menu.NONE, baseOptions[i]);
}
if (!TextUtils.isEmpty(post.id)) {
menu.add(Menu.NONE, 6, Menu.NONE, activity.getString(R.string.post_highlight_id));
}
// Only add the delete option when the post is a saved reply
if (ChanApplication.getDatabaseManager().isSavedReply(post.board, post.no)) {
menu.add(Menu.NONE, 7, Menu.NONE, activity.getString(R.string.delete));
}
if (ChanSettings.getDeveloper()) {
menu.add(Menu.NONE, 8, Menu.NONE, "Make this a saved reply");
}
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(final MenuItem item) {
switch (item.getItemId()) {
case 10: // Quick reply
openReply(false);
// Pass through
case 0: // Quote
// ChanApplication.getReplyManager().quote(post.no);
break;
case 1: // Quote inline
// ChanApplication.getReplyManager().quoteInline(post.no, post.comment.toString());
break;
case 2: // Info
showPostInfo(post);
break;
case 3: // Show clickables
showPostLinkables(post);
break;
case 4: // Copy text
copyToClipboard(post.comment.toString());
break;
case 5: // Report
AndroidUtils.openWebView(activity, "Report /" + post.board + "/" + post.no, ChanUrls.getReportUrl(post.board, post.no));
break;
case 6: // Id
highlightedId = post.id;
threadManagerListener.onRefreshView();
break;
case 7: // Delete
deletePost(post);
break;
case 8: // Save reply
ChanApplication.getDatabaseManager().saveReply(new SavedReply(post.board, post.no, "foo"));
break;
case 9: // Pin
ChanApplication.getWatchManager().addPin(post);
break;
}
return false;
}
});
}
public void openReply(boolean startInActivity) {
if (chanLoader == null)
return;
if (startInActivity) {
ReplyActivity.setLoadable(chanLoader.getLoadable());
Intent i = new Intent(activity, ReplyActivity.class);
activity.startActivity(i);
} else {
ReplyFragment reply = ReplyFragment.newInstance(chanLoader.getLoadable(), true);
reply.show(activity.getFragmentManager(), "replyDialog");
}
}
public void onPostLinkableClicked(PostLinkable linkable) {
handleLinkableSelected(linkable);
}
public void scrollToPost(int post) {
threadManagerListener.onScrollTo(post);
}
public void highlightPost(int post) {
highlightedPost = post;
}
public boolean isPostHightlighted(Post post) {
return (highlightedPost >= 0 && post.no == highlightedPost) || (highlightedId != null && post.id.equals(highlightedId));
}
public boolean isPostLastSeen(Post post) {
return post.no == chanLoader.getLoadable().lastViewed && post.no != lastPost;
}
private void copyToClipboard(String comment) {
ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("Post text", comment);
clipboard.setPrimaryClip(clip);
Toast.makeText(activity, R.string.post_text_copied_to_clipboard, Toast.LENGTH_SHORT).show();
}
private void showPostInfo(Post post) {
String text = "";
if (post.hasImage) {
text += "File: " + post.filename + "." + post.ext + " \nDimensions: " + post.imageWidth + "x"
+ post.imageHeight + "\nSize: " + AndroidUtils.getReadableFileSize(post.fileSize, false) + "\n\n";
}
text += "Time: " + post.date;
if (!TextUtils.isEmpty(post.id)) {
text += "\nId: " + post.id;
}
if (!TextUtils.isEmpty(post.tripcode)) {
text += "\nTripcode: " + post.tripcode;
}
if (!TextUtils.isEmpty(post.countryName)) {
text += "\nCountry: " + post.countryName;
}
if (!TextUtils.isEmpty(post.capcode)) {
text += "\nCapcode: " + post.capcode;
}
AlertDialog dialog = new AlertDialog.Builder(activity).setTitle(R.string.post_info).setMessage(text)
.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
}).create();
dialog.show();
}
/**
* Show a list of things that can be clicked in a list to the user.
*
* @param post The post that was clicked.
*/
public void showPostLinkables(Post post) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
final ArrayList<PostLinkable> linkables = post.linkables;
if (linkables.size() > 0) {
String[] keys = new String[linkables.size()];
for (int i = 0; i < linkables.size(); i++) {
keys[i] = linkables.get(i).key;
}
builder.setItems(keys, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
handleLinkableSelected(linkables.get(which));
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
}
public void showPostReplies(Post post) {
RepliesPopup l = new RepliesPopup();
List<Post> p = new ArrayList<>();
for (int no : post.repliesFrom) {
Post r = findPostById(no);
if (r != null) {
p.add(r);
}
}
l.posts = p;
l.forNo = post.no;
if (p.size() > 0) {
showPostsRepliesFragment(l);
}
}
public ThreadManager.ViewMode getViewMode() {
return threadManagerListener.getViewMode();
}
/**
* Handle when a linkable has been clicked.
*
* @param linkable the selected linkable.
*/
private void handleLinkableSelected(final PostLinkable linkable) {
if (linkable.type == PostLinkable.Type.QUOTE) {
Post post = findPostById((Integer) linkable.value);
if (post != null) {
RepliesPopup l = new RepliesPopup();
l.forNo = (Integer) linkable.value;
l.posts.add(post);
showPostsRepliesFragment(l);
}
} else if (linkable.type == PostLinkable.Type.LINK) {
if (ChanSettings.getOpenLinkConfirmation()) {
new AlertDialog.Builder(activity)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
AndroidUtils.openLink((String) linkable.value);
}
})
.setTitle(R.string.open_link_confirmation)
.setMessage((String) linkable.value)
.show();
} else {
AndroidUtils.openLink((String) linkable.value);
}
} else if (linkable.type == PostLinkable.Type.THREAD) {
final PostLinkable.ThreadLink link = (PostLinkable.ThreadLink) linkable.value;
final Loadable thread = new Loadable(link.board, link.threadId);
new AlertDialog.Builder(activity)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int which) {
threadManagerListener.onOpenThread(thread, link.postId);
}
})
.setTitle(R.string.open_thread_confirmation)
.setMessage("/" + thread.board + "/" + thread.no)
.show();
}
}
private void showPostsRepliesFragment(RepliesPopup repliesPopup) {
// Post popups are now queued up, more than 32 popups on top of each
// other makes the system crash!
popupQueue.add(repliesPopup);
if (currentPopupFragment != null) {
currentPopupFragment.dismissNoCallback();
}
// PostRepliesFragment popup = PostRepliesFragment.newInstance(repliesPopup, this);
PostRepliesFragment popup = null;
FragmentTransaction ft = activity.getFragmentManager().beginTransaction();
ft.add(popup, "postPopup");
ft.commitAllowingStateLoss();
currentPopupFragment = popup;
}
public void onPostRepliesPop() {
if (popupQueue.size() == 0)
return;
popupQueue.remove(popupQueue.size() - 1);
if (popupQueue.size() > 0) {
// PostRepliesFragment popup = PostRepliesFragment.newInstance(popupQueue.get(popupQueue.size() - 1), this);
PostRepliesFragment popup = null;
FragmentTransaction ft = activity.getFragmentManager().beginTransaction();
ft.add(popup, "postPopup");
ft.commit();
currentPopupFragment = popup;
} else {
currentPopupFragment = null;
}
}
public void closeAllPostFragments() {
popupQueue.clear();
currentPopupFragment = null;
}
public boolean arePostRepliesOpen() {
return popupQueue.size() > 0;
}
private void deletePost(final Post post) {
final CheckBox checkBox = new CheckBox(activity);
checkBox.setText(R.string.delete_image_only);
LinearLayout wrapper = new LinearLayout(activity);
wrapper.addView(checkBox);
int padding = dp(8f);
wrapper.setPadding(padding, padding, padding, padding);
new AlertDialog.Builder(activity).setTitle(R.string.delete_confirm).setView(wrapper)
.setPositiveButton(R.string.delete, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
doDeletePost(post, checkBox.isChecked());
}
}).setNegativeButton(R.string.cancel, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
}).show();
}
private void doDeletePost(Post post, boolean onlyImageDelete) {
SavedReply reply = ChanApplication.getDatabaseManager().getSavedReply(post.board, post.no);
if (reply == null) {
/*
* reply = new SavedReply(); reply.board = "g"; reply.no = 1234;
* reply.password = "boom";
*/
return;
}
final ProgressDialog dialog = ProgressDialog.show(activity, null, activity.getString(R.string.delete_wait));
ChanApplication.getReplyManager().postDelete(reply, onlyImageDelete, new DeleteListener() {
@Override
public void onResponse(DeleteResponse response) {
dialog.dismiss();
if (response.isNetworkError || response.isUserError) {
int resId;
if (response.isTooSoonError) {
resId = R.string.delete_too_soon;
} else if (response.isInvalidPassword) {
resId = R.string.delete_password_incorrect;
} else if (response.isTooOldError) {
resId = R.string.delete_too_old;
} else {
resId = R.string.delete_fail;
}
Toast.makeText(activity, resId, Toast.LENGTH_LONG).show();
} else if (response.isSuccessful) {
Toast.makeText(activity, R.string.delete_success, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(activity, R.string.delete_fail, Toast.LENGTH_LONG).show();
}
}
});
}
public interface ThreadManagerListener {
void onThreadLoaded(ChanThread thread);
void onThreadLoadError(VolleyError error);
void onPostClicked(Post post);
void onThumbnailClicked(Post post);
void onScrollTo(int post);
void onRefreshView();
void onOpenThread(Loadable thread, int highlightedPost);
ThreadManager.ViewMode getViewMode();
}
public static class RepliesPopup {
public List<Post> posts = new ArrayList<>();
public int listViewIndex;
public int listViewTop;
public int forNo = -1;
}
}

@ -20,7 +20,7 @@ package org.floens.chan.core.manager;
import android.content.Context;
import android.content.Intent;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Pin;
import org.floens.chan.core.model.Post;
@ -51,7 +51,7 @@ public class WatchManager {
public WatchManager(Context context) {
this.context = context;
pins = ChanApplication.getDatabaseManager().getPinned();
pins = Chan.getDatabaseManager().getPinned();
EventBus.getDefault().register(this);
@ -122,7 +122,7 @@ public class WatchManager {
}
pins.add(pin);
ChanApplication.getDatabaseManager().addPin(pin);
Chan.getDatabaseManager().addPin(pin);
onPinsChanged();
@ -161,7 +161,7 @@ public class WatchManager {
public void removePin(Pin pin) {
pins.remove(pin);
pin.destroyWatcher();
ChanApplication.getDatabaseManager().removePin(pin);
Chan.getDatabaseManager().removePin(pin);
onPinsChanged();
@ -174,7 +174,7 @@ public class WatchManager {
* @param pin
*/
public void updatePin(Pin pin) {
ChanApplication.getDatabaseManager().updatePin(pin);
Chan.getDatabaseManager().updatePin(pin);
onPinsChanged();
@ -185,15 +185,15 @@ public class WatchManager {
* Updates all the pins to the database.
*/
public void updateDatabase() {
ChanApplication.getDatabaseManager().updatePins(pins);
Chan.getDatabaseManager().updatePins(pins);
}
public void toggleWatch(Pin pin) {
pin.watching = !pin.watching;
EventBus.getDefault().post(new PinChangedMessage(pin));
ChanApplication.getWatchManager().onPinsChanged();
ChanApplication.getWatchManager().invokeLoadNow();
Chan.getWatchManager().onPinsChanged();
Chan.getWatchManager().invokeLoadNow();
}
public void pinWatcherUpdated(Pin pin) {
@ -231,7 +231,7 @@ public class WatchManager {
}
}
public void onEvent(ChanApplication.ForegroundChangedMessage message) {
public void onEvent(Chan.ForegroundChangedMessage message) {
updateNotificationServiceState();
updateTimerState(true);
}
@ -259,11 +259,11 @@ public class WatchManager {
}
public boolean getWatchBackgroundEnabled() {
return ChanSettings.getWatchBackgroundEnabled();
return ChanSettings.watchBackground.get();
}
private void updatePinWatchers() {
updatePinWatchers(ChanSettings.getWatchEnabled());
updatePinWatchers(ChanSettings.watchEnabled.get());
}
private void updatePinWatchers(boolean watchEnabled) {
@ -294,9 +294,9 @@ public class WatchManager {
}
private void updateTimerState(boolean watchEnabled, boolean backgroundEnabled, boolean invokeLoadNow) {
Logger.d(TAG, "updateTimerState watchEnabled=" + watchEnabled + " backgroundEnabled=" + backgroundEnabled + " invokeLoadNow=" + invokeLoadNow + " foreground=" + ChanApplication.getInstance().getApplicationInForeground());
Logger.d(TAG, "updateTimerState watchEnabled=" + watchEnabled + " backgroundEnabled=" + backgroundEnabled + " invokeLoadNow=" + invokeLoadNow + " foreground=" + Chan.getInstance().getApplicationInForeground());
if (watchEnabled) {
if (ChanApplication.getInstance().getApplicationInForeground()) {
if (Chan.getInstance().getApplicationInForeground()) {
setTimer(invokeLoadNow ? 1 : FOREGROUND_TIME);
} else {
if (backgroundEnabled) {

@ -57,22 +57,14 @@ public class Loadable {
public Loadable() {
}
/**
* Quick constructor for a board loadable.
*
* @param board
*/
public Loadable(String board) {
mode = Mode.BOARD;
mode = Mode.CATALOG;
this.board = board;
no = 0;
this.no = 0;
}
/**
* Quick constructor for a thread loadable.
*
* @param board
* @param no
*/
public Loadable(String board, int no) {
mode = Mode.THREAD;
@ -82,10 +74,6 @@ public class Loadable {
/**
* Quick constructor for a thread loadable with an title.
*
* @param board
* @param no
* @param title
*/
public Loadable(String board, int no, String title) {
mode = Mode.THREAD;
@ -115,10 +103,6 @@ public class Loadable {
return result;
}
public boolean isBoardMode() {
return mode == Mode.BOARD;
}
public boolean isThreadMode() {
return mode == Mode.THREAD;
}
@ -127,26 +111,6 @@ public class Loadable {
return mode == Mode.CATALOG;
}
public void readFromBundle(Context context, Bundle bundle) {
String p = context.getPackageName();
mode = bundle.getInt(p + ".mode", Mode.INVALID);
board = bundle.getString(p + ".board", "");
no = bundle.getInt(p + ".no", -1);
title = bundle.getString(p + ".subject", "");
listViewIndex = bundle.getInt(p + ".listViewIndex");
listViewTop = bundle.getInt(p + ".listViewTop");
}
public void writeToBundle(Context context, Bundle bundle) {
String p = context.getPackageName();
bundle.putInt(p + ".mode", mode);
bundle.putString(p + ".board", board);
bundle.putInt(p + ".no", no);
bundle.putString(p + ".subject", title);
bundle.putInt(p + ".listViewIndex", listViewIndex);
bundle.putInt(p + ".listViewTop", listViewTop);
}
public void readFromBundle(Context context, String tag, Bundle bundle) {
String p = context.getPackageName();
mode = bundle.getInt(p + "." + tag + ".mode", Mode.INVALID);

@ -20,7 +20,7 @@ package org.floens.chan.core.model;
import android.text.SpannableString;
import android.text.TextUtils;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.loader.ChanParser;
import org.jsoup.parser.Parser;
@ -131,7 +131,7 @@ public class Post {
filename = Parser.unescapeEntities(filename, false);
if (spoiler) {
Board b = ChanApplication.getBoardManager().getBoardByValue(board);
Board b = Chan.getBoardManager().getBoardByValue(board);
if (b != null && b.customSpoilers >= 0) {
thumbnailUrl = ChanUrls.getCustomSpoilerUrl(board, random.nextInt(b.customSpoilers) + 1);
} else {
@ -143,7 +143,7 @@ public class Post {
}
if (!TextUtils.isEmpty(country)) {
Board b = ChanApplication.getBoardManager().getBoardByValue(board);
Board b = Chan.getBoardManager().getBoardByValue(board);
countryUrl = b.trollFlags ? ChanUrls.getTrollCountryFlagUrl(country) : ChanUrls.getCountryFlagUrl(country);
}

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.model;
public class PostImage {

@ -1,47 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.net;
import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;
/**
* Request a plain byte[] Warning: no caching!
*/
public class ByteArrayRequest extends Request<byte[]> {
protected final Listener<byte[]> listener;
public ByteArrayRequest(String url, Listener<byte[]> listener, ErrorListener errorListener) {
super(Method.GET, url, errorListener);
this.listener = listener;
}
@Override
protected void deliverResponse(byte[] response) {
listener.onResponse(response);
}
@Override
protected Response<byte[]> parseNetworkResponse(NetworkResponse response) {
return Response.success(response.data, null);
}
}

@ -22,7 +22,7 @@ import android.util.JsonReader;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Post;
@ -44,9 +44,7 @@ public class ChanReaderRequest extends JsonReaderRequest<List<Post>> {
public static ChanReaderRequest newInstance(Loadable loadable, List<Post> cached, Listener<List<Post>> listener, ErrorListener errorListener) {
String url;
if (loadable.isBoardMode()) {
url = ChanUrls.getPageUrl(loadable.board, loadable.no);
} else if (loadable.isThreadMode()) {
if (loadable.isThreadMode()) {
url = ChanUrls.getThreadUrl(loadable.board, loadable.no);
} else if (loadable.isCatalogMode()) {
url = ChanUrls.getCatalogUrl(loadable.board);
@ -72,9 +70,7 @@ public class ChanReaderRequest extends JsonReaderRequest<List<Post>> {
public List<Post> readJson(JsonReader reader) throws Exception {
List<Post> list;
if (loadable.isBoardMode()) {
list = loadBoard(reader);
} else if (loadable.isThreadMode()) {
if (loadable.isThreadMode()) {
list = loadThread(reader);
} else if (loadable.isCatalogMode()) {
list = loadCatalog(reader);
@ -177,7 +173,7 @@ public class ChanReaderRequest extends JsonReaderRequest<List<Post>> {
}
for (Post post : totalList) {
post.isSavedReply = ChanApplication.getDatabaseManager().isSavedReply(post.board, post.no);
post.isSavedReply = Chan.getDatabaseManager().isSavedReply(post.board, post.no);
}
return totalList;
@ -207,44 +203,6 @@ public class ChanReaderRequest extends JsonReaderRequest<List<Post>> {
return list;
}
private List<Post> loadBoard(JsonReader reader) throws Exception {
ArrayList<Post> list = new ArrayList<>();
reader.beginObject(); // Threads array
if (reader.nextName().equals("threads")) {
reader.beginArray();
while (reader.hasNext()) {
reader.beginObject(); // Thread object
if (reader.nextName().equals("posts")) {
reader.beginArray();
list.add(readPostObject(reader));
// Only consume one post
while (reader.hasNext())
reader.skipValue();
reader.endArray();
} else {
reader.skipValue();
}
reader.endObject();
}
reader.endArray();
} else {
reader.skipValue();
}
reader.endObject();
return list;
}
private List<Post> loadCatalog(JsonReader reader) throws Exception {
ArrayList<Post> list = new ArrayList<>();

@ -1,58 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.net;
import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;
import com.android.volley.toolbox.DiskBasedCache;
import com.android.volley.toolbox.HttpHeaderParser;
import org.floens.chan.ChanApplication;
import java.io.File;
public class FileRequest extends Request<Void> {
protected final Listener<File> listener;
public FileRequest(String url, Listener<File> listener, ErrorListener errorListener) {
super(Method.GET, url, errorListener);
this.listener = listener;
setShouldCache(true);
}
@Override
protected Response<Void> parseNetworkResponse(NetworkResponse response) {
return Response.success(null, HttpHeaderParser.parseCacheHeaders(response));
}
@Override
protected void deliverResponse(Void response) {
DiskBasedCache cache = (DiskBasedCache) ChanApplication.getVolleyRequestQueue().getCache();
File file = cache.getFileForKey(getCacheKey());
if (file.exists()) {
listener.onResponse(file);
} else {
listener.onResponse(null);
}
}
}

@ -27,8 +27,9 @@ import com.android.volley.Response.Listener;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.HttpHeaderParser;
import org.floens.chan.utils.IOUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
@ -65,13 +66,7 @@ public abstract class JsonReaderRequest<T> extends Request<T> {
exception = e;
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
IOUtils.closeQuietly(reader);
if (read == null) {
if (exception != null) {

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.presenter;
import android.support.v4.view.ViewPager;

@ -1,8 +1,25 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.presenter;
import android.text.TextUtils;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.manager.BoardManager;
@ -55,10 +72,10 @@ public class ReplyPresenter implements ReplyManager.FileListener, ReplyManager.H
public ReplyPresenter(ReplyPresenterCallback callback) {
this.callback = callback;
replyManager = ChanApplication.getReplyManager();
boardManager = ChanApplication.getBoardManager();
watchManager = ChanApplication.getWatchManager();
databaseManager = ChanApplication.getDatabaseManager();
replyManager = Chan.getReplyManager();
boardManager = Chan.getBoardManager();
watchManager = Chan.getWatchManager();
databaseManager = Chan.getDatabaseManager();
}
public void bindLoadable(Loadable loadable) {
@ -144,7 +161,7 @@ public class ReplyPresenter implements ReplyManager.FileListener, ReplyManager.H
}
previewOpen = false;
} else {
ChanApplication.getReplyManager().pickFile(this);
Chan.getReplyManager().pickFile(this);
pickingFile = true;
}
}
@ -313,7 +330,7 @@ public class ReplyPresenter implements ReplyManager.FileListener, ReplyManager.H
String baseUrl = loadable.isThreadMode() ?
ChanUrls.getThreadUrlDesktop(loadable.board, loadable.no) :
ChanUrls.getBoardUrlDesktop(loadable.board);
callback.initCaptcha(baseUrl, ChanUrls.getCaptchaSiteKey(), ChanApplication.getInstance().getUserAgent(), this);
callback.initCaptcha(baseUrl, ChanUrls.getCaptchaSiteKey(), Chan.getInstance().getUserAgent(), this);
}
break;
}

@ -21,7 +21,7 @@ import android.text.TextUtils;
import com.android.volley.VolleyError;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.loader.ChanLoader;
@ -72,8 +72,8 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
public ThreadPresenter(ThreadPresenterCallback threadPresenterCallback) {
this.threadPresenterCallback = threadPresenterCallback;
watchManager = ChanApplication.getWatchManager();
databaseManager = ChanApplication.getDatabaseManager();
watchManager = Chan.getWatchManager();
databaseManager = Chan.getDatabaseManager();
}
public void bindLoadable(Loadable loadable) {
@ -287,7 +287,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
menu.add(new FloatingMenuItem(POST_OPTION_DELETE, R.string.delete));
}
if (ChanSettings.getDeveloper()) {
if (ChanSettings.developer.get()) {
menu.add(new FloatingMenuItem(POST_OPTION_SAVE, "Save"));
}
}

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.reply;
import com.squareup.okhttp.Callback;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.reply;
import android.text.TextUtils;

@ -19,18 +19,14 @@ package org.floens.chan.core.reply;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.FormEncodingBuilder;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.MultipartBuilder;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.model.Loadable;
@ -38,18 +34,14 @@ import org.floens.chan.core.model.Reply;
import org.floens.chan.core.model.SavedReply;
import org.floens.chan.ui.activity.ImagePickActivity;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.Logger;
import java.io.File;
import java.io.IOException;
import java.net.HttpCookie;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
@ -63,7 +55,6 @@ public class ReplyManager {
private final Context context;
private FileListener fileListener;
private final Random random = new Random();
private OkHttpClient client;
private Map<Loadable, Reply> drafts = new HashMap<>();
@ -169,7 +160,7 @@ public class ReplyManager {
public void onFailure(Request request, IOException e) {
final PassResponse res = new PassResponse();
res.isError = true;
res.message = context.getString(R.string.pass_error);
res.message = context.getString(R.string.setting_pass_error);
runUI(new Runnable() {
public void run() {
passListener.onResponse(res);
@ -317,131 +308,6 @@ public class ReplyManager {
public String responseData = "";
}
public void postReply(Reply reply, ReplyListener replyListener) {
if (reply.usePass) {
postReplyInternal(reply, replyListener, null);
} else {
postReplyInternal(reply, replyListener, reply.captchaResponse);
}
}
private void postReplyInternal(final Reply reply, final ReplyListener replyListener, String captchaHash) {
// reply.password = Long.toHexString(random.nextLong());
MultipartBuilder formBuilder = new MultipartBuilder();
formBuilder.type(MultipartBuilder.FORM);
formBuilder.addFormDataPart("mode", "regist");
// formBuilder.addFormDataPart("pwd", reply.password);
if (reply.resto >= 0) {
formBuilder.addFormDataPart("resto", String.valueOf(reply.resto));
}
formBuilder.addFormDataPart("name", reply.name);
formBuilder.addFormDataPart("email", reply.options);
if (reply.resto >= 0 && !TextUtils.isEmpty(reply.subject)) {
formBuilder.addFormDataPart("sub", reply.subject);
}
formBuilder.addFormDataPart("com", reply.comment);
if (captchaHash != null) {
formBuilder.addFormDataPart("g-recaptcha-response", captchaHash);
}
if (reply.file != null) {
formBuilder.addFormDataPart("upfile", reply.fileName, RequestBody.create(
MediaType.parse("application/octet-stream"), reply.file
));
}
if (reply.spoilerImage) {
formBuilder.addFormDataPart("spoiler", "on");
}
Request.Builder request = new Request.Builder()
.url(ChanUrls.getReplyUrl(reply.board))
.post(formBuilder.build());
if (reply.usePass) {
request.addHeader("Cookie", "pass_id=" + reply.passId);
}
makeOkHttpCall(request, new Callback() {
@Override
public void onFailure(Request request, IOException e) {
final ReplyResponse res = new ReplyResponse();
res.isNetworkError = true;
runUI(new Runnable() {
public void run() {
replyListener.onResponse(res);
}
});
}
@Override
public void onResponse(Response response) throws IOException {
final ReplyResponse res = new ReplyResponse();
if (response.isSuccessful()) {
onReplyPosted(response.body().string(), reply, res);
response.body().close();
} else {
res.isNetworkError = true;
}
runUI(new Runnable() {
public void run() {
replyListener.onResponse(res);
}
});
}
});
}
private ReplyResponse onReplyPosted(String responseString, Reply reply, ReplyResponse res) {
res.responseData = responseString;
if (res.responseData.contains("No file selected")) {
res.isUserError = true;
res.isFileError = true;
} else if (res.responseData.contains("You forgot to solve the CAPTCHA") || res.responseData.contains("You seem to have mistyped the CAPTCHA")) {
res.isUserError = true;
res.isCaptchaError = true;
} else if (res.responseData.toLowerCase(Locale.ENGLISH).contains("post successful")) {
res.isSuccessful = true;
Matcher matcher = POST_THREAD_NO_PATTERN.matcher(res.responseData);
int threadNo = -1;
int no = -1;
if (matcher.find()) {
try {
threadNo = Integer.parseInt(matcher.group(1));
no = Integer.parseInt(matcher.group(2));
} catch (NumberFormatException err) {
err.printStackTrace();
}
}
if (threadNo >= 0 && no >= 0) {
SavedReply savedReply = new SavedReply();
savedReply.board = reply.board;
savedReply.no = no;
// savedReply.password = reply.password;
ChanApplication.getDatabaseManager().saveReply(savedReply);
res.threadNo = threadNo;
res.no = no;
} else {
Logger.w(TAG, "No thread & no in the response");
}
}
return res;
}
public void makeHttpCall(HttpCall httpCall, HttpCallback callback) {
//noinspection unchecked
httpCall.setCallback(callback);
@ -450,7 +316,7 @@ public class ReplyManager {
httpCall.setup(requestBuilder);
requestBuilder.header("User-Agent", ChanApplication.getInstance().getUserAgent());
requestBuilder.header("User-Agent", Chan.getInstance().getUserAgent());
Request request = requestBuilder.build();
client.newCall(request).enqueue(httpCall);
@ -463,7 +329,7 @@ public class ReplyManager {
}
private void makeOkHttpCall(Request.Builder requestBuilder, Callback callback) {
requestBuilder.header("User-Agent", ChanApplication.getInstance().getUserAgent());
requestBuilder.header("User-Agent", Chan.getInstance().getUserAgent());
Request request = requestBuilder.build();
client.newCall(request).enqueue(callback);
}
@ -471,51 +337,4 @@ public class ReplyManager {
private void runUI(Runnable runnable) {
AndroidUtils.runOnUiThread(runnable);
}
public interface ReplyListener {
void onResponse(ReplyResponse response);
}
public static class ReplyResponse {
/**
* No response from server.
*/
public boolean isNetworkError = false;
/**
* Some user error, like no file or captcha wrong.
*/
public boolean isUserError = false;
/**
* The userError was an fileError
*/
public boolean isFileError = false;
/**
* The userError was an captchaError
*/
public boolean isCaptchaError = false;
/**
* Received 'post successful'
*/
public boolean isSuccessful = false;
/**
* Raw html from the response. Used to set html in an WebView to the
* client, when the error was not recognized by Clover.
*/
public String responseData = "";
/**
* The no the post has
*/
public int no = -1;
/**
* The thread no the post has
*/
public int threadNo = -1;
}
}

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.settings;
import android.content.SharedPreferences;

@ -20,7 +20,7 @@ package org.floens.chan.core.settings;
import android.content.SharedPreferences;
import android.os.Environment;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.utils.AndroidUtils;
@ -98,217 +98,32 @@ public class ChanSettings {
watchEnabled = new BooleanSetting(p, "preference_watch_enabled", false, new Setting.SettingCallback<Boolean>() {
@Override
public void onValueChange(Setting setting, Boolean value) {
ChanApplication.getWatchManager().onWatchEnabledChanged(value);
Chan.getWatchManager().onWatchEnabledChanged(value);
}
});
watchCountdown = new BooleanSetting(p, "preference_watch_countdown", false);
watchBackground = new BooleanSetting(p, "preference_watch_background_enabled", false, new Setting.SettingCallback<Boolean>() {
@Override
public void onValueChange(Setting setting, Boolean value) {
ChanApplication.getWatchManager().onBackgroundWatchingChanged(value);
Chan.getWatchManager().onBackgroundWatchingChanged(value);
}
});
watchBackgroundTimeout = new StringSetting(p, "preference_watch_background_timeout", "60");
watchNotifyMode = new StringSetting(p, "preference_watch_notify_mode", "all");
watchSound = new StringSetting(p, "preference_watch_sound", "all");
watchSound = new StringSetting(p, "preference_watch_sound", "quotes");
watchLed = new StringSetting(p, "preference_watch_led", "ffffffff");
passToken = new StringSetting(p, "preference_pass_token", "");
passPin = new StringSetting(p, "preference_pass_pin", "");
passId = new StringSetting(p, "preference_pass_id", "");
}
private static SharedPreferences p() {
return AndroidUtils.getPreferences();
// Old (but possibly still in some users phone)
// preference_board_view_mode default "list"
// preference_board_editor_filler default false
// preference_pass_enabled default false
}
public static boolean passLoggedIn() {
return passId.get().length() > 0;
}
public static boolean getOpenLinkConfirmation() {
return p().getBoolean("preference_open_link_confirmation", true);
}
public static String getDefaultName() {
return p().getString("preference_default_name", "");
}
public static boolean getPinOnPost() {
return p().getBoolean("preference_pin_on_post", false);
}
public static boolean getDeveloper() {
return p().getBoolean("preference_developer", false);
}
public static void setDeveloper(boolean developer) {
p().edit().putBoolean("preference_developer", developer).commit();
}
public static File getImageSaveDirectory() {
String path = p().getString("preference_image_save_location", null);
File file;
if (path == null) {
file = new File(Environment.getExternalStorageDirectory() + File.separator + "Clover");
} else {
file = new File(path);
}
return file;
}
public static void setImageSaveDirectory(File file) {
p().edit().putString("preference_image_save_location", file.getAbsolutePath()).commit();
}
public static boolean getImageSaveOriginalFilename() {
return p().getBoolean("preference_image_save_original", false);
}
public static boolean getImageShareUrl() {
return p().getBoolean("preference_image_share_url", false);
}
public static boolean getWatchEnabled() {
return p().getBoolean("preference_watch_enabled", false);
}
/**
* This also calls updateRunningState on the PinnedService to start/stop the
* service as needed.
*
* @param enabled
*/
public static void setWatchEnabled(boolean enabled) {
if (getWatchEnabled() != enabled) {
p().edit().putBoolean("preference_watch_enabled", enabled).commit();
ChanApplication.getWatchManager().onWatchEnabledChanged(enabled);
}
}
public static boolean getWatchCountdownVisibleEnabled() {
return p().getBoolean("preference_watch_countdown", false);
}
public static boolean getWatchBackgroundEnabled() {
return p().getBoolean("preference_watch_background_enabled", false);
}
public static int getWatchBackgroundTimeout() {
String number = p().getString("preference_watch_background_timeout", "60");
return Integer.parseInt(number);
}
public static String getWatchNotifyMode() {
return p().getString("preference_watch_notify_mode", "all");
}
public static String getWatchSound() {
return p().getString("preference_watch_sound", "quotes");
}
public static long getWatchLed() {
String raw = p().getString("preference_watch_led", "ffffffff");
return Long.parseLong(raw, 16);
}
public static boolean getVideoAutoPlay() {
return getImageAutoLoad() && !getVideoExternal() && p().getBoolean("preference_autoplay", false);
}
public static boolean getThreadAutoRefresh() {
return p().getBoolean("preference_auto_refresh_thread", true);
}
public static boolean getImageAutoLoad() {
return p().getBoolean("preference_image_auto_load", true);
}
public static boolean getPassEnabled() {
return p().getBoolean("preference_pass_enabled", false);
}
public static void setPassEnabled(boolean enabled) {
if (getPassEnabled() != enabled) {
p().edit().putBoolean("preference_pass_enabled", enabled).commit();
}
}
public static String getPassToken() {
return p().getString("preference_pass_token", "");
}
public static String getPassPin() {
return p().getString("preference_pass_pin", "");
}
public static void setPassId(String id) {
p().edit().putString("preference_pass_id", id).commit();
}
public static String getPassId() {
return p().getString("preference_pass_id", "");
}
public static String getTheme() {
return p().getString("preference_theme", "light");
}
public static boolean getForcePhoneLayout() {
return p().getBoolean("preference_force_phone_layout", false);
}
public static boolean getBoardEditorFillerEnabled() {
return p().getBoolean("preference_board_editor_filler", false);
}
public static boolean setBoardEditorFillerEnabled(boolean enabled) {
return p().edit().putBoolean("preference_board_editor_filler", enabled).commit();
}
public static String getBoardViewMode() {
return p().getString("preference_board_view_mode", "list");
}
public static void setBoardViewMode(String mode) {
p().edit().putString("preference_board_view_mode", mode).commit();
}
public static boolean getAnonymize() {
return p().getBoolean("preference_anonymize", false);
}
public static boolean getAnonymizeIds() {
return p().getBoolean("preference_anonymize_ids", false);
}
public static boolean getReplyButtonsBottom() {
return p().getBoolean("preference_buttons_bottom", false);
}
public static String getBoardMode() {
return p().getString("preference_board_mode", "catalog");
}
public static boolean getVideoErrorIgnore() {
return p().getBoolean("preference_video_error_ignore", false);
}
public static void setVideoErrorIgnore(boolean show) {
p().edit().putBoolean("preference_video_error_ignore", show).commit();
}
public static boolean getVideoExternal() {
return p().getBoolean("preference_video_external", false);
}
public static int getFontSize() {
String font = p().getString("preference_font", null);
return font == null ? 14 : Integer.parseInt(font);
}
public static boolean getNetworkHttps() {
return p().getBoolean("preference_network_https", true);
}
}

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.settings;
import android.content.SharedPreferences;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.settings;
import android.content.SharedPreferences;

@ -19,7 +19,7 @@ package org.floens.chan.core.watch;
import com.android.volley.VolleyError;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.core.loader.ChanLoader;
import org.floens.chan.core.loader.LoaderPool;
import org.floens.chan.core.model.ChanThread;
@ -122,7 +122,7 @@ public class PinWatcher implements ChanLoader.ChanLoaderCallback {
AndroidUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
ChanApplication.getWatchManager().onPinsChanged();
Chan.getWatchManager().onPinsChanged();
}
});
}
@ -195,7 +195,7 @@ public class PinWatcher implements ChanLoader.ChanLoaderCallback {
AndroidUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
ChanApplication.getWatchManager().pinWatcherUpdated(pin);
Chan.getWatchManager().pinWatcherUpdated(pin);
}
});
}

@ -27,12 +27,12 @@ import android.widget.LinearLayout;
import com.android.volley.VolleyError;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.core.cache.FileCache;
import org.floens.chan.core.loader.ChanLoader;
import org.floens.chan.core.model.ChanThread;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Post;
import org.floens.chan.utils.FileCache;
import org.floens.chan.utils.Logger;
import org.floens.chan.utils.ThemeHelper;
@ -90,7 +90,7 @@ public class TestActivity extends Activity implements View.OnClickListener {
File cacheDir = getExternalCacheDir() != null ? getExternalCacheDir() : getCacheDir();
File fileCacheDir = new File(cacheDir, "filecache");
fileCache = new FileCache(fileCacheDir, 50 * 1024 * 1024, ChanApplication.getInstance().getUserAgent());
fileCache = new FileCache(fileCacheDir, 50 * 1024 * 1024, Chan.getInstance().getUserAgent());
}
@Override

@ -1,48 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import org.floens.chan.R;
import org.floens.chan.utils.ThemeHelper;
public class ThemeActivity extends AppCompatActivity {
private Toolbar toolbar;
public void setTheme() {
setTheme(ThemeHelper.getInstance().getTheme().resValue);
}
public void setToolbar() {
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
}

@ -1,41 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.activity;
import android.os.Bundle;
import android.webkit.WebView;
import org.floens.chan.R;
import org.floens.chan.ui.ThemeActivity;
public class AboutActivity extends ThemeActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme();
setContentView(R.layout.toolbar_activity);
setToolbar();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
WebView webView = new WebView(this);
webView.loadUrl("file:///android_asset/html/licenses.html");
setContentView(webView);
}
}

@ -1,134 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.activity;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import org.floens.chan.R;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.ThemeActivity;
import org.floens.chan.ui.fragment.FolderPickFragment;
import java.io.File;
public class AdvancedSettingsActivity extends ThemeActivity {
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme();
setContentView(R.layout.toolbar_activity);
setToolbar();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getFragmentManager().beginTransaction().replace(R.id.content, new AdvancedSettingsFragment()).commit();
}
public static class AdvancedSettingsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preference_advanced);
findPreference("preference_force_phone_layout").setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(final Preference preference, final Object newValue) {
BaseActivity.doRestartOnResume = true;
return true;
}
});
findPreference("preference_anonymize").setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(final Preference preference, final Object newValue) {
BaseActivity.doRestartOnResume = true;
return true;
}
});
findPreference("preference_anonymize_ids").setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(final Preference preference, final Object newValue) {
BaseActivity.doRestartOnResume = true;
return true;
}
});
final ListPreference boardMode = (ListPreference) findPreference("preference_board_mode");
String currentModeValue = boardMode.getValue();
if (currentModeValue == null) {
boardMode.setValue((String) boardMode.getEntryValues()[0]);
currentModeValue = boardMode.getValue();
}
updateSummary(boardMode, currentModeValue);
boardMode.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
updateSummary(boardMode, newValue.toString());
BaseActivity.doRestartOnResume = true;
return true;
}
});
reloadSavePath();
final Preference saveLocation = findPreference("preference_image_save_location");
saveLocation.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
File dir = ChanSettings.getImageSaveDirectory();
dir.mkdirs();
FolderPickFragment frag = FolderPickFragment.newInstance(new FolderPickFragment.FolderPickListener() {
@Override
public void folderPicked(File path) {
ChanSettings.setImageSaveDirectory(path);
reloadSavePath();
}
}, dir);
getActivity().getFragmentManager().beginTransaction().add(frag, null).commit();
return true;
}
});
findPreference("preference_network_https").setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
ChanUrls.loadScheme((Boolean) newValue);
return true;
}
});
}
private void reloadSavePath() {
Preference saveLocation = findPreference("preference_image_save_location");
saveLocation.setSummary(ChanSettings.getImageSaveDirectory().getAbsolutePath());
}
private void updateSummary(ListPreference list, String value) {
int index = list.findIndexOfValue(value);
list.setSummary(list.getEntries()[index]);
}
}
}

@ -1,384 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.TypedArray;
import android.net.Uri;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SlidingPaneLayout;
import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.ShareActionProvider;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.EditText;
import android.widget.ListView;
import org.floens.chan.ChanApplication;
import org.floens.chan.R;
import org.floens.chan.core.model.ChanThread;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Pin;
import org.floens.chan.core.model.Post;
import org.floens.chan.ui.ThemeActivity;
import org.floens.chan.ui.adapter.PinnedAdapter;
import org.floens.chan.ui.animation.SwipeDismissListViewTouchListener;
import org.floens.chan.ui.animation.SwipeDismissListViewTouchListener.DismissCallbacks;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.ThemeHelper;
import static org.floens.chan.utils.AndroidUtils.dp;
public abstract class BaseActivity extends ThemeActivity implements PanelSlideListener {
public static boolean doRestartOnResume = false;
private final static int ACTION_OPEN_URL = 1;
protected PinnedAdapter pinnedAdapter;
protected DrawerLayout pinDrawer;
protected ListView pinDrawerView;
protected ActionBarDrawerToggle pinDrawerListener;
protected SlidingPaneLayout threadPane;
private String shareUrl;
private ShareActionProvider shareActionProvider;
private Intent pendingShareActionProviderIntent;
/**
* 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
*/
abstract public void onOPClicked(Post post);
abstract public void onOpenThread(Loadable thread);
abstract public void onThreadLoaded(ChanThread thread);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.getInstance().reloadPostViewColors(this);
setContentView(R.layout.activity_base);
setTheme();
setToolbar();
pinDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
initDrawer();
threadPane = (SlidingPaneLayout) findViewById(R.id.pane_container);
initPane();
updateIcon();
}
@Override
public void onBackPressed() {
if (pinDrawer.isDrawerOpen(pinDrawerView)) {
pinDrawer.closeDrawer(pinDrawerView);
} else {
super.onBackPressed();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
protected void onResume() {
super.onResume();
if (doRestartOnResume) {
doRestartOnResume = false;
recreate();
}
}
private void initPane() {
threadPane.setPanelSlideListener(this);
threadPane.setParallaxDistance(dp(100));
threadPane.setShadowResource(R.drawable.panel_shadow);
TypedArray ta = obtainStyledAttributes(null, R.styleable.BoardPane, R.attr.board_pane_style, 0);
int color = ta.getColor(R.styleable.BoardPane_fade_color, 0);
ta.recycle();
threadPane.setSliderFadeColor(color);
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(getSupportActionBar().getThemedContext(), pinDrawerView); // 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) {
Pin pin = pinnedAdapter.getItem(position);
if (pin == null)
return;
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)
return false;
onPinLongPress(post);
return true;
}
});
SwipeDismissListViewTouchListener touchListener = new SwipeDismissListViewTouchListener(pinDrawerView,
new DismissCallbacks() {
@Override
public void onDismiss(ListView listView, int[] reverseSortedPositions) {
for (int position : reverseSortedPositions) {
removePin(pinnedAdapter.getItem(position));
}
}
@Override
public boolean canDismiss(int position) {
return pinnedAdapter.getItem(position) != null;
}
}
);
pinDrawerView.setOnTouchListener(touchListener);
pinDrawerView.setOnScrollListener(touchListener.makeScrollListener());
pinDrawerView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
}
private void updateIcon() {
/*List<Pin> list = ChanApplication.getWatchManager().getWatchingPins();
if (list.size() > 0) {
int count = 0;
boolean color = false;
for (Pin p : list) {
count += p.getNewPostCount();
if (p.getNewQuoteCount() > 0) {
color = true;
}
}
if (count > 0) {
Drawable icon = BadgeDrawable.get(getResources(), R.drawable.ic_launcher, count, color);
getSupportActionBar().setIcon(icon);
} else {
getSupportActionBar().setIcon(R.drawable.ic_launcher);
}
} else {
getSupportActionBar().setIcon(R.drawable.ic_launcher);
}*/
}
public void removePin(Pin pin) {
ChanApplication.getWatchManager().removePin(pin);
}
public void updatePin(Pin pin) {
ChanApplication.getWatchManager().updatePin(pin);
}
private void onPinLongPress(final Pin pin) {
new AlertDialog.Builder(this)
.setNegativeButton(R.string.drawer_pinned_delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Delete pin
removePin(pin);
}
}).setPositiveButton(R.string.drawer_pinned_change_title, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Change pin title
final EditText text = new EditText(BaseActivity.this);
text.setSingleLine();
text.setText(pin.loadable.title);
text.setSelectAllOnFocus(true);
AlertDialog titleDialog = new AlertDialog.Builder(BaseActivity.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);
}
}
}).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface d, int which) {
}
}).setTitle(R.string.drawer_pinned_change_title).setView(text).create();
AndroidUtils.requestKeyboardFocus(titleDialog, text);
titleDialog.show();
}
}).show();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
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) MenuItemCompat.getActionProvider(menu.findItem(R.id.action_share));
if (pendingShareActionProviderIntent != null) {
shareActionProvider.setShareIntent(pendingShareActionProviderIntent);
pendingShareActionProviderIntent = null;
}*/
return true;
}
@Override
public void onPanelClosed(View view) {
}
@Override
public void onPanelOpened(View view) {
}
@Override
public void onPanelSlide(View view, float offset) {
}
/**
* Set the url that Android Beam and the share action will send.
*
* @param url
*/
public void setShareUrl(String url) {
shareUrl = url;
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
if (adapter != null) {
NdefRecord record = null;
try {
record = NdefRecord.createUri(url);
} catch (IllegalArgumentException e) {
e.printStackTrace();
return;
}
NdefMessage message = new NdefMessage(new NdefRecord[]{record});
try {
adapter.setNdefPushMessage(message, this);
} catch (Exception e) {
}
}
Intent share = new Intent(android.content.Intent.ACTION_SEND);
share.putExtra(android.content.Intent.EXTRA_TEXT, url);
share.setType("text/plain");
if (shareActionProvider != null) {
shareActionProvider.setShareIntent(share);
} else {
pendingShareActionProviderIntent = share;
}
}
public void openInBrowser() {
if (shareUrl != null) {
showUrlOpenPicker(shareUrl);
}
}
/**
* 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.
*
* @param url
*/
public void showUrlOpenPicker(String url) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
startActivity(intent);
}
/**
* 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);
}
}
}

@ -1,451 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.mobeta.android.dslv.DragSortController;
import com.mobeta.android.dslv.DragSortListView;
import org.floens.chan.ChanApplication;
import org.floens.chan.R;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.model.Board;
import org.floens.chan.ui.animation.SwipeDismissListViewTouchListener;
import org.floens.chan.ui.ThemeActivity;
import org.floens.chan.utils.AndroidUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class BoardEditor extends ThemeActivity {
private final BoardManager boardManager = ChanApplication.getBoardManager();
private List<Board> list;
private DragSortListView listView;
private BoardEditAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme();
setContentView(R.layout.toolbar_activity);
setToolbar();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
list = boardManager.getSavedBoards();
adapter = new BoardEditAdapter(this, 0, list);
listView = new DragSortListView(this, null);
listView.setAdapter(adapter);
listView.setDivider(new ColorDrawable(Color.TRANSPARENT));
final DragSortController controller = new NiceDragSortController(listView, adapter);
listView.setFloatViewManager(controller);
listView.setOnTouchListener(controller);
listView.setDragEnabled(true);
listView.setDropListener(new DragSortListView.DropListener() {
@Override
public void drop(int from, int to) {
if (from != to) {
Board board = list.remove(from);
list.add(to, board);
adapter.notifyDataSetChanged();
}
}
});
final SwipeDismissListViewTouchListener touchListener = new SwipeDismissListViewTouchListener(listView,
new SwipeDismissListViewTouchListener.DismissCallbacks() {
@Override
public void onDismiss(ListView listView, int[] reverseSortedPositions) {
for (int position : reverseSortedPositions) {
if (position >= 0 && position < adapter.getCount()) {
Board b = adapter.getItem(position);
adapter.remove(b);
b.saved = false;
}
}
adapter.notifyDataSetChanged();
}
@Override
public boolean canDismiss(int position) {
return list.size() > 1;
}
}
);
listView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
return controller.onTouch(view, motionEvent)
|| (!listView.isDragging() && touchListener.onTouch(view, motionEvent));
}
});
listView.setOnScrollListener(touchListener.makeScrollListener());
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, final int position, long id) {
new AlertDialog.Builder(BoardEditor.this)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (position >= 0 && position < adapter.getCount()) {
Board b = adapter.getItem(position);
adapter.remove(b);
b.saved = false;
adapter.notifyDataSetChanged();
}
}
})
.setMessage(R.string.board_delete)
.show();
return true;
}
});
((ViewGroup) findViewById(R.id.content)).addView(listView);
}
@Override
protected void onPause() {
super.onPause();
if (list.size() > 0) {
// Order
for (int i = 0; i < list.size(); i++) {
list.get(i).order = i;
}
boardManager.updateSavedBoards();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.board_edit, menu);
menu.findItem(R.id.action_show_filler).setChecked(ChanSettings.getBoardEditorFillerEnabled());
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_add_board:
showAddBoardDialog();
return true;
case R.id.action_show_filler:
ChanSettings.setBoardEditorFillerEnabled(!ChanSettings.getBoardEditorFillerEnabled());
item.setChecked(ChanSettings.getBoardEditorFillerEnabled());
return true;
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
private void addBoard(String value) {
value = value.trim();
value = value.replace("/", "");
value = value.replace("\\", ""); // what are you doing?!
// Duplicate
for (Board board : list) {
if (board.value.equals(value)) {
Toast.makeText(this, R.string.board_add_duplicate, Toast.LENGTH_LONG).show();
return;
}
}
// Normal add
List<Board> all = ChanApplication.getBoardManager().getAllBoards();
for (Board board : all) {
if (board.value.equals(value)) {
board.saved = true;
list.add(board);
adapter.notifyDataSetChanged();
Toast.makeText(this, getString(R.string.board_add_success) + " " + board.key, Toast.LENGTH_LONG).show();
return;
}
}
// Unknown
new AlertDialog.Builder(this)
.setTitle(R.string.board_add_unknown_title)
.setMessage(getString(R.string.board_add_unknown, value))
.setPositiveButton(R.string.ok, null)
.show();
}
private void showAddBoardDialog() {
final AutoCompleteTextView text = new AutoCompleteTextView(this);
text.setSingleLine();
FillAdapter fillAdapter = new FillAdapter(this, 0);
fillAdapter.setEditingList(list);
fillAdapter.setAutoCompleteView(text);
text.setAdapter(fillAdapter);
text.setThreshold(1);
text.setDropDownHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
text.setHint(R.string.board_add_hint);
text.setImeOptions(EditorInfo.IME_FLAG_NO_FULLSCREEN);
AlertDialog dialog = new AlertDialog.Builder(this)
.setPositiveButton(R.string.add, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface d, int which) {
String value = text.getText().toString();
if (!TextUtils.isEmpty(value)) {
addBoard(value.toLowerCase(Locale.ENGLISH));
}
}
}).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface d, int which) {
}
}).setTitle(R.string.board_add).setView(text).create();
AndroidUtils.requestKeyboardFocus(dialog, text);
dialog.show();
}
private static class FillAdapter extends ArrayAdapter<String> implements Filterable {
private List<Board> currentlyEditing;
private View autoCompleteView;
private final Filter filter;
private final List<Board> filtered = new ArrayList<>();
public FillAdapter(Context context, int resource) {
super(context, resource);
filter = new Filter() {
@Override
protected synchronized FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
if (TextUtils.isEmpty(constraint) || (constraint.toString().startsWith(" "))) {
results.values = null;
results.count = 0;
} else {
List<Board> keys = getFiltered(constraint.toString());
results.values = keys;
results.count = keys.size();
}
return results;
}
@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
filtered.clear();
if (ChanSettings.getBoardEditorFillerEnabled()) {
if (results.values != null) {
filtered.addAll((List<Board>) results.values);
} else {
filtered.addAll(getBoards());
}
}
notifyDataSetChanged();
}
};
}
public void setEditingList(List<Board> list) {
currentlyEditing = list;
}
public void setAutoCompleteView(View autoCompleteView) {
this.autoCompleteView = autoCompleteView;
}
@Override
public int getCount() {
return filtered.size();
}
@Override
public String getItem(int position) {
return filtered.get(position).value;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
TextView view = (TextView) inflater.inflate(android.R.layout.simple_list_item_1, null);
Board b = filtered.get(position);
view.setText(b.value + " - " + b.key);
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(autoCompleteView.getWindowToken(), 0);
}
return false;
}
});
return view;
}
@Override
public Filter getFilter() {
return filter;
}
private List<Board> getFiltered(String filter) {
String lowered = filter.toLowerCase(Locale.ENGLISH);
List<Board> list = new ArrayList<>();
for (Board b : getBoards()) {
if ((b.key.toLowerCase(Locale.ENGLISH).contains(lowered) || b.value.toLowerCase(Locale.ENGLISH)
.contains(lowered))) {
list.add(b);
}
}
return list;
}
private boolean haveBoard(String value) {
for (Board b : currentlyEditing) {
if (b.value.equals(value))
return true;
}
return false;
}
private List<Board> getBoards() {
// Lets be cheaty here: if the user has nsfw boards in the list,
// show them in the autofiller.
boolean showUnsafe = false;
for (Board has : currentlyEditing) {
if (!has.workSafe) {
showUnsafe = true;
break;
}
}
List<Board> s = new ArrayList<>();
for (Board b : ChanApplication.getBoardManager().getAllBoards()) {
if (!haveBoard(b.value) && (showUnsafe || b.workSafe))
s.add(b);
}
return s;
}
}
private class NiceDragSortController extends DragSortController {
private final ListView listView;
private final ArrayAdapter<Board> adapter;
public NiceDragSortController(DragSortListView listView, ArrayAdapter<Board> adapter) {
super(listView, R.id.drag_handle, DragSortController.ON_DOWN, 0);
this.listView = listView;
this.adapter = adapter;
setSortEnabled(true);
setRemoveEnabled(false);
}
@Override
public View onCreateFloatView(int position) {
return adapter.getView(position, null, listView);
}
@Override
public void onDragFloatView(View floatView, Point floatPoint, Point touchPoint) {
}
@Override
public void onDestroyFloatView(View floatView) {
}
}
private class BoardEditAdapter extends ArrayAdapter<Board> {
public BoardEditAdapter(Context context, int textViewResourceId, List<Board> objects) {
super(context, textViewResourceId, objects);
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public long getItemId(int position) {
return getItem(position).hashCode();
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View inflated = LayoutInflater.from(getContext()).inflate(R.layout.board_edit_item, null);
TextView text = (TextView) inflated.findViewById(R.id.text);
Board b = getItem(position);
text.setText(b.value + " - " + b.key);
return inflated;
}
}
}

@ -1,759 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.activity;
import android.app.AlertDialog;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarDrawerToggle;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.Spinner;
import android.widget.TextView;
import org.floens.chan.ChanApplication;
import org.floens.chan.R;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.loader.ChanLoader;
import org.floens.chan.core.manager.ThreadManager;
import org.floens.chan.core.model.Board;
import org.floens.chan.core.model.ChanThread;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Pin;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.fragment.ThreadFragment;
import org.floens.chan.utils.Logger;
import java.util.List;
import static org.floens.chan.utils.AndroidUtils.dp;
public class ChanActivity extends BaseActivity implements AdapterView.OnItemSelectedListener {
private static final String TAG = "ChanActivity";
private Loadable boardLoadable;
private Loadable threadLoadable;
private ThreadFragment boardFragment;
private ThreadFragment threadFragment;
private boolean ignoreNextOnItemSelected = false;
private Spinner boardSpinner;
private BoardSpinnerAdapter spinnerAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
boardLoadable = new Loadable();
threadLoadable = new Loadable();
boardFragment = ThreadFragment.newInstance(this);
setBoardFragmentViewMode();
threadFragment = ThreadFragment.newInstance(this);
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.left_pane, boardFragment);
ft.replace(R.id.right_pane, threadFragment);
ft.commitAllowingStateLoss();
final ActionBar actionBar = getSupportActionBar();
boardSpinner = new Spinner(this);
spinnerAdapter = new BoardSpinnerAdapter(this, boardSpinner);
boardSpinner.setAdapter(spinnerAdapter);
boardSpinner.setOnItemSelectedListener(this);
actionBar.setCustomView(boardSpinner);
actionBar.setDisplayShowCustomEnabled(true);
updatePaneState();
Intent startIntent = getIntent();
Uri startUri = startIntent.getData();
if (savedInstanceState != null) {
Loadable threadTmp = new Loadable();
threadTmp.readFromBundle(this, "thread", savedInstanceState);
startLoadingThread(threadTmp);
// Reset page etc.
Loadable tmp = new Loadable();
tmp.readFromBundle(this, "board", savedInstanceState);
startLoadingBoard(new Loadable(tmp.board));
} else {
if (startUri != null) {
handleIntentURI(startUri);
}
if (boardLoadable.mode == Loadable.Mode.INVALID) {
List<Board> savedValues = ChanApplication.getBoardManager().getSavedBoards();
if (savedValues.size() > 0) {
startLoadingBoard(new Loadable(savedValues.get(0).value));
}
}
}
if (startIntent.getExtras() != null) {
handleExtraBundle(startIntent.getExtras());
}
ignoreNextOnItemSelected = true;
}
@Override
protected void onNewIntent(final Intent intent) {
super.onNewIntent(intent);
if (intent.getExtras() != null) {
handleExtraBundle(intent.getExtras());
}
}
@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();
ChanApplication.getInstance().activityEnteredForeground();
}
@Override
protected void onStop() {
super.onStop();
ChanApplication.getInstance().activityEnteredBackground();
}
@Override
protected void onPause() {
super.onPause();
ChanApplication.getWatchManager().updateDatabase();
}
@Override
protected void onDestroy() {
super.onDestroy();
// ChanApplication.getBoardManager().removeListener(this);
}
@Override
protected void initDrawer() {
pinDrawerListener = new ActionBarDrawerToggle(this, pinDrawer, R.string.drawer_open, R.string.drawer_close);
super.initDrawer();
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
pinDrawerListener.syncState();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
pinDrawerListener.onConfigurationChanged(newConfig);
updatePaneState();
}
@Override
public void onBackPressed() {
if (pinDrawer.isDrawerOpen(pinDrawerView)) {
pinDrawer.closeDrawer(pinDrawerView);
} else {
if (threadPane.isOpen()) {
super.onBackPressed();
} else {
threadPane.openPane();
}
}
}
@Override
public void openPin(Pin pin) {
startLoadingThread(pin.loadable);
pinDrawer.closeDrawer(pinDrawerView);
}
@Override
public void onOPClicked(Post post) {
Loadable l = new Loadable(post.board, post.no);
l.generateTitle(post);
startLoadingThread(l);
}
@Override
public void onOpenThread(Loadable thread) {
startLoadingThread(thread);
}
@Override
public void onThreadLoaded(ChanThread thread) {
updateActionBarState();
pinnedAdapter.notifyDataSetChanged();
}
@Override
public void updatePin(Pin pin) {
super.updatePin(pin);
updateActionBarState();
}
@Override
public void removePin(Pin pin) {
super.removePin(pin);
updateActionBarState();
}
@Override
public void onNothingSelected(final AdapterView<?> parent) {
}
@Override
public void onPanelClosed(View view) {
updateActionBarState();
}
@Override
public void onPanelOpened(View view) {
updateActionBarState();
}
// @Override
// public void onBoardsChanged() {
// spinnerAdapter.setBoards();
// spinnerAdapter.notifyDataSetChanged();
// }
private void handleExtraBundle(Bundle extras) {
int pinId = extras.getInt("pin_id", -2);
if (pinId != -2) {
if (pinId == -1) {
pinDrawer.openDrawer(pinDrawerView);
} else {
Pin pin = ChanApplication.getWatchManager().findPinById(pinId);
if (pin != null) {
startLoadingThread(pin.loadable);
}
}
}
}
private void updatePaneState() {
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
int width = metrics.widthPixels;
FrameLayout left = (FrameLayout) findViewById(R.id.left_pane);
FrameLayout right = (FrameLayout) findViewById(R.id.right_pane);
LayoutParams leftParams = left.getLayoutParams();
LayoutParams rightParams = right.getLayoutParams();
boolean wasSlidable = threadPane.isSlideable();
boolean isSlidable;
// Content view dp's:
// Nexus 4 is 384 x 640 dp
// Nexus 7 is 600 x 960 dp
// Nexus 10 is 800 x 1280 dp
if (ChanSettings.getForcePhoneLayout()) {
leftParams.width = width - dp(30);
rightParams.width = width;
isSlidable = true;
} else {
if (width < dp(400)) {
leftParams.width = width - dp(30);
rightParams.width = width;
isSlidable = true;
} else if (width < dp(800)) {
leftParams.width = width - dp(60);
rightParams.width = width;
isSlidable = true;
} else if (width < dp(1000)) {
leftParams.width = dp(300);
rightParams.width = width - dp(300);
isSlidable = false;
} else {
leftParams.width = dp(400);
rightParams.width = width - dp(400);
isSlidable = false;
}
}
left.setLayoutParams(leftParams);
right.setLayoutParams(rightParams);
threadPane.requestLayout();
left.requestLayout();
right.requestLayout();
LayoutParams drawerParams = pinDrawerView.getLayoutParams();
if (width < dp(340)) {
drawerParams.width = dp(280);
} else {
drawerParams.width = dp(320);
}
pinDrawerView.setLayoutParams(drawerParams);
updateActionBarState();
if (isSlidable != wasSlidable) {
// Terrible hack to sync state for some devices when it changes slidable mode
threadPane.postDelayed(new Runnable() {
@Override
public void run() {
updateActionBarState();
}
}, 1000);
}
}
private void updateActionBarState() {
// Force the actionbar state after the ThreadPane layout,
// otherwise the ThreadPane incorrectly reports that it's not slidable.
threadPane.post(new Runnable() {
@Override
public void run() {
updateActionBarStateCallback();
}
});
}
private void updateActionBarStateCallback() {
final ActionBar actionBar = getSupportActionBar();
if (threadPane.isSlideable()) {
if (threadPane.isOpen()) {
actionBar.setDisplayShowCustomEnabled(true);
spinnerAdapter.setBoard(boardLoadable.board);
actionBar.setTitle("");
pinDrawerListener.setDrawerIndicatorEnabled(true);
if (boardLoadable.isBoardMode()) {
setShareUrl(ChanUrls.getBoardUrlDesktop(boardLoadable.board));
} else if (boardLoadable.isCatalogMode()) {
setShareUrl(ChanUrls.getCatalogUrlDesktop(boardLoadable.board));
}
} else {
actionBar.setDisplayShowCustomEnabled(false);
actionBar.setTitle(threadLoadable.title);
pinDrawerListener.setDrawerIndicatorEnabled(false);
if (threadLoadable.isThreadMode())
setShareUrl(ChanUrls.getThreadUrlDesktop(threadLoadable.board, threadLoadable.no));
}
} else {
actionBar.setDisplayShowCustomEnabled(true);
pinDrawerListener.setDrawerIndicatorEnabled(true);
actionBar.setTitle(threadLoadable.title);
if (threadLoadable.isThreadMode()) {
setShareUrl(ChanUrls.getThreadUrlDesktop(threadLoadable.board, threadLoadable.no));
}
}
actionBar.setHomeButtonEnabled(true);
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_download_album), !slidable || !open);
setMenuItemEnabled(menu.findItem(R.id.action_reply), slidable);
setMenuItemEnabled(menu.findItem(R.id.action_reply_tablet), !slidable);
setMenuItemEnabled(menu.findItem(R.id.action_board_view_mode), !slidable || open);
if (ChanSettings.getBoardViewMode().equals("list")) {
menu.findItem(R.id.action_board_view_mode_list).setChecked(true);
} else if (ChanSettings.getBoardViewMode().equals("grid")) {
menu.findItem(R.id.action_board_view_mode_grid).setChecked(true);
}
setMenuItemEnabled(menu.findItem(R.id.action_search), slidable);
setMenuItemEnabled(menu.findItem(R.id.action_search_tablet), !slidable);
boolean bookmarkedFilled = false;
if (threadLoadable.mode == Loadable.Mode.THREAD) {
Pin pin = ChanApplication.getWatchManager().findPinByLoadable(threadLoadable);
if (pin != null) {
bookmarkedFilled = true;
}
}
menu.findItem(R.id.action_pin).setIcon(bookmarkedFilled ? R.drawable.ic_bookmark_filled : R.drawable.ic_bookmark);
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:
boardFragment.reload();
return true;
case R.id.action_reload_thread:
case R.id.action_reload_tablet_thread:
threadFragment.reload();
return true;
case R.id.action_reply:
if (threadPane.isOpen()) {
boardFragment.openReply();
} else {
threadFragment.openReply();
}
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()) {
ChanLoader chanLoader = threadFragment.getLoader();
if (chanLoader != null && chanLoader.getLoadable().isThreadMode() && chanLoader.getThread() != null) {
Pin pin = ChanApplication.getWatchManager().findPinByLoadable(threadLoadable);
if (pin != null) {
ChanApplication.getWatchManager().removePin(pin);
} else {
ChanApplication.getWatchManager().addPin(chanLoader.getLoadable(), chanLoader.getThread().op);
}
updateActionBarState();
}
}
return true;
case R.id.action_open_browser:
openInBrowser();
return true;
case R.id.action_board_view_mode_grid:
if (!ChanSettings.getBoardViewMode().equals("grid")) {
ChanSettings.setBoardViewMode("grid");
setBoardFragmentViewMode();
startLoadingBoard(boardLoadable);
}
return true;
case R.id.action_board_view_mode_list:
if (!ChanSettings.getBoardViewMode().equals("list")) {
ChanSettings.setBoardViewMode("list");
setBoardFragmentViewMode();
startLoadingBoard(boardLoadable);
}
return true;
case R.id.action_search:
if (threadPane.isOpen()) {
boardFragment.startFiltering();
} else {
threadFragment.startFiltering();
}
return true;
case R.id.action_search_board:
boardFragment.startFiltering();
return true;
case R.id.action_search_thread:
threadFragment.startFiltering();
return true;
case android.R.id.home:
threadPane.openPane();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onItemSelected(final AdapterView<?> parent, final View view, final int position, final long id) {
if (ignoreNextOnItemSelected) {
Logger.d(TAG, "Ignoring onItemSelected");
ignoreNextOnItemSelected = false;
return;
}
spinnerAdapter.onItemSelected(position);
}
private void startLoadingBoard(Loadable loadable) {
if (loadable.mode == Loadable.Mode.INVALID)
return;
boardLoadable = loadable;
if (ChanSettings.getBoardMode().equals("catalog")) {
boardLoadable.mode = Loadable.Mode.CATALOG;
} else if (ChanSettings.getBoardMode().equals("pages")) {
boardLoadable.mode = Loadable.Mode.BOARD;
}
// Force catalog mode when using grid
if (boardFragment.getViewMode() == ThreadManager.ViewMode.GRID) {
boardLoadable.mode = Loadable.Mode.CATALOG;
}
boardFragment.bindLoadable(boardLoadable);
boardFragment.requestData();
updateActionBarState();
}
private void startLoadingThread(Loadable loadable) {
if (loadable.mode == Loadable.Mode.INVALID)
return;
Pin pin = ChanApplication.getWatchManager().findPinByLoadable(loadable);
if (pin != null) {
// Use the loadable from the pin.
// This way we can store the listview position in the pin loadable,
// and not in a separate loadable instance.
loadable = pin.loadable;
}
if (threadLoadable.equals(loadable)) {
threadFragment.requestNextData();
} else {
threadLoadable = loadable;
threadFragment.bindLoadable(loadable);
threadFragment.requestData();
}
threadPane.closePane();
updateActionBarState();
}
/**
* Handle opening from an external url.
*
* @param startUri
*/
private void handleIntentURI(Uri startUri) {
Logger.d(TAG, "Opening " + startUri.getPath());
List<String> parts = startUri.getPathSegments();
if (parts.size() == 1) {
// Board mode
String rawBoard = parts.get(0);
if (ChanApplication.getBoardManager().getBoardExists(rawBoard)) {
startLoadingBoard(new Loadable(rawBoard));
} else {
handleIntentURIFallback(startUri.toString());
}
} else if (parts.size() >= 3) {
// Thread mode
String rawBoard = parts.get(0);
int no = -1;
try {
no = Integer.parseInt(parts.get(2));
} catch (NumberFormatException e) {
}
int post = -1;
String fragment = startUri.getFragment();
if (fragment != null) {
int index = fragment.indexOf("p");
if (index >= 0) {
try {
post = Integer.parseInt(fragment.substring(index + 1));
} catch (NumberFormatException e) {
}
}
}
if (no >= 0 && ChanApplication.getBoardManager().getBoardExists(rawBoard)) {
startLoadingThread(new Loadable(rawBoard, no));
if (post >= 0) {
threadFragment.highlightPost(post);
}
} else {
handleIntentURIFallback(startUri.toString());
}
} else {
showUrlOpenPicker(startUri.toString());
}
}
private void handleIntentURIFallback(final String url) {
new AlertDialog.Builder(this).setTitle(R.string.open_unknown_title).setMessage(R.string.open_unknown)
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
}).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
showUrlOpenPicker(url);
}
}).setCancelable(false).create().show();
}
private void setBoardFragmentViewMode() {
if (ChanSettings.getBoardViewMode().equals("list")) {
boardFragment.setViewMode(ThreadManager.ViewMode.LIST);
} else if (ChanSettings.getBoardViewMode().equals("grid")) {
boardFragment.setViewMode(ThreadManager.ViewMode.GRID);
}
}
private class BoardSpinnerAdapter extends BaseAdapter {
private Context context;
private Spinner spinner;
private List<Board> boards;
private int lastSelectedPosition = 0;
public BoardSpinnerAdapter(Context context, Spinner spinner) {
this.context = context;
this.spinner = spinner;
setBoards();
}
public void setBoards() {
boards = ChanApplication.getBoardManager().getSavedBoards();
}
public void setBoard(String boardValue) {
for (int i = 0; i < boards.size(); i++) {
if (boards.get(i).value.equals(boardValue)) {
spinner.setSelection(i);
return;
}
}
}
public void onItemSelected(int position) {
if (position >= 0 && position < boards.size()) {
Loadable board = new Loadable(boards.get(position).value);
// onItemSelected is called after the view initializes,
// ignore if it's the same board
if (boardLoadable.equals(board))
return;
startLoadingBoard(board);
lastSelectedPosition = position;
} else {
startActivity(new Intent(context, BoardEditor.class));
spinner.setSelection(lastSelectedPosition);
}
}
@Override
public int getCount() {
return boards.size() + 1;
}
@Override
public long getItemId(final int position) {
return position;
}
@Override
public String getItem(final int position) {
if (position == getCount() - 1) {
return context.getString(R.string.board_select_add);
} else {
return boards.get(position).key;
}
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return createView(position, convertView, parent, true);
}
@Override
public View getView(final int position, View convertView, final ViewGroup parent) {
return createView(position, convertView, parent, false);
}
private View createView(int position, View convertView, ViewGroup parent, boolean dropDown) {
if (position == getCount() - 1) {
TextView textView = (TextView) LayoutInflater.from(context).inflate(R.layout.board_select_add, parent, false);
textView.setText(getItem(position));
return textView;
} else {
TextView textView = (TextView) LayoutInflater.from(context).inflate(
dropDown ? R.layout.board_select_spinner_dropdown : R.layout.board_select_spinner, parent, false);
textView.setText(getItem(position));
return textView;
}
}
}
}

@ -1,109 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.floens.chan.ChanApplication;
import org.floens.chan.R;
import org.floens.chan.core.model.SavedReply;
import org.floens.chan.ui.ThemeActivity;
import java.util.Random;
public class DeveloperActivity extends ThemeActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme();
setContentView(R.layout.toolbar_activity);
setToolbar();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
LinearLayout wrapper = new LinearLayout(this);
wrapper.setOrientation(LinearLayout.VERTICAL);
Button crashButton = new Button(this);
crashButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@SuppressWarnings({"unused", "NumericOverflow"})
int i = 1 / 0;
}
});
crashButton.setText("Crash the app");
wrapper.addView(crashButton);
String dbSummary = "";
dbSummary += "Database summary:\n";
dbSummary += ChanApplication.getDatabaseManager().getSummary();
TextView db = new TextView(this);
db.setPadding(0, 25, 0, 0);
db.setText(dbSummary);
wrapper.addView(db);
Button resetDbButton = new Button(this);
resetDbButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ChanApplication.getDatabaseManager().reset();
System.exit(0);
}
});
resetDbButton.setText("Delete database");
wrapper.addView(resetDbButton);
Button savedReplyDummyAdd = new Button(this);
savedReplyDummyAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
Random r = new Random();
int j = 0;
for (int i = 0; i < 100; i++) {
j += r.nextInt(10000);
ChanApplication.getDatabaseManager().saveReply(new SavedReply("g", j, "pass"));
}
recreate();
}
});
savedReplyDummyAdd.setText("Add test rows to savedReply");
wrapper.addView(savedReplyDummyAdd);
Button trimSavedReply = new Button(this);
trimSavedReply.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
ChanApplication.getDatabaseManager().trimSavedRepliesTable(10);
recreate();
}
});
trimSavedReply.setText("Trim savedreply table");
wrapper.addView(trimSavedReply);
setContentView(wrapper);
}
}

@ -25,7 +25,7 @@ import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.provider.OpenableColumns;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.core.reply.ReplyManager;
import org.floens.chan.utils.IOUtils;
import org.floens.chan.utils.Logger;
@ -53,7 +53,7 @@ public class ImagePickActivity extends Activity implements Runnable {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
replyManager = ChanApplication.getReplyManager();
replyManager = Chan.getReplyManager();
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);

@ -1,271 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.activity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.widget.ProgressBar;
import org.floens.chan.R;
import org.floens.chan.chan.ImageSearch;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.manager.ThreadManager;
import org.floens.chan.core.model.Post;
import org.floens.chan.ui.ThemeActivity;
import org.floens.chan.ui.adapter.ImageViewAdapter;
import org.floens.chan.ui.adapter.PostAdapter;
import org.floens.chan.ui.fragment.ImageViewFragment;
import org.floens.chan.utils.ImageSaver;
import org.floens.chan.utils.Logger;
import java.util.ArrayList;
import java.util.List;
/**
* An fragment pager that contains images. Call setPosts first, and then start
* the activity with startActivity()
*/
public class ImageViewActivity extends ThemeActivity implements ViewPager.OnPageChangeListener {
private static final String TAG = "ImageViewActivity";
private static PostAdapter postAdapterStatic;
private static int selectedNoStatic = -1;
private static ThreadManager threadManagerStatic;
private PostAdapter postAdapter;
private ThreadManager threadManager;
private int selectedNo;
private ImageViewAdapter adapter;
private ViewPager viewPager;
private ProgressBar progressBar;
private int currentPosition;
/**
* Set the posts to show
*
* @param adapter the adapter to get image data from
* @param selected the no that the user clicked on
*/
public static void launch(Activity activity, PostAdapter adapter, int selected, ThreadManager threadManager) {
postAdapterStatic = adapter;
selectedNoStatic = selected;
threadManagerStatic = threadManager;
Intent intent = new Intent(activity, ImageViewActivity.class);
activity.startActivity(intent);
activity.overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
if (postAdapterStatic == null || threadManagerStatic == null) {
Logger.e(TAG, "postadapter or threadmanager null");
finish();
return;
}
super.onCreate(savedInstanceState);
threadManager = threadManagerStatic;
threadManagerStatic = null;
postAdapter = postAdapterStatic;
postAdapterStatic = null;
selectedNo = selectedNoStatic;
selectedNoStatic = -1;
setContentView(R.layout.image_view);
setToolbar();
initProgressBar();
initPager();
}
private void initProgressBar() {
progressBar = (ProgressBar) findViewById(R.id.progress_bar);
// progressBar.setProgressDrawable(getResources().getDrawable(R.drawable.progressbar_no_bg));
progressBar.setIndeterminate(false);
progressBar.setMax(1000000);
}
private void initPager() {
// Get the posts with images
ArrayList<Post> imagePosts = new ArrayList<>();
// for (Post post : postAdapter.getList()) {
// if (post.hasImage) {
// imagePosts.add(post);
// }
// }
// Setup our pages and adapter
viewPager = (ViewPager) findViewById(R.id.image_pager);
adapter = new ImageViewAdapter(getFragmentManager(), this);
adapter.setList(imagePosts);
viewPager.setAdapter(adapter);
viewPager.setOnPageChangeListener(this);
// Select the right image
for (int i = 0; i < imagePosts.size(); i++) {
if (imagePosts.get(i).no == selectedNo) {
viewPager.setCurrentItem(i);
onPageSelected(i);
break;
}
}
}
@Override
protected void onStop() {
super.onStop();
// Avoid things like out of sync, since this is an activity.
finish();
}
@Override
public void finish() {
super.finish();
overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
}
@Override
public void onPageScrollStateChanged(int state) {
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
currentPosition = position;
for (int i = -1; i <= 1; i++) {
ImageViewFragment fragment = getFragment(position + i);
if (fragment != null) {
fragment.onDeselected();
}
}
ImageViewFragment fragment = getFragment(currentPosition);
if (fragment != null) {
fragment.onSelected(adapter, position);
}
Post post = adapter.getPost(position);
if (!threadManager.arePostRepliesOpen()) {
// postAdapter.scrollToPost(post.no); //TODO
}
}
public void invalidateActionBar() {
invalidateOptionsMenu();
}
public void updateActionBarIfSelected(ImageViewFragment targetFragment) {
ImageViewFragment fragment = getFragment(currentPosition);
if (fragment != null && fragment == targetFragment) {
fragment.onSelected(adapter, currentPosition);
}
}
public void setProgressBar(long current, long total, boolean done) {
if (done) {
progressBar.setVisibility(View.GONE);
} else {
progressBar.setVisibility(View.VISIBLE);
progressBar.setProgress((int) (((double) current / total) * progressBar.getMax()));
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
case R.id.action_download_album:
if (adapter.getList().size() > 0) {
List<ImageSaver.DownloadPair> list = new ArrayList<>();
String folderName = Post.generateTitle(adapter.getList().get(0), 10);
String filename;
for (Post post : adapter.getList()) {
filename = (ChanSettings.getImageSaveOriginalFilename() ? post.tim : post.filename) + "." + post.ext;
list.add(new ImageSaver.DownloadPair(post.imageUrl, filename));
}
ImageSaver.getInstance().saveAll(this, folderName, list);
}
return true;
default:
ImageViewFragment fragment = getFragment(currentPosition);
if (fragment != null) {
fragment.customOnOptionsItemSelected(item);
}
return super.onOptionsItemSelected(item);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.image_view, menu);
MenuItem imageSearch = menu.findItem(R.id.action_image_search);
SubMenu subMenu = imageSearch.getSubMenu();
for (ImageSearch engine : ImageSearch.engines) {
subMenu.add(Menu.NONE, engine.getId(), Menu.NONE, engine.getName());
}
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
ImageViewFragment fragment = getFragment(currentPosition);
if (fragment != null) {
fragment.onPrepareOptionsMenu(menu);
}
return super.onPrepareOptionsMenu(menu);
}
private ImageViewFragment getFragment(int i) {
if (adapter == null) {
return null;
} else if (i >= 0 && i < adapter.getCount()) {
Object o = adapter.instantiateItem(viewPager, i);
if (o instanceof ImageViewFragment) {
return (ImageViewFragment) o;
} else {
return null;
}
} else {
return null;
}
}
}

@ -1,38 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.activity;
import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
import org.floens.chan.utils.ThemeHelper;
public class LicenseActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this);
WebView webView = new WebView(this);
webView.loadUrl("file:///android_asset/html/license.html");
setContentView(webView);
}
}

@ -1,188 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.support.v7.widget.SwitchCompat;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;
import org.floens.chan.ChanApplication;
import org.floens.chan.R;
import org.floens.chan.core.reply.ReplyManager;
import org.floens.chan.core.reply.ReplyManager.PassResponse;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.ThemeActivity;
import org.floens.chan.utils.AndroidUtils;
public class PassSettingsActivity extends ThemeActivity implements OnCheckedChangeListener {
private SwitchCompat onSwitch;
private TextView toggleStatus;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme();
setContentView(R.layout.header_switch_layout);
setToolbar();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
findViewById(R.id.toggle_bar).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onSwitch.toggle();
}
});
toggleStatus = (TextView) findViewById(R.id.toggle_status);
onSwitch = (SwitchCompat) findViewById(R.id.toggle);
onSwitch.setOnCheckedChangeListener(this);
setSwitch(ChanSettings.getPassEnabled());
setFragment(ChanSettings.getPassEnabled());
}
@Override
public void onPause() {
super.onPause();
if (TextUtils.isEmpty(ChanSettings.getPassId())) {
ChanSettings.setPassEnabled(false);
setSwitch(false);
}
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setFragment(isChecked);
setSwitch(isChecked);
}
private void setSwitch(boolean enabled) {
onSwitch.setChecked(enabled);
toggleStatus.setText(enabled ? R.string.on : R.string.off);
ChanSettings.setPassEnabled(enabled);
}
private void setFragment(boolean enabled) {
if (enabled) {
FragmentTransaction t = getFragmentManager().beginTransaction();
t.replace(R.id.content, new PassSettingsFragment());
t.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
t.commit();
} else {
FragmentTransaction t = getFragmentManager().beginTransaction();
t.replace(R.id.content, new TextFragment());
t.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
t.commit();
}
}
public static class TextFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup group, Bundle savedInstanceState) {
View container = inflater.inflate(R.layout.preference_pass, null);
TextView link = (TextView) container.findViewById(R.id.pass_link);
link.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AndroidUtils.openLink(v.getContext().getString(R.string.pass_info_link));
}
});
return container;
}
}
public static class PassSettingsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preference_pass);
Preference login = findPreference("preference_pass_login");
login.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
onLoginClick(ChanSettings.getPassToken(), ChanSettings.getPassPin());
return true;
}
});
updateLoginButton();
}
private void updateLoginButton() {
findPreference("preference_pass_login").setTitle(TextUtils.isEmpty(ChanSettings.getPassId()) ? R.string.pass_login : R.string.pass_logout);
}
private void onLoginClick(String token, String pin) {
if (TextUtils.isEmpty(ChanSettings.getPassId())) {
// Login
final ProgressDialog dialog = ProgressDialog.show(getActivity(), null, "Logging in");
ChanApplication.getReplyManager().postPass(token, pin, new ReplyManager.PassListener() {
@Override
public void onResponse(PassResponse response) {
dialog.dismiss();
if (getActivity() == null)
return;
if (response.unknownError) {
WebView webView = new WebView(getActivity());
WebSettings settings = webView.getSettings();
settings.setSupportZoom(true);
webView.loadData(response.responseData, "text/html", null);
new AlertDialog.Builder(getActivity()).setView(webView).setNeutralButton(R.string.ok, null).show();
} else {
new AlertDialog.Builder(getActivity()).setMessage(response.message)
.setNeutralButton(R.string.ok, null).show();
ChanSettings.setPassId(response.passId);
}
updateLoginButton();
}
});
} else {
// Logout
ChanSettings.setPassId("");
updateLoginButton();
}
}
}
}

@ -1,81 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.view.MenuItem;
import org.floens.chan.R;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.ui.ThemeActivity;
import org.floens.chan.ui.fragment.ReplyFragment;
import org.floens.chan.utils.Logger;
public class ReplyActivity extends ThemeActivity {
private static final String TAG = "ReplyActivity";
private static Loadable staticLoadable;
public static void setLoadable(Loadable l) {
staticLoadable = l;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Loadable loadable = staticLoadable;
staticLoadable = null;
if (loadable != null && savedInstanceState == null) {
setTheme();
setContentView(R.layout.toolbar_activity);
setToolbar();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.content, ReplyFragment.newInstance(loadable, false), "reply");
ft.commitAllowingStateLoss();
} else if (savedInstanceState == null) {
Logger.e(TAG, "Loadable was null, exiting!");
finish();
}
}
@Override
public void onBackPressed() {
Fragment f = getFragmentManager().findFragmentByTag("reply");
if (f != null && ((ReplyFragment) f).onBackPressed()) {
super.onBackPressed();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

@ -1,90 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import org.floens.chan.R;
import org.floens.chan.ui.ThemeActivity;
import org.floens.chan.ui.fragment.SettingsFragment;
import org.floens.chan.utils.ThemeHelper;
public class SettingsActivity extends ThemeActivity {
private static boolean doingThemeRestart = false;
private static ThemeHelper.Theme lastTheme;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme();
setContentView(R.layout.toolbar_activity);
setToolbar();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (!doingThemeRestart) {
lastTheme = ThemeHelper.getInstance().getTheme();
}
SettingsFragment frag = new SettingsFragment();
frag.setArguments(getIntent().getExtras());
getFragmentManager().beginTransaction().replace(R.id.content, frag).commit();
}
public void restart(Intent intent) {
doingThemeRestart = true;
startActivity(intent);
finish();
doingThemeRestart = false;
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
@Override
protected void onPause() {
super.onPause();
if (ThemeHelper.getInstance().getTheme() != lastTheme) {
lastTheme = ThemeHelper.getInstance().getTheme();
BaseActivity.doRestartOnResume = true;
}
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.settings, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
if (item.getItemId() == R.id.action_settings_advanced) {
startActivity(new Intent(this, AdvancedSettingsActivity.class));
return true;
} else if (item.getItemId() == android.R.id.home) {
finish();
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
}

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.activity;
import android.app.AlertDialog;
@ -7,7 +24,7 @@ import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.ViewGroup;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.settings.ChanSettings;
@ -103,21 +120,21 @@ public class StartActivity extends AppCompatActivity {
protected void onStart() {
super.onStart();
ChanApplication.getInstance().activityEnteredForeground();
Chan.getInstance().activityEnteredForeground();
}
@Override
protected void onStop() {
super.onStop();
ChanApplication.getInstance().activityEnteredBackground();
Chan.getInstance().activityEnteredBackground();
}
@Override
protected void onPause() {
super.onPause();
ChanApplication.getWatchManager().updateDatabase();
Chan.getWatchManager().updateDatabase();
}
private Controller stackTop() {

@ -1,198 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceFragment;
import android.support.v7.widget.SwitchCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;
import org.floens.chan.ChanApplication;
import org.floens.chan.R;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.ThemeActivity;
public class WatchSettingsActivity extends ThemeActivity implements OnCheckedChangeListener {
private SwitchCompat watchSwitch;
private TextView toggleStatus;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme();
setContentView(R.layout.header_switch_layout);
setToolbar();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
findViewById(R.id.toggle_bar).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
watchSwitch.toggle();
}
});
toggleStatus = (TextView) findViewById(R.id.toggle_status);
watchSwitch = (SwitchCompat) findViewById(R.id.toggle);
watchSwitch.setOnCheckedChangeListener(this);
setSwitch(ChanSettings.getWatchEnabled());
setFragment(ChanSettings.getWatchEnabled());
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setFragment(isChecked);
setSwitch(isChecked);
}
private void setSwitch(boolean enabled) {
watchSwitch.setChecked(enabled);
toggleStatus.setText(enabled ? R.string.on : R.string.off);
ChanSettings.setWatchEnabled(enabled);
}
private void setFragment(boolean enabled) {
FragmentTransaction t = getFragmentManager().beginTransaction();
if (enabled) {
t.replace(R.id.content, new WatchSettingsFragment());
} else {
t.replace(R.id.content, TextFragment.newInstance(R.string.watch_info_text));
}
t.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
t.commit();
}
public static class TextFragment extends Fragment {
public static TextFragment newInstance(int textResource) {
TextFragment f = new TextFragment();
Bundle bundle = new Bundle();
bundle.putInt("text_resource", textResource);
f.setArguments(bundle);
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup group, Bundle savedInstanceState) {
ViewGroup container = (ViewGroup) inflater.inflate(R.layout.watch_description, null);
TextView text = (TextView) container.findViewById(R.id.text);
text.setText(getArguments().getInt("text_resource"));
return container;
}
}
public static class WatchSettingsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preference_watch);
final ListPreference backgroundTimeout = (ListPreference) findPreference("preference_watch_background_timeout");
String currentValue = backgroundTimeout.getValue();
if (currentValue == null) {
backgroundTimeout.setValue((String) backgroundTimeout.getEntryValues()[0]);
currentValue = backgroundTimeout.getValue();
}
updateListSummary(backgroundTimeout, currentValue);
backgroundTimeout.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
updateListSummary(backgroundTimeout, newValue.toString());
return true;
}
});
final ListPreference notifyMode = (ListPreference) findPreference("preference_watch_notify_mode");
String currentNotifyMode = notifyMode.getValue();
if (currentNotifyMode == null) {
notifyMode.setValue((String) notifyMode.getEntryValues()[0]);
currentNotifyMode = notifyMode.getValue();
}
updateListSummary(notifyMode, currentNotifyMode);
notifyMode.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
updateListSummary(notifyMode, newValue.toString());
return true;
}
});
final ListPreference sound = (ListPreference) findPreference("preference_watch_sound");
String currentSound = sound.getValue();
if (currentSound == null) {
sound.setValue((String) sound.getEntryValues()[0]);
currentSound = sound.getValue();
}
updateListSummary(sound, currentSound);
sound.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
updateListSummary(sound, newValue.toString());
return true;
}
});
final ListPreference led = (ListPreference) findPreference("preference_watch_led");
String currentLed = led.getValue();
if (currentLed == null) {
led.setValue((String) led.getEntryValues()[0]);
currentLed = led.getValue();
}
updateListSummary(led, currentLed);
led.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
updateListSummary(led, newValue.toString());
return true;
}
});
findPreference("preference_watch_background_enabled").setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(final Preference preference, final Object newValue) {
ChanApplication.getWatchManager().onBackgroundWatchingChanged((Boolean) newValue);
return true;
}
});
}
private void updateListSummary(ListPreference backgroundTimeout, String value) {
int index = backgroundTimeout.findIndexOfValue(value);
backgroundTimeout.setSummary(backgroundTimeout.getEntries()[index]);
}
}
}

@ -1,67 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.adapter;
import android.app.Fragment;
import android.app.FragmentManager;
import android.support.v13.app.FragmentStatePagerAdapter;
import org.floens.chan.core.model.Post;
import org.floens.chan.ui.activity.ImageViewActivity;
import org.floens.chan.ui.fragment.ImageViewFragment;
import java.util.ArrayList;
import java.util.List;
public class ImageViewAdapter extends FragmentStatePagerAdapter {
private final ImageViewActivity activity;
private final ArrayList<Post> postList = new ArrayList<>();
public ImageViewAdapter(FragmentManager fragmentManager, ImageViewActivity activity) {
super(fragmentManager);
this.activity = activity;
}
@Override
public int getCount() {
return postList.size();
}
@Override
public Fragment getItem(int position) {
return ImageViewFragment.newInstance(postList.get(position), activity, position);
}
public Post getPost(int position) {
if (position < 0 || position >= getCount())
return null;
return postList.get(position);
}
public void setList(ArrayList<Post> list) {
postList.clear();
postList.addAll(list);
notifyDataSetChanged();
}
public List<Post> getList() {
return postList;
}
}

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.adapter;
import android.content.Context;

@ -1,6 +1,22 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.adapter;
import android.os.Build;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
@ -8,7 +24,7 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.core.model.Pin;
import org.floens.chan.core.settings.ChanSettings;
@ -21,7 +37,7 @@ import java.util.List;
import static org.floens.chan.utils.AndroidUtils.ROBOTO_MEDIUM;
import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AndroidUtils.getAttrDrawable;
import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground;
public class PinAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements SwipeListener.Callback {
private static final int PIN_OFFSET = 3;
@ -156,7 +172,7 @@ public class PinAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> im
@Override
public void removeItem(int position) {
ChanApplication.getWatchManager().removePin(pins.get(position - PIN_OFFSET));
Chan.getWatchManager().removePin(pins.get(position - PIN_OFFSET));
}
@Override
@ -240,11 +256,7 @@ public class PinAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> im
watchCountText = (TextView) itemView.findViewById(R.id.watch_count);
watchCountText.setTypeface(ROBOTO_MEDIUM);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
watchCountText.setBackground(getAttrDrawable(itemView.getContext(), android.R.attr.selectableItemBackgroundBorderless));
} else {
watchCountText.setBackgroundResource(R.drawable.item_background);
}
setRoundItemBackground(watchCountText);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
@ -271,11 +283,7 @@ public class PinAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> im
text = (TextView) itemView.findViewById(R.id.text);
text.setTypeface(ROBOTO_MEDIUM);
image = (ImageView) itemView.findViewById(R.id.image);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
image.setBackground(getAttrDrawable(itemView.getContext(), android.R.attr.selectableItemBackgroundBorderless));
} else {
image.setBackgroundResource(R.drawable.item_background);
}
setRoundItemBackground(image);
image.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

@ -1,231 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.adapter;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.floens.chan.ChanApplication;
import org.floens.chan.R;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.model.Pin;
import org.floens.chan.ui.view.CustomNetworkImageView;
import java.util.ArrayList;
import java.util.List;
import static org.floens.chan.utils.AndroidUtils.dp;
public class PinnedAdapter extends BaseAdapter {
private final static int VIEW_TYPE_ITEM = 0;
private final static int VIEW_TYPE_HEADER = 1;
private Context context;
private ListView listView;
private List<Pin> pins = new ArrayList<>();
private boolean postInvalidated = false;
public PinnedAdapter(Context context, ListView listView) {
this.context = context;
this.listView = listView;
}
@Override
public int getCount() {
return pins.size() + 1;
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(final int position) {
return position == 0 ? VIEW_TYPE_HEADER : VIEW_TYPE_ITEM;
}
@Override
public Pin getItem(final int position) {
switch (getItemViewType(position)) {
case VIEW_TYPE_ITEM:
int itemPosition = position - 1;
if (itemPosition >= 0 && itemPosition < pins.size()) {
return pins.get(itemPosition);
} else {
return null;
}
case VIEW_TYPE_HEADER:
return null;
default:
return null;
}
}
@Override
public long getItemId(int position) {
switch (getItemViewType(position)) {
case VIEW_TYPE_ITEM:
int itemPosition = position - 1;
if (itemPosition >= 0 && itemPosition < pins.size()) {
return pins.get(itemPosition).id;
} else {
return -1;
}
case VIEW_TYPE_HEADER:
return -1;
default:
return -1;
}
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
switch (getItemViewType(position)) {
case VIEW_TYPE_ITEM: {
final Pin pin = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.pin_item, null);
}
CustomNetworkImageView imageView = (CustomNetworkImageView) convertView.findViewById(R.id.image);
if (pin.thumbnailUrl != null) {
imageView.setVisibility(View.VISIBLE);
imageView.setFadeIn(0);
imageView.forceImageDimensions(dp(48), dp(48));
imageView.setImageUrl(pin.thumbnailUrl, ChanApplication.getVolleyImageLoader());
} else {
imageView.setVisibility(View.GONE);
}
((TextView) convertView.findViewById(R.id.text)).setText(pin.loadable.title);
FrameLayout timeContainer = (FrameLayout) convertView.findViewById(R.id.time_container);
FrameLayout countContainer = (FrameLayout) convertView.findViewById(R.id.pin_count_container);
if (ChanSettings.getWatchEnabled()) {
countContainer.setVisibility(View.VISIBLE);
TextView timeView = (TextView) convertView.findViewById(R.id.time);
if (pin.watching && pin.getPinWatcher() != null && ChanSettings.getWatchCountdownVisibleEnabled()) {
timeContainer.setVisibility(View.VISIBLE);
long timeRaw = pin.getPinWatcher().getTimeUntilNextLoad();
long time = 0;
if (timeRaw > 0) {
time = timeRaw / 1000L;
time = Math.min(9999, time);
}
timeView.setText(Long.toString(time));
postInvalidate();
} else {
timeContainer.setVisibility(View.GONE);
}
TextView countView = (TextView) convertView.findViewById(R.id.pin_count);
ProgressBar loadView = (ProgressBar) convertView.findViewById(R.id.pin_load);
if (pin.isError) {
countView.setText("Err");
} else {
int count = pin.getNewPostCount();
String total = Integer.toString(count);
if (count > 999) {
total = "1k+";
}
countView.setText(total);
}
if (pin.getPinWatcher() != null && pin.getPinWatcher().isLoading()) {
countView.setVisibility(View.GONE);
loadView.setVisibility(View.VISIBLE);
} else {
loadView.setVisibility(View.GONE);
countView.setVisibility(View.VISIBLE);
}
countContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// pin.toggleWatch();
}
});
if (!pin.watching) {
countContainer.setBackgroundResource(R.drawable.pin_icon_gray);
} else if (pin.getNewQuoteCount() > 0) {
countContainer.setBackgroundResource(R.drawable.pin_icon_red);
} else {
countContainer.setBackgroundResource(R.drawable.pin_icon_blue);
}
} else {
timeContainer.setVisibility(View.GONE);
countContainer.setVisibility(View.GONE);
}
return convertView;
}
case VIEW_TYPE_HEADER: {
if (convertView == null) {
convertView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.pin_item_header, null);
((TextView) convertView.findViewById(R.id.pin_header)).setText(R.string.drawer_pinned);
}
return convertView;
}
default:
return null;
}
}
public void reload() {
pins.clear();
pins.addAll(ChanApplication.getWatchManager().getPins());
notifyDataSetChanged();
}
private void postInvalidate() {
if (!postInvalidated) {
postInvalidated = true;
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
postInvalidated = false;
listView.invalidateViews();
}
}, 1000);
}
}
}

@ -1,140 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.animation;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.AbsListView;
import android.widget.ListView;
public class ScrollerRunnable implements Runnable {
private static final int SCROLL_DURATION = 300;
private static final int MOVE_DOWN_POS = 1;
private static final int MOVE_UP_POS = 2;
private final AbsListView mList;
private int mMode;
private int mTargetPos;
private int mLastSeenPos;
private int mScrollDuration;
private final int mExtraScroll;
public ScrollerRunnable(AbsListView listView) {
mList = listView;
mExtraScroll = ViewConfiguration.get(mList.getContext()).getScaledFadingEdgeLength();
}
public void start(int position) {
stop();
final int firstPos = mList.getFirstVisiblePosition();
final int lastPos = firstPos + mList.getChildCount() - 1;
int viewTravelCount;
if (position <= firstPos) {
viewTravelCount = firstPos - position + 1;
mMode = MOVE_UP_POS;
} else if (position >= lastPos) {
viewTravelCount = position - lastPos + 1;
mMode = MOVE_DOWN_POS;
} else {
// Already on screen, nothing to do
return;
}
if (viewTravelCount > 0) {
mScrollDuration = SCROLL_DURATION / viewTravelCount;
} else {
mScrollDuration = SCROLL_DURATION;
}
mTargetPos = position;
mLastSeenPos = ListView.INVALID_POSITION;
mList.post(this);
}
void stop() {
mList.removeCallbacks(this);
}
@Override
public void run() {
final int listHeight = mList.getHeight();
final int firstPos = mList.getFirstVisiblePosition();
switch (mMode) {
case MOVE_DOWN_POS: {
final int lastViewIndex = mList.getChildCount() - 1;
final int lastPos = firstPos + lastViewIndex;
if (lastViewIndex < 0) {
return;
}
if (lastPos == mLastSeenPos) {
// No new views, let things keep going.
mList.post(this);
return;
}
final View lastView = mList.getChildAt(lastViewIndex);
final int lastViewHeight = lastView.getHeight();
final int lastViewTop = lastView.getTop();
final int lastViewPixelsShowing = listHeight - lastViewTop;
final int extraScroll = lastPos < mList.getCount() - 1 ? mExtraScroll : mList.getPaddingBottom();
mList.smoothScrollBy(lastViewHeight - lastViewPixelsShowing + extraScroll, mScrollDuration);
mLastSeenPos = lastPos;
if (lastPos < mTargetPos) {
mList.post(this);
}
break;
}
case MOVE_UP_POS: {
if (firstPos == mLastSeenPos) {
// No new views, let things keep going.
mList.post(this);
return;
}
final View firstView = mList.getChildAt(0);
if (firstView == null) {
return;
}
final int firstViewTop = firstView.getTop();
final int extraScroll = firstPos > 0 ? mExtraScroll : mList.getPaddingTop();
mList.smoothScrollBy(firstViewTop - extraScroll, mScrollDuration);
mLastSeenPos = firstPos;
if (firstPos > mTargetPos) {
mList.post(this);
}
break;
}
default:
break;
}
}
}

@ -1,409 +0,0 @@
/*
* Copyright 2013 Google Inc.
*
* 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 org.floens.chan.ui.animation;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A {@link android.view.View.OnTouchListener} that makes the list items in a
* {@link ListView} dismissable. {@link ListView} is given special treatment
* because by default it handles touches for its list items... i.e. it's in
* charge of drawing the pressed state (the list selector), handling list item
* clicks, etc.
* <p/>
* <p>
* After creating the listener, the caller should also call
* {@link ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)}
* , passing in the scroll listener returned by {@link #makeScrollListener()}.
* If a scroll listener is already assigned, the caller should still pass scroll
* changes through to this listener. This will ensure that this
* {@link SwipeDismissListViewTouchListener} is paused during list view
* scrolling.
* </p>
* <p/>
* <p>
* Example usage:
* </p>
* <p/>
* <pre>
* SwipeDismissListViewTouchListener touchListener = new SwipeDismissListViewTouchListener(listView,
* new SwipeDismissListViewTouchListener.OnDismissCallback() {
* public void onDismiss(ListView listView, int[] reverseSortedPositions) {
* for (int position : reverseSortedPositions) {
* adapter.remove(adapter.getItem(position));
* }
* adapter.notifyDataSetChanged();
* }
* });
* listView.setOnTouchListener(touchListener);
* listView.setOnScrollListener(touchListener.makeScrollListener());
* </pre>
* <p/>
* <p>
* This class Requires API level 12 or later due to use of
* {@link android.view.ViewPropertyAnimator}.
* </p>
*/
public class SwipeDismissListViewTouchListener implements View.OnTouchListener {
// Cached ViewConfiguration and system-wide constant values
private int mSlop;
private final int mMinFlingVelocity;
private final int mMaxFlingVelocity;
private final long mAnimationTime;
// Fixed properties
private final ListView mListView;
private final DismissCallbacks mCallbacks;
private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
// Transient properties
private final List<PendingDismissData> mPendingDismisses = new ArrayList<>();
private int mDismissAnimationRefCount = 0;
private float mDownX;
private boolean mSwiping;
private VelocityTracker mVelocityTracker;
private int mDownPosition;
private View mDownView;
private boolean mPaused;
/**
* The callback interface used by {@link SwipeDismissListViewTouchListener}
* to inform its client about a successful dismissal of one or more list
* item positions.
*/
public interface DismissCallbacks {
/**
* Called to determine whether the given position can be dismissed.
*/
boolean canDismiss(int position);
/**
* Called when the user has indicated they she would like to dismiss one
* or more list item positions.
*
* @param listView The originating {@link ListView}.
* @param reverseSortedPositions An array of positions to dismiss, sorted in descending
* order for convenience.
*/
void onDismiss(ListView listView, int[] reverseSortedPositions);
}
/**
* Constructs a new swipe-to-dismiss touch listener for the given list view.
*
* @param listView The list view whose items should be dismissable.
* @param callbacks The callback to trigger when the user has indicated that she
* would like to dismiss one or more list items.
*/
public SwipeDismissListViewTouchListener(ListView listView, DismissCallbacks callbacks) {
ViewConfiguration vc = ViewConfiguration.get(listView.getContext());
mSlop = vc.getScaledTouchSlop();
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16;
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
mAnimationTime = listView.getContext().getResources().getInteger(android.R.integer.config_shortAnimTime);
mListView = listView;
mCallbacks = callbacks;
}
public int getSlop() {
return mSlop;
}
public void setSlop(int slop) {
mSlop = slop;
}
/**
* Enables or disables (pauses or resumes) watching for swipe-to-dismiss
* gestures.
*
* @param enabled Whether or not to watch for gestures.
*/
public void setEnabled(boolean enabled) {
mPaused = !enabled;
}
/**
* Returns an {@link android.widget.AbsListView.OnScrollListener} to be
* added to the {@link ListView} using
* {@link ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)}
* . If a scroll listener is already assigned, the caller should still pass
* scroll changes through to this listener. This will ensure that this
* {@link SwipeDismissListViewTouchListener} is paused during list view
* scrolling.</p>
*
* @see SwipeDismissListViewTouchListener
*/
public AbsListView.OnScrollListener makeScrollListener() {
return new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
setEnabled(scrollState != AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
}
@Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
}
};
}
/**
* Manually cause the item at the given position to be dismissed (trigger
* the dismiss animation).
*/
public void dismiss(int position) {
dismiss(getViewForPosition(position), position, true);
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (mViewWidth < 2) {
mViewWidth = mListView.getWidth();
}
mListView.requestDisallowInterceptTouchEvent(true);
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
if (mPaused) {
return false;
}
// TODO: ensure this is a finger, and set a flag
// Find the child view that was touched (perform a hit test)
Rect rect = new Rect();
int childCount = mListView.getChildCount();
int[] listViewCoords = new int[2];
mListView.getLocationOnScreen(listViewCoords);
int x = (int) motionEvent.getRawX() - listViewCoords[0];
int y = (int) motionEvent.getRawY() - listViewCoords[1];
View child;
for (int i = 0; i < childCount; i++) {
child = mListView.getChildAt(i);
child.getHitRect(rect);
if (rect.contains(x, y)) {
mDownView = child;
break;
}
}
if (mDownView != null) {
mDownX = motionEvent.getRawX();
mDownPosition = mListView.getPositionForView(mDownView);
if (mCallbacks.canDismiss(mDownPosition)) {
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(motionEvent);
} else {
mDownView = null;
}
}
view.onTouchEvent(motionEvent);
return true;
}
case MotionEvent.ACTION_UP: {
if (mVelocityTracker == null) {
break;
}
float deltaX = motionEvent.getRawX() - mDownX;
mVelocityTracker.addMovement(motionEvent);
mVelocityTracker.computeCurrentVelocity(1000);
float velocityX = mVelocityTracker.getXVelocity();
float absVelocityX = Math.abs(velocityX);
float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());
boolean dismiss = false;
boolean dismissRight = false;
if (Math.abs(deltaX) > mViewWidth / 2) {
dismiss = true;
dismissRight = deltaX > 0;
} else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity
&& absVelocityY < absVelocityX) {
// dismiss only if flinging in the same direction as dragging
dismiss = (velocityX < 0) == (deltaX < 0);
dismissRight = mVelocityTracker.getXVelocity() > 0;
}
if (dismiss) {
// dismiss
dismiss(mDownView, mDownPosition, dismissRight);
} else {
// cancel
mDownView.animate().translationX(0).alpha(1).setDuration(mAnimationTime).setListener(null);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
mDownX = 0;
mDownView = null;
mDownPosition = ListView.INVALID_POSITION;
mSwiping = false;
break;
}
case MotionEvent.ACTION_CANCEL: {
if (mVelocityTracker == null) {
break;
}
if (mDownView != null) {
// cancel
mDownView.animate().translationX(0).alpha(1).setDuration(mAnimationTime).setListener(null);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
mDownX = 0;
mDownView = null;
mDownPosition = ListView.INVALID_POSITION;
mSwiping = false;
break;
}
case MotionEvent.ACTION_MOVE: {
if (mVelocityTracker == null || mPaused) {
break;
}
mVelocityTracker.addMovement(motionEvent);
float deltaX = motionEvent.getRawX() - mDownX;
if (Math.abs(deltaX) > mSlop) {
mSwiping = true;
mListView.requestDisallowInterceptTouchEvent(true);
// Cancel ListView's touch (un-highlighting the item)
MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL
| (motionEvent.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
mListView.onTouchEvent(cancelEvent);
cancelEvent.recycle();
}
if (mSwiping) {
mDownView.setTranslationX(deltaX);
mDownView.setAlpha(Math.max(0.15f, Math.min(1f, 1f - 2f * Math.abs(deltaX) / mViewWidth)));
return true;
}
break;
}
}
return false;
}
private void dismiss(final View view, final int position, boolean dismissRight) {
++mDismissAnimationRefCount;
if (view == null) {
// No view, shortcut to calling onDismiss to let it deal with adapter
// updates and all that.
mCallbacks.onDismiss(mListView, new int[]{position});
return;
}
view.animate().translationX(dismissRight ? mViewWidth : -mViewWidth).alpha(0).setDuration(mAnimationTime)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
performDismiss(view, position);
}
});
}
private View getViewForPosition(int position) {
int index = position - (mListView.getFirstVisiblePosition() - mListView.getHeaderViewsCount());
return (index >= 0 && index < mListView.getChildCount()) ? mListView.getChildAt(index) : null;
}
class PendingDismissData implements Comparable<PendingDismissData> {
public int position;
public View view;
public PendingDismissData(int position, View view) {
this.position = position;
this.view = view;
}
@Override
public int compareTo(PendingDismissData other) {
// Sort by descending position
return other.position - position;
}
}
private void performDismiss(final View dismissView, final int dismissPosition) {
// Animate the dismissed list item to zero-height and fire the dismiss callback when
// all dismissed list item animations have completed. This triggers layout on each animation
// frame; in the future we may want to do something smarter and more performant.
final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
final int originalHeight = dismissView.getHeight();
ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
--mDismissAnimationRefCount;
if (mDismissAnimationRefCount == 0) {
// No active animations, process all pending dismisses.
// Sort by descending position
Collections.sort(mPendingDismisses);
int[] dismissPositions = new int[mPendingDismisses.size()];
for (int i = mPendingDismisses.size() - 1; i >= 0; i--) {
dismissPositions[i] = mPendingDismisses.get(i).position;
}
mCallbacks.onDismiss(mListView, dismissPositions);
ViewGroup.LayoutParams lp;
for (PendingDismissData pendingDismiss : mPendingDismisses) {
// Reset view presentation
pendingDismiss.view.setAlpha(1f);
pendingDismiss.view.setTranslationX(0);
lp = pendingDismiss.view.getLayoutParams();
lp.height = originalHeight;
pendingDismiss.view.setLayoutParams(lp);
}
mPendingDismisses.clear();
}
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
lp.height = (Integer) valueAnimator.getAnimatedValue();
dismissView.setLayoutParams(lp);
}
});
mPendingDismisses.add(new PendingDismissData(dismissPosition, dismissView));
animator.start();
}
}

@ -1,55 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.animation;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
/**
* Contains the TranslateAnimation's for a horizontal scrolling ViewFlipper.
*/
public class ViewFlipperAnimations {
public static TranslateAnimation BACK_IN;
public static TranslateAnimation BACK_OUT;
public static TranslateAnimation NEXT_IN;
public static TranslateAnimation NEXT_OUT;
static {
// Setup the static TranslateAnimations for the ViewFlipper
BACK_IN = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, -1f, Animation.RELATIVE_TO_PARENT, 0f, 0, 0f, 0,
0f);
BACK_IN.setInterpolator(new AccelerateDecelerateInterpolator());
BACK_IN.setDuration(300);
BACK_OUT = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, 0f, Animation.RELATIVE_TO_PARENT, 1f, 0, 0f, 0,
0f);
BACK_OUT.setInterpolator(new AccelerateDecelerateInterpolator());
BACK_OUT.setDuration(300);
NEXT_IN = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, 1f, Animation.RELATIVE_TO_PARENT, 0f, 0, 0f, 0,
0f);
NEXT_IN.setInterpolator(new AccelerateDecelerateInterpolator());
NEXT_IN.setDuration(300);
NEXT_OUT = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, 0f, Animation.RELATIVE_TO_PARENT, -1f, 0, 0f,
0, 0f);
NEXT_OUT.setInterpolator(new AccelerateDecelerateInterpolator());
NEXT_OUT.setDuration(300);
}
}

@ -1,10 +1,26 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.cell;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.support.annotation.NonNull;
import android.text.Layout;
import android.text.Spannable;
@ -29,7 +45,7 @@ import android.widget.TextView;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Post;
@ -46,8 +62,8 @@ import java.util.ArrayList;
import java.util.List;
import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AndroidUtils.getAttrDrawable;
import static org.floens.chan.utils.AndroidUtils.getRes;
import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground;
import static org.floens.chan.utils.AndroidUtils.sp;
public class PostCell extends RelativeLayout implements PostLinkable.Callback {
@ -132,17 +148,9 @@ public class PostCell extends RelativeLayout implements PostLinkable.Callback {
replies.setTextSize(textSizeSp);
replies.setPadding(paddingPx, 0, paddingPx, paddingPx);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
replies.setBackground(getAttrDrawable(getContext(), android.R.attr.selectableItemBackgroundBorderless));
} else {
replies.setBackgroundResource(R.drawable.item_background);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
options.setBackground(getAttrDrawable(getContext(), android.R.attr.selectableItemBackgroundBorderless));
} else {
options.setBackgroundResource(R.drawable.item_background);
}
setRoundItemBackground(replies);
setRoundItemBackground(options);
RelativeLayout.LayoutParams dividerParams = (LayoutParams) divider.getLayoutParams();
dividerParams.leftMargin = paddingPx;
@ -382,7 +390,7 @@ public class PostCell extends RelativeLayout implements PostLinkable.Callback {
private void loadCountryIcon() {
final Post requestedPost = post;
ChanApplication.getVolleyImageLoader().get(post.countryUrl, new ImageLoader.ImageListener() {
Chan.getVolleyImageLoader().get(post.countryUrl, new ImageLoader.ImageListener() {
@Override
public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null && PostCell.this.post == requestedPost) {

@ -1,7 +1,23 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.cell;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
@ -14,7 +30,6 @@ import org.floens.chan.core.model.ChanThread;
import org.floens.chan.core.model.Post;
import static org.floens.chan.utils.AndroidUtils.ROBOTO_MEDIUM;
import static org.floens.chan.utils.AndroidUtils.getAttrDrawable;
public class ThreadStatusCell extends LinearLayout implements View.OnClickListener {
private static final int UPDATE_INTERVAL = 1000;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.controller;
import android.app.Activity;
@ -42,10 +59,11 @@ public class AdvancedSettingsController extends SettingsController {
private void populatePreferences() {
SettingsGroup settings = new SettingsGroup(string(R.string.settings_group_advanced));
// TODO change this to a presenting controller
saveLocation = (LinkSettingView) settings.add(new LinkSettingView(this, string(R.string.setting_save_folder), null, new View.OnClickListener() {
@Override
public void onClick(View v) {
File dir = ChanSettings.getImageSaveDirectory();
File dir = new File(ChanSettings.saveLocation.get());
if (!dir.mkdirs() && !dir.isDirectory()) {
new AlertDialog.Builder(context).setMessage(R.string.setting_save_folder_error_create_folder).show();
} else {
@ -66,8 +84,8 @@ public class AdvancedSettingsController extends SettingsController {
settings.add(new BooleanSettingView(this, ChanSettings.shareUrl, string(R.string.setting_share_url), string(R.string.setting_share_url_description)));
settings.add(new BooleanSettingView(this, ChanSettings.networkHttps, string(R.string.setting_network_https), string(R.string.setting_network_https_description)));
settings.add(new BooleanSettingView(this, ChanSettings.forcePhoneLayout, string(R.string.setting_force_phone_layout), null));
settings.add(new BooleanSettingView(this, ChanSettings.anonymize, string(R.string.preference_anonymize), null));
settings.add(new BooleanSettingView(this, ChanSettings.anonymizeIds, string(R.string.preference_anonymize_ids), null));
settings.add(new BooleanSettingView(this, ChanSettings.anonymize, string(R.string.setting_anonymize), null));
settings.add(new BooleanSettingView(this, ChanSettings.anonymizeIds, string(R.string.setting_anonymize_ids), null));
settings.add(new BooleanSettingView(this, ChanSettings.repliesButtonsBottom, string(R.string.setting_buttons_bottom), null));
settings.add(new BooleanSettingView(this, ChanSettings.confirmExit, string(R.string.setting_confirm_exit), null));

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.controller;
import android.app.AlertDialog;
@ -19,7 +36,7 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.manager.BoardManager;
@ -40,7 +57,7 @@ import static org.floens.chan.utils.AndroidUtils.dp;
public class BoardEditController extends Controller implements SwipeListener.Callback, ToolbarMenuItem.ToolbarMenuItemCallback {
private static final int ADD_ID = 1;
private final BoardManager boardManager = ChanApplication.getBoardManager();
private final BoardManager boardManager = Chan.getBoardManager();
private RecyclerView recyclerView;
private BoardEditAdapter adapter;
@ -173,7 +190,7 @@ public class BoardEditController extends Controller implements SwipeListener.Cal
}
// Normal add
List<Board> all = ChanApplication.getBoardManager().getAllBoards();
List<Board> all = Chan.getBoardManager().getAllBoards();
for (Board board : all) {
if (board.value.equals(value)) {
board.saved = true;

@ -25,7 +25,7 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.manager.BoardManager;
@ -68,8 +68,8 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
navigationItem.menu = menu;
navigationItem.hasBack = false;
menu.addItem(new ToolbarMenuItem(context, this, REFRESH_ID, R.drawable.ic_action_refresh));
menu.addItem(new ToolbarMenuItem(context, this, POST_ID, R.drawable.ic_action_write));
menu.addItem(new ToolbarMenuItem(context, this, REFRESH_ID, R.drawable.ic_refresh_white_24dp));
menu.addItem(new ToolbarMenuItem(context, this, POST_ID, R.drawable.ic_create_white_24dp));
ToolbarMenuItem overflow = menu.createOverflow(this);
@ -80,7 +80,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
overflow.setSubMenu(new FloatingMenu(context, overflow.getView(), items));
loadBoard(ChanApplication.getBoardManager().getSavedBoards().get(0));
loadBoard(Chan.getBoardManager().getSavedBoards().get(0));
}
@Override
@ -168,7 +168,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
}
private void loadBoards() {
List<Board> boards = ChanApplication.getBoardManager().getSavedBoards();
List<Board> boards = Chan.getBoardManager().getSavedBoards();
boardItems = new ArrayList<>();
for (Board board : boards) {
FloatingMenuItem item = new FloatingMenuItemBoard(board);
@ -222,7 +222,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
if (position >= 0 && position < items.size()) {
return items.get(position).getText();
} else {
return context.getString(R.string.board_select_add);
return context.getString(R.string.thread_board_select_add);
}
}

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.controller;
import android.content.Context;
@ -7,7 +24,7 @@ import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.model.SavedReply;
@ -53,7 +70,7 @@ public class DeveloperSettingsController extends Controller {
resetDbButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ChanApplication.getDatabaseManager().reset();
Chan.getDatabaseManager().reset();
System.exit(0);
}
});
@ -68,7 +85,7 @@ public class DeveloperSettingsController extends Controller {
int j = 0;
for (int i = 0; i < 100; i++) {
j += r.nextInt(10000);
ChanApplication.getDatabaseManager().saveReply(new SavedReply("g", j, "pass"));
Chan.getDatabaseManager().saveReply(new SavedReply("g", j, "pass"));
}
setDbSummary();
}
@ -80,7 +97,7 @@ public class DeveloperSettingsController extends Controller {
trimSavedReply.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
ChanApplication.getDatabaseManager().trimSavedRepliesTable(10);
Chan.getDatabaseManager().trimSavedRepliesTable(10);
setDbSummary();
}
});
@ -95,7 +112,7 @@ public class DeveloperSettingsController extends Controller {
private void setDbSummary() {
String dbSummary = "";
dbSummary += "Database summary:\n";
dbSummary += ChanApplication.getDatabaseManager().getSummary();
dbSummary += Chan.getDatabaseManager().getSummary();
summaryText.setText(dbSummary);
}
}

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.controller;
import android.animation.Animator;
@ -5,6 +22,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
@ -28,7 +46,7 @@ import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import com.davemorrissey.labs.subscaleview.ImageViewState;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.chan.ImageSearch;
import org.floens.chan.controller.Controller;
@ -249,6 +267,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis
if (ChanSettings.videoErrorIgnore.get()) {
Toast.makeText(context, R.string.image_open_failed, Toast.LENGTH_SHORT).show();
} else {
@SuppressLint("InflateParams")
View notice = LayoutInflater.from(context).inflate(R.layout.dialog_video_error, null);
final CheckBox dontShowAgain = (CheckBox) notice.findViewById(R.id.checkbox);
@ -309,7 +328,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis
}
});
ChanApplication.getVolleyImageLoader().get(postImage.thumbnailUrl, new ImageLoader.ImageListener() {
Chan.getVolleyImageLoader().get(postImage.thumbnailUrl, new ImageLoader.ImageListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "onErrorResponse for preview in transition in ImageViewerController, cannot show correct transition bitmap");
@ -331,7 +350,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis
return;
}
ChanApplication.getVolleyImageLoader().get(postImage.thumbnailUrl, new ImageLoader.ImageListener() {
Chan.getVolleyImageLoader().get(postImage.thumbnailUrl, new ImageLoader.ImageListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "onErrorResponse for preview out transition in ImageViewerController, cannot show correct transition bitmap");

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.controller;
import android.content.Context;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.controller;
import android.content.Context;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.controller;
import android.app.AlertDialog;
@ -12,7 +29,7 @@ import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.reply.ReplyManager;
@ -100,7 +117,7 @@ public class PassSettingsController extends Controller implements View.OnClickLi
ChanSettings.passToken.set(inputToken.getText().toString());
ChanSettings.passPin.set(inputPin.getText().toString());
ChanApplication.getReplyManager().postPass(ChanSettings.passToken.get(), ChanSettings.passPin.get(), new ReplyManager.PassListener() {
Chan.getReplyManager().postPass(ChanSettings.passToken.get(), ChanSettings.passPin.get(), new ReplyManager.PassListener() {
@Override
public void onResponse(ReplyManager.PassResponse response) {
if (response.isError) {

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.controller;
import android.animation.ValueAnimator;
@ -24,7 +41,6 @@ import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.cell.PostCell;
import org.floens.chan.ui.helper.PostPopupHelper;
import org.floens.chan.ui.view.LoadView;
import org.floens.chan.ui.view.PostView;
import org.floens.chan.ui.view.ThumbnailView;
import org.floens.chan.utils.ThemeHelper;
@ -133,8 +149,8 @@ public class PostRepliesController extends Controller {
});
if (!ThemeHelper.getInstance().getTheme().isLightTheme) {
((TextView) dataView.findViewById(R.id.replies_back_icon)).setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_action_back_dark, 0, 0, 0);
((TextView) dataView.findViewById(R.id.replies_close_icon)).setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_action_done_dark, 0, 0, 0);
((TextView) dataView.findViewById(R.id.replies_back_icon)).setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_arrow_back_white_24dp, 0, 0, 0);
((TextView) dataView.findViewById(R.id.replies_close_icon)).setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_done_white_24dp, 0, 0, 0);
dataView.findViewById(R.id.container).setBackgroundResource(R.drawable.dialog_full_dark);
}
@ -142,24 +158,15 @@ public class PostRepliesController extends Controller {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
PostCell postCell;
if (convertView instanceof PostView) {
if (convertView instanceof PostCell) {
postCell = (PostCell) convertView;
} else {
postCell = (PostCell) LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_post, parent, false);
postCell = (PostCell) LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_post, parent, false);
}
final Post p = getItem(position);
postCell.setPost(p, presenter, false, data.forPost.no);
// postView.setPost(p, presenter, false);
// postView.setHighlightQuotesWithNo(data.forPost.no);
/*postCell.setOnClickListeners(new View.OnClickListener() {
@Override
public void onClick(View v) {
postPopupHelper.postClicked(p);
}
});*/
return postCell;
}
};

@ -25,7 +25,7 @@ import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.controller.Controller;
import org.floens.chan.controller.ControllerTransition;
@ -72,7 +72,7 @@ public class RootNavigationController extends NavigationController implements Pi
new SwipeListener(context, recyclerView, pinAdapter);
pinAdapter.onPinsChanged(ChanApplication.getWatchManager().getPins());
pinAdapter.onPinsChanged(Chan.getWatchManager().getPins());
toolbar.setCallback(this);
@ -162,7 +162,7 @@ public class RootNavigationController extends NavigationController implements Pi
@Override
public void onWatchCountClicked(Pin pin) {
ChanApplication.getWatchManager().toggleWatch(pin);
Chan.getWatchManager().toggleWatch(pin);
}
@Override

@ -1,8 +1,25 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.controller;
import android.content.Context;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.PostImage;
@ -45,7 +62,7 @@ public abstract class ThreadController extends Controller implements ThreadLayou
return threadLayout.onBack();
}
public void onEvent(ChanApplication.ForegroundChangedMessage message) {
public void onEvent(Chan.ForegroundChangedMessage message) {
threadLayout.getPresenter().onForegroundChanged(message.inForeground);
}

@ -21,7 +21,7 @@ import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.manager.WatchManager;
@ -62,8 +62,8 @@ public class ViewThreadController extends ThreadController implements ThreadLayo
navigationItem.hasDrawer = true;
navigationItem.menu = new ToolbarMenu(context);
navigationItem.menu.addItem(new ToolbarMenuItem(context, this, POST_ID, R.drawable.ic_action_write));
pinItem = navigationItem.menu.addItem(new ToolbarMenuItem(context, this, PIN_ID, R.drawable.ic_bookmark));
navigationItem.menu.addItem(new ToolbarMenuItem(context, this, POST_ID, R.drawable.ic_create_white_24dp));
pinItem = navigationItem.menu.addItem(new ToolbarMenuItem(context, this, PIN_ID, R.drawable.ic_bookmark_outline_white_24dp));
navigationItem.createOverflow(context, this, Arrays.asList(
new FloatingMenuItem(REFRESH_ID, context.getString(R.string.action_reload)),
new FloatingMenuItem(SEARCH_ID, context.getString(R.string.action_search)),
@ -177,11 +177,11 @@ public class ViewThreadController extends ThreadController implements ThreadLayo
}
private void setPinIconState() {
WatchManager wm = ChanApplication.getWatchManager();
WatchManager wm = Chan.getWatchManager();
setPinIconState(wm.findPinByLoadable(loadable) != null);
}
private void setPinIconState(boolean pinned) {
pinItem.setImage(pinned ? R.drawable.ic_bookmark_filled : R.drawable.ic_bookmark);
pinItem.setImage(pinned ? R.drawable.ic_bookmark_white_24dp : R.drawable.ic_bookmark_outline_white_24dp);
}
}

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.controller;
import android.content.Context;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.drawable;
import android.graphics.Canvas;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.drawable;
import android.graphics.Canvas;
@ -9,7 +26,6 @@ import android.graphics.drawable.Drawable;
import static org.floens.chan.utils.AndroidUtils.dp;
public class ThumbDrawable extends Drawable {
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Path path = new Path();

@ -71,7 +71,7 @@ public class FolderPickFragment extends DialogFragment {
return null;
}
View container = inflater.inflate(R.layout.folder_pick, parent);
View container = inflater.inflate(R.layout.fragment_folder_pick, parent);
statusPath = (TextView) container.findViewById(R.id.folder_status);
listView = (ListView) container.findViewById(R.id.folder_list);
@ -94,8 +94,8 @@ public class FolderPickFragment extends DialogFragment {
});
if (!ThemeHelper.getInstance().getTheme().isLightTheme) {
((TextView) container.findViewById(R.id.pick_back_icon)).setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_action_cancel_dark, 0, 0, 0);
((TextView) container.findViewById(R.id.pick_ok_icon)).setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_action_done_dark, 0, 0, 0);
((TextView) container.findViewById(R.id.pick_back_icon)).setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_arrow_back_white_24dp, 0, 0, 0);
((TextView) container.findViewById(R.id.pick_ok_icon)).setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_done_white_24dp, 0, 0, 0);
}
adapter = new ArrayAdapter<String>(inflater.getContext(), 0) {

@ -1,354 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.fragment;
import android.app.AlertDialog;
import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.VideoView;
import org.floens.chan.R;
import org.floens.chan.chan.ImageSearch;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.activity.ImageViewActivity;
import org.floens.chan.ui.adapter.ImageViewAdapter;
import org.floens.chan.ui.view.MultiImageView;
import org.floens.chan.ui.view.MultiImageView.Callback;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.ImageSaver;
import static org.floens.chan.utils.AndroidUtils.dp;
public class ImageViewFragment extends Fragment implements Callback {
private Context context;
private ImageViewActivity activity;
private MultiImageView imageView;
private Post post;
private boolean showProgressBar = true;
private boolean isVideo = false;
private boolean videoVisible = false;
private boolean videoSetIconToPause = false;
private boolean tapToLoad = false;
private boolean loaded = false;
private long progressCurrent;
private long progressTotal;
private boolean progressDone;
public static ImageViewFragment newInstance(Post post, ImageViewActivity activity, int index) {
ImageViewFragment imageViewFragment = new ImageViewFragment();
imageViewFragment.post = post;
imageViewFragment.activity = activity;
return imageViewFragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (post == null) {
// No restoring
return null;
} else {
context = inflater.getContext();
imageView = new MultiImageView(context);
imageView.setCallback(this);
int padding = getResources().getDimensionPixelSize(R.dimen.image_view_padding);
imageView.setPadding(padding, padding, padding, padding);
return imageView;
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// No restoring
if (post != null) {
if (!post.hasImage) {
throw new IllegalArgumentException("Post has no image");
}
// After layout has been done so getWidth & getHeight don't return 0
imageView.post(new Runnable() {
@Override
public void run() {
// When the viewpager is created, it starts loading the first two views,
// then we set a position to the viewpager and it loads three more views!
// Check for these unused views here to avoid unnecessary loads
if (imageView.getWidth() == 0 || imageView.getHeight() == 0)
return;
imageView.setThumbnail(post.thumbnailUrl);
if (ChanSettings.getImageAutoLoad() && !post.spoiler) {
load();
} else {
tapToLoad = true;
showProgressBar(false);
if (post.ext.equals("webm")) {
isVideo = true;
activity.invalidateActionBar();
}
}
}
});
}
}
private void load() {
if (loaded) return;
loaded = true;
switch (post.ext) {
case "gif":
imageView.setGif(post.imageUrl);
break;
case "webm":
isVideo = true;
activity.invalidateActionBar();
showProgressBar(false);
if (tapToLoad) {
if (!videoVisible) {
startVideo();
} else {
if (imageView.getVideoView() != null) {
imageView.getVideoView().start();
}
}
}
break;
default:
imageView.setBigImage(post.imageUrl);
break;
}
}
@Override
public void onSaveInstanceState(Bundle bundle) {
// https://code.google.com/p/android/issues/detail?id=19917
bundle.putString("bug_19917", "bug_19917");
super.onSaveInstanceState(bundle);
}
@Override
public void onDestroy() {
super.onDestroy();
if (imageView != null) {
imageView.cancelLoad();
}
}
public void onSelected(ImageViewAdapter adapter, int position) {
activity.setProgressBarIndeterminateVisibility(showProgressBar);
String filename = post.filename + "." + post.ext;
activity.getSupportActionBar().setTitle(filename);
String text = (position + 1) + "/" + adapter.getCount();
activity.getSupportActionBar().setSubtitle(text);
activity.invalidateActionBar();
if (isVideo && ChanSettings.getVideoAutoPlay() && imageView != null) {
if (!videoVisible) {
startVideo();
} else {
if (imageView.getVideoView() != null) {
imageView.getVideoView().start();
}
}
}
activity.setProgressBar(progressCurrent, progressTotal, progressDone);
}
public void onDeselected() {
if (imageView != null && imageView.getVideoView() != null) {
imageView.getVideoView().pause();
}
}
public void onPrepareOptionsMenu(Menu menu) {
MenuItem item = menu.findItem(R.id.action_image_play_state);
item.setVisible(isVideo);
item.setEnabled(isVideo);
if (imageView != null) {
VideoView view = imageView.getVideoView();
if (view != null) {
item.setIcon((videoSetIconToPause || view.isPlaying()) ? R.drawable.ic_action_pause
: R.drawable.ic_action_play);
videoSetIconToPause = false;
}
}
}
public void customOnOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_image_play_state:
if (!videoVisible) {
startVideo();
} else {
VideoView view = imageView.getVideoView();
if (view != null) {
if (!view.isPlaying()) {
view.start();
} else {
view.pause();
}
}
}
activity.invalidateActionBar();
break;
case R.id.action_open_browser:
AndroidUtils.openLink(post.imageUrl);
break;
case R.id.action_image_save:
case R.id.action_share:
if (ChanSettings.getImageShareUrl()) {
shareImageUrl(post.imageUrl);
} else {
ImageSaver.getInstance().saveImage(context, post.imageUrl,
ChanSettings.getImageSaveOriginalFilename() ? Long.toString(post.tim) : post.filename, post.ext,
item.getItemId() == R.id.action_share);
}
break;
default:
// Search if it was an ImageSearch item
for (ImageSearch engine : ImageSearch.engines) {
if (item.getItemId() == engine.getId()) {
AndroidUtils.openLink(engine.getUrl(post.imageUrl));
break;
}
}
break;
}
}
private void shareImageUrl(String url) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, url);
context.startActivity(Intent.createChooser(intent, context.getString(R.string.action_share)));
}
public void onVideoError(MultiImageView view) {
if (ChanSettings.getVideoErrorIgnore()) {
Toast.makeText(context, R.string.image_open_failed, Toast.LENGTH_SHORT).show();
} else {
showVideoWarning();
}
}
@Override
public void onModeLoaded(MultiImageView multiImageView, MultiImageView.Mode mode) {
}
private void showVideoWarning() {
LinearLayout notice = new LinearLayout(context);
notice.setOrientation(LinearLayout.VERTICAL);
TextView noticeText = new TextView(context);
noticeText.setText(R.string.video_playback_warning);
noticeText.setTextSize(16f);
notice.addView(noticeText, AndroidUtils.MATCH_WRAP_PARAMS);
final CheckBox dontShowAgain = new CheckBox(context);
dontShowAgain.setText(R.string.video_playback_ignore);
notice.addView(dontShowAgain, AndroidUtils.MATCH_WRAP_PARAMS);
int padding = dp(16f);
notice.setPadding(padding, padding, padding, padding);
new AlertDialog.Builder(context)
.setTitle(R.string.video_playback_warning_title)
.setView(notice)
.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (dontShowAgain.isChecked()) {
ChanSettings.setVideoErrorIgnore(true);
}
}
})
.setCancelable(false)
.show();
}
private void startVideo() {
if (videoVisible) return;
videoVisible = true;
imageView.setVideo(post.imageUrl);
}
public void showProgressBar(boolean e) {
showProgressBar = e;
activity.updateActionBarIfSelected(this);
}
@Override
public void onTap(MultiImageView view) {
if (tapToLoad) {
if (loaded) {
activity.finish();
} else {
load();
}
} else {
activity.finish();
}
}
@Override
public void showProgress(MultiImageView view, boolean progress) {
showProgressBar(progress);
}
@Override
public void onProgress(MultiImageView view, long current, long total) {
progressCurrent = current;
progressTotal = total;
progressDone = true;
activity.updateActionBarIfSelected(this);
}
}

@ -1,175 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.fragment;
import android.app.Activity;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import org.floens.chan.R;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.presenter.ThreadPresenter;
import org.floens.chan.ui.helper.PostPopupHelper;
import org.floens.chan.ui.view.PostView;
import org.floens.chan.utils.ThemeHelper;
/**
* A DialogFragment that shows a list of posts. Use the newInstance method for
* instantiating.
*/
public class PostRepliesFragment extends DialogFragment {
private ListView listView;
private Activity activity;
private PostPopupHelper.RepliesData repliesData;
private PostPopupHelper postPopupHelper;
private ThreadPresenter presenter;
public static PostRepliesFragment newInstance(PostPopupHelper.RepliesData repliesData, PostPopupHelper postPopupHelper, ThreadPresenter presenter) {
PostRepliesFragment fragment = new PostRepliesFragment();
fragment.repliesData = repliesData;
fragment.postPopupHelper = postPopupHelper;
fragment.presenter = presenter;
return fragment;
}
public void dismissNoCallback() {
postPopupHelper = null;
dismiss();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(STYLE_NO_TITLE, 0);
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (postPopupHelper != null) {
// postPopupHelper.onPostRepliesPop();
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup unused, Bundle savedInstanceState) {
View container;
if (ChanSettings.getReplyButtonsBottom()) {
container = inflater.inflate(R.layout.post_replies_bottombuttons, null);
} else {
container = inflater.inflate(R.layout.post_replies, null);
}
listView = (ListView) container.findViewById(R.id.post_list);
container.findViewById(R.id.replies_back).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
container.findViewById(R.id.replies_close).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (postPopupHelper != null) {
// postPopupHelper.closeAllPostFragments();
}
}
});
if (!ThemeHelper.getInstance().getTheme().isLightTheme) {
((TextView) container.findViewById(R.id.replies_back_icon)).setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_action_back_dark, 0, 0, 0);
((TextView) container.findViewById(R.id.replies_close_icon)).setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_action_done_dark, 0, 0, 0);
}
return container;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
activity = getActivity();
if (repliesData == null) {
// Restoring from background.
dismiss();
} else {
ArrayAdapter<Post> adapter = new ArrayAdapter<Post>(activity, 0) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
PostView postView;
if (convertView instanceof PostView) {
postView = (PostView) convertView;
} else {
postView = new PostView(activity);
}
final Post p = getItem(position);
// postView.setPost(p, presenter, false);
// postView.setHighlightQuotesWithNo(repliesData.forPost.no);
postView.setOnClickListeners(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (postPopupHelper != null) {
postPopupHelper.postClicked(p);
}
dismiss();
}
});
return postView;
}
};
adapter.addAll(repliesData.posts);
listView.setAdapter(adapter);
listView.setSelectionFromTop(repliesData.listViewIndex, repliesData.listViewTop);
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (repliesData != null) {
repliesData.listViewIndex = view.getFirstVisiblePosition();
View v = view.getChildAt(0);
repliesData.listViewTop = (v == null) ? 0 : v.getTop();
}
}
});
}
}
}

@ -1,609 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.fragment;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ViewFlipper;
import org.floens.chan.ChanApplication;
import org.floens.chan.R;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.reply.ReplyManager;
import org.floens.chan.core.model.Board;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Reply;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.animation.ViewFlipperAnimations;
import org.floens.chan.ui.layout.CaptchaLayout;
import org.floens.chan.ui.view.LoadView;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.ImageDecoder;
import org.floens.chan.utils.Logger;
import org.floens.chan.utils.ThemeHelper;
import java.io.File;
import static org.floens.chan.utils.AndroidUtils.dp;
public class ReplyFragment extends DialogFragment implements CaptchaLayout.CaptchaCallback {
private static final String TAG = "ReplyFragment";
private int page = 0;
private Loadable loadable;
private boolean quickMode = false;
private final Reply draft = new Reply();
private boolean shouldSaveDraft = true;
private String captchaResponse;
private int defaultTextColor;
private int maxCommentCount;
// Views
private View container;
private ViewFlipper flipper;
private Button cancelButton;
private ImageButton fileButton;
private Button submitButton;
private EditText nameView;
private EditText emailView;
private EditText subjectView;
private EditText commentView;
private EditText fileNameView;
private CheckBox spoilerImageView;
private LoadView imageViewContainer;
private CaptchaLayout captchaLayout;
private LoadView responseContainer;
private Button insertSpoiler;
private Button insertCode;
private TextView commentCountView;
private TextView fileStatusView;
private AppCompatActivity context;
public static ReplyFragment newInstance(Loadable loadable, boolean quickMode) {
ReplyFragment reply = new ReplyFragment();
reply.loadable = loadable;
reply.quickMode = quickMode;
return reply;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
loadable.writeToBundle(context, outState);
outState.putBoolean(context.getPackageName() + ".quickmode", quickMode);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
context = (AppCompatActivity) getActivity();
if (loadable == null && savedInstanceState != null) {
loadable = new Loadable();
loadable.readFromBundle(context, savedInstanceState);
quickMode = savedInstanceState.getBoolean(context.getPackageName() + ".quickmode");
}
if (loadable != null) {
setClosable(true);
Dialog dialog = getDialog();
String title = (loadable.isThreadMode() ? context.getString(R.string.reply_to_board) : context.getString(R.string.reply_to_board)) + " " + loadable.title;
if (dialog == null) {
context.getSupportActionBar().setTitle(title);
} else {
dialog.setTitle(title);
// todo move elsewhere
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
dialog.setOnKeyListener(new Dialog.OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialogInterface, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
onBackPressed();
return true;
} else
return false;
}
});
}
Reply draft = null;
if (TextUtils.isEmpty(draft.name)) {
draft.name = ChanSettings.getDefaultName();
}
nameView.setText(draft.name);
emailView.setText(draft.options);
subjectView.setText(draft.subject);
commentView.setText(draft.comment);
commentView.setSelection(draft.selection);
setFile(draft.fileName, draft.file);
spoilerImageView.setChecked(draft.spoilerImage);
if (loadable.isThreadMode()) {
subjectView.setVisibility(View.GONE);
}
if (quickMode) {
nameView.setVisibility(View.GONE);
emailView.setVisibility(View.GONE);
subjectView.setVisibility(View.GONE);
}
defaultTextColor = commentView.getCurrentTextColor();
Board b = ChanApplication.getBoardManager().getBoardByValue(loadable.board);
if (b != null) {
insertSpoiler.setVisibility(b.spoilers ? View.VISIBLE : View.GONE);
insertCode.setVisibility(b.codeTags ? View.VISIBLE : View.GONE);
maxCommentCount = b.maxCommentChars;
}
commentView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
showCommentCount();
}
});
showCommentCount();
String baseUrl = loadable.isThreadMode() ? ChanUrls.getThreadUrlDesktop(loadable.board, loadable.no) : ChanUrls.getBoardUrlDesktop(loadable.board);
captchaLayout.initCaptcha(baseUrl, ChanUrls.getCaptchaSiteKey(),
ThemeHelper.getInstance().getTheme().isLightTheme, ChanApplication.getInstance().getUserAgent(), this);
} else {
Logger.e(TAG, "Loadable in ReplyFragment was null");
closeReply();
}
}
@Override
public void onPause() {
super.onPause();
ReplyManager replyManager = ChanApplication.getReplyManager();
if (shouldSaveDraft) {
draft.name = nameView.getText().toString();
draft.options = emailView.getText().toString();
draft.subject = subjectView.getText().toString();
draft.comment = commentView.getText().toString();
draft.fileName = fileNameView.getText().toString();
draft.spoilerImage = spoilerImageView.isChecked();
draft.selection = commentView.getSelectionStart();
// replyManager.setReplyDraft(draft);
} else {
// replyManager.removeReplyDraft();
setFile(null, null);
}
}
@Override
public void onDestroy() {
super.onDestroy();
ReplyManager replyManager = ChanApplication.getReplyManager();
replyManager.removeFileListener();
context = null;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
// Setup the views with listeners
container = inflater.inflate(R.layout.reply_view, null);
flipper = (ViewFlipper) container.findViewById(R.id.reply_flipper);
nameView = (EditText) container.findViewById(R.id.reply_name);
emailView = (EditText) container.findViewById(R.id.reply_email);
subjectView = (EditText) container.findViewById(R.id.reply_subject);
commentView = (EditText) container.findViewById(R.id.reply_comment);
commentView.requestFocus();
fileNameView = (EditText) container.findViewById(R.id.reply_file_name);
spoilerImageView = (CheckBox) container.findViewById(R.id.reply_spoiler_image);
imageViewContainer = (LoadView) container.findViewById(R.id.reply_image);
responseContainer = (LoadView) container.findViewById(R.id.reply_response);
captchaLayout = (CaptchaLayout) container.findViewById(R.id.captcha_layout);
cancelButton = (Button) container.findViewById(R.id.reply_cancel);
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (page == 1) {
flipPage(0);
} else {
closeReply();
}
}
});
fileButton = (ImageButton) container.findViewById(R.id.reply_file);
fileButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (draft.file == null) {
/*ChanApplication.getReplyManager().pickFile(new ReplyManager.FileListener() {
@Override
public void onFile(String name, File file) {
setFile(name, file);
}
@Override
public void onFileLoading() {
imageViewContainer.setVisibility(View.VISIBLE);
imageViewContainer.setView(null);
}
});*/
} else {
setFile(null, null);
}
}
});
submitButton = (Button) container.findViewById(R.id.reply_submit);
submitButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (page == 1 || ChanSettings.passLoggedIn()) {
flipPage(2);
submit();
} else {
flipPage(1);
}
}
});
insertSpoiler = (Button) container.findViewById(R.id.insert_spoiler);
insertSpoiler.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
insertAtCursor("[spoiler]", "[/spoiler]");
}
});
insertCode = (Button) container.findViewById(R.id.insert_code);
insertCode.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
insertAtCursor("[code]", "[/code]");
}
});
commentCountView = (TextView) container.findViewById(R.id.reply_comment_counter);
fileStatusView = (TextView) container.findViewById(R.id.reply_file_status);
return container;
}
public boolean onBackPressed() {
if (page == 1) {
flipPage(0);
return false;
} else {
return true;
}
}
@Override
public void captchaLoaded(CaptchaLayout captchaLayout) {
}
@Override
public void captchaEntered(CaptchaLayout captchaLayout, String response) {
captchaResponse = response;
if (page == 1) {
flipPage(2);
submit();
}
}
private void insertAtCursor(String before, String after) {
int pos = commentView.getSelectionStart();
String text = commentView.getText().toString();
text = new StringBuilder(text).insert(pos, before + after).toString();
commentView.setText(text);
commentView.setSelection(pos + before.length());
}
private void showCommentCount() {
int count = commentView.getText().length();
commentCountView.setText(count + "/" + maxCommentCount);
if (count > maxCommentCount) {
commentCountView.setTextColor(0xffff0000);
} else {
commentCountView.setTextColor(defaultTextColor);
}
}
private void closeReply() {
if (getDialog() != null) {
dismissAllowingStateLoss();
} else {
context.finish();
}
}
/**
* Set if the dialog is able to be closed, by pressing outside of the
* dialog, or something else.
*/
private void setClosable(boolean e) {
if (getDialog() != null) {
getDialog().setCanceledOnTouchOutside(e);
setCancelable(e);
}
}
/**
* Flip to an page with an animation. Sets the correct text on the
* cancelButton:
*
* @param position 0-2
*/
private void flipPage(int position) {
boolean flipBack = position < page;
page = position;
if (flipBack) {
flipper.setInAnimation(ViewFlipperAnimations.BACK_IN);
flipper.setOutAnimation(ViewFlipperAnimations.BACK_OUT);
} else {
flipper.setInAnimation(ViewFlipperAnimations.NEXT_IN);
flipper.setOutAnimation(ViewFlipperAnimations.NEXT_OUT);
}
flipper.setDisplayedChild(position);
if (page == 0) {
cancelButton.setText(R.string.cancel);
} else if (page == 1) {
cancelButton.setText(R.string.back);
} else if (page == 2) {
cancelButton.setText(R.string.close);
}
if (page == 1) {
captchaLayout.load();
submitButton.setEnabled(captchaResponse != null);
} else if (page == 0) {
submitButton.setEnabled(true);
}
}
/**
* Set the picked image in the imageView. Sets the file in the draft. Call
* null on the file to empty the imageView.
*
* @param name the filename
* @param file the file
*/
private void setFile(final String name, final File file) {
draft.file = file;
draft.fileName = name;
if (file == null) {
fileButton.setImageResource(ThemeHelper.getInstance().getTheme().isLightTheme ? R.drawable.ic_action_attachment : R.drawable.ic_action_attachment_dark);
imageViewContainer.removeAllViews();
imageViewContainer.setVisibility(View.GONE);
fileNameView.setText("");
fileNameView.setVisibility(View.GONE);
spoilerImageView.setVisibility(View.GONE);
spoilerImageView.setChecked(false);
fileStatusView.setVisibility(View.GONE);
} else {
fileButton.setImageResource(ThemeHelper.getInstance().getTheme().isLightTheme ? R.drawable.ic_action_cancel : R.drawable.ic_action_cancel_dark);
fileNameView.setVisibility(View.VISIBLE);
fileNameView.setText(name);
Board b = ChanApplication.getBoardManager().getBoardByValue(loadable.board);
spoilerImageView.setVisibility(b != null && b.spoilers ? View.VISIBLE : View.GONE);
if (b != null) {
boolean probablyWebm = name.endsWith(".webm");
int maxSize = probablyWebm ? b.maxWebmSize : b.maxFileSize;
if (file.length() > maxSize) {
String fileSize = AndroidUtils.getReadableFileSize((int) file.length(), false);
String maxSizeString = AndroidUtils.getReadableFileSize(maxSize, false);
String text = getString(probablyWebm ? R.string.reply_webm_too_big : R.string.reply_file_too_big, fileSize, maxSizeString);
fileStatusView.setVisibility(View.VISIBLE);
fileStatusView.setText(text);
} else {
fileStatusView.setVisibility(View.GONE);
}
}
imageViewContainer.setVisibility(View.VISIBLE);
imageViewContainer.setView(null);
imageViewContainer.post(new Runnable() {
public void run() {
if (file.length() < 10 * 1024 * 1024) {
new Thread(new Runnable() {
@Override
public void run() {
if (context == null)
return;
final Bitmap bitmap = ImageDecoder.decodeFile(file, imageViewContainer.getWidth(), imageViewContainer.getWidth());
context.runOnUiThread(new Runnable() {
@Override
public void run() {
if (context != null) {
if (bitmap != null) {
ImageView imageView = new ImageView(context);
imageViewContainer.setView(imageView);
imageView.setAdjustViewBounds(true);
imageView.setMaxWidth(imageViewContainer.getWidth());
imageView.setMaxHeight(imageViewContainer.getWidth());
imageView.setImageBitmap(bitmap);
} else {
noPreview(imageViewContainer);
}
}
}
});
}
}).start();
} else {
noPreview(imageViewContainer);
}
}
});
}
}
private void noPreview(LoadView loadView) {
TextView text = new TextView(context);
text.setLayoutParams(AndroidUtils.MATCH_WRAP_PARAMS);
text.setGravity(Gravity.CENTER);
text.setText(R.string.reply_no_preview);
text.setTextSize(16f);
int padding = dp(16);
text.setPadding(padding, padding, padding, padding);
loadView.setView(text);
}
/**
* Submit button clicked at page 1
*/
private void submit() {
submitButton.setEnabled(false);
cancelButton.setEnabled(false);
setClosable(false);
responseContainer.setView(null);
draft.name = nameView.getText().toString();
draft.options = emailView.getText().toString();
draft.subject = subjectView.getText().toString();
draft.comment = commentView.getText().toString();
draft.captchaResponse = captchaResponse;
draft.fileName = "image";
String n = fileNameView.getText().toString();
if (!TextUtils.isEmpty(n)) {
draft.fileName = n;
}
draft.resto = loadable.isThreadMode() ? loadable.no : -1;
draft.board = loadable.board;
if (ChanSettings.getPassEnabled()) {
draft.usePass = true;
draft.passId = ChanSettings.getPassId();
}
Board b = ChanApplication.getBoardManager().getBoardByValue(loadable.board);
draft.spoilerImage = b != null && b.spoilers && spoilerImageView.isChecked();
ChanApplication.getReplyManager().postReply(draft, new ReplyManager.ReplyListener() {
@Override
public void onResponse(ReplyManager.ReplyResponse response) {
handleSubmitResponse(response);
}
});
}
/**
* Got response about or reply from ReplyManager
*
* @param response
*/
private void handleSubmitResponse(ReplyManager.ReplyResponse response) {
if (context == null)
return;
if (response.isNetworkError || response.isUserError) {
int resId = R.string.reply_error;
Toast.makeText(context, resId, Toast.LENGTH_LONG).show();
submitButton.setEnabled(true);
cancelButton.setEnabled(true);
setClosable(true);
captchaResponse = null;
if (ChanSettings.passLoggedIn()) {
flipPage(0);
} else {
flipPage(1);
captchaLayout.reset();
}
} else if (response.isSuccessful) {
shouldSaveDraft = false;
Toast.makeText(context, R.string.reply_success, Toast.LENGTH_SHORT).show();
// Pin thread on successful post
if (ChanSettings.getPinOnPost() && loadable.isThreadMode()) {
ChanApplication.getWatchManager().addPin(loadable);
}
closeReply();
} else {
cancelButton.setEnabled(true);
setClosable(true);
WebView webView = new WebView(context);
WebSettings settings = webView.getSettings();
settings.setSupportZoom(true);
webView.loadData(response.responseData, "text/html", null);
responseContainer.setView(webView);
}
}
}

@ -1,201 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.fragment;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
import android.view.View;
import android.widget.ListView;
import android.widget.Toast;
import org.floens.chan.R;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.activity.AboutActivity;
import org.floens.chan.ui.activity.BaseActivity;
import org.floens.chan.ui.activity.LicenseActivity;
import org.floens.chan.ui.activity.SettingsActivity;
import org.floens.chan.utils.ThemeHelper;
public class SettingsFragment extends PreferenceFragment {
private int clickCount = 0;
private boolean argumentsRead = false;
private Preference developerPreference;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preference);
Preference aboutLicences = findPreference("about_licenses");
if (aboutLicences != null) {
aboutLicences.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
startActivity(new Intent(getActivity(), AboutActivity.class));
return true;
}
});
}
Preference aboutLicence = findPreference("about_license");
if (aboutLicence != null) {
aboutLicence.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
startActivity(new Intent(getActivity(), LicenseActivity.class));
return true;
}
});
}
Preference aboutVersion = findPreference("about_version");
if (aboutVersion != null) {
aboutVersion.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
if (++clickCount >= 5) {
clickCount = 0;
boolean enabled = !ChanSettings.getDeveloper();
ChanSettings.setDeveloper(enabled);
updateDeveloperPreference();
Toast.makeText(getActivity(), (enabled ? "Enabled " : "Disabled ") + "developer options",
Toast.LENGTH_LONG).show();
}
return true;
}
});
String version = "";
try {
version = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0).versionName;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
aboutVersion.setTitle(R.string.app_name);
aboutVersion.setSummary(version);
}
developerPreference = findPreference("about_developer");
((PreferenceGroup) findPreference("group_about")).removePreference(developerPreference);
updateDeveloperPreference();
final ListPreference theme = (ListPreference) findPreference("preference_theme");
String currentValue = theme.getValue();
if (currentValue == null) {
theme.setValue((String) theme.getEntryValues()[0]);
currentValue = theme.getValue();
}
updateSummary(theme, currentValue);
theme.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
updateSummary(theme, newValue.toString());
// Thanks! https://github.com/CyanogenMod/android_packages_apps_Calculator/blob/cm-10.2/src/com/android/calculator2/view/PreferencesFragment.java
if (!newValue.toString().equals(ThemeHelper.getInstance().getTheme().name)) {
Intent intent = new Intent(getActivity(), SettingsActivity.class);
intent.putExtra("pos", getListView().getFirstVisiblePosition());
View child = getListView().getChildAt(0);
intent.putExtra("off", child != null ? child.getTop() : 0);
((SettingsActivity) getActivity()).restart(intent);
}
return true;
}
});
final ListPreference font = (ListPreference) findPreference("preference_font");
String currentFontValue = font.getValue();
if (currentFontValue == null) {
font.setValue((String) font.getEntryValues()[0]);
currentFontValue = font.getValue();
}
updateSummary(font, currentFontValue);
font.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
updateSummary(font, newValue.toString());
BaseActivity.doRestartOnResume = true;
return true;
}
});
}
public void onStart() {
super.onStart();
final Bundle args = getArguments();
if (args != null && !argumentsRead) {
argumentsRead = true;
getListView().setSelectionFromTop(args.getInt("pos", 0), args.getInt("off", 0));
}
}
@Override
public void onResume() {
super.onResume();
final Preference watchPreference = findPreference("watch_settings");
if (watchPreference != null) {
watchPreference.setSummary(ChanSettings.getWatchEnabled() ? R.string.watch_summary_enabled
: R.string.watch_summary_disabled);
}
final Preference passPreference = findPreference("pass_settings");
if (passPreference != null) {
passPreference.setSummary(ChanSettings.getPassEnabled() ? R.string.pass_summary_enabled
: R.string.pass_summary_disabled);
}
}
private ListView getListView() {
return (ListView) getView().findViewById(android.R.id.list);
}
private void updateDeveloperPreference() {
if (ChanSettings.getDeveloper()) {
((PreferenceGroup) findPreference("group_about")).addPreference(developerPreference);
} else {
((PreferenceGroup) findPreference("group_about")).removePreference(developerPreference);
}
}
private void updateSummary(ListPreference list, String value) {
int index = list.findIndexOfValue(value);
list.setSummary(list.getEntries()[index]);
}
}

@ -1,648 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.fragment;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.SearchView;
import android.widget.TextView;
import com.android.volley.NetworkError;
import com.android.volley.NoConnectionError;
import com.android.volley.ServerError;
import com.android.volley.VolleyError;
import org.floens.chan.R;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.loader.ChanLoader;
import org.floens.chan.core.loader.EndOfLineException;
import org.floens.chan.core.manager.ThreadManager;
import org.floens.chan.core.model.ChanThread;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Post;
import org.floens.chan.ui.activity.BaseActivity;
import org.floens.chan.ui.activity.ImageViewActivity;
import org.floens.chan.ui.adapter.PostAdapter;
import org.floens.chan.ui.view.LoadView;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.ImageSaver;
import org.floens.chan.utils.ThemeHelper;
import java.util.ArrayList;
import java.util.List;
import javax.net.ssl.SSLException;
import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AndroidUtils.setItemBackground;
public class ThreadFragment extends Fragment implements ThreadManager.ThreadManagerListener, PostAdapter.PostAdapterCallback {
private ThreadManager threadManager;
private Loadable loadable;
private PostAdapter postAdapter;
private LoadView container;
private AbsListView listView;
private ImageView skip;
private FilterView filterView;
private SkipLogic skipLogic;
private int highlightedPost = -1;
private ThreadManager.ViewMode viewMode = ThreadManager.ViewMode.LIST;
private boolean isFiltering = false;
public static ThreadFragment newInstance(BaseActivity activity) {
ThreadFragment fragment = new ThreadFragment();
fragment.threadManager = new ThreadManager(activity, fragment);
return fragment;
}
public void bindLoadable(Loadable l) {
if (loadable != null) {
threadManager.unbindLoader();
}
setEmpty();
loadable = l;
threadManager.bindLoader(loadable);
}
public void requestData() {
threadManager.requestData();
}
public void requestNextData() {
threadManager.requestNextData();
}
public void reload() {
setEmpty();
threadManager.requestData();
}
public void openReply() {
if (threadManager.hasLoader()) {
threadManager.openReply(true);
}
}
public boolean hasLoader() {
return threadManager.hasLoader();
}
public void setViewMode(ThreadManager.ViewMode viewMode) {
this.viewMode = viewMode;
}
public ChanLoader getLoader() {
return threadManager.getChanLoader();
}
public void startFiltering() {
if (filterView != null) {
isFiltering = true;
filterView.setVisibility(View.VISIBLE);
filterView.focusSearch();
}
}
public void highlightPost(int no) {
highlightedPost = no;
}
@Override
public void onDestroy() {
super.onDestroy();
if (threadManager != null) {
threadManager.onDestroy();
}
threadManager = null;
loadable = null;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onStart() {
super.onStart();
if (threadManager != null) {
threadManager.onStart();
}
}
@Override
public void onStop() {
super.onStop();
if (threadManager != null) {
threadManager.onStop();
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
container = new LoadView(inflater.getContext());
if (loadable == null) {
container.setView(getCenteredMessageView(R.string.thread_not_specified));
}
return container;
}
@Override
public void onPostClicked(Post post) {
if (loadable.isBoardMode() || loadable.isCatalogMode()) {
((BaseActivity) getActivity()).onOPClicked(post);
} else if (loadable.isThreadMode() && isFiltering) {
filterView.clearSearch();
// postAdapter.scrollToPost(post.no);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (loadable.isThreadMode()) {
switch (item.getItemId()) {
case R.id.action_download_album:
// Get the posts with images
ArrayList<Post> imagePosts = new ArrayList<>();
// for (Post post : postAdapter.getList()) {
// if (post.hasImage) {
// imagePosts.add(post);
// }
// }
if (imagePosts.size() > 0) {
List<ImageSaver.DownloadPair> list = new ArrayList<>();
String folderName = Post.generateTitle(imagePosts.get(0), 10);
String filename;
for (Post post : imagePosts) {
filename = (ChanSettings.getImageSaveOriginalFilename() ? post.tim : post.filename) + "." + post.ext;
list.add(new ImageSaver.DownloadPair(post.imageUrl, filename));
}
ImageSaver.getInstance().saveAll(getActivity(), folderName, list);
}
return true;
}
}
return super.onOptionsItemSelected(item);
}
@Override
public void onThumbnailClicked(Post source) {
if (postAdapter != null) {
ImageViewActivity.launch(getActivity(), postAdapter, source.no, threadManager);
}
}
@Override
public void onScrollTo(int post) {
if (postAdapter != null) {
// postAdapter.scrollToPost(post);
}
}
@Override
public void onRefreshView() {
if (postAdapter != null) {
postAdapter.notifyDataSetChanged();
}
}
@Override
public void onOpenThread(final Loadable thread, int highlightedPost) {
((BaseActivity) getActivity()).onOpenThread(thread);
this.highlightedPost = highlightedPost;
}
@Override
public ThreadManager.ViewMode getViewMode() {
return viewMode;
}
@Override
public void onThreadLoaded(ChanThread thread) {
if (postAdapter == null) {
if (container != null) {
container.setView(createView());
}
}
// postAdapter.setError(null);
postAdapter.setThread(thread);
if (highlightedPost >= 0) {
threadManager.highlightPost(highlightedPost);
// postAdapter.scrollToPost(highlightedPost);
highlightedPost = -1;
}
((BaseActivity) getActivity()).onThreadLoaded(thread);
}
@Override
public void onThreadLoadError(VolleyError error) {
if (error instanceof EndOfLineException) {
// postAdapter.setEndOfLine(true);
} else {
if (postAdapter == null) {
if (container != null) {
container.setView(getLoadErrorView(error));
}
} else {
// postAdapter.setError(getLoadErrorText(error));
}
}
highlightedPost = -1;
}
public void onFilteredResults(String filter, int count, boolean all) {
isFiltering = !all;
if (filterView != null) {
filterView.setText(filter, count, all);
}
}
@Override
public Loadable getLoadable() {
return loadable;
}
@Override
public void onListScrolledToBottom() {
}
private RelativeLayout createView() {
RelativeLayout compound = new RelativeLayout(getActivity());
LinearLayout listViewContainer = new LinearLayout(getActivity());
listViewContainer.setOrientation(LinearLayout.VERTICAL);
filterView = new FilterView(getActivity());
filterView.setVisibility(View.GONE);
listViewContainer.addView(filterView, AndroidUtils.MATCH_WRAP_PARAMS);
if (viewMode == ThreadManager.ViewMode.LIST) {
ListView list = new ListView(getActivity());
listView = list;
// postAdapter = new PostAdapter(getActivity(), threadManager, listView, this);
// listView.setAdapter(postAdapter);
list.setSelectionFromTop(loadable.listViewIndex, loadable.listViewTop);
} else if (viewMode == ThreadManager.ViewMode.GRID) {
GridView grid = new GridView(getActivity());
grid.setNumColumns(GridView.AUTO_FIT);
int postGridWidth = getActivity().getResources().getDimensionPixelSize(R.dimen.post_grid_width);
grid.setColumnWidth(postGridWidth);
listView = grid;
// postAdapter = new PostAdapter(getActivity(), threadManager, listView, this);
// listView.setAdapter(postAdapter);
listView.setSelection(loadable.listViewIndex);
}
listView.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (!isFiltering) {
if (skipLogic != null) {
skipLogic.onScrollStateChanged(view, scrollState);
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (!isFiltering) {
if (loadable != null) {
int index = view.getFirstVisiblePosition();
View v = view.getChildAt(0);
int top = v == null ? 0 : v.getTop();
if (index != 0 || top != 0) {
loadable.listViewIndex = index;
loadable.listViewTop = top;
}
}
if (skipLogic != null) {
skipLogic.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
}
}
});
listViewContainer.addView(listView, AndroidUtils.MATCH_PARAMS);
compound.addView(listViewContainer, AndroidUtils.MATCH_PARAMS);
if (loadable.isThreadMode()) {
skip = new ImageView(getActivity());
skip.setImageResource(R.drawable.skip_arrow_down);
skip.setVisibility(View.GONE);
compound.addView(skip, AndroidUtils.WRAP_PARAMS);
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) skip.getLayoutParams();
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
params.setMargins(0, 0, dp(8), dp(8));
skip.setLayoutParams(params);
skipLogic = new SkipLogic(skip, listView);
}
return compound;
}
private void setEmpty() {
postAdapter = null;
if (container != null) {
container.setView(null);
}
if (listView != null) {
listView.setOnScrollListener(null);
listView = null;
}
skip = null;
skipLogic = null;
filterView = null;
}
private void doFilter(String filter) {
if (postAdapter != null) {
// postAdapter.setFilter(filter);
}
}
/**
* Returns an TextView containing the appropriate error message
*
* @param error
* @return
*/
private View getLoadErrorView(VolleyError error) {
String errorMessage = getLoadErrorText(error);
LinearLayout wrapper = new LinearLayout(getActivity());
wrapper.setLayoutParams(AndroidUtils.MATCH_PARAMS);
wrapper.setGravity(Gravity.CENTER);
wrapper.setOrientation(LinearLayout.VERTICAL);
TextView text = new TextView(getActivity());
text.setLayoutParams(AndroidUtils.WRAP_PARAMS);
text.setText(errorMessage);
text.setTextSize(24f);
wrapper.addView(text);
Button retry = new Button(getActivity());
retry.setText(R.string.thread_load_failed_retry);
retry.setLayoutParams(AndroidUtils.WRAP_PARAMS);
retry.setGravity(Gravity.CENTER);
retry.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (threadManager != null) {
reload();
}
}
});
wrapper.addView(retry);
LinearLayout.LayoutParams retryParams = (LinearLayout.LayoutParams) retry.getLayoutParams();
retryParams.topMargin = dp(12);
retry.setLayoutParams(retryParams);
return wrapper;
}
private String getLoadErrorText(VolleyError error) {
String errorMessage;
if (error.getCause() instanceof SSLException) {
errorMessage = getString(R.string.thread_load_failed_ssl);
} else if ((error instanceof NoConnectionError) || (error instanceof NetworkError)) {
errorMessage = getString(R.string.thread_load_failed_network);
} else if (error instanceof ServerError) {
errorMessage = getString(R.string.thread_load_failed_server);
} else {
errorMessage = getString(R.string.thread_load_failed_parsing);
}
return errorMessage;
}
private View getCenteredMessageView(int stringResourceId) {
LinearLayout layout = new LinearLayout(getActivity());
layout.setGravity(Gravity.CENTER);
TextView messageView = new TextView(getActivity());
messageView.setText(getString(stringResourceId));
layout.addView(messageView);
return layout;
}
private static class SkipLogic {
private final ImageView skip;
private int lastFirstVisibleItem;
private int lastTop;
private boolean up = false;
private final AbsListView listView;
public SkipLogic(ImageView skipView, AbsListView list) {
skip = skipView;
listView = list;
skip.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (up) {
listView.setSelection(0);
} else {
listView.setSelection(listView.getCount() - 1);
}
skip.setVisibility(View.GONE);
}
});
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState != AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
skip.setVisibility(View.VISIBLE);
}
}
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
View v = view.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
if (firstVisibleItem == lastFirstVisibleItem) {
if (top > lastTop) {
onUp();
} else if (top < lastTop) {
onDown();
}
} else {
if (firstVisibleItem > lastFirstVisibleItem) {
onDown();
} else {
onUp();
}
}
lastFirstVisibleItem = firstVisibleItem;
lastTop = top;
}
private void onUp() {
skip.setImageResource(R.drawable.skip_arrow_up);
up = true;
}
private void onDown() {
skip.setImageResource(R.drawable.skip_arrow_down);
up = false;
}
}
public class FilterView extends LinearLayout {
private SearchView searchView;
private TextView textView;
public FilterView(Context activity) {
super(activity);
init();
}
public FilterView(Context activity, AttributeSet attr) {
super(activity, attr);
init();
}
public FilterView(Context activity, AttributeSet attr, int style) {
super(activity, attr, style);
init();
}
public void focusSearch() {
searchView.requestFocus();
}
public void clearSearch() {
searchView.setQuery("", false);
doFilter("");
setVisibility(View.GONE);
}
private void init() {
setOrientation(LinearLayout.VERTICAL);
LinearLayout searchViewContainer = new LinearLayout(getContext());
searchViewContainer.setOrientation(LinearLayout.HORIZONTAL);
searchView = new SearchView(getContext());
searchView.setIconifiedByDefault(false);
searchView.setImeOptions(EditorInfo.IME_FLAG_NO_FULLSCREEN);
searchViewContainer.addView(searchView);
LinearLayout.LayoutParams searchViewParams = (LinearLayout.LayoutParams) searchView.getLayoutParams();
searchViewParams.weight = 1f;
searchViewParams.width = 0;
searchViewParams.height = LayoutParams.MATCH_PARENT;
searchView.setLayoutParams(searchViewParams);
ImageView closeButton = new ImageView(getContext());
searchViewContainer.addView(closeButton);
closeButton.setImageResource(ThemeHelper.getInstance().getTheme().isLightTheme ? R.drawable.ic_action_cancel : R.drawable.ic_action_cancel_dark);
LinearLayout.LayoutParams closeButtonParams = (LinearLayout.LayoutParams) closeButton.getLayoutParams();
searchViewParams.width = dp(48);
searchViewParams.height = LayoutParams.MATCH_PARENT;
closeButton.setLayoutParams(closeButtonParams);
setItemBackground(closeButton);
int padding = dp(8);
closeButton.setPadding(padding, padding, padding, padding);
closeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
clearSearch();
}
});
addView(searchViewContainer, new LayoutParams(LayoutParams.MATCH_PARENT, dp(48)));
searchView.setQueryHint(getString(R.string.search_hint));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
doFilter(query);
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
doFilter(newText);
return false;
}
});
textView = new TextView(getContext());
textView.setGravity(Gravity.CENTER);
addView(textView, new LayoutParams(LayoutParams.MATCH_PARENT, dp(28)));
}
private void setText(String filter, int count, boolean all) {
if (all) {
textView.setText("");
} else {
// String posts = getContext().getString(count == 1 ? R.string.one_post : R.string.multiple_posts);
// String text = getContext().getString(R.string.search_results, Integer.toString(count), posts, filter);
// textView.setText(text);
}
}
}
}

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.helper;
import android.content.res.Resources;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.helper;
import android.support.v4.view.ViewCompat;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.helper;
import android.content.Context;
@ -35,7 +52,6 @@ public class SwipeListener extends RecyclerView.ItemDecoration implements Recycl
private final int flingPixels;
private final int maxFlingPixels;
private final Context context;
private Callback callback;
private final RecyclerView recyclerView;
private final LinearLayoutManager layoutManager;
@ -83,7 +99,6 @@ public class SwipeListener extends RecyclerView.ItemDecoration implements Recycl
};
public SwipeListener(Context context, RecyclerView rv, Callback callback) {
this.context = context;
recyclerView = rv;
this.callback = callback;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.layout;
import android.content.Context;
@ -326,7 +343,7 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
@Override
public void onFilePickError() {
Toast.makeText(getContext(), R.string.file_open_failed, Toast.LENGTH_LONG).show();
Toast.makeText(getContext(), R.string.reply_file_open_failed, Toast.LENGTH_LONG).show();
}
@Override

@ -199,12 +199,12 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres
ClipboardManager clipboard = (ClipboardManager) AndroidUtils.getAppRes().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("Post text", post.comment.toString());
clipboard.setPrimaryClip(clip);
Toast.makeText(getContext(), R.string.post_text_copied_to_clipboard, Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), R.string.post_text_copied, Toast.LENGTH_SHORT).show();
}
@Override
public void openLink(final String link) {
if (ChanSettings.getOpenLinkConfirmation()) {
if (ChanSettings.openLinkConfirmation.get()) {
new AlertDialog.Builder(getContext())
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@ -223,7 +223,7 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres
@Override
public void openWebView(String title, String link) {
AndroidUtils.openWebView((Activity)getContext(), title, link);
AndroidUtils.openWebView((Activity) getContext(), title, link);
}
@Override

@ -26,16 +26,14 @@ import android.content.Intent;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import org.floens.chan.ChanApplication;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.manager.WatchManager;
import org.floens.chan.core.model.Pin;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.watch.PinWatcher;
import org.floens.chan.ui.activity.BoardActivity;
import org.floens.chan.ui.activity.ChanActivity;
import org.floens.chan.ui.activity.StartActivity;
import org.floens.chan.utils.AndroidUtils;
import java.util.ArrayList;
@ -61,7 +59,7 @@ public class WatchNotifier extends Service {
super.onCreate();
nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
wm = ChanApplication.getWatchManager();
wm = Chan.getWatchManager();
startForeground(NOTIFICATION_ID, createNotification());
}
@ -92,8 +90,8 @@ public class WatchNotifier extends Service {
}
private Notification createNotification() {
boolean notifyQuotesOnly = ChanSettings.getWatchNotifyMode().equals("quotes");
boolean soundQuotesOnly = ChanSettings.getWatchSound().equals("quotes");
boolean notifyQuotesOnly = ChanSettings.watchNotifyMode.get().equals("quotes");
boolean soundQuotesOnly = ChanSettings.watchSound.get().equals("quotes");
List<Post> list = new ArrayList<>();
List<Post> listQuoting = new ArrayList<>();
@ -138,7 +136,7 @@ public class WatchNotifier extends Service {
}
}
if (ChanApplication.getInstance().getApplicationInForeground()) {
if (Chan.getInstance().getApplicationInForeground()) {
ticker = false;
sound = false;
}
@ -210,7 +208,6 @@ public class WatchNotifier extends Service {
* @param expandedLines A list of lines for the big notification, or null if not shown
* @param makeSound Should the notification make a sound
* @param target The target pin, or null to open the pinned pane on tap
* @return
*/
@SuppressWarnings("deprecation")
private Notification getNotificationFor(String tickerText, String contentTitle, String contentText, int contentNumber,
@ -231,7 +228,7 @@ public class WatchNotifier extends Service {
}
if (light) {
long watchLed = ChanSettings.getWatchLed();
long watchLed = Long.parseLong(ChanSettings.watchLed.get(), 16);
if (watchLed >= 0) {
builder.setLights((int) watchLed, 1000, 1000);
}

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.settings;
import android.support.v7.widget.SwitchCompat;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.settings;
import android.view.View;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.settings;
import android.view.Gravity;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.settings;
import android.view.View;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.settings;
import android.content.Context;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.settings;
import java.util.ArrayList;

@ -1,3 +1,20 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.settings;
import android.app.AlertDialog;

@ -20,9 +20,7 @@ package org.floens.chan.ui.toolbar;
import android.content.Context;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.floens.chan.R;
import org.floens.chan.ui.view.FloatingMenu;
import org.floens.chan.ui.view.FloatingMenuItem;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save