Forgot to commit for a while:

Chan now remembers what you posted, and colors those posts.
You can delete those posts.
Tablet reply picker.
Developer options, acces through tapping about 5 times.
Better logger with tags.
Reloader at the bottom of threads.
captchafix
Florens Douwes 12 years ago
parent c8d5b387ad
commit 28a44d68c7
  1. 6
      Chan/AndroidManifest.xml
  2. 18
      Chan/res/menu/base.xml
  3. 29
      Chan/res/values/strings.xml
  4. 6
      Chan/res/xml/preference.xml
  5. 23
      Chan/src/org/floens/chan/activity/BoardActivity.java
  6. 4
      Chan/src/org/floens/chan/activity/BoardEditor.java
  7. 38
      Chan/src/org/floens/chan/activity/DeveloperActivity.java
  8. 6
      Chan/src/org/floens/chan/activity/ReplyActivity.java
  9. 9
      Chan/src/org/floens/chan/adapter/PostAdapter.java
  10. 27
      Chan/src/org/floens/chan/database/DatabaseHelper.java
  11. 78
      Chan/src/org/floens/chan/database/DatabaseManager.java
  12. 8
      Chan/src/org/floens/chan/fragment/ReplyFragment.java
  13. 2
      Chan/src/org/floens/chan/fragment/ThreadFragment.java
  14. 11
      Chan/src/org/floens/chan/imageview/ImageSaver.java
  15. 6
      Chan/src/org/floens/chan/imageview/activity/ImageViewActivity.java
  16. 10
      Chan/src/org/floens/chan/manager/BoardManager.java
  17. 3
      Chan/src/org/floens/chan/manager/PinnedManager.java
  18. 179
      Chan/src/org/floens/chan/manager/ReplyManager.java
  19. 113
      Chan/src/org/floens/chan/manager/ThreadManager.java
  20. 8
      Chan/src/org/floens/chan/model/Post.java
  21. 1
      Chan/src/org/floens/chan/model/Reply.java
  22. 19
      Chan/src/org/floens/chan/model/SavedReply.java
  23. 3
      Chan/src/org/floens/chan/net/ChanReaderRequest.java
  24. 5
      Chan/src/org/floens/chan/net/ChanUrls.java
  25. 10
      Chan/src/org/floens/chan/net/ThreadLoader.java
  26. 4
      Chan/src/org/floens/chan/utils/ChanPreferences.java
  27. 56
      Chan/src/org/floens/chan/utils/Logger.java
  28. 13
      Chan/src/org/floens/chan/utils/Utils.java
  29. 12
      Chan/src/org/floens/chan/view/PostView.java
  30. 8
      Chan/src/org/floens/chan/watch/PinWatcher.java
  31. 14
      Chan/src/org/floens/chan/watch/WatchLogic.java
  32. 8
      docs/4chanresponses.txt
  33. 24
      docs/delform.txt
  34. BIN
      docs/images/4chanlogo.png
  35. BIN
      docs/images/Icon512.png
  36. BIN
      docs/images/Icon512.psd
  37. BIN
      docs/images/IconWhite24.png
  38. BIN
      docs/images/IconWhite36.png
  39. BIN
      docs/images/IconWhite400.png
  40. BIN
      docs/images/IconWhite48.png
  41. BIN
      docs/images/IconWhite512.psd
  42. BIN
      docs/images/IconWhite72.png
  43. BIN
      docs/images/Screenshot_1.png
  44. BIN
      docs/images/Screenshot_2.png
  45. BIN
      docs/images/new screenshots/Screenshot_2014-02-23-00-01-02.png
  46. BIN
      docs/images/new screenshots/Screenshot_2014-02-23-00-01-29.png
  47. BIN
      docs/images/new screenshots/Screenshot_2014-02-23-00-01-44.png

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.floens.chan"
android:versionCode="11"
android:versionName="Chan v0.9"
android:versionCode="12"
android:versionName="Chan v0.10"
android:installLocation="auto" >
<uses-sdk
@ -59,7 +59,7 @@
</activity>
<activity
android:name="org.floens.chan.activity.AboutActivity"
android:label="@string/about"
android:label="@string/preference_about"
android:parentActivityName="org.floens.chan.activity.BoardActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"

@ -21,6 +21,24 @@
android:showAsAction="ifRoom"
android:title="@string/action_reply" />
<item
android:id="@+id/action_reply_tablet"
android:icon="@drawable/ic_action_chat"
android:orderInCategory="4"
android:showAsAction="ifRoom"
android:title="@string/action_reply" >
<menu>
<item
android:id="@+id/action_reply_board"
android:title="@string/action_reply_board" />
<item
android:id="@+id/action_reply_thread"
android:title="@string/action_reply_thread" />
</menu>
</item>
<item
android:id="@+id/action_share"
android:orderInCategory="5"

@ -1,29 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="cancel">Cancel</string>
<string name="change">Change</string>
<string name="add">Add</string>
<string name="app_name">Chan</string>
<string name="action_settings">Settings</string>
<string name="action_reload">Reload</string>
<string name="action_pin">Pin</string>
<string name="action_reply">Reply</string>
<string name="action_reply_board">Reply to board</string>
<string name="action_reply_thread">Reply to thread</string>
<string name="action_open_browser">Open in browser</string>
<string name="action_catalog">Open catalog</string>
<string name="action_share">Share</string>
<string name="preference_general">General</string>
<string name="about">About</string>
<string name="about_licenses">Open Source Licenses</string>
<string name="about_licences_summary">Legal information about licenses</string>
<string name="preference_about">About</string>
<string name="preference_licenses">Open Source Licenses</string>
<string name="preference_licences_summary">Legal information about licenses</string>
<string name="one_reply">reply</string>
<string name="multiple_replies">replies</string>
<string name="image_save">Save image</string>
<string name="image_save_folder_name">Chan</string>
<string name="image_save_succeeded">Saved image to</string>
<string name="image_save_failed">Saving image failed</string>
<string name="image_save_directory_error">Cannot make save directory</string>
@ -35,13 +35,11 @@
<string name="thread_load_failed_network">No network</string>
<string name="thread_load_failed_parsing">Server inaccessible</string>
<string name="thread_load_failed_server">404 not found</string>
<string name="end_of_line">No more posts</string>
<string name="thread_load_end_of_line">No more posts</string>
<string name="board_edit">Edit my boards</string>
<string name="board_add">Add board</string>
<string name="board_add_confirm">Add</string>
<string name="board_add_cancel">Cancel</string>
<string name="board_add_fail">Unknown board code</string>
<string name="board_add_success">Added</string>
<string name="board_add_duplicate">You already have that board</string>
@ -85,6 +83,16 @@
<string name="developer">Developer options</string>
<string name="delete">Delete</string>
<string name="delete_confirm">Delete your post?</string>
<string name="delete_wait">Deleting post…</string>
<string name="delete_success">Post deleted</string>
<string name="delete_password_incorrect">Password incorrect</string>
<string name="delete_too_soon">You must wait longer before deleting this post</string>
<string name="delete_too_old">You cannot delete a post this old</string>
<string name="delete_fail">Error deleting post</string>
<string name="delete_image_only">Only delete the image</string>
<string name="post_info">Info</string>
<string-array name="post_options">
<item>Reply</item>
@ -93,4 +101,5 @@
<item>Show clickables</item>
<item>Copy text</item>
</string-array>
</resources>

@ -27,12 +27,12 @@
</PreferenceCategory>
<PreferenceCategory
android:title="@string/about"
android:title="@string/preference_about"
android:key="group_about" >
<Preference
android:title="@string/about_licenses"
android:summary="@string/about_licences_summary"
android:title="@string/preference_licenses"
android:summary="@string/preference_licences_summary"
android:key="about_licences" />
<Preference

@ -183,8 +183,12 @@ public class BoardActivity extends BaseActivity implements ActionBar.OnNavigatio
public boolean onPrepareOptionsMenu(Menu menu) {
boolean open = threadPane.isOpen();
setMenuItemEnabled(menu.findItem(R.id.action_reload), !threadPane.isSlideable() || open);
setMenuItemEnabled(menu.findItem(R.id.action_pin), !threadPane.isSlideable() || !open);
setMenuItemEnabled(menu.findItem(R.id.action_reply), threadPane.isSlideable());
setMenuItemEnabled(menu.findItem(R.id.action_reply_tablet), !threadPane.isSlideable());
return super.onPrepareOptionsMenu(menu);
}
@ -203,22 +207,25 @@ public class BoardActivity extends BaseActivity implements ActionBar.OnNavigatio
switch(item.getItemId()) {
case R.id.action_reload:
if (threadPane.isOpen()) {
boardFragment.reload();
} else {
if (threadFragment.getThreadManager().hasLoadable()) {
threadFragment.reload();
}
}
return true;
case R.id.action_reply:
if (threadPane.isOpen()) {
boardFragment.getThreadManager().openReply(true); // todo if tablet
boardFragment.getThreadManager().openReply(true);
} else {
if (threadFragment.getThreadManager().hasLoadable()) {
threadFragment.getThreadManager().openReply(true); // todo if tablet
threadFragment.getThreadManager().openReply(true);
}
}
return true;
case R.id.action_reply_board:
boardFragment.getThreadManager().openReply(true);
return true;
case R.id.action_reply_thread:
if (threadFragment.getThreadManager().hasLoadable()) {
threadFragment.getThreadManager().openReply(true);
}
return true;
case R.id.action_pin:

@ -96,7 +96,7 @@ public class BoardEditor extends Activity {
text.setSingleLine();
dialog = new AlertDialog.Builder(this)
.setPositiveButton(R.string.board_add_confirm, new DialogInterface.OnClickListener() {
.setPositiveButton(R.string.add, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface d, int which) {
String value = text.getText().toString();
@ -106,7 +106,7 @@ public class BoardEditor extends Activity {
}
}
})
.setNegativeButton(R.string.board_add_cancel, new DialogInterface.OnClickListener() {
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface d, int which) {
}

@ -1,10 +1,13 @@
package org.floens.chan.activity;
import org.floens.chan.database.DatabaseManager;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
public class DeveloperActivity extends Activity {
@Override
@ -12,20 +15,47 @@ public class DeveloperActivity extends Activity {
super.onCreate(savedInstanceState);
LinearLayout wrapper = new LinearLayout(this);
wrapper.setOrientation(LinearLayout.VERTICAL);
Button button = new Button(this);
Button crashButton = new Button(this);
button.setOnClickListener(new View.OnClickListener() {
crashButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@SuppressWarnings("unused")
int i = 1 / 0;
}
});
button.setText("Crash the app");
crashButton.setText("Crash the app");
wrapper.addView(crashButton);
String dbSummary = "";
dbSummary += "Database summary:\n";
dbSummary += DatabaseManager.getInstance().getSummary();
wrapper.addView(button);
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) {
DatabaseManager.getInstance().reset();
System.exit(0);
}
});
resetDbButton.setText("Delete database");
wrapper.addView(resetDbButton);
setContentView(wrapper);
}
}

