diff --git a/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseHelper.java b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseHelper.java index 7e33bef8..baaabde6 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseHelper.java +++ b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseHelper.java @@ -45,7 +45,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { private static final String TAG = "DatabaseHelper"; private static final String DATABASE_NAME = "ChanDB"; - private static final int DATABASE_VERSION = 22; + private static final int DATABASE_VERSION = 23; public Dao pinDao; public Dao loadableDao; @@ -230,6 +230,14 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { SiteService.addSiteForLegacy(); } + + if (oldVersion < 23) { + try { + pinDao.executeRawNoArgs("ALTER TABLE board ADD COLUMN \"archive\" INTEGER;"); + } catch (SQLException e) { + Logger.e(TAG, "Error upgrading to version 14", e); + } + } } public void reset() { diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/Archive.java b/Clover/app/src/main/java/org/floens/chan/core/model/Archive.java new file mode 100644 index 00000000..08709ab5 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/model/Archive.java @@ -0,0 +1,47 @@ +/* + * 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 . + */ +package org.floens.chan.core.model; + +import java.util.List; + +public class Archive { + public final List items; + + public static Archive fromItems(List items) { + return new Archive(items); + } + + private Archive(List items) { + this.items = items; + } + + public static class ArchiveItem { + public final String description; + + public final int id; + + public static ArchiveItem fromDescriptionId(String description, int id) { + return new ArchiveItem(description, id); + } + + private ArchiveItem(String description, int id) { + this.description = description; + this.id = id; + } + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/orm/Board.java b/Clover/app/src/main/java/org/floens/chan/core/model/orm/Board.java index b05bbfc5..35ad825b 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/orm/Board.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/orm/Board.java @@ -131,6 +131,9 @@ public class Board implements SiteReference { @DatabaseField public String description; + @DatabaseField + public boolean archive = false; + @Deprecated // public, at least public Board() { } @@ -200,6 +203,7 @@ public class Board implements SiteReference { trollFlags = o.trollFlags; mathTags = o.mathTags; description = o.description; + archive = o.archive; } /** @@ -238,6 +242,7 @@ public class Board implements SiteReference { b.trollFlags = trollFlags; b.mathTags = mathTags; b.description = description; + b.archive = archive; return b; } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/net/HtmlReaderRequest.java b/Clover/app/src/main/java/org/floens/chan/core/net/HtmlReaderRequest.java new file mode 100644 index 00000000..d13cab28 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/net/HtmlReaderRequest.java @@ -0,0 +1,62 @@ +/* + * 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 . + */ +package org.floens.chan.core.net; + +import com.android.volley.NetworkResponse; +import com.android.volley.Request; +import com.android.volley.Response; +import com.android.volley.Response.Listener; +import com.android.volley.VolleyError; +import com.android.volley.toolbox.HttpHeaderParser; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +public abstract class HtmlReaderRequest extends Request { + protected final Listener listener; + + public HtmlReaderRequest(String url, Listener listener, Response.ErrorListener errorListener) { + super(Request.Method.GET, url, errorListener); + + this.listener = listener; + } + + @Override + protected void deliverResponse(T response) { + listener.onResponse(response); + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + Document document = Jsoup.parse(new ByteArrayInputStream(response.data), + HttpHeaderParser.parseCharset(response.headers), getUrl()); + + T result = readDocument(document); + + return Response.success(result, HttpHeaderParser.parseCacheHeaders(response)); + } catch (IOException e) { + return Response.error(new VolleyError(e)); + } + } + + public abstract T readDocument(Document document) throws IOException; +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/ArchivePresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/ArchivePresenter.java new file mode 100644 index 00000000..20c63fb7 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/ArchivePresenter.java @@ -0,0 +1,138 @@ +/* + * 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 . + */ +package org.floens.chan.core.presenter; + +import org.floens.chan.core.database.DatabaseManager; +import org.floens.chan.core.model.Archive; +import org.floens.chan.core.model.orm.Board; +import org.floens.chan.core.model.orm.Loadable; +import org.floens.chan.core.site.SiteActions; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import javax.inject.Inject; + +import static android.text.TextUtils.isEmpty; + +public class ArchivePresenter implements SiteActions.ArchiveListener { + private DatabaseManager databaseManager; + + private Callback callback; + private Board board; + + private boolean inRequest = false; + + private String filter; + private List items = new ArrayList<>(); + private List filteredItems = new ArrayList<>(); + + @Inject + public ArchivePresenter(DatabaseManager databaseManager) { + this.databaseManager = databaseManager; + } + + public void create(Callback callback, Board board) { + this.callback = callback; + this.board = board; + + loadArchive(); + } + + public void onRefresh() { + if (!inRequest) { + loadArchive(); + } + } + + private void loadArchive() { + inRequest = true; + callback.showError(false); + board.site.actions().archive(board, this); + } + + public void onSearchEntered(String query) { + filterArchive(query); + } + + public void onSearchVisibility(boolean visible) { + if (!visible) { + filterArchive(null); + } + } + + public void onItemClicked(Archive.ArchiveItem item) { + Loadable loadable = databaseManager.getDatabaseLoadableManager() + .get(Loadable.forThread(board.site, board, item.id)); + + callback.openThread(loadable); + } + + @Override + public void onArchive(Archive archive) { + inRequest = false; + callback.hideRefreshing(); + callback.showList(); + items = archive.items; + updateWithFilter(); + } + + @Override + public void onArchiveError() { + inRequest = false; + callback.hideRefreshing(); + callback.showError(true); + } + + private void filterArchive(String query) { + filter = query; + updateWithFilter(); + } + + private void updateWithFilter() { + filteredItems.clear(); + if (isEmpty(filter)) { + filteredItems.addAll(items); + } else { + for (Archive.ArchiveItem item : items) { + if (filterApplies(item, filter)) { + filteredItems.add(item); + } + } + } + + callback.setArchiveItems(filteredItems); + } + + private boolean filterApplies(Archive.ArchiveItem item, String filter) { + return item.description.toLowerCase(Locale.ENGLISH).contains(filter.toLowerCase()); + } + + public interface Callback { + void setArchiveItems(List items); + + void hideRefreshing(); + + void showList(); + + void showError(boolean show); + + void openThread(Loadable loadable); + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/BrowsePresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/BrowsePresenter.java index 1f018bd7..27f03c48 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/presenter/BrowsePresenter.java +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/BrowsePresenter.java @@ -118,11 +118,14 @@ public class BrowsePresenter implements Observer { private void loadBoard(Board board) { currentBoard = board; callback.loadBoard(getLoadableForBoard(board)); + callback.showArchiveOption(board.site.boardFeature(Site.BoardFeature.ARCHIVE, board)); } public interface Callback { void loadBoard(Loadable loadable); void loadSiteSetup(Site site); + + void showArchiveOption(boolean show); } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/Site.java b/Clover/app/src/main/java/org/floens/chan/core/site/Site.java index edd56f45..a40557a6 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/Site.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/Site.java @@ -79,6 +79,11 @@ public interface Site { */ // TODO(multisite) use this POSTING_SPOILER, + + /** + * This board support loading the archive, a list of threads that are locked after expiring. + */ + ARCHIVE, } /** diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/SiteActions.java b/Clover/app/src/main/java/org/floens/chan/core/site/SiteActions.java index 6a20d309..8306c9b6 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/SiteActions.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/SiteActions.java @@ -17,6 +17,8 @@ */ package org.floens.chan.core.site; +import org.floens.chan.core.model.Archive; +import org.floens.chan.core.model.orm.Board; import org.floens.chan.core.site.http.DeleteRequest; import org.floens.chan.core.site.http.DeleteResponse; import org.floens.chan.core.site.http.HttpCall; @@ -63,6 +65,14 @@ public interface SiteActions { void onDeleteError(HttpCall httpCall); } + void archive(Board board, ArchiveListener archiveListener); + + interface ArchiveListener { + void onArchive(Archive archive); + + void onArchiveError(); + } + /* TODO(multi-site) this login mechanism is probably not generic enough right now, * especially if we're thinking about what a login really is * We'll expand this later when we have a better idea of what other sites require. diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/SiteEndpoints.java b/Clover/app/src/main/java/org/floens/chan/core/site/SiteEndpoints.java index ac7b7976..a210cc4c 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/SiteEndpoints.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/SiteEndpoints.java @@ -43,6 +43,8 @@ public interface SiteEndpoints { HttpUrl boards(); + HttpUrl archive(Board board); + HttpUrl reply(Loadable thread); HttpUrl delete(Post post); diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/common/CommonSite.java b/Clover/app/src/main/java/org/floens/chan/core/site/common/CommonSite.java index b2dfe889..3a810501 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/common/CommonSite.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/common/CommonSite.java @@ -357,6 +357,11 @@ public abstract class CommonSite extends SiteBase { return null; } + @Override + public HttpUrl archive(Board board) { + return null; + } + @Override public HttpUrl reply(Loadable thread) { return null; @@ -522,6 +527,10 @@ public abstract class CommonSite extends SiteBase { } } + @Override + public void archive(Board board, ArchiveListener archiveListener) { + } + @Override public void login(LoginRequest loginRequest, LoginListener loginListener) { } diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4.java b/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4.java index efa346cc..bbdd1dbf 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4.java @@ -30,16 +30,15 @@ import org.floens.chan.core.settings.Setting; import org.floens.chan.core.settings.SettingProvider; import org.floens.chan.core.settings.SharedPreferencesSettingProvider; import org.floens.chan.core.settings.StringSetting; -import org.floens.chan.core.site.SiteAuthentication; import org.floens.chan.core.site.Boards; -import org.floens.chan.core.site.SiteUrlHandler; import org.floens.chan.core.site.Site; import org.floens.chan.core.site.SiteActions; +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.parser.ChanReader; +import org.floens.chan.core.site.SiteUrlHandler; import org.floens.chan.core.site.common.CommonReplyHttpCall; import org.floens.chan.core.site.common.FutabaChanReader; import org.floens.chan.core.site.http.DeleteRequest; @@ -47,6 +46,7 @@ import org.floens.chan.core.site.http.HttpCall; import org.floens.chan.core.site.http.LoginRequest; import org.floens.chan.core.site.http.LoginResponse; import org.floens.chan.core.site.http.Reply; +import org.floens.chan.core.site.parser.ChanReader; import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.Logger; @@ -174,6 +174,11 @@ public class Chan4 extends SiteBase { .host("sys.4chan.org") .build(); + private final HttpUrl b = new HttpUrl.Builder() + .scheme("https") + .host("boards.4chan.org") + .build(); + @Override public HttpUrl catalog(Board board) { return a.newBuilder() @@ -248,6 +253,14 @@ public class Chan4 extends SiteBase { .build(); } + @Override + public HttpUrl archive(Board board) { + return b.newBuilder() + .addPathSegment(board.code) + .addPathSegment("archive") + .build(); + } + @Override public HttpUrl reply(Loadable loadable) { return sys.newBuilder() @@ -332,6 +345,13 @@ public class Chan4 extends SiteBase { })); } + @Override + public void archive(Board board, ArchiveListener archiveListener) { + requestQueue.add(new Chan4ArchiveRequest(Chan4.this, board, + archiveListener::onArchive, + error -> archiveListener.onArchiveError())); + } + @Override public void post(Reply reply, final PostListener postListener) { httpCallManager.makeHttpCall(new Chan4ReplyCall(Chan4.this, reply), new HttpCall.HttpCallback() { @@ -519,12 +539,13 @@ public class Chan4 extends SiteBase { case POSTING_SPOILER: // depends if the board supports it. return board.spoilers; + case ARCHIVE: + return board.archive; default: return false; } } - @Override public SiteEndpoints endpoints() { return endpoints; diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4ArchiveRequest.java b/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4ArchiveRequest.java new file mode 100644 index 00000000..71d8987d --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4ArchiveRequest.java @@ -0,0 +1,57 @@ +/* + * 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 . + */ +package org.floens.chan.core.site.sites.chan4; + +import com.android.volley.Response; + +import org.floens.chan.core.model.Archive; +import org.floens.chan.core.model.orm.Board; +import org.floens.chan.core.net.HtmlReaderRequest; +import org.floens.chan.core.site.Site; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class Chan4ArchiveRequest extends HtmlReaderRequest { + public Chan4ArchiveRequest(Site site, Board board, + Response.Listener listener, + Response.ErrorListener errorListener) { + super(site.endpoints().archive(board).toString(), listener, errorListener); + } + + @Override + public Archive readDocument(Document document) throws IOException { + List items = new ArrayList<>(); + + Element table = document.getElementById("arc-list"); + Element tableBody = table.getElementsByTag("tbody").first(); + Elements trs = tableBody.getElementsByTag("tr"); + for (Element tr : trs) { + Elements dataElements = tr.getElementsByTag("td"); + String description = dataElements.get(1).text(); + int id = Integer.parseInt(dataElements.get(0).text()); + items.add(Archive.ArchiveItem.fromDescriptionId(description, id)); + } + + return Archive.fromItems(items); + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4BoardsRequest.java b/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4BoardsRequest.java index 090011fb..5a5ca4e5 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4BoardsRequest.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4BoardsRequest.java @@ -161,6 +161,9 @@ public class Chan4BoardsRequest extends JsonReaderRequest> { case "meta_description": board.description = reader.nextString(); break; + case "is_archived": + board.archive = reader.nextInt() == 1; + break; default: reader.skipValue(); break; diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ArchiveController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ArchiveController.java new file mode 100644 index 00000000..bbfe83fc --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ArchiveController.java @@ -0,0 +1,211 @@ +/* + * 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 . + */ +package org.floens.chan.ui.controller; + +import android.content.Context; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.floens.chan.R; +import org.floens.chan.controller.Controller; +import org.floens.chan.core.model.Archive; +import org.floens.chan.core.model.orm.Board; +import org.floens.chan.core.model.orm.Loadable; +import org.floens.chan.core.presenter.ArchivePresenter; +import org.floens.chan.ui.helper.BoardHelper; +import org.floens.chan.ui.toolbar.ToolbarMenu; +import org.floens.chan.ui.toolbar.ToolbarMenuItem; +import org.floens.chan.ui.view.CrossfadeView; +import org.floens.chan.ui.view.DividerItemDecoration; +import org.floens.chan.ui.view.FastScrollerHelper; +import org.floens.chan.ui.view.FloatingMenuItem; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import static org.floens.chan.Chan.inject; + +public class ArchiveController extends Controller implements ArchivePresenter.Callback, + ToolbarMenuItem.ToolbarMenuItemCallback, + ToolbarNavigationController.ToolbarSearchCallback, + SwipeRefreshLayout.OnRefreshListener { + private static final int SEARCH_ID = 1; + + private CrossfadeView crossfadeView; + private SwipeRefreshLayout swipeRefreshLayout; + private RecyclerView archiveRecyclerview; + private View progress; + private View errorView; + + @Inject + private ArchivePresenter presenter; + + private ArchiveAdapter adapter; + + private Board board; + + public ArchiveController(Context context) { + super(context); + } + + public void setBoard(Board board) { + this.board = board; + } + + @Override + public void onCreate() { + super.onCreate(); + inject(this); + + // Inflate + view = inflateRes(R.layout.controller_archive); + + // Navigation + navigation.title = context.getString(R.string.archive_title, BoardHelper.getName(board)); + ToolbarMenu menu = new ToolbarMenu(context); + navigation.menu = menu; + menu.addItem(new ToolbarMenuItem(context, this, SEARCH_ID, R.drawable.ic_search_white_24dp)); + + // View binding + crossfadeView = view.findViewById(R.id.crossfade); + swipeRefreshLayout = view.findViewById(R.id.swipe_refresh); + archiveRecyclerview = view.findViewById(R.id.recycler_view); + progress = view.findViewById(R.id.progress); + errorView = view.findViewById(R.id.error_text); + + // Adapters + adapter = new ArchiveAdapter(); + + // View setup + archiveRecyclerview.setLayoutManager(new LinearLayoutManager(context)); + archiveRecyclerview.setAdapter(adapter); + archiveRecyclerview.addItemDecoration( + new DividerItemDecoration(context, DividerItemDecoration.VERTICAL)); + FastScrollerHelper.create(archiveRecyclerview); +// archiveRecyclerview.setVerticalScrollBarEnabled(false); + crossfadeView.toggle(false, false); + swipeRefreshLayout.setOnRefreshListener(this); + + // Presenter + presenter.create(this, board); + } + + @Override + public void onMenuItemClicked(ToolbarMenuItem item) { + ((ToolbarNavigationController) navigationController).showSearch(); + } + + @Override + public void onSubMenuItemClicked(ToolbarMenuItem parent, FloatingMenuItem item) { + } + + @Override + public void onSearchEntered(String entered) { + presenter.onSearchEntered(entered); + } + + @Override + public void onSearchVisibilityChanged(boolean visible) { + presenter.onSearchVisibility(visible); + } + + @Override + public void onRefresh() { + presenter.onRefresh(); + } + + @Override + public void setArchiveItems(List items) { + adapter.setArchiveItems(items); + } + + @Override + public void hideRefreshing() { + swipeRefreshLayout.setRefreshing(false); + } + + @Override + public void showList() { + crossfadeView.toggle(true, true); + } + + @Override + public void showError(boolean show) { + progress.setVisibility(show ? View.GONE : View.VISIBLE); + errorView.setVisibility(show ? View.VISIBLE : View.GONE); + } + + @Override + public void openThread(Loadable loadable) { + ViewThreadController threadController = new ViewThreadController(context); + threadController.setLoadable(loadable); + navigationController.pushController(threadController); + } + + private void onItemClicked(Archive.ArchiveItem item) { + presenter.onItemClicked(item); + } + + private class ArchiveAdapter extends RecyclerView.Adapter { + private List archiveItems = new ArrayList<>(); + + @Override + public int getItemCount() { + return archiveItems.size(); + } + + @Override + public ArchiveCell onCreateViewHolder(ViewGroup parent, int viewType) { + return new ArchiveCell(LayoutInflater.from(context) + .inflate(R.layout.cell_archive, parent, false)); + } + + @Override + public void onBindViewHolder(ArchiveCell holder, int position) { + Archive.ArchiveItem archiveItem = archiveItems.get(position); + + holder.item = archiveItem; + holder.text.setText(archiveItem.description); + } + + public void setArchiveItems(List archiveItems) { + this.archiveItems = archiveItems; + notifyDataSetChanged(); + } + } + + private class ArchiveCell extends RecyclerView.ViewHolder { + private TextView text; + private Archive.ArchiveItem item; + + public ArchiveCell(View itemView) { + super(itemView); + + itemView.setOnClickListener(v -> onItemClicked(item)); + + text = itemView.findViewById(R.id.text); + } + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java index 24352476..bfb4ad05 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java @@ -59,6 +59,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte private static final int VIEW_MODE_ID = 104; private static final int ORDER_ID = 105; private static final int OPEN_BROWSER_ID = 106; + private static final int ARCHIVE_ID = 107; @Inject BrowsePresenter presenter; @@ -70,6 +71,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte private ToolbarMenuItem search; private ToolbarMenuItem refresh; private ToolbarMenuItem overflow; + private FloatingMenuItem archive; public BrowseController(Context context) { super(context); @@ -129,6 +131,11 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte viewModeMenuItem = new FloatingMenuItem(VIEW_MODE_ID, postViewMode == ChanSettings.PostViewMode.LIST ? R.string.action_switch_catalog : R.string.action_switch_board); items.add(viewModeMenuItem); + + archive = new FloatingMenuItem(ARCHIVE_ID, R.string.thread_view_archive); + items.add(archive); + archive.setEnabled(false); + items.add(new FloatingMenuItem(ORDER_ID, R.string.action_order)); items.add(new FloatingMenuItem(OPEN_BROWSER_ID, R.string.action_open_browser)); items.add(new FloatingMenuItem(SHARE_ID, R.string.action_share)); @@ -207,6 +214,25 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte handleOrder(presenter); break; + case ARCHIVE_ID: + openArchive(); + break; + } + } + + private void openArchive() { + Board board = presenter.currentBoard(); + if (board == null) { + return; + } + + ArchiveController archiveController = new ArchiveController(context); + archiveController.setBoard(board); + + if (doubleNavigationController != null) { + doubleNavigationController.pushController(archiveController); + } else { + navigationController.pushController(archiveController); } } @@ -316,6 +342,11 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte } } + @Override + public void showArchiveOption(boolean show) { + archive.setEnabled(show); + } + @Override public void openPin(Pin pin) { showThread(pin.loadable); diff --git a/Clover/app/src/main/res/layout/cell_archive.xml b/Clover/app/src/main/res/layout/cell_archive.xml new file mode 100644 index 00000000..92ca4c8d --- /dev/null +++ b/Clover/app/src/main/res/layout/cell_archive.xml @@ -0,0 +1,34 @@ + + + + + + diff --git a/Clover/app/src/main/res/layout/controller_archive.xml b/Clover/app/src/main/res/layout/controller_archive.xml new file mode 100644 index 00000000..b45bfb2a --- /dev/null +++ b/Clover/app/src/main/res/layout/controller_archive.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + diff --git a/Clover/app/src/main/res/values/strings.xml b/Clover/app/src/main/res/values/strings.xml index aa9742cd..d54f98d2 100644 --- a/Clover/app/src/main/res/values/strings.xml +++ b/Clover/app/src/main/res/values/strings.xml @@ -175,6 +175,7 @@ Re-enable this permission in the app settings if you permanently disabled it."Please select a thread Scroll to top/bottom Bookmark this thread + Archive Clover Browse your favorite imageboards @@ -273,6 +274,9 @@ Re-enable this permission in the app settings if you permanently disabled it."Clear posting history? Clear + %s archive + Error loading archive + Board Catalog Bookmarked threads diff --git a/docs/database.txt b/docs/database.txt index 02ad0630..24344de1 100644 --- a/docs/database.txt +++ b/docs/database.txt @@ -66,9 +66,13 @@ Changes in version 21: ALTER TABLE loadable ADD COLUMN lastLoaded default -1; -Changes in version 22: +Changes in version 22: (also configures the 4chan site with id 0) CREATE TABLE `site` (`configuration` VARCHAR , `id` INTEGER PRIMARY KEY AUTOINCREMENT , `userSettings` VARCHAR ); ALTER TABLE loadable 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 threadhide ADD COLUMN site INTEGER default 0; + + +Changes in version 23: +ALTER TABLE board ADD COLUMN "archive" INTEGER;