From 85d12cda39bd9cd6abc39cd91ca498e8ddb6032a Mon Sep 17 00:00:00 2001 From: Floens Date: Mon, 6 Oct 2014 22:40:51 +0200 Subject: [PATCH] Create a dedicated start activity. --- Clover/app/src/main/AndroidManifest.xml | 93 +-- .../chan/ui/activity/BoardActivity.java | 739 +---------------- .../floens/chan/ui/activity/ChanActivity.java | 753 ++++++++++++++++++ .../floens/chan/ui/service/WatchNotifier.java | 4 +- 4 files changed, 820 insertions(+), 769 deletions(-) create mode 100644 Clover/app/src/main/java/org/floens/chan/ui/activity/ChanActivity.java diff --git a/Clover/app/src/main/AndroidManifest.xml b/Clover/app/src/main/AndroidManifest.xml index 7d5976e1..87c921e0 100644 --- a/Clover/app/src/main/AndroidManifest.xml +++ b/Clover/app/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - - - - - - - - + + + . android:label="@string/app_name" android:theme="@style/AppTheme"> + android:name=".ui.activity.BoardActivity" + android:configChanges="keyboardHidden|orientation|screenSize"> - - - + + + + - - - + + - - + + + android:scheme="http" /> + android:scheme="https" /> + android:scheme="http" /> + android:scheme="https" /> + android:scheme="http" /> + android:scheme="https" /> - + android:windowSoftInputMode="adjustResize" /> + android:parentActivityName=".ui.activity.ChanActivity"> + android:value="org.floens.chan.ui.activity.BoardActivity" /> + android:parentActivityName=".ui.activity.ChanActivity"> + android:value="org.floens.chan.ui.activity.BoardActivity" /> + android:parentActivityName=".ui.activity.ChanActivity"> + android:value="org.floens.chan.ui.activity.BoardActivity" /> + android:parentActivityName=".ui.activity.ChanActivity"> + android:value="org.floens.chan.ui.activity.BoardActivity" /> + android:parentActivityName=".ui.activity.ChanActivity"> + android:value="org.floens.chan.ui.activity.BoardActivity" /> - - - - - + android:theme="@style/Theme.ImageList" /> + + + android:parentActivityName=".ui.activity.ChanActivity"> + android:value="org.floens.chan.ui.activity.BoardActivity" /> - + android:exported="false" /> - \ No newline at end of file + diff --git a/Clover/app/src/main/java/org/floens/chan/ui/activity/BoardActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/BoardActivity.java index d4507383..d9178f95 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/activity/BoardActivity.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/BoardActivity.java @@ -17,737 +17,44 @@ */ package org.floens.chan.ui.activity; -import android.app.ActionBar; -import android.app.AlertDialog; -import android.app.FragmentTransaction; -import android.content.Context; -import android.content.DialogInterface; +import android.app.Activity; import android.content.Intent; -import android.content.res.Configuration; -import android.net.Uri; import android.os.Bundle; -import android.support.v4.app.ActionBarDrawerToggle; -import android.util.DisplayMetrics; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.FrameLayout; -import android.widget.Spinner; -import android.widget.TextView; -import org.floens.chan.ChanApplication; -import org.floens.chan.R; -import org.floens.chan.chan.ChanUrls; -import org.floens.chan.core.ChanPreferences; -import org.floens.chan.core.loader.Loader; -import org.floens.chan.core.manager.BoardManager; -import org.floens.chan.core.manager.ThreadManager; -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.model.Post; -import org.floens.chan.ui.fragment.ThreadFragment; import org.floens.chan.utils.Logger; -import org.floens.chan.utils.Utils; -import java.util.List; - -public class BoardActivity extends BaseActivity implements AdapterView.OnItemSelectedListener, BoardManager.BoardChangeListener { - private static final String TAG = "BoardActivity"; - - private Loadable boardLoadable; - private Loadable threadLoadable; - private ThreadFragment boardFragment; - private ThreadFragment threadFragment; - - private boolean ignoreNextOnItemSelected = false; - private Spinner boardSpinner; - private BoardSpinnerAdapter spinnerAdapter; +/** + * Not called StartActivity because than the launcher icon would disappear. + * Instead it's called like the old launcher activity, BoardActivity. + */ +public class BoardActivity extends Activity { + private static final String TAG = "StartActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - ChanApplication.getBoardManager().addListener(this); - - boardLoadable = new Loadable(); - threadLoadable = new Loadable(); - - boardFragment = ThreadFragment.newInstance(this); - setBoardFragmentViewMode(); - - threadFragment = ThreadFragment.newInstance(this); - - FragmentTransaction ft = getFragmentManager().beginTransaction(); - ft.replace(R.id.left_pane, boardFragment); - ft.replace(R.id.right_pane, threadFragment); - ft.commitAllowingStateLoss(); - - final ActionBar actionBar = getActionBar(); - - boardSpinner = new Spinner(actionBar.getThemedContext()); - spinnerAdapter = new BoardSpinnerAdapter(this, boardSpinner); - boardSpinner.setAdapter(spinnerAdapter); - boardSpinner.setOnItemSelectedListener(this); - - actionBar.setCustomView(boardSpinner); - actionBar.setDisplayShowCustomEnabled(true); - - updatePaneState(); - - Intent startIntent = getIntent(); - Uri startUri = startIntent.getData(); - - if (savedInstanceState != null) { - threadLoadable.readFromBundle(this, "thread", savedInstanceState); - startLoadingThread(threadLoadable); - - // Reset page etc. - Loadable tmp = new Loadable(); - tmp.readFromBundle(this, "board", savedInstanceState); - startLoadingBoard(new Loadable(tmp.board)); - } else { - if (startUri != null) { - handleIntentURI(startUri); - } - - if (boardLoadable.mode == Loadable.Mode.INVALID) { - List savedValues = ChanApplication.getBoardManager().getSavedBoards(); - if (savedValues.size() > 0) { - startLoadingBoard(new Loadable(savedValues.get(0).value)); - } + // http://stackoverflow.com/a/7748416/1001608 + // Possible work around for market launches. See http://code.google.com/p/android/issues/detail?id=2373 + // for more details. Essentially, the market launches the main activity on top of other activities. + // we never want this to happen. Instead, we check if we are the root and if not, we finish. + if (!isTaskRoot()) { + final Intent intent = getIntent(); + final String intentAction = intent.getAction(); + if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && intentAction != null && intentAction.equals(Intent.ACTION_MAIN)) { + Logger.w(TAG, "StartActivity is not the root. Finishing StartActivity instead of launching."); + finish(); + return; } } - if (startIntent.getExtras() != null) { - handleExtraBundle(startIntent.getExtras()); - } - - ignoreNextOnItemSelected = true; - } - - @Override - protected void onNewIntent(final Intent intent) { - super.onNewIntent(intent); - - if (intent.getExtras() != null) { - handleExtraBundle(intent.getExtras()); - } - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - boardLoadable.writeToBundle(this, "board", outState); - threadLoadable.writeToBundle(this, "thread", outState); - } - - @Override - protected void onStart() { - super.onStart(); - - ChanApplication.getInstance().activityEnteredForeground(); - } - - @Override - protected void onStop() { - super.onStop(); - - ChanApplication.getInstance().activityEnteredBackground(); - } - - @Override - protected void onPause() { - super.onPause(); - - ChanApplication.getWatchManager().updateDatabase(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - - ChanApplication.getBoardManager().removeListener(this); - } - - @Override - protected void initDrawer() { - pinDrawerListener = new ActionBarDrawerToggle(this, pinDrawer, R.drawable.ic_drawer, R.string.drawer_open, - R.string.drawer_close) { - }; - - super.initDrawer(); - } - - @Override - protected void onPostCreate(Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - pinDrawerListener.syncState(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - pinDrawerListener.onConfigurationChanged(newConfig); - - updatePaneState(); - } - - @Override - public void onBackPressed() { - if (threadPane.isOpen()) { - super.onBackPressed(); - } else { - threadPane.openPane(); - } - } - - @Override - public void openPin(Pin pin) { - startLoadingThread(pin.loadable); - - pinDrawer.closeDrawer(pinDrawerView); - } - - @Override - public void onOPClicked(Post post) { - Loadable l = new Loadable(post.board, post.no); - l.generateTitle(post); - startLoadingThread(l); - } - - @Override - public void onOpenThread(Loadable thread) { - startLoadingThread(thread); - } - - @Override - public void onThreadLoaded(Loadable loadable, List posts) { - updateActionBarState(); - pinnedAdapter.notifyDataSetChanged(); - } - - @Override - public void updatePin(Pin pin) { - super.updatePin(pin); - updateActionBarState(); - } - - @Override - public void onNothingSelected(final AdapterView parent) { + Intent intent = new Intent(this, ChanActivity.class); + startActivity(intent); } @Override - public void onPanelClosed(View view) { - updateActionBarState(); - } - - @Override - public void onPanelOpened(View view) { - updateActionBarState(); - } - - @Override - public void onBoardsChanged() { - spinnerAdapter.setBoards(); - spinnerAdapter.notifyDataSetChanged(); - } - - private void handleExtraBundle(Bundle extras) { - int pinId = extras.getInt("pin_id", -2); - if (pinId != -2) { - if (pinId == -1) { - pinDrawer.openDrawer(pinDrawerView); - } else { - Pin pin = ChanApplication.getWatchManager().findPinById(pinId); - if (pin != null) { - startLoadingThread(pin.loadable); - } - } - } - } - - private void updatePaneState() { - DisplayMetrics metrics = new DisplayMetrics(); - getWindowManager().getDefaultDisplay().getMetrics(metrics); - int width = metrics.widthPixels; - - FrameLayout left = (FrameLayout) findViewById(R.id.left_pane); - FrameLayout right = (FrameLayout) findViewById(R.id.right_pane); - - LayoutParams leftParams = left.getLayoutParams(); - LayoutParams rightParams = right.getLayoutParams(); - - boolean wasSlidable = threadPane.isSlideable(); - boolean isSlidable; - - // Content view dp's: - // Nexus 4 is 384 x 640 dp - // Nexus 7 is 600 x 960 dp - // Nexus 10 is 800 x 1280 dp - - if (ChanPreferences.getForcePhoneLayout()) { - leftParams.width = width - Utils.dp(30); - rightParams.width = width; - isSlidable = true; - } else { - if (width < Utils.dp(400)) { - leftParams.width = width - Utils.dp(30); - rightParams.width = width; - isSlidable = true; - } else if (width < Utils.dp(800)) { - leftParams.width = width - Utils.dp(60); - rightParams.width = width; - isSlidable = true; - } else if (width < Utils.dp(1000)) { - leftParams.width = Utils.dp(300); - rightParams.width = width - Utils.dp(300); - isSlidable = false; - } else { - leftParams.width = Utils.dp(400); - rightParams.width = width - Utils.dp(400); - isSlidable = false; - } - } - - left.setLayoutParams(leftParams); - right.setLayoutParams(rightParams); - - threadPane.requestLayout(); - left.requestLayout(); - right.requestLayout(); - - LayoutParams drawerParams = pinDrawerView.getLayoutParams(); - - if (width < Utils.dp(340)) { - drawerParams.width = Utils.dp(280); - } else { - drawerParams.width = Utils.dp(320); - } - - pinDrawerView.setLayoutParams(drawerParams); - - updateActionBarState(); - - if (isSlidable != wasSlidable) { - // Terrible hack to sync state for some devices when it changes slidable mode - threadPane.postDelayed(new Runnable() { - @Override - public void run() { - updateActionBarState(); - } - }, 1000); - } - } - - private void updateActionBarState() { - // Force the actionbar state after the ThreadPane layout, - // otherwise the ThreadPane incorrectly reports that it's not slidable. - threadPane.post(new Runnable() { - @Override - public void run() { - updateActionBarStateCallback(); - } - }); - } - - private void updateActionBarStateCallback() { - final ActionBar actionBar = getActionBar(); - - if (threadPane.isSlideable()) { - if (threadPane.isOpen()) { - actionBar.setDisplayShowCustomEnabled(true); - spinnerAdapter.setBoard(boardLoadable.board); - actionBar.setTitle(""); - pinDrawerListener.setDrawerIndicatorEnabled(true); - - if (boardLoadable.isBoardMode()) { - setShareUrl(ChanUrls.getBoardUrlDesktop(boardLoadable.board)); - } else if (boardLoadable.isCatalogMode()) { - setShareUrl(ChanUrls.getCatalogUrlDesktop(boardLoadable.board)); - } - } else { - actionBar.setDisplayShowCustomEnabled(false); - actionBar.setTitle(threadLoadable.title); - pinDrawerListener.setDrawerIndicatorEnabled(false); - - if (threadLoadable.isThreadMode()) - setShareUrl(ChanUrls.getThreadUrlDesktop(threadLoadable.board, threadLoadable.no)); - } - } else { - actionBar.setDisplayShowCustomEnabled(true); - pinDrawerListener.setDrawerIndicatorEnabled(true); - actionBar.setTitle(threadLoadable.title); - - if (threadLoadable.isThreadMode()) { - setShareUrl(ChanUrls.getThreadUrlDesktop(threadLoadable.board, threadLoadable.no)); - } - } - - actionBar.setHomeButtonEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setDisplayShowTitleEnabled(true); - - invalidateOptionsMenu(); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - boolean open = threadPane.isOpen(); - boolean slidable = threadPane.isSlideable(); - - setMenuItemEnabled(menu.findItem(R.id.action_reload_board), slidable && open); - setMenuItemEnabled(menu.findItem(R.id.action_reload_thread), slidable && !open); - setMenuItemEnabled(menu.findItem(R.id.action_reload_tablet), !slidable); - - setMenuItemEnabled(menu.findItem(R.id.action_pin), !slidable || !open); - - setMenuItemEnabled(menu.findItem(R.id.action_reply), slidable); - setMenuItemEnabled(menu.findItem(R.id.action_reply_tablet), !slidable); - - setMenuItemEnabled(menu.findItem(R.id.action_board_view_mode), !slidable || open); - - if (ChanPreferences.getBoardViewMode().equals("list")) { - menu.findItem(R.id.action_board_view_mode_list).setChecked(true); - } else if (ChanPreferences.getBoardViewMode().equals("grid")) { - menu.findItem(R.id.action_board_view_mode_grid).setChecked(true); - } - - setMenuItemEnabled(menu.findItem(R.id.action_search), slidable); - setMenuItemEnabled(menu.findItem(R.id.action_search_tablet), !slidable); - - return super.onPrepareOptionsMenu(menu); - } - - private void setMenuItemEnabled(MenuItem item, boolean enabled) { - if (item != null) { - item.setVisible(enabled); - item.setEnabled(enabled); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (pinDrawerListener.onOptionsItemSelected(item)) { - return true; - } - - switch (item.getItemId()) { - case R.id.action_reload_board: - case R.id.action_reload_tablet_board: - boardFragment.reload(); - return true; - case R.id.action_reload_thread: - case R.id.action_reload_tablet_thread: - threadFragment.reload(); - return true; - case R.id.action_reply: - if (threadPane.isOpen()) { - boardFragment.openReply(); - } else { - threadFragment.openReply(); - } - return true; - case R.id.action_reply_board: - boardFragment.openReply(); - - return true; - case R.id.action_reply_thread: - threadFragment.openReply(); - - return true; - case R.id.action_pin: - if (threadFragment.hasLoader()) { - Loader loader = threadFragment.getLoader(); - if (loader.getCachedPosts().size() > 0) { - ChanApplication.getWatchManager().addPin(loader.getLoadable(), loader.getCachedPosts().get(0)); - pinDrawer.openDrawer(pinDrawerView); - } - } - - return true; - case R.id.action_open_browser: - openInBrowser(); - - return true; - case R.id.action_board_view_mode_grid: - if (!ChanPreferences.getBoardViewMode().equals("grid")) { - ChanPreferences.setBoardViewMode("grid"); - setBoardFragmentViewMode(); - startLoadingBoard(boardLoadable); - } - return true; - case R.id.action_board_view_mode_list: - if (!ChanPreferences.getBoardViewMode().equals("list")) { - ChanPreferences.setBoardViewMode("list"); - setBoardFragmentViewMode(); - startLoadingBoard(boardLoadable); - } - return true; - case R.id.action_search: - if (threadPane.isOpen()) { - boardFragment.startFiltering(); - } else { - threadFragment.startFiltering(); - } - return true; - case R.id.action_search_board: - boardFragment.startFiltering(); - return true; - case R.id.action_search_thread: - threadFragment.startFiltering(); - return true; - case android.R.id.home: - threadPane.openPane(); - - return true; - } - - return super.onOptionsItemSelected(item); - } - - @Override - public void onItemSelected(final AdapterView parent, final View view, final int position, final long id) { - if (ignoreNextOnItemSelected) { - Logger.d(TAG, "Ignoring onItemSelected"); - ignoreNextOnItemSelected = false; - return; - } - - spinnerAdapter.onItemSelected(position); - } - - private void startLoadingBoard(Loadable loadable) { - if (loadable.mode == Loadable.Mode.INVALID) - return; - - boardLoadable = loadable; - - if (ChanPreferences.getBoardMode().equals("catalog")) { - boardLoadable.mode = Loadable.Mode.CATALOG; - } else if (ChanPreferences.getBoardMode().equals("pages")) { - boardLoadable.mode = Loadable.Mode.BOARD; - } - - // Force catalog mode when using grid - if (boardFragment.getViewMode() == ThreadManager.ViewMode.GRID) { - boardLoadable.mode = Loadable.Mode.CATALOG; - } - - boardFragment.bindLoadable(boardLoadable); - boardFragment.requestData(); - - updateActionBarState(); - } - - private void startLoadingThread(Loadable loadable) { - if (loadable.mode == Loadable.Mode.INVALID) - return; - - Pin pin = ChanApplication.getWatchManager().findPinByLoadable(loadable); - if (pin != null) { - // Use the loadable from the pin. - // This way we can store the listview position in the pin loadable, - // and not in a separate loadable instance. - loadable = pin.loadable; - } - - threadLoadable = loadable; - - threadFragment.bindLoadable(loadable); - threadFragment.requestData(); - - threadPane.closePane(); - - updateActionBarState(); - } - - /** - * Handle opening from an external url. - * - * @param startUri - */ - private void handleIntentURI(Uri startUri) { - Logger.d(TAG, "Opening " + startUri.getPath()); - - List parts = startUri.getPathSegments(); - - if (parts.size() == 1) { - // Board mode - String rawBoard = parts.get(0); - if (ChanApplication.getBoardManager().getBoardExists(rawBoard)) { - startLoadingBoard(new Loadable(rawBoard)); - } else { - handleIntentURIFallback(startUri.toString()); - } - } else if (parts.size() >= 3) { - // Thread mode - String rawBoard = parts.get(0); - int no = -1; - - try { - no = Integer.parseInt(parts.get(2)); - } catch (NumberFormatException e) { - } - - int post = -1; - String fragment = startUri.getFragment(); - if (fragment != null) { - int index = fragment.indexOf("p"); - if (index >= 0) { - try { - post = Integer.parseInt(fragment.substring(index + 1)); - } catch (NumberFormatException e) { - } - } - } - - if (no >= 0 && ChanApplication.getBoardManager().getBoardExists(rawBoard)) { - startLoadingThread(new Loadable(rawBoard, no)); - if (post >= 0) { - threadFragment.highlightPost(post); - } - } else { - handleIntentURIFallback(startUri.toString()); - } - } else { - showUrlOpenPicker(startUri.toString()); - } - } - - private void handleIntentURIFallback(final String url) { - new AlertDialog.Builder(this).setTitle(R.string.open_unknown_title).setMessage(R.string.open_unknown) - .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - showUrlOpenPicker(url); - } - }).setCancelable(false).create().show(); - } - - private void setBoardFragmentViewMode() { - if (ChanPreferences.getBoardViewMode().equals("list")) { - boardFragment.setViewMode(ThreadManager.ViewMode.LIST); - } else if (ChanPreferences.getBoardViewMode().equals("grid")) { - boardFragment.setViewMode(ThreadManager.ViewMode.GRID); - } - } - - private class BoardSpinnerAdapter extends BaseAdapter { - private static final int VIEW_TYPE_ITEM = 0; - private static final int VIEW_TYPE_ADD = 1; - - private Context context; - private Spinner spinner; - private List boards; - private int lastSelectedPosition = 0; - - public BoardSpinnerAdapter(Context context, Spinner spinner) { - this.context = context; - this.spinner = spinner; - setBoards(); - } - - public void setBoards() { - boards = ChanApplication.getBoardManager().getSavedBoards(); - } - - public void setBoard(String boardValue) { - for (int i = 0; i < boards.size(); i++) { - if (boards.get(i).value.equals(boardValue)) { - spinner.setSelection(i); - return; - } - } - } - - public void onItemSelected(int position) { - if (position >= 0 && position < boards.size()) { - Loadable board = new Loadable(boards.get(position).value); - - // onItemSelected is called after the view initializes, - // ignore if it's the same board - if (boardLoadable.equals(board)) - return; - - startLoadingBoard(board); - - lastSelectedPosition = position; - } else { - startActivity(new Intent(context, BoardEditor.class)); - spinner.setSelection(lastSelectedPosition); - } - } - - @Override - public int getCount() { - return boards.size() + 1; - } - - @Override - public int getViewTypeCount() { - return 2; - } - - @Override - public int getItemViewType(int position) { - return position == getCount() - 1 ? VIEW_TYPE_ADD : VIEW_TYPE_ITEM; - } - - @Override - public long getItemId(final int position) { - return position; - } - - @Override - public String getItem(final int position) { - switch (getItemViewType(position)) { - case VIEW_TYPE_ITEM: - return boards.get(position).key; - case VIEW_TYPE_ADD: - return context.getString(R.string.board_select_add); - default: - return ""; - } - } - - @Override - public View getView(final int position, View convertView, final ViewGroup parent) { - switch (getItemViewType(position)) { - case VIEW_TYPE_ITEM: { - if (convertView == null || (Integer) convertView.getTag() != VIEW_TYPE_ITEM) { - convertView = LayoutInflater.from(context).inflate(R.layout.board_select_spinner, null); - convertView.setTag(VIEW_TYPE_ITEM); - } - - TextView textView = (TextView) convertView; - textView.setText(getItem(position)); - return textView; - } - case VIEW_TYPE_ADD: { - if (convertView == null || (Integer) convertView.getTag() != VIEW_TYPE_ADD) { - convertView = LayoutInflater.from(context).inflate(R.layout.board_select_add, null); - convertView.setTag(VIEW_TYPE_ADD); - } - - TextView textView = (TextView) convertView; - textView.setText(getItem(position)); - return textView; - } - } - - return null; - } + protected void onRestart() { + super.onRestart(); + finish(); } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/activity/ChanActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/ChanActivity.java new file mode 100644 index 00000000..dc2e7747 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/ChanActivity.java @@ -0,0 +1,753 @@ +/* + * 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.activity; + +import android.app.ActionBar; +import android.app.AlertDialog; +import android.app.FragmentTransaction; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.ActionBarDrawerToggle; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.FrameLayout; +import android.widget.Spinner; +import android.widget.TextView; + +import org.floens.chan.ChanApplication; +import org.floens.chan.R; +import org.floens.chan.chan.ChanUrls; +import org.floens.chan.core.ChanPreferences; +import org.floens.chan.core.loader.Loader; +import org.floens.chan.core.manager.BoardManager; +import org.floens.chan.core.manager.ThreadManager; +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.model.Post; +import org.floens.chan.ui.fragment.ThreadFragment; +import org.floens.chan.utils.Logger; +import org.floens.chan.utils.Utils; + +import java.util.List; + +public class ChanActivity extends BaseActivity implements AdapterView.OnItemSelectedListener, BoardManager.BoardChangeListener { + private static final String TAG = "ChanActivity"; + + private Loadable boardLoadable; + private Loadable threadLoadable; + private ThreadFragment boardFragment; + private ThreadFragment threadFragment; + + private boolean ignoreNextOnItemSelected = false; + private Spinner boardSpinner; + private BoardSpinnerAdapter spinnerAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + ChanApplication.getBoardManager().addListener(this); + + boardLoadable = new Loadable(); + threadLoadable = new Loadable(); + + boardFragment = ThreadFragment.newInstance(this); + setBoardFragmentViewMode(); + + threadFragment = ThreadFragment.newInstance(this); + + FragmentTransaction ft = getFragmentManager().beginTransaction(); + ft.replace(R.id.left_pane, boardFragment); + ft.replace(R.id.right_pane, threadFragment); + ft.commitAllowingStateLoss(); + + final ActionBar actionBar = getActionBar(); + + boardSpinner = new Spinner(actionBar.getThemedContext()); + spinnerAdapter = new BoardSpinnerAdapter(this, boardSpinner); + boardSpinner.setAdapter(spinnerAdapter); + boardSpinner.setOnItemSelectedListener(this); + + actionBar.setCustomView(boardSpinner); + actionBar.setDisplayShowCustomEnabled(true); + + updatePaneState(); + + Intent startIntent = getIntent(); + Uri startUri = startIntent.getData(); + + if (savedInstanceState != null) { + threadLoadable.readFromBundle(this, "thread", savedInstanceState); + startLoadingThread(threadLoadable); + + // Reset page etc. + Loadable tmp = new Loadable(); + tmp.readFromBundle(this, "board", savedInstanceState); + startLoadingBoard(new Loadable(tmp.board)); + } else { + if (startUri != null) { + handleIntentURI(startUri); + } + + if (boardLoadable.mode == Loadable.Mode.INVALID) { + List savedValues = ChanApplication.getBoardManager().getSavedBoards(); + if (savedValues.size() > 0) { + startLoadingBoard(new Loadable(savedValues.get(0).value)); + } + } + } + + if (startIntent.getExtras() != null) { + handleExtraBundle(startIntent.getExtras()); + } + + ignoreNextOnItemSelected = true; + } + + @Override + protected void onNewIntent(final Intent intent) { + super.onNewIntent(intent); + + if (intent.getExtras() != null) { + handleExtraBundle(intent.getExtras()); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + boardLoadable.writeToBundle(this, "board", outState); + threadLoadable.writeToBundle(this, "thread", outState); + } + + @Override + protected void onStart() { + super.onStart(); + + ChanApplication.getInstance().activityEnteredForeground(); + } + + @Override + protected void onStop() { + super.onStop(); + + ChanApplication.getInstance().activityEnteredBackground(); + } + + @Override + protected void onPause() { + super.onPause(); + + ChanApplication.getWatchManager().updateDatabase(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + ChanApplication.getBoardManager().removeListener(this); + } + + @Override + protected void initDrawer() { + pinDrawerListener = new ActionBarDrawerToggle(this, pinDrawer, R.drawable.ic_drawer, R.string.drawer_open, + R.string.drawer_close) { + }; + + super.initDrawer(); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + pinDrawerListener.syncState(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + pinDrawerListener.onConfigurationChanged(newConfig); + + updatePaneState(); + } + + @Override + public void onBackPressed() { + if (threadPane.isOpen()) { + super.onBackPressed(); + } else { + threadPane.openPane(); + } + } + + @Override + public void openPin(Pin pin) { + startLoadingThread(pin.loadable); + + pinDrawer.closeDrawer(pinDrawerView); + } + + @Override + public void onOPClicked(Post post) { + Loadable l = new Loadable(post.board, post.no); + l.generateTitle(post); + startLoadingThread(l); + } + + @Override + public void onOpenThread(Loadable thread) { + startLoadingThread(thread); + } + + @Override + public void onThreadLoaded(Loadable loadable, List posts) { + updateActionBarState(); + pinnedAdapter.notifyDataSetChanged(); + } + + @Override + public void updatePin(Pin pin) { + super.updatePin(pin); + updateActionBarState(); + } + + @Override + public void onNothingSelected(final AdapterView parent) { + } + + @Override + public void onPanelClosed(View view) { + updateActionBarState(); + } + + @Override + public void onPanelOpened(View view) { + updateActionBarState(); + } + + @Override + public void onBoardsChanged() { + spinnerAdapter.setBoards(); + spinnerAdapter.notifyDataSetChanged(); + } + + private void handleExtraBundle(Bundle extras) { + int pinId = extras.getInt("pin_id", -2); + if (pinId != -2) { + if (pinId == -1) { + pinDrawer.openDrawer(pinDrawerView); + } else { + Pin pin = ChanApplication.getWatchManager().findPinById(pinId); + if (pin != null) { + startLoadingThread(pin.loadable); + } + } + } + } + + private void updatePaneState() { + DisplayMetrics metrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(metrics); + int width = metrics.widthPixels; + + FrameLayout left = (FrameLayout) findViewById(R.id.left_pane); + FrameLayout right = (FrameLayout) findViewById(R.id.right_pane); + + LayoutParams leftParams = left.getLayoutParams(); + LayoutParams rightParams = right.getLayoutParams(); + + boolean wasSlidable = threadPane.isSlideable(); + boolean isSlidable; + + // Content view dp's: + // Nexus 4 is 384 x 640 dp + // Nexus 7 is 600 x 960 dp + // Nexus 10 is 800 x 1280 dp + + if (ChanPreferences.getForcePhoneLayout()) { + leftParams.width = width - Utils.dp(30); + rightParams.width = width; + isSlidable = true; + } else { + if (width < Utils.dp(400)) { + leftParams.width = width - Utils.dp(30); + rightParams.width = width; + isSlidable = true; + } else if (width < Utils.dp(800)) { + leftParams.width = width - Utils.dp(60); + rightParams.width = width; + isSlidable = true; + } else if (width < Utils.dp(1000)) { + leftParams.width = Utils.dp(300); + rightParams.width = width - Utils.dp(300); + isSlidable = false; + } else { + leftParams.width = Utils.dp(400); + rightParams.width = width - Utils.dp(400); + isSlidable = false; + } + } + + left.setLayoutParams(leftParams); + right.setLayoutParams(rightParams); + + threadPane.requestLayout(); + left.requestLayout(); + right.requestLayout(); + + LayoutParams drawerParams = pinDrawerView.getLayoutParams(); + + if (width < Utils.dp(340)) { + drawerParams.width = Utils.dp(280); + } else { + drawerParams.width = Utils.dp(320); + } + + pinDrawerView.setLayoutParams(drawerParams); + + updateActionBarState(); + + if (isSlidable != wasSlidable) { + // Terrible hack to sync state for some devices when it changes slidable mode + threadPane.postDelayed(new Runnable() { + @Override + public void run() { + updateActionBarState(); + } + }, 1000); + } + } + + private void updateActionBarState() { + // Force the actionbar state after the ThreadPane layout, + // otherwise the ThreadPane incorrectly reports that it's not slidable. + threadPane.post(new Runnable() { + @Override + public void run() { + updateActionBarStateCallback(); + } + }); + } + + private void updateActionBarStateCallback() { + final ActionBar actionBar = getActionBar(); + + if (threadPane.isSlideable()) { + if (threadPane.isOpen()) { + actionBar.setDisplayShowCustomEnabled(true); + spinnerAdapter.setBoard(boardLoadable.board); + actionBar.setTitle(""); + pinDrawerListener.setDrawerIndicatorEnabled(true); + + if (boardLoadable.isBoardMode()) { + setShareUrl(ChanUrls.getBoardUrlDesktop(boardLoadable.board)); + } else if (boardLoadable.isCatalogMode()) { + setShareUrl(ChanUrls.getCatalogUrlDesktop(boardLoadable.board)); + } + } else { + actionBar.setDisplayShowCustomEnabled(false); + actionBar.setTitle(threadLoadable.title); + pinDrawerListener.setDrawerIndicatorEnabled(false); + + if (threadLoadable.isThreadMode()) + setShareUrl(ChanUrls.getThreadUrlDesktop(threadLoadable.board, threadLoadable.no)); + } + } else { + actionBar.setDisplayShowCustomEnabled(true); + pinDrawerListener.setDrawerIndicatorEnabled(true); + actionBar.setTitle(threadLoadable.title); + + if (threadLoadable.isThreadMode()) { + setShareUrl(ChanUrls.getThreadUrlDesktop(threadLoadable.board, threadLoadable.no)); + } + } + + actionBar.setHomeButtonEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setDisplayShowTitleEnabled(true); + + invalidateOptionsMenu(); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + boolean open = threadPane.isOpen(); + boolean slidable = threadPane.isSlideable(); + + setMenuItemEnabled(menu.findItem(R.id.action_reload_board), slidable && open); + setMenuItemEnabled(menu.findItem(R.id.action_reload_thread), slidable && !open); + setMenuItemEnabled(menu.findItem(R.id.action_reload_tablet), !slidable); + + setMenuItemEnabled(menu.findItem(R.id.action_pin), !slidable || !open); + + setMenuItemEnabled(menu.findItem(R.id.action_reply), slidable); + setMenuItemEnabled(menu.findItem(R.id.action_reply_tablet), !slidable); + + setMenuItemEnabled(menu.findItem(R.id.action_board_view_mode), !slidable || open); + + if (ChanPreferences.getBoardViewMode().equals("list")) { + menu.findItem(R.id.action_board_view_mode_list).setChecked(true); + } else if (ChanPreferences.getBoardViewMode().equals("grid")) { + menu.findItem(R.id.action_board_view_mode_grid).setChecked(true); + } + + setMenuItemEnabled(menu.findItem(R.id.action_search), slidable); + setMenuItemEnabled(menu.findItem(R.id.action_search_tablet), !slidable); + + return super.onPrepareOptionsMenu(menu); + } + + private void setMenuItemEnabled(MenuItem item, boolean enabled) { + if (item != null) { + item.setVisible(enabled); + item.setEnabled(enabled); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (pinDrawerListener.onOptionsItemSelected(item)) { + return true; + } + + switch (item.getItemId()) { + case R.id.action_reload_board: + case R.id.action_reload_tablet_board: + boardFragment.reload(); + return true; + case R.id.action_reload_thread: + case R.id.action_reload_tablet_thread: + threadFragment.reload(); + return true; + case R.id.action_reply: + if (threadPane.isOpen()) { + boardFragment.openReply(); + } else { + threadFragment.openReply(); + } + return true; + case R.id.action_reply_board: + boardFragment.openReply(); + + return true; + case R.id.action_reply_thread: + threadFragment.openReply(); + + return true; + case R.id.action_pin: + if (threadFragment.hasLoader()) { + Loader loader = threadFragment.getLoader(); + if (loader.getCachedPosts().size() > 0) { + ChanApplication.getWatchManager().addPin(loader.getLoadable(), loader.getCachedPosts().get(0)); + pinDrawer.openDrawer(pinDrawerView); + } + } + + return true; + case R.id.action_open_browser: + openInBrowser(); + + return true; + case R.id.action_board_view_mode_grid: + if (!ChanPreferences.getBoardViewMode().equals("grid")) { + ChanPreferences.setBoardViewMode("grid"); + setBoardFragmentViewMode(); + startLoadingBoard(boardLoadable); + } + return true; + case R.id.action_board_view_mode_list: + if (!ChanPreferences.getBoardViewMode().equals("list")) { + ChanPreferences.setBoardViewMode("list"); + setBoardFragmentViewMode(); + startLoadingBoard(boardLoadable); + } + return true; + case R.id.action_search: + if (threadPane.isOpen()) { + boardFragment.startFiltering(); + } else { + threadFragment.startFiltering(); + } + return true; + case R.id.action_search_board: + boardFragment.startFiltering(); + return true; + case R.id.action_search_thread: + threadFragment.startFiltering(); + return true; + case android.R.id.home: + threadPane.openPane(); + + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Override + public void onItemSelected(final AdapterView parent, final View view, final int position, final long id) { + if (ignoreNextOnItemSelected) { + Logger.d(TAG, "Ignoring onItemSelected"); + ignoreNextOnItemSelected = false; + return; + } + + spinnerAdapter.onItemSelected(position); + } + + private void startLoadingBoard(Loadable loadable) { + if (loadable.mode == Loadable.Mode.INVALID) + return; + + boardLoadable = loadable; + + if (ChanPreferences.getBoardMode().equals("catalog")) { + boardLoadable.mode = Loadable.Mode.CATALOG; + } else if (ChanPreferences.getBoardMode().equals("pages")) { + boardLoadable.mode = Loadable.Mode.BOARD; + } + + // Force catalog mode when using grid + if (boardFragment.getViewMode() == ThreadManager.ViewMode.GRID) { + boardLoadable.mode = Loadable.Mode.CATALOG; + } + + boardFragment.bindLoadable(boardLoadable); + boardFragment.requestData(); + + updateActionBarState(); + } + + private void startLoadingThread(Loadable loadable) { + if (loadable.mode == Loadable.Mode.INVALID) + return; + + Pin pin = ChanApplication.getWatchManager().findPinByLoadable(loadable); + if (pin != null) { + // Use the loadable from the pin. + // This way we can store the listview position in the pin loadable, + // and not in a separate loadable instance. + loadable = pin.loadable; + } + + threadLoadable = loadable; + + threadFragment.bindLoadable(loadable); + threadFragment.requestData(); + + threadPane.closePane(); + + updateActionBarState(); + } + + /** + * Handle opening from an external url. + * + * @param startUri + */ + private void handleIntentURI(Uri startUri) { + Logger.d(TAG, "Opening " + startUri.getPath()); + + List parts = startUri.getPathSegments(); + + if (parts.size() == 1) { + // Board mode + String rawBoard = parts.get(0); + if (ChanApplication.getBoardManager().getBoardExists(rawBoard)) { + startLoadingBoard(new Loadable(rawBoard)); + } else { + handleIntentURIFallback(startUri.toString()); + } + } else if (parts.size() >= 3) { + // Thread mode + String rawBoard = parts.get(0); + int no = -1; + + try { + no = Integer.parseInt(parts.get(2)); + } catch (NumberFormatException e) { + } + + int post = -1; + String fragment = startUri.getFragment(); + if (fragment != null) { + int index = fragment.indexOf("p"); + if (index >= 0) { + try { + post = Integer.parseInt(fragment.substring(index + 1)); + } catch (NumberFormatException e) { + } + } + } + + if (no >= 0 && ChanApplication.getBoardManager().getBoardExists(rawBoard)) { + startLoadingThread(new Loadable(rawBoard, no)); + if (post >= 0) { + threadFragment.highlightPost(post); + } + } else { + handleIntentURIFallback(startUri.toString()); + } + } else { + showUrlOpenPicker(startUri.toString()); + } + } + + private void handleIntentURIFallback(final String url) { + new AlertDialog.Builder(this).setTitle(R.string.open_unknown_title).setMessage(R.string.open_unknown) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + showUrlOpenPicker(url); + } + }).setCancelable(false).create().show(); + } + + private void setBoardFragmentViewMode() { + if (ChanPreferences.getBoardViewMode().equals("list")) { + boardFragment.setViewMode(ThreadManager.ViewMode.LIST); + } else if (ChanPreferences.getBoardViewMode().equals("grid")) { + boardFragment.setViewMode(ThreadManager.ViewMode.GRID); + } + } + + private class BoardSpinnerAdapter extends BaseAdapter { + private static final int VIEW_TYPE_ITEM = 0; + private static final int VIEW_TYPE_ADD = 1; + + private Context context; + private Spinner spinner; + private List boards; + private int lastSelectedPosition = 0; + + public BoardSpinnerAdapter(Context context, Spinner spinner) { + this.context = context; + this.spinner = spinner; + setBoards(); + } + + public void setBoards() { + boards = ChanApplication.getBoardManager().getSavedBoards(); + } + + public void setBoard(String boardValue) { + for (int i = 0; i < boards.size(); i++) { + if (boards.get(i).value.equals(boardValue)) { + spinner.setSelection(i); + return; + } + } + } + + public void onItemSelected(int position) { + if (position >= 0 && position < boards.size()) { + Loadable board = new Loadable(boards.get(position).value); + + // onItemSelected is called after the view initializes, + // ignore if it's the same board + if (boardLoadable.equals(board)) + return; + + startLoadingBoard(board); + + lastSelectedPosition = position; + } else { + startActivity(new Intent(context, BoardEditor.class)); + spinner.setSelection(lastSelectedPosition); + } + } + + @Override + public int getCount() { + return boards.size() + 1; + } + + @Override + public int getViewTypeCount() { + return 2; + } + + @Override + public int getItemViewType(int position) { + return position == getCount() - 1 ? VIEW_TYPE_ADD : VIEW_TYPE_ITEM; + } + + @Override + public long getItemId(final int position) { + return position; + } + + @Override + public String getItem(final int position) { + switch (getItemViewType(position)) { + case VIEW_TYPE_ITEM: + return boards.get(position).key; + case VIEW_TYPE_ADD: + return context.getString(R.string.board_select_add); + default: + return ""; + } + } + + @Override + public View getView(final int position, View convertView, final ViewGroup parent) { + switch (getItemViewType(position)) { + case VIEW_TYPE_ITEM: { + if (convertView == null || (Integer) convertView.getTag() != VIEW_TYPE_ITEM) { + convertView = LayoutInflater.from(context).inflate(R.layout.board_select_spinner, null); + convertView.setTag(VIEW_TYPE_ITEM); + } + + TextView textView = (TextView) convertView; + textView.setText(getItem(position)); + return textView; + } + case VIEW_TYPE_ADD: { + if (convertView == null || (Integer) convertView.getTag() != VIEW_TYPE_ADD) { + convertView = LayoutInflater.from(context).inflate(R.layout.board_select_add, null); + convertView.setTag(VIEW_TYPE_ADD); + } + + TextView textView = (TextView) convertView; + textView.setText(getItem(position)); + return textView; + } + } + + return null; + } + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java b/Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java index 49b21361..dd3de266 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java @@ -33,7 +33,7 @@ import org.floens.chan.core.manager.WatchManager; import org.floens.chan.core.model.Pin; import org.floens.chan.core.model.Post; import org.floens.chan.core.watch.PinWatcher; -import org.floens.chan.ui.activity.BoardActivity; +import org.floens.chan.ui.activity.ChanActivity; import org.floens.chan.utils.Utils; import java.util.ArrayList; @@ -213,7 +213,7 @@ public class WatchNotifier extends Service { @SuppressWarnings("deprecation") private Notification getNotificationFor(String tickerText, String contentTitle, String contentText, int contentNumber, List expandedLines, boolean light, boolean makeSound, Pin target) { - Intent intent = new Intent(this, BoardActivity.class); + Intent intent = new Intent(this, ChanActivity.class); intent.setAction(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_LAUNCHER); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK |