mirror of https://github.com/kurisufriend/Clover
reads the html because there is no api endpoint for it. adds a new `archive` boolean to the Board model.refactor-toolbar
parent
162a0b2f68
commit
9c4f4334f9
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.core.model; |
||||
|
||||
import java.util.List; |
||||
|
||||
public class Archive { |
||||
public final List<ArchiveItem> items; |
||||
|
||||
public static Archive fromItems(List<ArchiveItem> items) { |
||||
return new Archive(items); |
||||
} |
||||
|
||||
private Archive(List<ArchiveItem> 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; |
||||
} |
||||
} |
||||
} |
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.core.net; |
||||
|
||||
import com.android.volley.NetworkResponse; |
||||
import com.android.volley.Request; |
||||
import com.android.volley.Response; |
||||
import com.android.volley.Response.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<T> extends Request<T> { |
||||
protected final Listener<T> listener; |
||||
|
||||
public HtmlReaderRequest(String url, Listener<T> 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<T> 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; |
||||
} |
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
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<Archive.ArchiveItem> items = new ArrayList<>(); |
||||
private List<Archive.ArchiveItem> 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<Archive.ArchiveItem> items); |
||||
|
||||
void hideRefreshing(); |
||||
|
||||
void showList(); |
||||
|
||||
void showError(boolean show); |
||||
|
||||
void openThread(Loadable loadable); |
||||
} |
||||
} |
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
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<Archive> { |
||||
public Chan4ArchiveRequest(Site site, Board board, |
||||
Response.Listener<Archive> listener, |
||||
Response.ErrorListener errorListener) { |
||||
super(site.endpoints().archive(board).toString(), listener, errorListener); |
||||
} |
||||
|
||||
@Override |
||||
public Archive readDocument(Document document) throws IOException { |
||||
List<Archive.ArchiveItem> 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); |
||||
} |
||||
} |
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
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<Archive.ArchiveItem> 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<ArchiveCell> { |
||||
private List<Archive.ArchiveItem> 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<Archive.ArchiveItem> 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); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,34 @@ |
||||
<?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:background="?attr/selectableItemBackground" |
||||
android:minHeight="48dp" |
||||
android:orientation="horizontal"> |
||||
|
||||
<TextView |
||||
android:id="@+id/text" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:gravity="center_vertical" |
||||
android:padding="8dp" |
||||
android:textColor="?text_color_primary" |
||||
android:textSize="14sp" /> |
||||
|
||||
</LinearLayout> |
@ -0,0 +1,63 @@ |
||||
<?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/>. |
||||
--> |
||||
<org.floens.chan.ui.view.CrossfadeView xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:id="@+id/crossfade" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:background="?backcolor"> |
||||
|
||||
<android.support.v4.widget.SwipeRefreshLayout |
||||
android:id="@+id/swipe_refresh" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:visibility="gone"> |
||||
|
||||
<android.support.v7.widget.RecyclerView |
||||
android:id="@+id/recycler_view" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:clipToPadding="false" |
||||
android:padding="8dp" /> |
||||
|
||||
</android.support.v4.widget.SwipeRefreshLayout> |
||||
|
||||
<FrameLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:animateLayoutChanges="true" |
||||
android:padding="8dp" |
||||
android:visibility="gone"> |
||||
|
||||
<ProgressBar |
||||
android:id="@+id/progress" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_gravity="center" /> |
||||
|
||||
<TextView |
||||
android:id="@+id/error_text" |
||||
style="?android:attr/textAppearanceMedium" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_gravity="center" |
||||
android:text="@string/archive_error" |
||||
android:visibility="gone" /> |
||||
|
||||
</FrameLayout> |
||||
|
||||
</org.floens.chan.ui.view.CrossfadeView> |
Loading…
Reference in new issue