Hardcode another site, board adding and removing from the new editor.

multisite
Floens 8 years ago
parent 345c22de7f
commit 08df656d05
  1. BIN
      Clover/app/src/main/assets/icons/8chan.png
  2. 42
      Clover/app/src/main/java/org/floens/chan/core/database/DatabaseBoardManager.java
  3. 39
      Clover/app/src/main/java/org/floens/chan/core/database/DatabaseHelper.java
  4. 9
      Clover/app/src/main/java/org/floens/chan/core/database/DatabaseManager.java
  5. 14
      Clover/app/src/main/java/org/floens/chan/core/di/AppModule.java
  6. 86
      Clover/app/src/main/java/org/floens/chan/core/manager/BoardManager.java
  7. 44
      Clover/app/src/main/java/org/floens/chan/core/model/orm/Board.java
  8. 109
      Clover/app/src/main/java/org/floens/chan/core/presenter/BoardSetupPresenter.java
  9. 8
      Clover/app/src/main/java/org/floens/chan/core/presenter/SiteSetupPresenter.java
  10. 2
      Clover/app/src/main/java/org/floens/chan/core/site/Site.java
  11. 3
      Clover/app/src/main/java/org/floens/chan/core/site/SiteIcon.java
  12. 19
      Clover/app/src/main/java/org/floens/chan/core/site/SiteManager.java
  13. 20
      Clover/app/src/main/java/org/floens/chan/core/site/Sites.java
  14. 16
      Clover/app/src/main/java/org/floens/chan/core/site/common/ChanReaderRequest.java
  15. 3
      Clover/app/src/main/java/org/floens/chan/core/site/common/PostParseCallable.java
  16. 5
      Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4.java
  17. 254
      Clover/app/src/main/java/org/floens/chan/core/site/sites/chan8/Chan8.java
  18. 9
      Clover/app/src/main/java/org/floens/chan/ui/controller/BoardEditController.java
  19. 237
      Clover/app/src/main/java/org/floens/chan/ui/controller/BoardSetupController.java
  20. 3
      Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java
  21. 17
      Clover/app/src/main/java/org/floens/chan/ui/controller/SiteSetupController.java
  22. 82
      Clover/app/src/main/java/org/floens/chan/ui/drawable/ThumbDrawable.java
  23. 11
      Clover/app/src/main/java/org/floens/chan/ui/helper/PostHelper.java
  24. 2
      Clover/app/src/main/java/org/floens/chan/ui/layout/FilterLayout.java
  25. 17
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadSlidingPaneLayout.java
  26. 1
      Clover/app/src/main/java/org/floens/chan/ui/view/LoadView.java
  27. BIN
      Clover/app/src/main/res/drawable-hdpi/ic_reorder_black_24dp.png
  28. BIN
      Clover/app/src/main/res/drawable-mdpi/ic_reorder_black_24dp.png
  29. BIN
      Clover/app/src/main/res/drawable-xhdpi/ic_reorder_black_24dp.png
  30. BIN
      Clover/app/src/main/res/drawable-xxhdpi/ic_reorder_black_24dp.png
  31. BIN
      Clover/app/src/main/res/drawable-xxxhdpi/ic_reorder_black_24dp.png
  32. 10
      Clover/app/src/main/res/layout/cell_board_edit.xml
  33. 42
      Clover/app/src/main/res/layout/cell_board_suggestion.xml
  34. 7
      Clover/app/src/main/res/layout/cell_saved_board.xml
  35. 5
      Clover/app/src/main/res/layout/controller_board_setup.xml
  36. 1
      docs/database.txt

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