@ -2,14 +2,16 @@ package org.floens.chan.activity;
import org.floens.chan.fragment.ReplyFragment;
import org.floens.chan.model.Loadable;
import org.floens.chan.utils.Logger;
import android.app.Activity;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.util.Log;
import android.view.MenuItem;
public class ReplyActivity extends Activity {
private static final String TAG = "ReplyActivity";
private static Loadable loadable;
public static void setLoadable(Loadable l) {
@ -29,7 +31,7 @@ public class ReplyActivity extends Activity {
loadable = null;
} else {
Log.e("Chan", "ThreadFragment was null, exiting!");
Logger.e(TAG, "ThreadFragment was null, exiting!");
finish();
}
}

@ -6,12 +6,11 @@ import java.util.List;
import org.floens.chan.R;
import org.floens.chan.manager.ThreadManager;
import org.floens.chan.model.Post;
import org.floens.chan.utils.ViewUtils;
import org.floens.chan.utils.Utils;
import org.floens.chan.view.PostView;
import org.floens.chan.view.ThreadWatchCounterView;
import android.content.Context;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
@ -74,8 +73,6 @@ public class PostAdapter extends BaseAdapter {
}
postView.setPost(postList.get(position), threadManager);
} else {
Log.e("Chan", "PostAdapter: Invalid index: " + position + ", size: " + postList.size() + ", count: " + count);
}
return postView;
@ -85,7 +82,7 @@ public class PostAdapter extends BaseAdapter {
private View createThreadEndView() {
if (threadManager.getWatchLogic() != null) {
ThreadWatchCounterView view = new ThreadWatchCounterView(context);
ViewUtils.setPressedDrawable(view);
Utils.setPressedDrawable(view);
view.init(threadManager, listView, this);
int padding = context.getResources().getDimensionPixelSize(R.dimen.general_padding);
view.setPadding(padding, padding, padding, padding);
@ -96,7 +93,7 @@ public class PostAdapter extends BaseAdapter {
} else {
if (endOfLine) {
TextView textView = new TextView(context);
textView.setText(context.getString(R.string.end_of_line));
textView.setText(context.getString(R.string.thread_load_end_of_line));
int padding = context.getResources().getDimensionPixelSize(R.dimen.general_padding);
textView.setPadding(padding, padding, padding, padding);
return textView;

@ -4,6 +4,7 @@ import java.sql.SQLException;
import org.floens.chan.model.Loadable;
import org.floens.chan.model.Pin;
import org.floens.chan.model.SavedReply;
import org.floens.chan.utils.Logger;
import android.content.Context;
@ -15,18 +16,26 @@ import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
private static final String TAG = "DatabaseHelper";
private static final String DATABASE_NAME = "ChanDB";
private static final int DATABASE_VERSION = 3;
private static final int DATABASE_VERSION = 7;
public Dao<Pin, Integer> pinDao;
public Dao<Loadable, Integer> loadableDao;
public Dao<SavedReply, Integer> savedDao;
private final Context context;
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
this.context = context;
try {
pinDao = getDao(Pin.class);
loadableDao = getDao(Loadable.class);
savedDao = getDao(SavedReply.class);
} catch (SQLException e) {
e.printStackTrace();
}
@ -37,8 +46,9 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
try {
TableUtils.createTable(connectionSource, Pin.class);
TableUtils.createTable(connectionSource, Loadable.class);
TableUtils.createTable(connectionSource, SavedReply.class);
} catch (SQLException e) {
Logger.e("Error creating db", e);
Logger.e(TAG, "Error creating db", e);
throw new RuntimeException(e);
}
}
@ -50,9 +60,22 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
}
// Drop the tables and recreate them for now
reset(database, connectionSource);
}
public void reset() {
Logger.i(TAG, "Resetting database!");
if (context.deleteDatabase(DATABASE_NAME)) {
Logger.i(TAG, "Deleted database");
}
}
private void reset(SQLiteDatabase database, ConnectionSource connectionSource) {
try {
TableUtils.dropTable(connectionSource, Pin.class, true);
TableUtils.dropTable(connectionSource, Loadable.class, true);
TableUtils.dropTable(connectionSource, SavedReply.class, true);
onCreate(database, connectionSource);
} catch (SQLException e) {

@ -4,14 +4,18 @@ import java.sql.SQLException;
import java.util.List;
import org.floens.chan.model.Pin;
import org.floens.chan.model.SavedReply;
import org.floens.chan.utils.Logger;
import android.content.Context;
public class DatabaseManager {
private static final String TAG = "DatabaseManager";
private static DatabaseManager instance;
private final DatabaseHelper helper;
private List<SavedReply> savedReplies;
public DatabaseManager(Context context) {
instance = this;
@ -23,12 +27,52 @@ public class DatabaseManager {
return instance;
}
public void saveReply(SavedReply saved) {
Logger.e(TAG, "Saving " + saved.board + ", " + saved.no);
try {
helper.savedDao.create(saved);
} catch (SQLException e) {
Logger.e(TAG, "Error saving reply", e);
}
loadSavedReplies();
}
public SavedReply getSavedReply(String board, int no) {
if (savedReplies == null) {
loadSavedReplies();
}
// TODO: optimize this
for (SavedReply r : savedReplies) {
if (r.board.equals(board) && r.no == no) {
return r;
}
}
return null;
}
public boolean isSavedReply(String board, int no) {
return getSavedReply(board, no) != null;
}
private void loadSavedReplies() {
// TODO trim the table if it gets too large
try {
savedReplies = helper.savedDao.queryForAll();
} catch (SQLException e) {
Logger.e(TAG, "Error loading saved replies", e);
}
}
public void addPin(Pin pin) {
try {
helper.loadableDao.create(pin.loadable);
helper.pinDao.create(pin);
} catch (SQLException e) {
Logger.e("Error adding pin to db", e);
Logger.e(TAG, "Error adding pin to db", e);
}
}
@ -37,7 +81,7 @@ public class DatabaseManager {
helper.pinDao.delete(pin);
helper.loadableDao.delete(pin.loadable);
} catch (SQLException e) {
Logger.e("Error removing pin from db", e);
Logger.e(TAG, "Error removing pin from db", e);
}
}
@ -46,7 +90,7 @@ public class DatabaseManager {
helper.pinDao.update(pin);
helper.loadableDao.update(pin.loadable);
} catch (SQLException e) {
Logger.e("Error updating pin in db", e);
Logger.e(TAG, "Error updating pin in db", e);
}
}
@ -60,7 +104,7 @@ public class DatabaseManager {
helper.loadableDao.update(pin.loadable);
}
} catch(SQLException e) {
Logger.e("Error updating pins in db", e);
Logger.e(TAG, "Error updating pins in db", e);
}
}
@ -72,9 +116,33 @@ public class DatabaseManager {
helper.loadableDao.refresh(p.loadable);
}
} catch (SQLException e) {
Logger.e("Error getting pins from db", e);
Logger.e(TAG, "Error getting pins from db", e);
}
return list;
}
public String getSummary() {
String o = "";
try {
o += "Loadable rows: " + helper.loadableDao.countOf() + "\n";
o += "Pin rows: " + helper.pinDao.countOf() + "\n";
o += "SavedReply rows: " + helper.savedDao.countOf() + "\n";
} catch (SQLException e) {
e.printStackTrace();
}
return o;
}
public void reset() {
helper.reset();
loadSavedReplies();
}
}

