Work on making all components work without a complete board list.

We want to support sites which have no boards.json like endpoint or have
support for infine boards, so we want to work without boards being
available.
Don't crash when no board is loaded.
Partial workaround for filters, will get to that later.
multisite
Floens 8 years ago
parent 29450067b8
commit 2f31d19279
  1. 17
      Clover/app/src/main/java/org/floens/chan/core/manager/BoardManager.java
  2. 41
      Clover/app/src/main/java/org/floens/chan/core/manager/FilterEngine.java
  3. 2
      Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java
  4. 4
      Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java
  5. 10
      Clover/app/src/main/java/org/floens/chan/core/site/Site.java
  6. 57
      Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4.java
  7. 42
      Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java
  8. 46
      Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java
  9. 8
      Clover/app/src/main/java/org/floens/chan/ui/controller/HistoryController.java
  10. 88
      Clover/app/src/main/java/org/floens/chan/ui/layout/FilterLayout.java
  11. 36
      Clover/app/src/main/res/layout/layout_site_board_select.xml
  12. 2
      Clover/app/src/main/res/values/strings.xml

@ -35,6 +35,9 @@ import javax.inject.Singleton;
import de.greenrobot.event.EventBus;
/**
* Keeps track of {@link Board}s that the user has "saved" to their list.
*/
@Singleton
public class BoardManager {
private static final String TAG = "BoardManager";
@ -67,13 +70,6 @@ public class BoardManager {
boards = databaseManager.runTaskSync(databaseManager.getDatabaseBoardManager().getBoards(defaultSite));
if (boards.isEmpty()) {
defaultSite.boards(new Site.BoardsListener() {
@Override
public void onBoardsReceived(Boards boards) {
appendBoards(boards);
}
});
} else {
update(false);
}
}
@ -118,12 +114,7 @@ public class BoardManager {
}
// Thread-safe
public boolean getBoardExists(String code) {
return getBoardByCode(code) != null;
}
// Thread-safe
public Board getBoardByCode(String code) {
private Board getBoardByCode(String code) {
synchronized (boardsByCode) {
return boardsByCode.get(code);
}

@ -22,9 +22,9 @@ import android.text.TextUtils;
import org.floens.chan.core.database.DatabaseFilterManager;
import org.floens.chan.core.database.DatabaseManager;
import org.floens.chan.core.model.Board;
import org.floens.chan.core.model.Filter;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.site.Sites;
import org.floens.chan.utils.Logger;
import java.util.ArrayList;
@ -67,6 +67,21 @@ public class FilterEngine {
}
}
// This is messy but required now that we can't know the Board immediately.
public static class SiteIdBoardCode {
public final int siteId;
public final String boardCode;
private SiteIdBoardCode(int site, String board) {
siteId = site;
boardCode = board;
}
public static SiteIdBoardCode fromSiteIdBoardCode(int siteId, String boardCode) {
return new SiteIdBoardCode(siteId, boardCode);
}
}
private final DatabaseManager databaseManager;
private final BoardManager boardManager;
@ -221,15 +236,21 @@ public class FilterEngine {
return pattern;
}
public List<Board> getBoardsForFilter(Filter filter) {
public List<SiteIdBoardCode> getBoardsForFilter(Filter filter) {
if (filter.allBoards) {
return boardManager.getSavedBoards();
return Collections.emptyList();
} else if (!TextUtils.isEmpty(filter.boards)) {
List<Board> appliedBoards = new ArrayList<>();
List<SiteIdBoardCode> appliedBoards = new ArrayList<>();
for (String value : filter.boards.split(",")) {
Board boardByValue = boardManager.getBoardByCode(value);
if (boardByValue != null) {
appliedBoards.add(boardByValue);
if (value.contains(";")) {
String[] siteAndBoard = value.split(";");
if (siteAndBoard.length == 1) {
appliedBoards.add(SiteIdBoardCode.fromSiteIdBoardCode(Integer.parseInt(siteAndBoard[0]), ""));
} else {
appliedBoards.add(SiteIdBoardCode.fromSiteIdBoardCode(Integer.parseInt(siteAndBoard[0]), siteAndBoard[1]));
}
} else {
appliedBoards.add(SiteIdBoardCode.fromSiteIdBoardCode(Sites.defaultSite().id(), value));
}
}
return appliedBoards;
@ -238,11 +259,11 @@ public class FilterEngine {
}
}
public void saveBoardsToFilter(List<Board> appliedBoards, Filter filter) {
public void saveBoardsToFilter(List<SiteIdBoardCode> appliedBoards, Filter filter) {
filter.boards = "";
for (int i = 0; i < appliedBoards.size(); i++) {
Board board = appliedBoards.get(i);
filter.boards += board.code;
SiteIdBoardCode siteAndBoard = appliedBoards.get(i);
filter.boards += siteAndBoard.siteId + ";" + siteAndBoard.boardCode;
if (i < appliedBoards.size() - 1) {
filter.boards += ",";
}

@ -106,7 +106,7 @@ public class ReplyPresenter implements CaptchaCallback, ImagePickDelegate.ImageP
callback.setCaptchaVersion(ChanSettings.postNewCaptcha.get());
this.board = boardManager.getBoardByCode(loadable.boardCode);
this.board = loadable.board;
draft = replyManager.getReply(loadable);

@ -141,6 +141,10 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
}
}
public boolean isBound() {
return chanLoader != null;
}
public void requestInitialData() {
if (chanLoader.getThread() == null) {
requestData();

@ -109,6 +109,8 @@ public interface Site {
*/
int id();
String name();
boolean feature(Feature feature);
boolean boardFeature(BoardFeature boardFeature, Board board);
@ -129,7 +131,13 @@ public interface Site {
void onBoardsReceived(Boards boards);
}
Board board(String name);
Board board(String code);
interface BoardListener {
void onBoardReceived(Board board);
void onBoardNonexistent();
}
ChanLoaderRequest loaderRequest(ChanLoaderRequestParams request);

@ -274,6 +274,11 @@ public class Chan4 implements Site {
return 0;
}
@Override
public String name() {
return "4chan";
}
@Override
public boolean feature(Feature feature) {
switch (feature) {
@ -330,10 +335,34 @@ public class Chan4 implements Site {
}
@Override
public Board board(String name) {
public void boards(final BoardsListener listener) {
requestQueue.add(new Chan4BoardsRequest(this, new Response.Listener<List<Board>>() {
@Override
public void onResponse(List<Board> response) {
listener.onBoardsReceived(new Boards(response));
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Logger.e(TAG, "Failed to get boards from server", error);
// API fail, provide some default boards
List<Board> list = new ArrayList<>();
list.add(new Board(Chan4.this, "Technology", "g", true, true));
list.add(new Board(Chan4.this, "Food & Cooking", "ck", true, true));
list.add(new Board(Chan4.this, "Do It Yourself", "diy", true, true));
list.add(new Board(Chan4.this, "Animals & Nature", "an", true, true));
Collections.shuffle(list);
listener.onBoardsReceived(new Boards(list));
}
}));
}
@Override
public Board board(String code) {
List<Board> allBoards = getGraph().getBoardManager().getAllBoards();
for (Board board : allBoards) {
if (board.code.equals(name)) {
if (board.code.equals(code)) {
return board;
}
}
@ -356,30 +385,6 @@ public class Chan4 implements Site {
return authentication;
}
@Override
public void boards(final BoardsListener listener) {
requestQueue.add(new Chan4BoardsRequest(this, new Response.Listener<List<Board>>() {
@Override
public void onResponse(List<Board> response) {
listener.onBoardsReceived(new Boards(response));
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Logger.e(TAG, "Failed to get boards from server", error);
// API fail, provide some default boards
List<Board> list = new ArrayList<>();
list.add(new Board(Chan4.this, "Technology", "g", true, true));
list.add(new Board(Chan4.this, "Food & Cooking", "ck", true, true));
list.add(new Board(Chan4.this, "Do It Yourself", "diy", true, true));
list.add(new Board(Chan4.this, "Animals & Nature", "an", true, true));
Collections.shuffle(list);
listener.onBoardsReceived(new Boards(list));
}
}));
}
@Override
public ChanLoaderRequest loaderRequest(ChanLoaderRequestParams request) {
return new ChanLoaderRequest(new Chan4ReaderRequest(request));

@ -28,6 +28,7 @@ import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@ -45,6 +46,8 @@ import org.floens.chan.core.model.Board;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Pin;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.site.Site;
import org.floens.chan.core.site.Sites;
import org.floens.chan.ui.controller.BrowseController;
import org.floens.chan.ui.controller.DoubleNavigationController;
import org.floens.chan.ui.controller.DrawerController;
@ -53,8 +56,8 @@ import org.floens.chan.ui.controller.StyledToolbarNavigationController;
import org.floens.chan.ui.controller.ThreadSlideController;
import org.floens.chan.ui.controller.ViewThreadController;
import org.floens.chan.ui.helper.ImagePickDelegate;
import org.floens.chan.ui.helper.VersionHandler;
import org.floens.chan.ui.helper.RuntimePermissionsHelper;
import org.floens.chan.ui.helper.VersionHandler;
import org.floens.chan.ui.state.ChanState;
import org.floens.chan.ui.theme.ThemeHelper;
import org.floens.chan.utils.AndroidUtils;
@ -131,16 +134,16 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
if (chanState == null) {
Logger.w(TAG, "savedInstanceState was not null, but no ChanState was found!");
} else {
DatabaseLoadableManager loadableManager = databaseManager.getDatabaseLoadableManager();
chanState.board = loadableManager.get(chanState.board);
chanState.thread = loadableManager.get(chanState.thread);
Pair<Loadable, Loadable> boardThreadPair = resolveChanState(chanState);
if (boardThreadPair != null && boardThreadPair.first != null) {
loadDefault = false;
Board board = boardManager.getBoardByCode(chanState.board.boardCode);
browseController.loadBoard(board);
if (chanState.thread.mode == Loadable.Mode.THREAD) {
browseController.showThread(chanState.thread, false);
browseController.loadBoard(boardThreadPair.first.board);
if (boardThreadPair.second != null) {
browseController.showThread(boardThreadPair.second);
}
}
}
} else {
@ -149,8 +152,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
Loadable fromUri = ChanHelper.getLoadableFromStartUri(data);
if (fromUri != null) {
loadDefault = false;
Board board = boardManager.getBoardByCode(fromUri.boardCode);
browseController.loadBoard(board);
browseController.loadBoard(fromUri.board);
if (fromUri.isThreadMode()) {
browseController.showThread(fromUri, false);
@ -176,6 +178,26 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
versionHandler.run();
}
private Pair<Loadable, Loadable> resolveChanState(ChanState state) {
DatabaseLoadableManager loadableManager = databaseManager.getDatabaseLoadableManager();
Site site = Sites.forId(state.board.siteId);
Board board = site.board(state.board.boardCode);
if (board != null) {
state.board.site = site;
state.board.board = board;
state.thread.site = site;
state.thread.board = board;
Loadable boardLoadable = loadableManager.get(state.board);
Loadable threadLoadable = loadableManager.get(state.thread);
return new Pair<>(boardLoadable, threadLoadable.mode == Loadable.Mode.THREAD ? threadLoadable : null);
}
return null;
}
private void setupLayout() {
mainNavigationController = new StyledToolbarNavigationController(this);

@ -19,13 +19,13 @@ package org.floens.chan.ui.controller;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import org.floens.chan.R;
@ -72,8 +72,6 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
private PostsFilter.Order order;
private List<FloatingMenuItem> boardItems;
private ProgressDialog waitingForBoardsDialog;
private FloatingMenuItem viewModeMenuItem;
private ToolbarMenuItem search;
private ToolbarMenuItem refresh;
@ -123,20 +121,32 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
@Override
public void onMenuItemClicked(ToolbarMenuItem item) {
ThreadPresenter presenter = threadLayout.getPresenter();
switch ((Integer) item.getId()) {
case SEARCH_ID:
if (presenter.isBound()) {
((ToolbarNavigationController) navigationController).showSearch();
}
break;
case REFRESH_ID:
threadLayout.getPresenter().requestData();
refresh.getView().setRotation(0f);
refresh.getView().animate().rotation(360f).setDuration(500).setInterpolator(new DecelerateInterpolator(2f));
if (presenter.isBound()) {
presenter.requestData();
ImageView refreshView = refresh.getView();
refreshView.setRotation(0f);
refreshView.animate()
.rotation(360f)
.setDuration(500)
.setInterpolator(new DecelerateInterpolator(2f));
}
break;
}
}
@Override
public void onSubMenuItemClicked(ToolbarMenuItem parent, FloatingMenuItem item) {
final ThreadPresenter presenter = threadLayout.getPresenter();
Integer id = (Integer) item.getId();
switch (id) {
case REPLY_ID:
@ -144,14 +154,15 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
break;
case SHARE_ID:
case OPEN_BROWSER_ID:
String link = ChanUrls.getCatalogUrlDesktop(threadLayout.getPresenter().getLoadable().boardCode);
if (presenter.isBound()) {
String link = ChanUrls.getCatalogUrlDesktop(presenter.getLoadable().boardCode);
if (id == SHARE_ID) {
AndroidUtils.shareLink(link);
} else {
AndroidUtils.openLinkInBrowser((Activity) context, link);
}
}
break;
case VIEW_MODE_ID:
if (postViewMode == ChanSettings.PostViewMode.LIST) {
@ -205,7 +216,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
PostsFilter.Order order = (PostsFilter.Order) item.getId();
ChanSettings.boardOrder.set(order.name);
BrowseController.this.order = order;
threadLayout.getPresenter().setOrder(order);
presenter.setOrder(order);
}
@Override
@ -341,19 +352,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
private void loadBoards() {
List<Board> boards = boardManager.getSavedBoards();
if (boards.isEmpty()) {
if (waitingForBoardsDialog == null) {
String title = getString(R.string.thread_fetching_boards_title);
String message = getString(R.string.thread_fetching_boards);
waitingForBoardsDialog = ProgressDialog.show(context, title, message, true, false);
waitingForBoardsDialog.show();
}
} else {
boolean wasWaiting = waitingForBoardsDialog != null;
if (waitingForBoardsDialog != null) {
waitingForBoardsDialog.dismiss();
waitingForBoardsDialog = null;
}
boolean wasEmpty = boardItems == null;
boardItems = new ArrayList<>();
for (Board board : boards) {
@ -364,11 +363,10 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
navigationItem.middleMenu.setItems(boardItems);
navigationItem.middleMenu.setAdapter(new BoardsAdapter(context, boardItems));
if (wasWaiting) {
if (wasEmpty) {
loadDefault();
}
}
}
private static class FloatingMenuItemBoard extends FloatingMenuItem {
public Board board;

@ -31,7 +31,6 @@ import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.database.DatabaseHistoryManager;
@ -47,7 +46,6 @@ import org.floens.chan.ui.toolbar.ToolbarMenuItem;
import org.floens.chan.ui.view.CrossfadeView;
import org.floens.chan.ui.view.FloatingMenuItem;
import org.floens.chan.ui.view.ThumbnailView;
import org.floens.chan.utils.Logger;
import java.util.ArrayList;
import java.util.List;
@ -205,12 +203,8 @@ public class HistoryController extends Controller implements CompoundButton.OnCh
History history = displayList.get(position);
holder.thumbnail.setUrl(history.thumbnailUrl, dp(48), dp(48));
if (history.loadable == null) {
Logger.test("null!");
}
holder.text.setText(history.loadable.title);
Board board = boardManager.getBoardByCode(history.loadable.boardCode);
Board board = history.loadable.board;
holder.subtext.setText(board == null ? null : ("/" + board.code + "/ \u2013 " + board.name));
}

@ -33,23 +33,29 @@ import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import org.floens.chan.R;
import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.manager.FilterEngine;
import org.floens.chan.core.manager.FilterType;
import org.floens.chan.core.model.Board;
import org.floens.chan.core.model.Filter;
import org.floens.chan.core.site.Site;
import org.floens.chan.core.site.Sites;
import org.floens.chan.ui.controller.FiltersController;
import org.floens.chan.ui.dialog.ColorPickerView;
import org.floens.chan.ui.drawable.DropdownArrowDrawable;
import org.floens.chan.ui.helper.BoardHelper;
import org.floens.chan.ui.view.FloatingMenu;
import org.floens.chan.ui.view.FloatingMenuItem;
import org.floens.chan.utils.Logger;
import java.util.ArrayList;
import java.util.List;
@ -84,7 +90,7 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener {
private FilterLayoutCallback callback;
private Filter filter;
private List<Board> appliedBoards = new ArrayList<>();
private List<FilterEngine.SiteIdBoardCode> appliedBoards = new ArrayList<>();
public FilterLayout(Context context) {
super(context);
@ -226,49 +232,75 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener {
})
.show();
} else if (v == boardsSelector) {
@SuppressWarnings("unchecked")
final SelectLayout<Board> selectLayout = (SelectLayout<Board>) LayoutInflater.from(getContext()).inflate(R.layout.layout_select, null);
// TODO(multi-site): fix this crap.
// we need a new proper recyclerview layout where you can individually select each site and board combination
// and if you don't select anything, it becomes a global filter.
final LinearLayout selectLayout = (LinearLayout) LayoutInflater.from(getContext())
.inflate(R.layout.layout_site_board_select, null);
final Spinner spinner = (Spinner) selectLayout.findViewById(R.id.spinner);
List<SelectLayout.SelectItem<Board>> items = new ArrayList<>();
List<Board> savedList = boardManager.getSavedBoards();
for (int i = 0; i < savedList.size(); i++) {
Board saved = savedList.get(i);
String name = BoardHelper.getName(saved);
String description = BoardHelper.getDescription(saved);
String search = name + " " + saved.code;
final List<? extends Site> allSites = Sites.ALL_SITES;
boolean checked = false;
for (int j = 0; j < appliedBoards.size(); j++) {
Board appliedBoard = appliedBoards.get(j);
if (appliedBoard.code.equals(saved.code)) {
checked = true;
break;
final Site[] selectedSite = {allSites.get(0)};
List<String> allSitesNames = new ArrayList<>(allSites.size());
for (int i = 0; i < allSites.size(); i++) {
Site site = allSites.get(i);
allSitesNames.add(site.name());
}
SpinnerAdapter adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, allSitesNames);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Site site = allSites.get(position);
selectedSite[0] = site;
Logger.test(site.name());
}
items.add(new SelectLayout.SelectItem<>(
saved, saved.id, name, description, search, checked
));
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
selectLayout.setItems(items);
final EditText editText = (EditText) selectLayout.findViewById(R.id.boards);
String text = "";
for (int i = 0; i < appliedBoards.size(); i++) {
FilterEngine.SiteIdBoardCode siteIdBoardCode = appliedBoards.get(i);
text += siteIdBoardCode.boardCode;
if (i < appliedBoards.size() - 1) {
text += ",";
}
}
editText.setText(text);
new AlertDialog.Builder(getContext())
.setView(selectLayout)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Site site = selectedSite[0];
appliedBoards.clear();
List<SelectLayout.SelectItem<Board>> items = selectLayout.getItems();
for (int i = 0; i < items.size(); i++) {
SelectLayout.SelectItem<Board> selectItem = items.get(i);
if (selectItem.checked) {
appliedBoards.add(selectItem.item);
String[] codes = editText.getText().toString().split(",");
if (codes.length == 0) {
filter.allBoards = true;
} else {
filter.allBoards = false;
for (String code : codes) {
appliedBoards.add(FilterEngine.SiteIdBoardCode.fromSiteIdBoardCode(
site.id(), code
));
}
}
filter.allBoards = selectLayout.areAllChecked();
updateBoardsSummary();
}
})

@ -0,0 +1,36 @@
<?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="wrap_content"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<Spinner
android:id="@+id/spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/boards"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

@ -145,8 +145,6 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="image_open_failed">Failed to open image</string>
<string name="image_spoiler_filename">Spoiler image</string>
<string name="thread_fetching_boards_title">Fetching boards</string>
<string name="thread_fetching_boards">Please wait for the boards to be fetched, this may take up to a minute.</string>
<string name="thread_board_select_add">Add more&#8230;</string>
<string name="thread_load_failed_ssl">HTTPS error</string>
<string name="thread_load_failed_network">Network error</string>

Loading…
Cancel
Save