@ -20,14 +20,18 @@ public class DatabaseBoardManager {
this.helper = helper; this.helper = helper;
} }
/** public Callable<Board> createOrUpdate(final Board board) {
* Save the boards listed in the database.<br> return new Callable<Board>() {
* The boards need to either come from a {@link Site} or from {@link #getBoards(Site)}. @Override
* public Board call() throws Exception {
* @param boards boards to set or update helper.boardsDao.createOrUpdate(board);
* @return void
*/ return board;
public Callable<Void> setBoards(final List<Board> boards) { }
};
}
public Callable<Void> createAll(final List<Board> boards) {
return new Callable<Void>() { return new Callable<Void>() {
@Override @Override
public Void call() throws Exception { public Void call() throws Exception {
@ -62,6 +66,28 @@ public class DatabaseBoardManager {
}; };
} }
public Callable<List<Board>> getSavedBoards() {
return new Callable<List<Board>>() {
@Override
public List<Board> call() throws Exception {
List<Board> boards = null;
try {
boards = helper.boardsDao.queryBuilder()
.where().eq("saved", true)
.query();
for (int i = 0; i < boards.size(); i++) {
Board board = boards.get(i);
board.site = Sites.forId(board.siteId);
}
} catch (SQLException e) {
Logger.e(TAG, "Error getting boards from db", e);
}
return boards;
}
};
}
public Callable<List<Board>> getAllBoards() { public Callable<List<Board>> getAllBoards() {
return new Callable<List<Board>>() { return new Callable<List<Board>>() {
@Override @Override

@ -44,7 +44,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
private static final String TAG = "DatabaseHelper"; private static final String TAG = "DatabaseHelper";
private static final String DATABASE_NAME = "ChanDB"; private static final String DATABASE_NAME = "ChanDB";
private static final int DATABASE_VERSION = 23; private static final int DATABASE_VERSION = 22;
public Dao<Pin, Integer> pinDao; public Dao<Pin, Integer> pinDao;
public Dao<Loadable, Integer> loadableDao; public Dao<Loadable, Integer> loadableDao;
@ -57,6 +57,8 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
private final Context context; private final Context context;
public boolean isUpgrading = false;
public DatabaseHelper(Context context) { public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION); super(context, DATABASE_NAME, null, DATABASE_VERSION);
@ -96,6 +98,8 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
@Override @Override
public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) { public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) {
Logger.i(TAG, "Upgrading database from " + oldVersion + " to " + newVersion); Logger.i(TAG, "Upgrading database from " + oldVersion + " to " + newVersion);
isUpgrading = true;
if (oldVersion < 12) { if (oldVersion < 12) {
try { try {
boardsDao.executeRawNoArgs("ALTER TABLE board ADD COLUMN perPage INTEGER;"); boardsDao.executeRawNoArgs("ALTER TABLE board ADD COLUMN perPage INTEGER;");
@ -210,22 +214,39 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
if (oldVersion < 22) { if (oldVersion < 22) {
try { try {
boardsDao.executeRawNoArgs("ALTER TABLE loadable ADD COLUMN site INTEGER default 0;"); siteDao.executeRawNoArgs("CREATE TABLE `site` (`configuration` VARCHAR , `id` INTEGER PRIMARY KEY AUTOINCREMENT , `userSettings` VARCHAR );");
boardsDao.executeRawNoArgs("ALTER TABLE board ADD COLUMN site INTEGER default 0;");
boardsDao.executeRawNoArgs("ALTER TABLE savedreply ADD COLUMN site INTEGER default 0;");
boardsDao.executeRawNoArgs("ALTER TABLE threadhide ADD COLUMN site INTEGER default 0;");
} catch (SQLException e) { } catch (SQLException e) {
Logger.e(TAG, "Error upgrading to version 22", e); Logger.e(TAG, "Error upgrading to version 22", e);
} }
}
if (oldVersion < 23) { // final Site[] siteRef = new Site[1];
// getGraph().get(SiteManager.class).addSiteFromClass(Chan4.class, new SiteManager.SiteAddCallback() {
// @Override
// public void onSiteAdded(Site site) {
// siteRef[0] = site;
// }
//
// @Override
// public void onSiteAddFailed(String message) {
// throw new RuntimeException("Error adding site for db upgrade: " + message);
// }
// });
//
// // Will always be 1
// int siteId = siteRef[0].id();
int siteId = 0;
try { try {
siteDao.executeRawNoArgs("CREATE TABLE `site` (`configuration` VARCHAR , `id` INTEGER PRIMARY KEY AUTOINCREMENT , `userSettings` VARCHAR );"); boardsDao.executeRawNoArgs("ALTER TABLE loadable ADD COLUMN site INTEGER default " + siteId + ";");
boardsDao.executeRawNoArgs("ALTER TABLE board ADD COLUMN site INTEGER default " + siteId + ";");
boardsDao.executeRawNoArgs("ALTER TABLE savedreply ADD COLUMN site INTEGER default " + siteId + ";");
boardsDao.executeRawNoArgs("ALTER TABLE threadhide ADD COLUMN site INTEGER default " + siteId + ";");
} catch (SQLException e) { } catch (SQLException e) {
Logger.e(TAG, "Error upgrading to version 23", e); Logger.e(TAG, "Error upgrading to version 22", e);
} }
} }
isUpgrading = false;
} }
public void reset() { public void reset() {

@ -281,6 +281,15 @@ public class DatabaseManager {
} }
private <T> Future<T> executeTask(final Callable<T> taskCallable, final TaskResult<T> taskResult) { private <T> Future<T> executeTask(final Callable<T> taskCallable, final TaskResult<T> taskResult) {
if (helper.isUpgrading) {
try {
taskCallable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}
return backgroundExecutor.submit(new Callable<T>() { return backgroundExecutor.submit(new Callable<T>() {
@Override @Override
public T call() { public T call() {

@ -13,7 +13,8 @@ import org.floens.chan.core.database.DatabaseManager;
import org.floens.chan.core.manager.BoardManager; import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.manager.FilterEngine; import org.floens.chan.core.manager.FilterEngine;
import org.floens.chan.core.manager.ReplyManager; import org.floens.chan.core.manager.ReplyManager;
import org.floens.chan.core.presenter.SetupPresenter; import org.floens.chan.core.presenter.BoardSetupPresenter;
import org.floens.chan.core.presenter.SiteSetupPresenter;
import org.floens.chan.core.site.SiteManager; import org.floens.chan.core.site.SiteManager;
import org.floens.chan.core.manager.WatchManager; import org.floens.chan.core.manager.WatchManager;
import org.floens.chan.core.net.BitmapLruImageCache; import org.floens.chan.core.net.BitmapLruImageCache;
@ -24,12 +25,13 @@ import org.floens.chan.core.receiver.WatchUpdateReceiver;
import org.floens.chan.core.saver.ImageSaveTask; import org.floens.chan.core.saver.ImageSaveTask;
import org.floens.chan.core.site.http.HttpCallManager; import org.floens.chan.core.site.http.HttpCallManager;
import org.floens.chan.core.site.sites.chan4.Chan4; import org.floens.chan.core.site.sites.chan4.Chan4;
import org.floens.chan.core.site.sites.chan4.Chan4ReaderRequest; import org.floens.chan.core.site.common.ChanReaderRequest;
import org.floens.chan.core.update.UpdateManager; import org.floens.chan.core.update.UpdateManager;
import org.floens.chan.ui.activity.BoardActivity; import org.floens.chan.ui.activity.BoardActivity;
import org.floens.chan.ui.adapter.DrawerAdapter; import org.floens.chan.ui.adapter.DrawerAdapter;
import org.floens.chan.ui.adapter.PostsFilter; import org.floens.chan.ui.adapter.PostsFilter;
import org.floens.chan.ui.controller.BoardEditController; import org.floens.chan.ui.controller.BoardEditController;
import org.floens.chan.ui.controller.BoardSetupController;
import org.floens.chan.ui.controller.BrowseController; import org.floens.chan.ui.controller.BrowseController;
import org.floens.chan.ui.controller.DeveloperSettingsController; import org.floens.chan.ui.controller.DeveloperSettingsController;
import org.floens.chan.ui.controller.DrawerController; import org.floens.chan.ui.controller.DrawerController;
@ -38,6 +40,7 @@ import org.floens.chan.ui.controller.HistoryController;
import org.floens.chan.ui.controller.ImageViewerController; import org.floens.chan.ui.controller.ImageViewerController;
import org.floens.chan.ui.controller.MainSettingsController; import org.floens.chan.ui.controller.MainSettingsController;
import org.floens.chan.ui.controller.PassSettingsController; import org.floens.chan.ui.controller.PassSettingsController;
import org.floens.chan.ui.controller.SiteSetupController;
import org.floens.chan.ui.controller.ViewThreadController; import org.floens.chan.ui.controller.ViewThreadController;
import org.floens.chan.ui.helper.ImagePickDelegate; import org.floens.chan.ui.helper.ImagePickDelegate;
import org.floens.chan.ui.layout.FilterLayout; import org.floens.chan.ui.layout.FilterLayout;
@ -65,7 +68,7 @@ import dagger.Provides;
ChanApplication.class, ChanApplication.class,
MainSettingsController.class, MainSettingsController.class,
ReplyPresenter.class, ReplyPresenter.class,
Chan4ReaderRequest.class, ChanReaderRequest.class,
ThreadLayout.class, ThreadLayout.class,
DeveloperSettingsController.class, DeveloperSettingsController.class,
BoardActivity.class, BoardActivity.class,
@ -92,7 +95,10 @@ import dagger.Provides;
WatchManager.PinWatcher.class, WatchManager.PinWatcher.class,
UpdateManager.class, UpdateManager.class,
SiteManager.class, SiteManager.class,
SetupPresenter.class, SiteSetupPresenter.class,
BoardSetupPresenter.class,
SiteSetupController.class,
BoardSetupController.class,
Chan4.class, Chan4.class,
}, },

@ -22,13 +22,11 @@ import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.site.Boards; import org.floens.chan.core.site.Boards;
import org.floens.chan.core.site.Site; import org.floens.chan.core.site.Site;
import org.floens.chan.core.site.Sites; import org.floens.chan.core.site.Sites;
import org.floens.chan.utils.Logger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -36,7 +34,15 @@ import javax.inject.Singleton;
import de.greenrobot.event.EventBus; import de.greenrobot.event.EventBus;
/** /**
* Keeps track of {@link Board}s that the user has "saved" to their list. * <p>Keeps track of {@link Board}s in the system.
* <p>There are a few types of sites, those who provide a list of all boards known,
* sites where users can create boards and have a very long list of known boards,
* and those who don't provide a board list at all.
* <p>We try to save as much info about boards as possible, this means that we try to save all
* boards we encounter.
* For sites with a small list of boards which does provide a board list api we save all those boards.
* <p>All boards have a {@link Board#saved} flag indicating if it should be visible in the user's
* favorite board list, along with a {@link Board#order} in which they appear.
*/ */
@Singleton @Singleton
public class BoardManager { public class BoardManager {
@ -57,24 +63,66 @@ public class BoardManager {
}; };
private final DatabaseManager databaseManager; private final DatabaseManager databaseManager;
private final Site defaultSite;
private final List<Board> boards;
private final List<Board> savedBoards = new ArrayList<>(); private final List<Board> savedBoards = new ArrayList<>();
private final Map<String, Board> boardsByCode = new HashMap<>();
@Inject @Inject
public BoardManager(DatabaseManager databaseManager) { public BoardManager(DatabaseManager databaseManager) {
this.databaseManager = databaseManager; this.databaseManager = databaseManager;
defaultSite = Sites.defaultSite();
boards = databaseManager.runTaskSync(databaseManager.getDatabaseBoardManager().getBoards(defaultSite));
if (boards.isEmpty()) { loadBoards();
update(false);
fetchLimitedSitesTheirBoards();
}
public List<Board> getSavedBoards() {
return savedBoards;
}
public void saveBoard(Board board) {
board.saved = true;
board = databaseManager.runTaskSync(databaseManager.getDatabaseBoardManager().createOrUpdate(board));
loadBoards();
}
public void unsaveBoard(Board board) {
board.saved = false;
board = databaseManager.runTaskSync(databaseManager.getDatabaseBoardManager().createOrUpdate(board));
loadBoards();
}
private void loadBoards() {
savedBoards.clear();
savedBoards.addAll(databaseManager.runTaskSync(databaseManager.getDatabaseBoardManager().getSavedBoards()));
EventBus.getDefault().post(new BoardsChangedMessage());
}
private void fetchLimitedSitesTheirBoards() {
List<Site> sites = Sites.allSites();
for (final Site site : sites) {
if (site.boardsType() == Site.BoardsType.DYNAMIC) {
site.boards(new Site.BoardsListener() {
@Override
public void onBoardsReceived(Boards boards) {
handleBoardsFetch(site, boards);
}
});
}
} }
} }
private void appendBoards(Boards response) { private void handleBoardsFetch(Site site, Boards boards) {
Logger.i(TAG, "Got boards for " + site.name());
databaseManager.runTask(databaseManager.getDatabaseBoardManager().createAll(boards.boards));
}
/*private void appendBoards(Boards response) {
List<Board> boardsToAddWs = new ArrayList<>(); List<Board> boardsToAddWs = new ArrayList<>();
List<Board> boardsToAddNws = new ArrayList<>(); List<Board> boardsToAddNws = new ArrayList<>();
@ -131,9 +179,9 @@ public class BoardManager {
public void flushOrderAndSaved() { public void flushOrderAndSaved() {
saveDatabase(); saveDatabase();
update(true); update(true);
} }*/
private void update(boolean notify) { /*private void update(boolean notify) {
savedBoards.clear(); savedBoards.clear();
savedBoards.addAll(filterSaved(boards)); savedBoards.addAll(filterSaved(boards));
synchronized (boardsByCode) { synchronized (boardsByCode) {
@ -145,13 +193,13 @@ public class BoardManager {
if (notify) { if (notify) {
EventBus.getDefault().post(new BoardsChangedMessage()); EventBus.getDefault().post(new BoardsChangedMessage());
} }
} }*/
private void saveDatabase() { /*private void saveDatabase() {
databaseManager.runTask(databaseManager.getDatabaseBoardManager().setBoards(boards)); databaseManager.runTask(databaseManager.getDatabaseBoardManager().setBoards(boards));
} }*/
private List<Board> filterSaved(List<Board> all) { /*private List<Board> filterSaved(List<Board> all) {
List<Board> saved = new ArrayList<>(all.size()); List<Board> saved = new ArrayList<>(all.size());
for (int i = 0; i < all.size(); i++) { for (int i = 0; i < all.size(); i++) {
Board board = all.get(i); Board board = all.get(i);
@ -161,7 +209,7 @@ public class BoardManager {
} }
Collections.sort(saved, ORDER_SORT); Collections.sort(saved, ORDER_SORT);
return saved; return saved;
} }*/
public static class BoardsChangedMessage { public static class BoardsChangedMessage {
} }

@ -27,18 +27,6 @@ import org.floens.chan.core.site.Site;
@DatabaseTable(tableName = "board") @DatabaseTable(tableName = "board")
public class Board implements SiteReference { public class Board implements SiteReference {
public Board() {
}
public Board(Site site, String name, String code, boolean saved, boolean workSafe) {
this.siteId = site.id();
this.site = site;
this.name = name;
this.code = code;
this.saved = saved;
this.workSafe = workSafe;
}
@DatabaseField(generatedId = true) @DatabaseField(generatedId = true)
public int id; public int id;
@ -50,9 +38,6 @@ public class Board implements SiteReference {
*/ */
public transient Site site; public transient Site site;
/**
* {@code true} if this board appears in the dropdown, {@code false} otherwise.
*/
@DatabaseField @DatabaseField
public boolean saved = false; public boolean saved = false;
@ -62,12 +47,10 @@ public class Board implements SiteReference {
@DatabaseField @DatabaseField
public int order; public int order;
// named key for legacy support @DatabaseField(columnName = "key") // named key for legacy support
@DatabaseField(columnName = "key")
public String name; public String name;
// named value for legacy support @DatabaseField(columnName = "value") // named value for legacy support
@DatabaseField(columnName = "value")
// TODO(sec) force filter this to ascii & numbers. // TODO(sec) force filter this to ascii & numbers.
public String code; public String code;
@ -142,6 +125,29 @@ public class Board implements SiteReference {
@DatabaseField @DatabaseField
public String description; public String description;
@Deprecated // public, at least
public Board() {
}
@Deprecated
public Board(Site site, String name, String code, boolean saved, boolean workSafe) {
this.siteId = site.id();
this.site = site;
this.name = name;
this.code = code;
this.saved = saved;
this.workSafe = workSafe;
}
public static Board fromSiteNameCode(Site site, String name, String code) {
Board board = new Board();
board.siteId = site.id();
board.site = site;
board.name = name;
board.code = code;
return board;
}
public boolean finish() { public boolean finish() {
if (TextUtils.isEmpty(name) || TextUtils.isEmpty(code) || perPage < 0 || pages < 0) { if (TextUtils.isEmpty(name) || TextUtils.isEmpty(code) || perPage < 0 || pages < 0) {
return false; return false;

@ -0,0 +1,109 @@
/*
* 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 org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.site.Site;
import org.floens.chan.core.site.Sites;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import static org.floens.chan.Chan.getGraph;
public class BoardSetupPresenter {
private Callback callback;
private final List<Site> sites = new ArrayList<>();
@Inject
BoardManager boardManager;
private List<Board> savedBoards;
@Inject
public BoardSetupPresenter() {
getGraph().inject(this);
}
public void create(Callback callback) {
this.callback = callback;
sites.addAll(Sites.allSites());
loadSavedBoards();
callback.setSavedBoards(savedBoards);
}
public void addFromSuggestion(BoardSuggestion suggestion) {
Board board = Board.fromSiteNameCode(suggestion.site, suggestion.key, suggestion.key);
boardManager.saveBoard(board);
loadSavedBoards();
callback.setSavedBoards(savedBoards);
}
public void move(int from, int to) {
Board item = savedBoards.remove(from);
savedBoards.add(to, item);
callback.setSavedBoards(savedBoards);
}
public void remove(int position) {
Board board = savedBoards.remove(position);
boardManager.unsaveBoard(board);
loadSavedBoards();
callback.setSavedBoards(savedBoards);
}
public List<BoardSuggestion> getSuggestionsForQuery(String query) {
List<BoardSuggestion> suggestions = new ArrayList<>();
for (Site site : sites) {
suggestions.add(new BoardSuggestion(query, site));
}
return suggestions;
}
private void loadSavedBoards() {
savedBoards = new ArrayList<>(boardManager.getSavedBoards());
}
public interface Callback {
void setSavedBoards(List<Board> savedBoards);
}
public static class BoardSuggestion {
public final String key;
public final Site site;
public BoardSuggestion(String key, Site site) {
this.key = key;
this.site = site;
}
}
}

@ -29,7 +29,7 @@ import javax.inject.Inject;
import static org.floens.chan.Chan.getGraph; import static org.floens.chan.Chan.getGraph;
public class SetupPresenter { public class SiteSetupPresenter {
@Inject @Inject
SiteManager siteManager; SiteManager siteManager;
@ -38,14 +38,14 @@ public class SetupPresenter {
private List<Site> sites = new ArrayList<>(); private List<Site> sites = new ArrayList<>();
@Inject @Inject
public SetupPresenter() { public SiteSetupPresenter() {
getGraph().inject(this); getGraph().inject(this);
} }
public void create(Callback callback) { public void create(Callback callback) {
this.callback = callback; this.callback = callback;
sites.addAll(Sites.ALL_SITES); sites.addAll(Sites.allSites());
this.callback.setAddedSites(sites); this.callback.setAddedSites(sites);
@ -80,7 +80,7 @@ public class SetupPresenter {
private void siteAdded(Site site) { private void siteAdded(Site site) {
sites.clear(); sites.clear();
sites.addAll(Sites.ALL_SITES); sites.addAll(Sites.allSites());
callback.setAddedSites(sites); callback.setAddedSites(sites);
callback.runSiteAddedAnimation(site); callback.runSiteAddedAnimation(site);

@ -88,7 +88,7 @@ public interface Site {
*/ */
enum BoardsType { enum BoardsType {
/** /**
* The site's boards are static, hard-coded in the site. * The site's boards are static, there is no extra info for a board in the api.
*/ */
STATIC, STATIC,

@ -46,6 +46,9 @@ public class SiteIcon {
return siteIcon; return siteIcon;
} }
private SiteIcon() {
}
public void get(SiteIconResult result) { public void get(SiteIconResult result) {
if (assetPath != null) { if (assetPath != null) {
Bitmap bitmap; Bitmap bitmap;

@ -51,9 +51,9 @@ public class SiteManager {
public void addSite(String url, SiteAddCallback callback) { public void addSite(String url, SiteAddCallback callback) {
SiteResolver.SiteResolverResult resolve = resolver.resolve(url); SiteResolver.SiteResolverResult resolve = resolver.resolve(url);
Site site; Class<? extends Site> siteClass;
if (resolve.match == SiteResolver.SiteResolverResult.Match.BUILTIN) { if (resolve.match == SiteResolver.SiteResolverResult.Match.BUILTIN) {
site = instantiateSiteClass(resolve.builtinResult); siteClass = resolve.builtinResult;
} else if (resolve.match == SiteResolver.SiteResolverResult.Match.EXTERNAL) { } else if (resolve.match == SiteResolver.SiteResolverResult.Match.EXTERNAL) {
callback.onSiteAddFailed("external todo"); callback.onSiteAddFailed("external todo");
return; return;
@ -62,11 +62,16 @@ public class SiteManager {
return; return;
} }
addSiteFromClass(siteClass, callback);
}
public void addSiteFromClass(Class<? extends Site> siteClass, SiteAddCallback callback) {
Site site = instantiateSiteClass(siteClass);
site = createNewSite(site); site = createNewSite(site);
List<Site> newAllSites = new ArrayList<>(Sites.ALL_SITES); List<Site> newAllSites = new ArrayList<>(Sites.allSites());
newAllSites.add(site); newAllSites.add(site);
Sites.initialize(newAllSites); setAvailableSites(newAllSites);
callback.onSiteAdded(site); callback.onSiteAdded(site);
} }
@ -91,7 +96,11 @@ public class SiteManager {
sites.add(site); sites.add(site);
} }
Sites.initialize(sites); setAvailableSites(sites);
}
private void setAvailableSites(List<Site> newAllSites) {
Sites.initialize(newAllSites);
} }
private List<Site> loadSitesFromDatabase() { private List<Site> loadSitesFromDatabase() {

@ -3,8 +3,10 @@ package org.floens.chan.core.site;
import android.util.SparseArray; import android.util.SparseArray;
import org.floens.chan.core.site.sites.chan4.Chan4; import org.floens.chan.core.site.sites.chan4.Chan4;
import org.floens.chan.core.site.sites.chan8.Chan8;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
public class Sites { public class Sites {
@ -15,15 +17,28 @@ public class Sites {
// This differs from the Site.id() id, that id is used for site instance linking, this is just to // This differs from the Site.id() id, that id is used for site instance linking, this is just to
// find the correct class to use. // find the correct class to use.
SITE_CLASSES.put(0, Chan4.class); SITE_CLASSES.put(0, Chan4.class);
SITE_CLASSES.put(1, Chan8.class);
} }
public static final List<Resolvable> RESOLVABLES = new ArrayList<>(); public static final List<Resolvable> RESOLVABLES = new ArrayList<>();
static { static {
RESOLVABLES.add(Chan4.RESOLVABLE); RESOLVABLES.add(Chan4.RESOLVABLE);
RESOLVABLES.add(Chan8.RESOLVABLE);
} }
public static final List<Site> ALL_SITES = new ArrayList<>(); private static List<Site> ALL_SITES = Collections.unmodifiableList(new ArrayList<Site>());
/**
* Return all sites known in the system.
* <p>This list is immutable. Changes to the known sites cause this function to return a new immutable list
* with the site changes.
* @return list of sites known in the system.
*/
public static List<Site> allSites() {
return ALL_SITES;
}
@Deprecated @Deprecated
private static Site defaultSite; private static Site defaultSite;
@ -46,7 +61,6 @@ public class Sites {
static void initialize(List<Site> sites) { static void initialize(List<Site> sites) {
Sites.defaultSite = sites.isEmpty() ? null : sites.get(0); Sites.defaultSite = sites.isEmpty() ? null : sites.get(0);
Sites.ALL_SITES.clear(); Sites.ALL_SITES = Collections.unmodifiableList(new ArrayList<>(sites));
Sites.ALL_SITES.addAll(sites);
} }
} }

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.floens.chan.core.site.sites.chan4; package org.floens.chan.core.site.common;
import android.util.JsonReader; import android.util.JsonReader;
@ -56,8 +56,8 @@ import static org.floens.chan.Chan.getGraph;
* This class is highly multithreaded, take good care to not access models that are to be only * This class is highly multithreaded, take good care to not access models that are to be only
* changed on the main thread. * changed on the main thread.
*/ */
public class Chan4ReaderRequest extends JsonReaderRequest<ChanLoaderResponse> { public class ChanReaderRequest extends JsonReaderRequest<ChanLoaderResponse> {
private static final String TAG = "Chan4ReaderRequest"; private static final String TAG = "ChanReaderRequest";
private static final boolean LOG_TIMING = false; private static final boolean LOG_TIMING = false;
private static final int THREAD_COUNT; private static final int THREAD_COUNT;
@ -85,7 +85,7 @@ public class Chan4ReaderRequest extends JsonReaderRequest<ChanLoaderResponse> {
private List<Filter> filters; private List<Filter> filters;
private long startLoad; private long startLoad;
public Chan4ReaderRequest(ChanLoaderRequestParams request) { public ChanReaderRequest(ChanLoaderRequestParams request) {
super(getChanUrl(request.loadable).toString(), request.listener, request.errorListener); super(getChanUrl(request.loadable).toString(), request.listener, request.errorListener);
getGraph().inject(this); getGraph().inject(this);
@ -360,7 +360,7 @@ public class Chan4ReaderRequest extends JsonReaderRequest<ChanLoaderResponse> {
builder.board(loadable.board); builder.board(loadable.board);
// File // File
long fileId = 0; String fileId = null;
String fileExt = null; String fileExt = null;
int fileWidth = 0; int fileWidth = 0;
int fileHeight = 0; int fileHeight = 0;
@ -397,7 +397,7 @@ public class Chan4ReaderRequest extends JsonReaderRequest<ChanLoaderResponse> {
builder.comment(reader.nextString()); builder.comment(reader.nextString());
break; break;
case "tim": case "tim":
fileId = reader.nextLong(); fileId = reader.nextString();
break; break;
case "time": case "time":
builder.setUnixTimestampSeconds(reader.nextLong()); builder.setUnixTimestampSeconds(reader.nextLong());
@ -491,9 +491,9 @@ public class Chan4ReaderRequest extends JsonReaderRequest<ChanLoaderResponse> {
} }
SiteEndpoints endpoints = loadable.getSite().endpoints(); SiteEndpoints endpoints = loadable.getSite().endpoints();
if (fileId != 0 && fileName != null && fileExt != null) { if (fileId != null && fileName != null && fileExt != null) {
Map<String, String> hack = new HashMap<>(2); Map<String, String> hack = new HashMap<>(2);
hack.put("tim", String.valueOf(fileId)); hack.put("tim", fileId);
hack.put("ext", fileExt); hack.put("ext", fileExt);
builder.image(new PostImage.Builder() builder.image(new PostImage.Builder()
.originalName(String.valueOf(fileId)) .originalName(String.valueOf(fileId))

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.floens.chan.core.site.sites.chan4; package org.floens.chan.core.site.common;
import org.floens.chan.chan.ChanParser; import org.floens.chan.chan.ChanParser;
import org.floens.chan.core.database.DatabaseSavedReplyManager; import org.floens.chan.core.database.DatabaseSavedReplyManager;
@ -27,6 +27,7 @@ import java.util.List;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
// Called concurrently to parse the post html and the filters on it // Called concurrently to parse the post html and the filters on it
// Belongs to ChanReaderRequest
class PostParseCallable implements Callable<Post> { class PostParseCallable implements Callable<Post> {
private static final String TAG = "PostParseCallable"; private static final String TAG = "PostParseCallable";

@ -41,6 +41,7 @@ import org.floens.chan.core.site.SiteBase;
import org.floens.chan.core.site.SiteEndpoints; import org.floens.chan.core.site.SiteEndpoints;
import org.floens.chan.core.site.SiteIcon; import org.floens.chan.core.site.SiteIcon;
import org.floens.chan.core.site.SiteRequestModifier; import org.floens.chan.core.site.SiteRequestModifier;
import org.floens.chan.core.site.common.ChanReaderRequest;
import org.floens.chan.core.site.http.DeleteRequest; import org.floens.chan.core.site.http.DeleteRequest;
import org.floens.chan.core.site.http.HttpCall; import org.floens.chan.core.site.http.HttpCall;
import org.floens.chan.core.site.http.HttpCallManager; import org.floens.chan.core.site.http.HttpCallManager;
@ -376,7 +377,7 @@ public class Chan4 extends SiteBase {
@Override @Override
public Board board(String code) { public Board board(String code) {
List<Board> allBoards = getGraph().get(BoardManager.class).getAllBoards(); List<Board> allBoards = getGraph().get(BoardManager.class).getSavedBoards();
for (Board board : allBoards) { for (Board board : allBoards) {
if (board.code.equals(code)) { if (board.code.equals(code)) {
return board; return board;
@ -403,7 +404,7 @@ public class Chan4 extends SiteBase {
@Override @Override
public ChanLoaderRequest loaderRequest(ChanLoaderRequestParams request) { public ChanLoaderRequest loaderRequest(ChanLoaderRequestParams request) {
return new ChanLoaderRequest(new Chan4ReaderRequest(request)); return new ChanLoaderRequest(new ChanReaderRequest(request));
} }
@Override @Override

@ -0,0 +1,254 @@
/*
* 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.site.sites.chan8;
import android.support.annotation.Nullable;
import android.webkit.WebView;
import org.floens.chan.chan.ChanLoaderRequest;
import org.floens.chan.chan.ChanLoaderRequestParams;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.site.Resolvable;
import org.floens.chan.core.site.Site;
import org.floens.chan.core.site.SiteAuthentication;
import org.floens.chan.core.site.SiteBase;
import org.floens.chan.core.site.SiteEndpoints;
import org.floens.chan.core.site.SiteIcon;
import org.floens.chan.core.site.SiteRequestModifier;
import org.floens.chan.core.site.common.ChanReaderRequest;
import org.floens.chan.core.site.http.DeleteRequest;
import org.floens.chan.core.site.http.HttpCall;
import org.floens.chan.core.site.http.LoginRequest;
import org.floens.chan.core.site.http.Reply;
import java.util.Locale;
import java.util.Map;
import okhttp3.HttpUrl;
import okhttp3.Request;
public class Chan8 extends SiteBase {
public static final Resolvable RESOLVABLE = new Resolvable() {
@Override
public ResolveResult resolve(String value) {
if (value.equals("8chan")) {
return ResolveResult.NAME_MATCH;
} else if (value.equals("https://8ch.net/")) {
return ResolveResult.FULL_MATCH;
} else {
return ResolveResult.NO;
}
}
@Override
public Class<? extends Site> getSiteClass() {
return Chan8.class;
}
};
private final SiteEndpoints endpoints = new SiteEndpoints() {
private final HttpUrl root = new HttpUrl.Builder()
.scheme("https")
.host("8ch.net")
.build();
private final HttpUrl media = new HttpUrl.Builder()
.scheme("https")
.host("media.8ch.net")
.build();
@Override
public HttpUrl catalog(Board board) {
return root.newBuilder()
.addPathSegment(board.code)
.addPathSegment("catalog.json")
.build();
}
@Override
public HttpUrl thread(Board board, Loadable loadable) {
return root.newBuilder()
.addPathSegment(board.code)
.addPathSegment("res")
.addPathSegment(loadable.no + ".json")
.build();
}
@Override
public HttpUrl imageUrl(Post.Builder post, Map<String, String> arg) {
return root.newBuilder()
.addPathSegment("file_store")
.addPathSegment(arg.get("tim") + "." + arg.get("ext"))
.build();
}
@Override
public HttpUrl thumbnailUrl(Post.Builder post, boolean spoiler, Map<String, String> arg) {
return root.newBuilder()
.addPathSegment("file_store")
.addPathSegment("thumb")
.addPathSegment(arg.get("tim") + "." + arg.get("ext"))
.build();
}
@Override
public HttpUrl icon(Post.Builder post, String icon, Map<String, String> arg) {
HttpUrl.Builder stat = root.newBuilder().addPathSegment("static");
switch (icon) {
case "country":
stat.addPathSegment("flags");
stat.addPathSegment(arg.get("country_code").toLowerCase(Locale.ENGLISH) + ".png");
break;
}
return stat.build();
}
@Override
public HttpUrl boards() {
return null;
}
@Override
public HttpUrl reply(Loadable loadable) {
return null;
}
@Override
public HttpUrl delete(Post post) {
return null;
}
@Override
public HttpUrl report(Post post) {
return null;
}
@Override
public HttpUrl login() {
return null;
}
};
private SiteRequestModifier siteRequestModifier = new SiteRequestModifier() {
@Override
public void modifyHttpCall(HttpCall httpCall, Request.Builder requestBuilder) {
}
@SuppressWarnings("deprecation")
@Override
public void modifyWebView(WebView webView) {
}
};
private SiteAuthentication authentication = new SiteAuthentication() {
@Override
public boolean requireAuthentication(AuthenticationRequestType type) {
return false;
}
};
@Override
public String name() {
return "8chan";
}
@Override
public SiteIcon icon() {
return SiteIcon.fromAssets("icons/8chan.png");
}
@Override
public boolean feature(Feature feature) {
return false;
}
@Override
public boolean boardFeature(BoardFeature boardFeature, Board board) {
return false;
}
@Override
public SiteEndpoints endpoints() {
return endpoints;
}
@Override
public SiteRequestModifier requestModifier() {
return siteRequestModifier;
}
@Override
public SiteAuthentication authentication() {
return authentication;
}
@Override
public BoardsType boardsType() {
return BoardsType.INFINITE;
}
@Override
public String desktopUrl(Loadable loadable, @Nullable Post post) {
return "https://8ch.net/";
}
@Override
public void boards(BoardsListener boardsListener) {
}
@Override
public Board board(String code) {
return null;
}
@Override
public ChanLoaderRequest loaderRequest(ChanLoaderRequestParams request) {
return new ChanLoaderRequest(new ChanReaderRequest(request));
}
@Override
public void post(Reply reply, PostListener postListener) {
}
@Override
public void delete(DeleteRequest deleteRequest, DeleteListener deleteListener) {
}
@Override
public void login(LoginRequest loginRequest, LoginListener loginListener) {
}
@Override
public void logout() {
}
@Override
public boolean isLoggedIn() {
return false;
}
@Override
public LoginRequest getLoginDetails() {
return null;
}
}

@ -44,7 +44,6 @@ import org.floens.chan.R;
import org.floens.chan.controller.Controller; import org.floens.chan.controller.Controller;
import org.floens.chan.core.manager.BoardManager; import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.model.orm.Board; import org.floens.chan.core.model.orm.Board;
import org.floens.chan.ui.drawable.ThumbDrawable;
import org.floens.chan.ui.helper.BoardHelper; import org.floens.chan.ui.helper.BoardHelper;
import org.floens.chan.ui.toolbar.ToolbarMenu; import org.floens.chan.ui.toolbar.ToolbarMenu;
import org.floens.chan.ui.toolbar.ToolbarMenuItem; import org.floens.chan.ui.toolbar.ToolbarMenuItem;
@ -182,7 +181,8 @@ public class BoardEditController extends Controller implements View.OnClickListe
boards.get(i).order = i; boards.get(i).order = i;
} }
boardManager.flushOrderAndSaved(); // TODO(multisite)
// boardManager.flushOrderAndSaved();
} }
@Override @Override
@ -243,7 +243,7 @@ public class BoardEditController extends Controller implements View.OnClickListe
} }
// Normal add // Normal add
List<Board> all = boardManager.getAllBoards(); List<Board> all = boardManager.getSavedBoards();
for (Board board : all) { for (Board board : all) {
if (board.code.equals(value)) { if (board.code.equals(value)) {
board.saved = true; board.saved = true;
@ -385,7 +385,7 @@ public class BoardEditController extends Controller implements View.OnClickListe
} }
}*/ }*/
List<Board> s = new ArrayList<>(); List<Board> s = new ArrayList<>();
for (Board b : boardManager.getAllBoards()) { for (Board b : boardManager.getSavedBoards()) {
if (!haveBoard(b.code)/* && (showUnsafe || b.workSafe)*/) { if (!haveBoard(b.code)/* && (showUnsafe || b.workSafe)*/) {
s.add(b); s.add(b);
} }
@ -454,7 +454,6 @@ public class BoardEditController extends Controller implements View.OnClickListe
thumb = (ImageView) itemView.findViewById(R.id.thumb); thumb = (ImageView) itemView.findViewById(R.id.thumb);
text = (TextView) itemView.findViewById(R.id.text); text = (TextView) itemView.findViewById(R.id.text);
description = (TextView) itemView.findViewById(R.id.description); description = (TextView) itemView.findViewById(R.id.description);
thumb.setImageDrawable(new ThumbDrawable());
thumb.setOnTouchListener(new View.OnTouchListener() { thumb.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) { public boolean onTouch(View v, MotionEvent event) {

@ -19,34 +19,49 @@ package org.floens.chan.ui.controller;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.drawable.Drawable;
import android.graphics.BitmapFactory; import android.support.v4.graphics.drawable.DrawableCompat;
import android.graphics.drawable.BitmapDrawable;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.AdapterView;
import android.widget.AutoCompleteTextView; import android.widget.AutoCompleteTextView;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.controller.Controller; import org.floens.chan.controller.Controller;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.presenter.BoardSetupPresenter;
import org.floens.chan.core.site.SiteIcon;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.floens.chan.utils.AndroidUtils.getAppContext; import javax.inject.Inject;
import static org.floens.chan.utils.AndroidUtils.getRes;
import static org.floens.chan.Chan.getGraph;
import static org.floens.chan.ui.helper.PostHelper.formatBoardCodeAndName;
import static org.floens.chan.utils.AndroidUtils.getAttrColor;
public class BoardSetupController extends Controller implements View.OnClickListener, AdapterView.OnItemClickListener, BoardSetupPresenter.Callback {
@Inject
BoardSetupPresenter presenter;
public class BoardSetupController extends Controller implements View.OnClickListener {
private AutoCompleteTextView code; private AutoCompleteTextView code;
private RecyclerView savedBoardsRecycler; private RecyclerView savedBoardsRecycler;
private SavedBoardsAdapter adapter; private SuggestBoardsAdapter suggestAdapter;
private SavedBoardsAdapter savedAdapter;
private ItemTouchHelper itemTouchHelper;
public BoardSetupController(Context context) { public BoardSetupController(Context context) {
super(context); super(context);
@ -56,71 +71,146 @@ public class BoardSetupController extends Controller implements View.OnClickList
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
getGraph().inject(this);
view = inflateRes(R.layout.controller_board_setup); view = inflateRes(R.layout.controller_board_setup);
navigationItem.setTitle(R.string.saved_boards_title); navigationItem.setTitle(R.string.saved_boards_title);
navigationItem.swipeable = false;
code = (AutoCompleteTextView) view.findViewById(R.id.code); code = (AutoCompleteTextView) view.findViewById(R.id.code);
code.setOnItemClickListener(this);
savedBoardsRecycler = (RecyclerView) view.findViewById(R.id.boards_recycler); savedBoardsRecycler = (RecyclerView) view.findViewById(R.id.boards_recycler);
savedBoardsRecycler.setLayoutManager(new LinearLayoutManager(context)); savedBoardsRecycler.setLayoutManager(new LinearLayoutManager(context));
adapter = new SavedBoardsAdapter(); savedAdapter = new SavedBoardsAdapter();
savedBoardsRecycler.setAdapter(adapter); savedBoardsRecycler.setAdapter(savedAdapter);
List<SavedBoard> savedBoards = new ArrayList<>(); itemTouchHelper = new ItemTouchHelper(touchHelperCallback);
for (int board = 0; board < 5; board++) { itemTouchHelper.attachToRecyclerView(savedBoardsRecycler);
savedBoards.add(new SavedBoard("foo - " + board, board));
} suggestAdapter = new SuggestBoardsAdapter();
adapter.setSavedBoards(savedBoards); code.setAdapter(suggestAdapter);
List<String> foo = new ArrayList<String>() {{
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("bar");
add("baz");
}};
ArrayAdapter<String> adapter = new ArrayAdapter<>(context, android.R.layout.simple_dropdown_item_1line, foo);
code.setAdapter(adapter);
code.setThreshold(1); code.setThreshold(1);
presenter.create(this);
}
private ItemTouchHelper.SimpleCallback touchHelperCallback = new ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP | ItemTouchHelper.DOWN,
ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT
) {
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int from = viewHolder.getAdapterPosition();
int to = target.getAdapterPosition();
presenter.move(from, to);
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
presenter.remove(position);
}
};
@Override
public void setSavedBoards(List<Board> savedBoards) {
savedAdapter.setSavedBoards(savedBoards);
} }
@Override @Override
public void onClick(View v) { public void onClick(View v) {
} }
private class SavedBoard { @Override
private String title; public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
private int id; BoardSetupPresenter.BoardSuggestion suggestion = suggestAdapter.getSuggestion(position);
presenter.addFromSuggestion(suggestion);
}
private class SuggestBoardsAdapter extends BaseAdapter implements Filterable {
private List<BoardSetupPresenter.BoardSuggestion> suggestions = new ArrayList<>();
public SavedBoard(String title, int id) { @Override
this.title = title; public int getCount() {
this.id = id; return suggestions.size();
}
@Override
public String getItem(int position) {
return getSuggestion(position).key;
}
public BoardSetupPresenter.BoardSuggestion getSuggestion(int position) {
return suggestions.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = LayoutInflater.from(context).inflate(R.layout.cell_board_suggestion, parent, false);
final ImageView image = (ImageView) view.findViewById(R.id.image);
TextView text = (TextView) view.findViewById(R.id.text);
BoardSetupPresenter.BoardSuggestion suggestion = getSuggestion(position);
final SiteIcon icon = suggestion.site.icon();
icon.get(new SiteIcon.SiteIconResult() {
@Override
public void onSiteIcon(SiteIcon siteIcon, Drawable icon) {
// TODO: don't if recycled
image.setImageDrawable(icon);
}
});
text.setText(suggestion.site.name() + " \u2013 /" + suggestion.key + "/");
return view;
}
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
// Invoked on a worker thread, do not use.
return null;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
suggestions.clear();
if (constraint != null) {
suggestions.addAll(presenter.getSuggestionsForQuery(constraint.toString()));
}
notifyDataSetChanged();
}
};
} }
} }
private class SavedBoardsAdapter extends RecyclerView.Adapter<SavedBoardCell> { private class SavedBoardsAdapter extends RecyclerView.Adapter<SavedBoardCell> {
private List<SavedBoard> savedBoards = new ArrayList<>(); private List<Board> savedBoards;
public SavedBoardsAdapter() { public SavedBoardsAdapter() {
setHasStableIds(true); setHasStableIds(true);
} }
private void setSavedBoards(List<SavedBoard> savedBoards) { private void setSavedBoards(List<Board> savedBoards) {
this.savedBoards.clear(); this.savedBoards = savedBoards;
this.savedBoards.addAll(savedBoards);
notifyDataSetChanged(); notifyDataSetChanged();
} }
@ -136,7 +226,7 @@ public class BoardSetupController extends Controller implements View.OnClickList
@Override @Override
public void onBindViewHolder(SavedBoardCell holder, int position) { public void onBindViewHolder(SavedBoardCell holder, int position) {
SavedBoard savedBoard = savedBoards.get(position); Board savedBoard = savedBoards.get(position);
holder.setSavedBoard(savedBoard); holder.setSavedBoard(savedBoard);
} }
@ -149,30 +239,43 @@ public class BoardSetupController extends Controller implements View.OnClickList
private class SavedBoardCell extends RecyclerView.ViewHolder { private class SavedBoardCell extends RecyclerView.ViewHolder {
private ImageView image; private ImageView image;
private TextView text; private TextView text;
private SiteIcon siteIcon;
private ImageView reorder;
public SavedBoardCell(View itemView) { public SavedBoardCell(View itemView) {
super(itemView); super(itemView);
image = (ImageView) itemView.findViewById(R.id.image); image = (ImageView) itemView.findViewById(R.id.image);
text = (TextView) itemView.findViewById(R.id.text); text = (TextView) itemView.findViewById(R.id.text);
reorder = (ImageView) itemView.findViewById(R.id.reorder);
Drawable drawable = DrawableCompat.wrap(context.getResources().getDrawable(R.drawable.ic_reorder_black_24dp)).mutate();
DrawableCompat.setTint(drawable, getAttrColor(context, R.attr.text_color_hint));
reorder.setImageDrawable(drawable);
reorder.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
itemTouchHelper.startDrag(SavedBoardCell.this);
}
return false;
}
});
} }
public void setSavedBoard(SavedBoard savedBoard) { public void setSavedBoard(Board savedBoard) {
Bitmap bitmap; siteIcon = savedBoard.site.icon();
try { siteIcon.get(new SiteIcon.SiteIconResult() {
BitmapFactory.Options opts = new BitmapFactory.Options(); @Override
opts.inScaled = false; public void onSiteIcon(SiteIcon siteIcon, Drawable icon) {
bitmap = BitmapFactory.decodeStream(getAppContext().getAssets().open("icons/4chan.png"), null, opts); if (SavedBoardCell.this.siteIcon == siteIcon) {
} catch (IOException e) { image.setImageDrawable(icon);
throw new RuntimeException(e); }
} }
});
BitmapDrawable drawable = new BitmapDrawable(getRes(), bitmap);
drawable = (BitmapDrawable) drawable.mutate(); text.setText(formatBoardCodeAndName(savedBoard));
drawable.getPaint().setFilterBitmap(false);
image.setImageDrawable(drawable);
text.setText(savedBoard.title);
} }
} }
} }

@ -30,6 +30,7 @@ import android.widget.TextView;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.chan.ChanUrls; import org.floens.chan.chan.ChanUrls;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.database.DatabaseManager; import org.floens.chan.core.database.DatabaseManager;
import org.floens.chan.core.manager.BoardManager; import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.model.orm.Board; import org.floens.chan.core.model.orm.Board;
@ -235,7 +236,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
if (item instanceof FloatingMenuItemBoard) { if (item instanceof FloatingMenuItemBoard) {
loadBoard(((FloatingMenuItemBoard) item).board); loadBoard(((FloatingMenuItemBoard) item).board);
} else { } else {
BoardEditController boardEditController = new BoardEditController(context); Controller boardEditController = new BoardSetupController(context);
if (doubleNavigationController != null) { if (doubleNavigationController != null) {
doubleNavigationController.pushController(boardEditController); doubleNavigationController.pushController(boardEditController);
} else { } else {

@ -45,7 +45,7 @@ import android.widget.TextView;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.controller.transition.FadeInTransition; import org.floens.chan.controller.transition.FadeInTransition;
import org.floens.chan.core.presenter.SetupPresenter; import org.floens.chan.core.presenter.SiteSetupPresenter;
import org.floens.chan.core.site.Site; import org.floens.chan.core.site.Site;
import org.floens.chan.core.site.SiteIcon; import org.floens.chan.core.site.SiteIcon;
import org.floens.chan.ui.animation.AnimationUtils; import org.floens.chan.ui.animation.AnimationUtils;
@ -53,10 +53,16 @@ import org.floens.chan.ui.animation.AnimationUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import static org.floens.chan.Chan.getGraph;
import static org.floens.chan.utils.AndroidUtils.dp; import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AndroidUtils.getAttrColor; import static org.floens.chan.utils.AndroidUtils.getAttrColor;
public class SiteSetupController extends StyledToolbarNavigationController implements View.OnClickListener, SetupPresenter.Callback { public class SiteSetupController extends StyledToolbarNavigationController implements View.OnClickListener, SiteSetupPresenter.Callback {
@Inject
SiteSetupPresenter presenter;
private EditText url; private EditText url;
private View urlSubmit; private View urlSubmit;
private View spinner; private View spinner;
@ -64,8 +70,6 @@ public class SiteSetupController extends StyledToolbarNavigationController imple
private boolean blocked = false; private boolean blocked = false;
private SetupPresenter presenter;
private RecyclerView sitesRecyclerview; private RecyclerView sitesRecyclerview;
private SitesAdapter sitesAdapter; private SitesAdapter sitesAdapter;
private List<Site> sites = new ArrayList<>(); private List<Site> sites = new ArrayList<>();
@ -78,6 +82,8 @@ public class SiteSetupController extends StyledToolbarNavigationController imple
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
getGraph().inject(this);
view = inflateRes(R.layout.controller_site_setup); view = inflateRes(R.layout.controller_site_setup);
navigationItem.setTitle(R.string.setup_title); navigationItem.setTitle(R.string.setup_title);
@ -90,8 +96,6 @@ public class SiteSetupController extends StyledToolbarNavigationController imple
next = (Button) view.findViewById(R.id.next_button); next = (Button) view.findViewById(R.id.next_button);
next.setOnClickListener(this); next.setOnClickListener(this);
presenter = new SetupPresenter();
sitesAdapter = new SitesAdapter(); sitesAdapter = new SitesAdapter();
sitesRecyclerview.setAdapter(sitesAdapter); sitesRecyclerview.setAdapter(sitesAdapter);
@ -106,6 +110,7 @@ public class SiteSetupController extends StyledToolbarNavigationController imple
presenter.onUrlSubmitClicked(url.getText().toString()); presenter.onUrlSubmitClicked(url.getText().toString());
} else if (v == next) { } else if (v == next) {
presenter.onNextClicked(); presenter.onNextClicked();
// navigationController.popController(false);
} }
} }

@ -1,82 +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.drawable;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import static org.floens.chan.utils.AndroidUtils.dp;
public class ThumbDrawable extends Drawable {
private Paint paint = new Paint();
private Path path = new Path();
private int width;
private int height;
public ThumbDrawable() {
width = dp(40);
height = dp(40);
paint.setStrokeWidth(dp(2));
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.BUTT);
paint.setColor(0xff757575);
path.reset();
for (int i = 0; i < 3; i++) {
int top = (int) (getMinimumHeight() / 2f + (i - 1) * dp(6));
path.moveTo(dp(8), top);
path.lineTo(getMinimumWidth() - dp(8), top);
}
path.close();
}
@Override
public void draw(Canvas canvas) {
canvas.drawPath(path, paint);
}
@Override
public int getIntrinsicWidth() {
return width;
}
@Override
public int getIntrinsicHeight() {
return height;
}
@Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
paint.setColorFilter(cf);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}

@ -25,8 +25,9 @@ import android.text.TextUtils;
import android.text.style.ImageSpan; import android.text.style.ImageSpan;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.model.Post; import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.AndroidUtils;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -86,6 +87,14 @@ public class PostHelper {
} }
} }
public static String formatSiteAndBoardName(Board board) {
return board.site.name() + " \u2013 /" + board.name + "/";
}
public static String formatBoardCodeAndName(Board board) {
return "/" + board.code + "/ \u2013 " + board.name;
}
private static SimpleDateFormat dateFormat = new SimpleDateFormat("LL/dd/yy(EEE)HH:mm:ss", Locale.US); private static SimpleDateFormat dateFormat = new SimpleDateFormat("LL/dd/yy(EEE)HH:mm:ss", Locale.US);
private static Date tmpDate = new Date(); private static Date tmpDate = new Date();

@ -241,7 +241,7 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener {
final Spinner spinner = (Spinner) selectLayout.findViewById(R.id.progress); final Spinner spinner = (Spinner) selectLayout.findViewById(R.id.progress);
final List<? extends Site> allSites = Sites.ALL_SITES; final List<? extends Site> allSites = Sites.allSites();
final Site[] selectedSite = {allSites.get(0)}; final Site[] selectedSite = {allSites.get(0)};

@ -21,10 +21,12 @@ import android.content.Context;
import android.os.Parcelable; import android.os.Parcelable;
import android.support.v4.widget.SlidingPaneLayout; import android.support.v4.widget.SlidingPaneLayout;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.ui.controller.ThreadSlideController; import org.floens.chan.ui.controller.ThreadSlideController;
import org.floens.chan.utils.AndroidUtils;
import static org.floens.chan.utils.AndroidUtils.dp; import static org.floens.chan.utils.AndroidUtils.dp;
@ -53,6 +55,21 @@ public class ThreadSlidingPaneLayout extends SlidingPaneLayout {
rightPane = (ViewGroup) findViewById(R.id.right_pane); rightPane = (ViewGroup) findViewById(R.id.right_pane);
} }
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// Forces a relayout after it has already been layed out, because SlidingPaneLayout sucks and otherwise
// gives the children too much room until they request a relayout.
AndroidUtils.waitForLayout(this, new AndroidUtils.OnMeasuredCallback() {
@Override
public boolean onMeasured(View view) {
requestLayout();
return false;
}
});
}
public void setThreadSlideController(ThreadSlideController threadSlideController) { public void setThreadSlideController(ThreadSlideController threadSlideController) {
this.threadSlideController = threadSlideController; this.threadSlideController = threadSlideController;
} }

@ -100,7 +100,6 @@ public class LoadView extends FrameLayout {
public View setView(View newView, boolean animate) { public View setView(View newView, boolean animate) {
if (newView == null) { if (newView == null) {
FrameLayout progressBar = new FrameLayout(getContext()); FrameLayout progressBar = new FrameLayout(getContext());
progressBar.setVisibility(View.GONE);
progressBar.addView(new ProgressBar(getContext()), new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER)); progressBar.addView(new ProgressBar(getContext()), new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER));
newView = progressBar; newView = progressBar;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 B

@ -15,7 +15,8 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -30,7 +31,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:paddingRight="8dp" android:paddingRight="8dp"
android:scaleType="center" android:scaleType="center"
tools:ignore="ContentDescription" /> android:src="@drawable/ic_reorder_black_24dp"
tools:ignore="ContentDescription"/>
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="0dp"
@ -48,7 +50,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:paddingTop="8dp" android:paddingTop="8dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?text_color_primary" android:textColor="?text_color_primary"
android:textSize="14sp" /> android:textSize="14sp"/>
<TextView <TextView
android:id="@+id/description" android:id="@+id/description"
@ -59,7 +61,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:paddingLeft="16dp" android:paddingLeft="16dp"
android:paddingRight="16dp" android:paddingRight="16dp"
android:textColor="?text_color_secondary" android:textColor="?text_color_secondary"
android:textSize="12sp" /> android:textSize="12sp"/>
</LinearLayout> </LinearLayout>

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?><!--
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/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="?attr/backcolor"
android:orientation="horizontal">
<ImageView
android:id="@+id/image"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="8dp"
android:scaleType="fitCenter"/>
<TextView
android:id="@+id/text"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:maxLines="1"
android:textColor="?text_color_primary"
android:textSize="14sp"/>
</LinearLayout>

@ -39,4 +39,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:textColor="?text_color_primary" android:textColor="?text_color_primary"
android:textSize="14sp"/> android:textSize="14sp"/>
<ImageView
android:id="@+id/reorder"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="8dp"
android:scaleType="centerInside"/>
</LinearLayout> </LinearLayout>

@ -20,7 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/backcolor"> android:background="?attr/backcolor"
android:clipChildren="false">
<TextView <TextView
android:id="@+id/introduction" android:id="@+id/introduction"
@ -75,6 +76,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
android:layout_marginTop="0dp" android:layout_marginTop="0dp"
android:clipChildren="false"
android:clipToPadding="false"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"

@ -67,6 +67,7 @@ ALTER TABLE loadable ADD COLUMN lastLoaded default -1;
Changes in version 22: Changes in version 22:
CREATE TABLE `site` (`configuration` VARCHAR , `id` INTEGER PRIMARY KEY AUTOINCREMENT , `userSettings` VARCHAR );
ALTER TABLE loadable ADD COLUMN site INTEGER default 0; ALTER TABLE loadable ADD COLUMN site INTEGER default 0;
ALTER TABLE board ADD COLUMN site INTEGER default 0; ALTER TABLE board ADD COLUMN site INTEGER default 0;
ALTER TABLE savedreply ADD COLUMN site INTEGER default 0; ALTER TABLE savedreply ADD COLUMN site INTEGER default 0;

Loading…
Cancel
Save