@ -12,6 +12,7 @@ import org.floens.chan.net.ChanUrls;
import org.floens.chan.utils.ChanPreferences;
import org.floens.chan.utils.ImageDecoder;
import org.floens.chan.utils.LoadView;
import org.floens.chan.utils.Logger;
import org.floens.chan.utils.ViewFlipperAnimations;
import android.app.Dialog;
@ -21,7 +22,6 @@ import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@ -44,6 +44,8 @@ import com.android.volley.toolbox.NetworkImageView;
import com.android.volley.toolbox.StringRequest;
public class ReplyFragment extends DialogFragment {
private static final String TAG = "ReplyFragment";
private int page = 0;
private Loadable loadable;
@ -138,7 +140,7 @@ public class ReplyFragment extends DialogFragment {
getCaptcha();
} else {
Log.e("Chan", "Loadable in ReplyFragment was null");
Logger.e(TAG, "Loadable in ReplyFragment was null");
closeReply();
}
}
@ -419,7 +421,7 @@ public class ReplyFragment extends DialogFragment {
} else if (response.isSuccessful) {
shouldSaveDraft = false;
Toast.makeText(getActivity(), R.string.reply_success, Toast.LENGTH_SHORT).show();
// threadFragment.reload(); // TODO
// threadFragment.reload(); // won't work: it takes 4chan a variable time to process the reply
closeReply();
} else {
if (isVisible()) {

@ -160,7 +160,7 @@ public class ThreadFragment extends Fragment implements ThreadListener {
postAdapter.setEndOfLine(true);
} else {
if (container != null) {
container.setView(threadManager.getTextViewError(error));
container.setView(threadManager.getLoadErrorTextView(error));
}
}
}

@ -7,18 +7,21 @@ import java.io.IOException;
import org.floens.chan.ChanApplication;
import org.floens.chan.R;
import org.floens.chan.net.ByteArrayRequest;
import org.floens.chan.utils.ChanPreferences;
import org.floens.chan.utils.Logger;
import android.content.Context;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
import com.android.volley.Response;
import com.android.volley.VolleyError;
public class ImageSaver {
private static final String TAG = "ImageSaver";
public static void save(final Context context, String imageUrl, final String name, final String extension) {
ByteArrayRequest request = new ByteArrayRequest(imageUrl, new Response.Listener<byte[]>() {
@Override
@ -44,7 +47,7 @@ public class ImageSaver {
throw new IOException(errorReason);
}
File path = new File(Environment.getExternalStorageDirectory() + File.separator + context.getString(R.string.image_save_folder_name));
File path = new File(Environment.getExternalStorageDirectory() + File.separator + ChanPreferences.getImageSaveDirectory());
if (!path.exists()) {
if (!path.mkdirs()) {
@ -62,7 +65,7 @@ public class ImageSaver {
nextFileNameNumber++;
}
Log.i("Chan", "Saving image to: " + file.getPath());
Logger.i(TAG, "Saving image to: " + file.getPath());
FileOutputStream outputStream = new FileOutputStream(file);
outputStream.write(data);
@ -71,7 +74,7 @@ public class ImageSaver {
MediaScannerConnection.scanFile(context, new String[] { file.toString() }, null, new MediaScannerConnection.OnScanCompletedListener() {
@Override
public void onScanCompleted(String path, Uri uri) {
Log.i("Chan", "Media scan succeeded: " + uri);
Logger.i(TAG, "Media scan succeeded: " + uri);
}
});

@ -7,12 +7,12 @@ import org.floens.chan.adapter.PostAdapter;
import org.floens.chan.imageview.ImageSaver;
import org.floens.chan.imageview.adapter.ImageViewAdapter;
import org.floens.chan.model.Post;
import org.floens.chan.utils.Logger;
import android.app.ActionBar;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
@ -22,6 +22,8 @@ import android.view.Window;
* and then start the activity with startActivity()
*/
public class ImageViewActivity extends Activity implements ViewPager.OnPageChangeListener {
private static final String TAG = "ImageViewActivity";
private static PostAdapter postAdapter;
private static int selectedId = -1;
@ -75,7 +77,7 @@ public class ImageViewActivity extends Activity implements ViewPager.OnPageChang
}
}
} else {
Log.e("Chan", "Posts in imageview list was null");
Logger.e(TAG, "Posts in imageview list was null");
finish();
}
}

@ -8,15 +8,17 @@ import org.floens.chan.R;
import org.floens.chan.model.Board;
import org.floens.chan.net.BoardsRequest;
import org.floens.chan.net.ChanUrls;
import org.floens.chan.utils.Logger;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;
import com.android.volley.Response;
import com.android.volley.VolleyError;
public class BoardManager {
private static final String TAG = "BoardManager";
private static BoardManager instance;
private final Context context;
@ -196,7 +198,7 @@ public class BoardManager {
board.key = splitted[0];
board.value = splitted[1];
if (!board.finish()) {
Log.wtf("Chan", "board.finish in loadfrompreferences threw up");
Logger.wtf(TAG, "board.finish in loadfrompreferences threw up");
}
list.add(board);
@ -219,12 +221,12 @@ public class BoardManager {
storeBoardListInDatabase("allBoards", data);
allBoards = data;
Log.i("Chan", "Got boards from server");
Logger.i(TAG, "Got boards from server");
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("Chan", "Failed to get boards from server");
Logger.e(TAG, "Failed to get boards from server");
}
}));
}

@ -12,14 +12,11 @@ import android.content.Context;
public class PinnedManager {
private static PinnedManager instance;
private final Context context;
private final List<PinListener> listeners = new ArrayList<PinListener>();
private final List<Pin> pins;
public PinnedManager(Context context) {
instance = this;
this.context = context;
pins = DatabaseManager.getInstance().getPinned();
}

@ -4,16 +4,20 @@ import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Locale;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.floens.chan.activity.ImagePickActivity;
import org.floens.chan.database.DatabaseManager;
import org.floens.chan.model.Reply;
import org.floens.chan.model.SavedReply;
import org.floens.chan.net.ChanUrls;
import org.floens.chan.utils.Logger;
import org.floens.chan.utils.Utils;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
import ch.boye.httpclientandroidlib.client.methods.HttpPost;
@ -30,14 +34,18 @@ import ch.boye.httpclientandroidlib.util.EntityUtils;
* To send an reply to 4chan.
*/
public class ReplyManager {
private static final String TAG = "ReplyManager";
private static ReplyManager instance;
private static final Pattern challengePattern = Pattern.compile("challenge.?:.?'([\\w-]+)'");
private static final Pattern responsePattern = Pattern.compile("<!-- thread:([0-9]+),no:([0-9]+) -->");
private static final int POST_TIMEOUT = 10000;
private final Context context;
private Reply draft;
private FileListener fileListener;
private final Random random = new Random();
public ReplyManager(Context context) {
ReplyManager.instance = this;
@ -139,11 +147,74 @@ public class ReplyManager {
* @param reply The reply object with all data needed, like captcha and the file.
* @param listener The listener, after server response.
*/
public void sendReply(Reply reply, ReplyListener listener) {
public void sendDelete(final SavedReply reply, boolean onlyImageDelete, final DeleteListener listener) {
Logger.i(TAG, "Sending delete request: " + reply.board + ", " + reply.no);
HttpPost httpPost = new HttpPost(ChanUrls.getDeleteUrl(reply.board));
MultipartEntity entity = new MultipartEntity();
try {
entity.addPart(Integer.toString(reply.no), new StringBody("delete"));
if (onlyImageDelete) {
entity.addPart("onlyimgdel", new StringBody("on"));
}
// res not necessary
entity.addPart("mode", new StringBody("usrdel"));
entity.addPart("pwd", new StringBody(reply.password));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return;
}
httpPost.setEntity(entity);
sendHttpPost(httpPost, new HttpPostSendListener() {
@Override
public void onReponse(String responseString) {
DeleteResponse e = new DeleteResponse();
if (responseString == null) {
e.isNetworkError = true;
} else {
e.responseData = responseString;
if (responseString.contains("You must wait longer before deleting this post")) {
e.isUserError = true;
e.isTooSoonError = true;
} else if (responseString.contains("Password incorrect")) {
e.isUserError = true;
e.isInvalidPassword = true;
} else if (responseString.contains("You cannot delete a post this old")) {
e.isUserError = true;
e.isTooOldError = true;
} else if (responseString.contains("Updating index")) {
e.isSuccessful = true;
}
}
listener.onResponse(e);
}
});
}
/**
* Send an reply off to the server.
* @param reply The reply object with all data needed, like captcha and the file.
* @param listener The listener, after server response.
*/
public void sendReply(final Reply reply, final ReplyListener listener) {
Logger.i(TAG, "Sending reply request: " + reply.board + ", " + reply.resto);
HttpPost httpPost = new HttpPost(ChanUrls.getPostUrl(reply.board));
MultipartEntity entity = new MultipartEntity();
reply.password = Long.toHexString(random.nextLong());
try {
entity.addPart("name", new StringBody(reply.name));
entity.addPart("email", new StringBody(reply.email));
@ -159,7 +230,7 @@ public class ReplyManager {
entity.addPart("recaptcha_response_field", new StringBody(reply.captchaResponse));
entity.addPart("mode", new StringBody("regist"));
entity.addPart("pwd", new StringBody(""));
entity.addPart("pwd", new StringBody(reply.password));
if (reply.file != null) {
entity.addPart("upfile", new FileBody(reply.file, reply.fileName, "application/octet-stream", "UTF-8"));
@ -171,7 +242,50 @@ public class ReplyManager {
httpPost.setEntity(entity);
new SendTask().execute(httpPost, listener);
sendHttpPost(httpPost, new HttpPostSendListener() {
@Override
public void onReponse(String responseString) {
ReplyResponse e = new ReplyResponse();
if (responseString == null) {
e.isNetworkError = true;
} else {
e.responseData = responseString;
if (responseString.contains("No file selected")) {
e.isUserError = true;
e.isFileError = true;
} else if (responseString.contains("You forgot to solve the CAPTCHA") ||
responseString.contains("You seem to have mistyped the CAPTCHA")) {
e.isUserError = true;
e.isCaptchaError = true;
} else if (responseString.toLowerCase(Locale.ENGLISH).contains("post successful")) {
e.isSuccessful = true;
}
}
if (e.isSuccessful) {
Matcher matcher = responsePattern.matcher(e.responseData);
if (matcher.find() && matcher.groupCount() == 2) {
try {
SavedReply savedReply = new SavedReply();
savedReply.board = reply.board;
savedReply.no = Integer.parseInt(matcher.group(2));
savedReply.password = reply.password;
DatabaseManager.getInstance().saveReply(savedReply);
} catch (NumberFormatException err) {
err.printStackTrace();
}
} else {
Logger.w(TAG, "No thread & no in the response");
}
}
listener.onResponse(e);
}
});
}
/**
@ -181,14 +295,10 @@ public class ReplyManager {
* that has another namespace: ch.boye.httpclientandroidlib
* This lib also has some fixes/improvements of HttpClient for Android.
*/
private class SendTask extends AsyncTask<Object, Void, ReplyResponse> {
private ReplyListener listener;
private void sendHttpPost(final HttpPost post, final HttpPostSendListener listener) {
new Thread(new Runnable() {
@Override
protected ReplyResponse doInBackground(Object... params) {
HttpPost post = (HttpPost) params[0];
listener = (ReplyListener) params[1];
public void run() {
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, POST_TIMEOUT);
HttpConnectionParams.setSoTimeout(httpParameters, POST_TIMEOUT);
@ -206,31 +316,20 @@ public class ReplyManager {
e.printStackTrace();
}
ReplyResponse e = new ReplyResponse();
final String finalResponseString = responseString;
if (responseString == null) {
e.isNetworkError = true;
} else {
e.responseData = responseString;
if (responseString.contains("No file selected")) {
e.isUserError = true;
e.isFileError = true;
} else if (responseString.contains("You forgot to solve the CAPTCHA") || responseString.contains("You seem to have mistyped the CAPTCHA")) {
e.isUserError = true;
e.isCaptchaError = true;
} else if (responseString.toLowerCase(Locale.ENGLISH).contains("post successful")) {
e.isSuccessful = true;
Utils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onReponse(finalResponseString);
}
});
}
return e;
}).start();
}
@Override
public void onPostExecute(ReplyResponse response) {
listener.onResponse(response);
}
private static interface HttpPostSendListener {
public void onReponse(String responseString);
}
public static abstract class FileListener {
@ -245,8 +344,22 @@ public class ReplyManager {
public abstract void onFileLoading();
}
public static abstract class ReplyListener {
public abstract void onResponse(ReplyResponse response);
public static interface DeleteListener {
public void onResponse(DeleteResponse response);
}
public static class DeleteResponse {
public boolean isNetworkError = false;
public boolean isUserError = false;
public boolean isInvalidPassword = false;
public boolean isTooSoonError = false;
public boolean isTooOldError = false;
public boolean isSuccessful = false;
public String responseData = "";
}
public static interface ReplyListener {
public void onResponse(ReplyResponse response);
}
public static class ReplyResponse {

@ -5,12 +5,16 @@ import java.util.List;
import org.floens.chan.R;
import org.floens.chan.activity.ReplyActivity;
import org.floens.chan.database.DatabaseManager;
import org.floens.chan.fragment.PostRepliesFragment;
import org.floens.chan.fragment.ReplyFragment;
import org.floens.chan.manager.ReplyManager.DeleteListener;
import org.floens.chan.manager.ReplyManager.DeleteResponse;
import org.floens.chan.model.Loadable;
import org.floens.chan.model.Pin;
import org.floens.chan.model.Post;
import org.floens.chan.model.PostLinkable;
import org.floens.chan.model.SavedReply;
import org.floens.chan.net.ThreadLoader;
import org.floens.chan.utils.ChanPreferences;
import org.floens.chan.utils.Logger;
@ -20,17 +24,20 @@ import org.floens.chan.watch.WatchLogic.WatchListener;
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.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.widget.CheckBox;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView;
import android.widget.Toast;
import com.android.volley.NetworkError;
import com.android.volley.NoConnectionError;
@ -44,6 +51,8 @@ import com.android.volley.VolleyError;
* onDestroy, onPause and onResume must be called from the activity/fragment
*/
public class ThreadManager implements ThreadLoader.ThreadLoaderListener, WatchListener {
private static final String TAG = "ThreadManager";
private final Activity activity;
private final ThreadLoader threadLoader;
private final ThreadManager.ThreadListener threadListener;
@ -81,7 +90,7 @@ public class ThreadManager implements ThreadLoader.ThreadLoaderListener, WatchLi
@Override
public void onWatchReloadRequested() {
Logger.test("ThreadManager reload requested");
Logger.d(TAG, "Reload requested");
if (!threadLoader.isLoading()) {
threadLoader.start(loadable);
@ -157,7 +166,7 @@ public class ThreadManager implements ThreadLoader.ThreadLoaderListener, WatchLi
public void reload() {
if (loadable == null) {
Log.e("Chan", "ThreadManager: loadable null");
Logger.e(TAG, "ThreadManager: loadable null");
} else {
if (loadable.isBoardMode()) {
loadable.no = 0;
@ -173,7 +182,7 @@ public class ThreadManager implements ThreadLoader.ThreadLoaderListener, WatchLi
if (threadLoader.isLoading()) return;
if (loadable == null) {
Log.e("Chan", "ThreadManager: loadable null");
Logger.e(TAG, "ThreadManager: loadable null");
} else {
if (loadable.isBoardMode()) {
if (!endOfLine) {
@ -200,7 +209,19 @@ public class ThreadManager implements ThreadLoader.ThreadLoaderListener, WatchLi
public void onPostLongClicked(final Post post) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setItems(R.array.post_options, new DialogInterface.OnClickListener() {
String[] items = null;
String[] temp = activity.getResources().getStringArray(R.array.post_options);
// Only add the delete option when the post is a saved reply
if (DatabaseManager.getInstance().isSavedReply(post.board, post.no)) {
items = new String[temp.length + 1];
System.arraycopy(temp, 0, items, 0, temp.length);
items[items.length - 1] = activity.getString(R.string.delete);
} else {
items = temp;
}
builder.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch(which) {
@ -217,7 +238,10 @@ public class ThreadManager implements ThreadLoader.ThreadLoaderListener, WatchLi
showPostLinkables(post);
break;
case 4: // Copy text
copyText(post.comment.toString());
copyToClipboard(post.comment.toString());
break;
case 5: // Delete
deletePost(post);
break;
}
}
@ -242,11 +266,11 @@ public class ThreadManager implements ThreadLoader.ThreadLoaderListener, WatchLi
}
/**
* Returns an TextView containing the appropiate error message
* Returns an TextView containing the appropriate error message
* @param error
* @return
*/
public TextView getTextViewError(VolleyError error) {
public TextView getLoadErrorTextView(VolleyError error) {
String errorMessage = "";
if ((error instanceof NoConnectionError) || (error instanceof NetworkError)) {
@ -266,19 +290,21 @@ public class ThreadManager implements ThreadLoader.ThreadLoaderListener, WatchLi
return view;
}
private void copyText(String comment) {
private void copyToClipboard(String comment) {
ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("post text", comment);
ClipData clip = ClipData.newPlainText("Post text", comment);
clipboard.setPrimaryClip(clip);
}
private void showPostInfo(Post post) {
String text = "Time: " + post.date;
String text = "";
if (post.hasImage) {
text += "\nFile: " + post.filename + " \nSize: " + post.imageWidth + "x" + post.imageHeight;
text += "File: " + post.filename + " \nSize: " + post.imageWidth + "x" + post.imageHeight + "\n\n";
}
text += "Time: " + post.date ;
if (!TextUtils.isEmpty(post.id)) {
text += "\nId: " + post.id;
}
@ -467,6 +493,69 @@ public class ThreadManager implements ThreadLoader.ThreadLoaderListener, WatchLi
currentPopupFragment = null;
}
private void deletePost(final Post post) {
final CheckBox view = new CheckBox(activity);
view.setText(R.string.delete_image_only);
int padding = activity.getResources().getDimensionPixelSize(R.dimen.general_padding);
view.setPadding(padding, padding, padding, padding);
new AlertDialog.Builder(activity)
.setTitle(R.string.delete_confirm)
.setView(view)
.setPositiveButton(R.string.delete, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
doDeletePost(post, view.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 = DatabaseManager.getInstance().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));
ReplyManager.getInstance().sendDelete(reply, onlyImageDelete, new DeleteListener() {
@Override
public void onResponse(DeleteResponse response) {
dialog.dismiss();
if (response.isNetworkError || response.isUserError) {
int resId = 0;
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 ThreadListener {
public void onThreadLoaded(List<Post> result);
public void onThreadLoadError(VolleyError error);

@ -24,12 +24,11 @@ import android.text.style.ForegroundColorSpan;
*/
public class Post {
public String board;
public boolean isOP = false;
public int no = -1;
public int resto = -1;
public boolean isOP = false;
public String date;
public String name = "";
private String rawComment;
public CharSequence comment = "";
public String subject = "";
public String tim;
@ -50,6 +49,7 @@ public class Post {
public String countryName = "";
public long time = 0;
public String email = "";
public boolean isSavedReply = false;
/**
* This post replies to the these ids
@ -61,12 +61,14 @@ public class Post {
*/
public List<Integer> repliesFrom = new ArrayList<Integer>();
private PostView linkableListener;
public final ArrayList<PostLinkable> linkables = new ArrayList<PostLinkable>();
/**
* The PostView the Post is currently bound to.
*/
private PostView linkableListener;
private String rawComment;
public Post() {
}

@ -16,4 +16,5 @@ public class Reply {
public String fileName = "";
public String captchaChallenge = "";
public String captchaResponse = "";
public String password = "";
}

@ -0,0 +1,19 @@
package org.floens.chan.model;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
@DatabaseTable
public class SavedReply {
@DatabaseField(generatedId = true)
private int id;
@DatabaseField
public String board = "";
@DatabaseField
public int no;
@DatabaseField
public String password = "";
}

@ -4,6 +4,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.floens.chan.database.DatabaseManager;
import org.floens.chan.model.Loadable;
import org.floens.chan.model.Post;
@ -80,6 +81,8 @@ public class ChanReaderRequest extends JsonReaderRequest<List<Post>> {
post.repliesFrom.add(other.no);
}
}
post.isSavedReply = DatabaseManager.getInstance().isSavedReply(post.board, post.no);
}
}

@ -44,6 +44,11 @@ public class ChanUrls {
// return "http://192.168.6.214/Testing/PostEchoer/post.php";
}
public static String getDeleteUrl(String board) {
return "https://sys.4chan.org/" + board + "/imgboard.php";
// return "http://192.168.6.214/Testing/PostEchoer/post.php";
}
public static String getBoardUrlDesktop(String board) {
return "https://boards.4chan.org/" + board + "/";
}

@ -5,8 +5,8 @@ import java.util.List;
import org.floens.chan.ChanApplication;
import org.floens.chan.model.Loadable;
import org.floens.chan.model.Post;
import org.floens.chan.utils.Logger;
import android.util.Log;
import android.util.SparseArray;
import com.android.volley.Response;
@ -14,6 +14,8 @@ import com.android.volley.ServerError;
import com.android.volley.VolleyError;
public class ThreadLoader {
private static final String TAG = "ThreadLoader";
private final ThreadLoaderListener listener;
private ChanReaderRequest loader;
private boolean stopped = false;
@ -34,6 +36,8 @@ public class ThreadLoader {
// public void start(int mode, String board, int pageOrThreadId) {
public void start(Loadable loadable) {
Logger.i(TAG, "Start loading " + loadable.board + ", " + loadable.no);
stop();
stopped = false;
@ -49,6 +53,8 @@ public class ThreadLoader {
}
public void stop() {
Logger.i(TAG, "Stop loading");
if (loader != null) {
loader.cancel();
loader = null;
@ -97,7 +103,7 @@ public class ThreadLoader {
private void onError(VolleyError error) {
if (stopped) return;
Log.e("Chan", "VolleyError in ThreadLoader: " + error.getMessage());
Logger.e(TAG, "Error loading" + error.getMessage(), error);
// 404 with more pages already loaded means endofline
if ((error instanceof ServerError) && loadable.isBoardMode() && loadable.no > 0) {

@ -22,4 +22,8 @@ public class ChanPreferences {
public static void setDeveloper(boolean developer) {
ChanApplication.getPreferences().edit().putBoolean("preference_developer", developer).commit();
}
public static String getImageSaveDirectory() {
return "Chan";
}
}

@ -5,66 +5,66 @@ import org.floens.chan.ChanApplication;
import android.util.Log;
public class Logger {
public static final String TAG = "Chan";
public static final String TAG_TEST = "ChanTest";
private static final String TAG = "Chan";
private static final String TAG_SPACER = " | ";
public static void v(String message) {
Log.v(TAG, message);
public static void v(String tag, String message) {
Log.v(TAG + TAG_SPACER + tag, message);
}
public static void v(String message, Throwable throwable) {
Log.v(TAG, message, throwable);
public static void v(String tag, String message, Throwable throwable) {
Log.v(TAG + TAG_SPACER + tag, message, throwable);
}
public static void d(String message) {
Log.d(TAG, message);
public static void d(String tag, String message) {
Log.d(TAG + TAG_SPACER + tag, message);
}
public static void d(String message, Throwable throwable) {
Log.d(TAG, message, throwable);
public static void d(String tag, String message, Throwable throwable) {
Log.d(TAG + TAG_SPACER + tag, message, throwable);
}
public static void i(String message) {
Log.i(TAG, message);
public static void i(String tag, String message) {
Log.i(TAG + TAG_SPACER + tag, message);
}
public static void i(String message, Throwable throwable) {
Log.i(TAG, message, throwable);
public static void i(String tag, String message, Throwable throwable) {
Log.i(TAG + TAG_SPACER + tag, message, throwable);
}
public static void w(String message) {
Log.w(TAG, message);
public static void w(String tag, String message) {
Log.w(TAG + TAG_SPACER + tag, message);
}
public static void w(String message, Throwable throwable) {
Log.w(TAG, message, throwable);
public static void w(String tag, String message, Throwable throwable) {
Log.w(TAG + TAG_SPACER + tag, message, throwable);
}
public static void e(String message) {
Log.e(TAG, message);
public static void e(String tag, String message) {
Log.e(TAG + TAG_SPACER + tag, message);
}
public static void e(String message, Throwable throwable) {
Log.e(TAG, message, throwable);
public static void e(String tag, String message, Throwable throwable) {
Log.e(TAG + TAG_SPACER + tag, message, throwable);
}
public static void wtf(String message) {
Log.wtf(TAG, message);
public static void wtf(String tag, String message) {
Log.wtf(TAG + TAG_SPACER + tag, message);
}
public static void wtf(String message, Throwable throwable) {
Log.wtf(TAG, message, throwable);
public static void wtf(String tag, String message, Throwable throwable) {
Log.wtf(TAG + TAG_SPACER + tag, message, throwable);
}
public static void test(String message) {
if (ChanApplication.DEVELOPER_MODE) {
Log.i(TAG_TEST, message);
Log.i(TAG + TAG_SPACER + "test", message);
}
}
public static void test(String message, Throwable throwable) {
if (ChanApplication.DEVELOPER_MODE) {
Log.i(TAG_TEST, message, throwable);
Log.i(TAG + TAG_SPACER + "test", message, throwable);
}
}
}

@ -2,9 +2,11 @@ package org.floens.chan.utils;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
public class ViewUtils {
public class Utils {
@SuppressWarnings("deprecation")
public static void setPressedDrawable(View view) {
TypedArray arr = view.getContext().obtainStyledAttributes(
@ -16,4 +18,13 @@ public class ViewUtils {
arr.recycle();
}
/**
* Causes the runnable to be added to the message queue.
* The runnable will be run on the ui thread.
* @param runnable
*/
public static void runOnUiThread(Runnable runnable) {
new Handler(Looper.getMainLooper()).post(runnable);
}
}

@ -7,7 +7,7 @@ import org.floens.chan.model.Post;
import org.floens.chan.model.PostLinkable;
import org.floens.chan.net.ChanUrls;
import org.floens.chan.utils.IconCache;
import org.floens.chan.utils.ViewUtils;
import org.floens.chan.utils.Utils;
import android.app.Activity;
import android.content.Context;
@ -226,7 +226,13 @@ public class PostView extends LinearLayout implements View.OnClickListener, View
full.setFocusable(true);
full.setOnClickListener(this);
ViewUtils.setPressedDrawable(full);
Utils.setPressedDrawable(full);
}
if (post.isSavedReply) {
full.setBackgroundColor(0xFFD6BAD0);
} else {
full.setBackgroundColor(0x00000000);
}
}
@ -308,7 +314,7 @@ public class PostView extends LinearLayout implements View.OnClickListener, View
// Set the drawable before the padding, because setting the background resets the padding
// This behavior differs with 4.4 / 4.1
ViewUtils.setPressedDrawable(repliesCountView);
Utils.setPressedDrawable(repliesCountView);
repliesCountView.setTextColor(Color.argb(255, 100, 100, 100));
repliesCountView.setPadding(postPadding, postPadding, postPadding, postPadding);

@ -12,6 +12,8 @@ import org.floens.chan.utils.Logger;
import com.android.volley.VolleyError;
public class PinWatcher implements ThreadLoader.ThreadLoaderListener {
private static final String TAG = "PinWatcher";
private final ThreadLoader watchLoader;
private final Loadable watchLoadable;
@ -45,12 +47,12 @@ public class PinWatcher implements ThreadLoader.ThreadLoaderListener {
public void onData(List<Post> result) {
int count = result.size();
Logger.i("PinWatcher onData");
Logger.i("Post size: " + count);
Logger.test("PinWatcher onData");
Logger.test("Post size: " + count);
pin.watchNewCount = count;
Logger.i("Load time: " + (System.currentTimeMillis() - startTime) + "ms");
Logger.test("Load time: " + (System.currentTimeMillis() - startTime) + "ms");
PinnedService.callOnPinsChanged();
}

@ -6,6 +6,8 @@ import java.util.TimerTask;
import org.floens.chan.utils.Logger;
public class WatchLogic {
private static final String TAG = "WatchLogic";
private static final int[] timeouts = {10, 15, 20, 30, 60, 90, 120, 180, 240, 300};
private WatchListener listener;
@ -23,16 +25,16 @@ public class WatchLogic {
* Stops the timer and removes the listener
*/
public void destroy() {
Logger.d(TAG, "Destroy");
this.listener = null;
clearTimer();
Logger.test("WatchLogic destroy()");
}
/**
* Starts the timer from the beginning
*/
public void startTimer() {
Logger.test("WatchLogic timer start");
Logger.d(TAG, "Start timer");
lastLoadTime = now();
selectedTimeout = 0;
@ -43,7 +45,7 @@ public class WatchLogic {
* Stops the timer
*/
public void stopTimer() {
Logger.test("WatchLogic timer paused");
Logger.d(TAG, "Stop timer");
clearTimer();
}
@ -66,7 +68,7 @@ public class WatchLogic {
* @param postCount how many posts there were loaded
*/
public void onLoaded(int postCount) {
Logger.test("WatchLogic onLoaded: " + (postCount > lastPostCount));
Logger.d(TAG, "Loaded. Post count " + postCount + ", last " + lastPostCount);
if (postCount > lastPostCount) {
selectedTimeout = 0;
@ -90,6 +92,8 @@ public class WatchLogic {
}
private void scheduleTimer() {
Logger.d(TAG, "Scheduling timer");
clearTimer();
timer.schedule(new TimerTask() {
@ -106,6 +110,8 @@ public class WatchLogic {
* Clear all the scheduled runnables
*/
private void clearTimer() {
Logger.d(TAG, "Clearing timer");
timer.cancel();
timer.purge();
timer = new Timer();

@ -0,0 +1,8 @@
Error: No file selected.
Error: You forgot to solve the CAPTCHA. Please try again.
Error: You seem to have mistyped the CAPTCHA. Please try again.
<!DOCTYPE html><head><meta http-equiv="refresh" content="1;URL=http://boards.4chan.org/b/res/515317358#p515318219"><link rel="shortcut icon" href="//static.4chan.org/image/favicon.ico"><title>Post successful!</title><link rel="stylesheet" title="switch" href="//static.4chan.org/css/yotsubanew.525.css"></head><body style="margin-top: 20%; text-align: center;"><h1 style="font-size:36pt;">Post successful!</h1><!-- thread:515317358,no:515318219 --></body></html>

@ -0,0 +1,24 @@
https://sys.4chan.org/ BOARD /imgboard.php
mode=usrdel
res=123456789
onlyimgdel=on (optional)
pwd=qwerty
90283402934=delete
example post:
39894014=delete&mode=usrdel&res=39894014&onlyimgdel=on&pwd=qwerty
39894014=delete&mode=usrdel&res=39894014&pwd=_LfF81U0aBiSgQ3MDj9pkJZYr4N27meWb
Responses:
Error: You cannot delete a post this old.
Error: Password incorrect.
Error: You must wait longer before deleting this post.
When successful:
<!doctype html><head><meta http-equiv="refresh" content="2;URL=http://boards.4chan.org/b/./"><title>Updating index...</title></head><body><table style="font-family:times,serif;font-size:36pt;text-align:center;width:100%;height:300px;"><td><strong>Updating index...</strong></td></table>

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Loading…
Cancel
Save