diff --git a/res/layout/cats_fragment.xml b/res/layout/cats_fragment.xml new file mode 100644 index 00000000..4101e5c8 --- /dev/null +++ b/res/layout/cats_fragment.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/res/layout/main_small.xml b/res/layout/main_small.xml index 7fd46add..0215f270 100644 --- a/res/layout/main_small.xml +++ b/res/layout/main_small.xml @@ -1,30 +1,56 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index ed26b0df..0f3b089f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -55,4 +55,5 @@ Showing ads to you supports the project URL of your tt-rss installation directory, e.g. http://site.com/tt-rss/ Download and display feed icons + Enable feed categories diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 190a706f..dff4afdf 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -24,8 +24,9 @@ android:entries="@array/pref_theme_names" android:entryValues="@array/pref_theme_values" android:summary="@string/pref_theme_long"/> - - + + + diff --git a/src/org/fox/ttrss/ArticleFragment.java b/src/org/fox/ttrss/ArticleFragment.java index 32664fff..9fc2619b 100644 --- a/src/org/fox/ttrss/ArticleFragment.java +++ b/src/org/fox/ttrss/ArticleFragment.java @@ -12,7 +12,6 @@ import android.preference.PreferenceManager; import android.support.v4.app.Fragment; import android.text.Html; import android.text.method.LinkMovementMethod; -import android.util.Log; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.LayoutInflater; diff --git a/src/org/fox/ttrss/FeedCategoriesFragment.java b/src/org/fox/ttrss/FeedCategoriesFragment.java new file mode 100644 index 00000000..8c1cd9b5 --- /dev/null +++ b/src/org/fox/ttrss/FeedCategoriesFragment.java @@ -0,0 +1,316 @@ +package org.fox.ttrss; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; + +public class FeedCategoriesFragment extends Fragment implements OnItemClickListener, OnSharedPreferenceChangeListener { + @SuppressWarnings("unused") + private final String TAG = this.getClass().getSimpleName(); + private SharedPreferences m_prefs; + private FeedCategoryListAdapter m_adapter; + private FeedCategoryList m_cats = new FeedCategoryList(); + private int m_selectedCatId; + private OnCatSelectedListener m_catSelectedListener; + + public interface OnCatSelectedListener { + public void onCatSelected(FeedCategory cat); + } + + class CatUnreadComparator implements Comparator { + @Override + public int compare(FeedCategory a, FeedCategory b) { + if (a.unread != b.unread) + return b.unread - a.unread; + else + return a.title.compareTo(b.title); + } + } + + + class CatTitleComparator implements Comparator { + + @Override + public int compare(FeedCategory a, FeedCategory b) { + if (a.id >= 0 && b.id >= 0) + return a.title.compareTo(b.title); + else + return a.id - b.id; + } + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + if (savedInstanceState != null) { + m_selectedCatId = savedInstanceState.getInt("selectedCatId"); + m_cats = savedInstanceState.getParcelable("cats"); + } + + View view = inflater.inflate(R.layout.cats_fragment, container, false); + + ListView list = (ListView)view.findViewById(R.id.feeds); + m_adapter = new FeedCategoryListAdapter(getActivity(), R.layout.feeds_row, (ArrayList)m_cats); + list.setAdapter(m_adapter); + list.setOnItemClickListener(this); + + if (m_cats == null || m_cats.size() == 0) + refresh(false); + else + view.findViewById(R.id.loading_progress).setVisibility(View.GONE); + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + m_catSelectedListener = (OnCatSelectedListener)activity; + + m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext()); + m_prefs.registerOnSharedPreferenceChangeListener(this); + + } + + @Override + public void onSaveInstanceState (Bundle out) { + super.onSaveInstanceState(out); + + out.putInt("selectedCatId", m_selectedCatId); + out.putParcelable("cats", m_cats); + } + + public void setLoadingStatus(int status, boolean showProgress) { + if (getView() != null) { + TextView tv = (TextView)getView().findViewById(R.id.loading_message); + + if (tv != null) { + tv.setText(status); + } + + View pb = getView().findViewById(R.id.loading_progress); + + if (pb != null) { + pb.setVisibility(showProgress ? View.VISIBLE : View.GONE); + } + } + } + + @SuppressWarnings("unchecked") + public void refresh(boolean background) { + CatsRequest req = new CatsRequest(getActivity().getApplicationContext()); + + final String sessionId = ((MainActivity)getActivity()).getSessionId(); + final boolean unreadOnly = ((MainActivity)getActivity()).getUnreadOnly(); + + if (sessionId != null) { + + if (!background) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + setLoadingStatus(R.string.blank, true); + } + }); + } + + @SuppressWarnings("serial") + HashMap map = new HashMap() { + { + put("op", "getCategories"); + put("sid", sessionId); + if (unreadOnly) { + put("unread_only", String.valueOf(unreadOnly)); + } + } + }; + + req.execute(map); + + } + } + + private class CatsRequest extends ApiRequest { + + public CatsRequest(Context context) { + super(context); + } + + protected void onPostExecute(JsonElement result) { + if (result != null) { + try { + JsonObject rv = result.getAsJsonObject(); + + Gson gson = new Gson(); + + int status = rv.get("status").getAsInt(); + + if (status == 0) { + JsonArray content = rv.get("content").getAsJsonArray(); + if (content != null) { + Type listType = new TypeToken>() {}.getType(); + final List cats = gson.fromJson(content, listType); + + m_cats.clear(); + + for (FeedCategory c : cats) + m_cats.add(c); + + sortCats(); + + if (m_cats.size() == 0) + setLoadingStatus(R.string.error_no_feeds, false); + else + setLoadingStatus(R.string.blank, false); + + } + } else { + MainActivity activity = (MainActivity)getActivity(); + activity.login(); + } + } catch (Exception e) { + e.printStackTrace(); + setLoadingStatus(R.string.error_invalid_object, false); + // report invalid object received + } + } else { + // report null object received, unless we've been awakened from sleep right in the right time + // so that current request failed + if (m_cats.size() == 0) setLoadingStatus(R.string.error_no_data, false); + } + + return; + } + } + + public void sortCats() { + Comparator cmp; + + if (m_prefs.getBoolean("sort_feeds_by_unread", false)) { + cmp = new CatUnreadComparator(); + } else { + cmp = new CatTitleComparator(); + } + + Collections.sort(m_cats, cmp); + m_adapter.notifyDataSetInvalidated(); + + } + + private class FeedCategoryListAdapter extends ArrayAdapter { + private ArrayList items; + + public static final int VIEW_NORMAL = 0; + public static final int VIEW_SELECTED = 1; + + public static final int VIEW_COUNT = VIEW_SELECTED+1; + + public FeedCategoryListAdapter(Context context, int textViewResourceId, ArrayList items) { + super(context, textViewResourceId, items); + this.items = items; + } + + public int getViewTypeCount() { + return VIEW_COUNT; + } + + @Override + public int getItemViewType(int position) { + FeedCategory cat = items.get(position); + + if (cat.id == m_selectedCatId) { + return VIEW_SELECTED; + } else { + return VIEW_NORMAL; + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View v = convertView; + + FeedCategory cat = items.get(position); + + if (v == null) { + int layoutId = R.layout.feeds_row; + + switch (getItemViewType(position)) { + case VIEW_SELECTED: + layoutId = R.layout.feeds_row_selected; + break; + } + + LayoutInflater vi = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + v = vi.inflate(layoutId, null); + + } + + TextView tt = (TextView) v.findViewById(R.id.title); + + if (tt != null) { + tt.setText(cat.title); + } + + TextView tu = (TextView) v.findViewById(R.id.unread_counter); + + if (tu != null) { + tu.setText(String.valueOf(cat.unread)); + tu.setVisibility((cat.unread > 0) ? View.VISIBLE : View.INVISIBLE); + } + + ImageView icon = (ImageView)v.findViewById(R.id.icon); + + if (icon != null) { + icon.setImageResource(cat.unread > 0 ? R.drawable.ic_rss : R.drawable.ic_rss_bw); + } + + return v; + } + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + // TODO Auto-generated method stub + + } + + @Override + public void onItemClick(AdapterView av, View view, int position, long id) { + ListView list = (ListView)av; + + if (list != null) { + FeedCategory cat = (FeedCategory)list.getItemAtPosition(position); + m_catSelectedListener.onCatSelected(cat); + m_selectedCatId = cat.id; + m_adapter.notifyDataSetChanged(); + } + } +} diff --git a/src/org/fox/ttrss/FeedsFragment.java b/src/org/fox/ttrss/FeedsFragment.java index 06d48fbe..c3f1f01e 100644 --- a/src/org/fox/ttrss/FeedsFragment.java +++ b/src/org/fox/ttrss/FeedsFragment.java @@ -1,11 +1,9 @@ package org.fox.ttrss; import java.io.BufferedInputStream; -import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; -import java.io.InputStreamReader; import java.lang.reflect.Type; import java.net.MalformedURLException; import java.net.URL; @@ -20,11 +18,9 @@ import org.apache.http.HttpResponse; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.params.BasicHttpParams; @@ -56,11 +52,9 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import com.google.gson.reflect.TypeToken; public class FeedsFragment extends Fragment implements OnItemClickListener, OnSharedPreferenceChangeListener { - @SuppressWarnings("unused") private final String TAG = this.getClass().getSimpleName(); private SharedPreferences m_prefs; private FeedListAdapter m_adapter; @@ -166,6 +160,10 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh final String sessionId = ((MainActivity)getActivity()).getSessionId(); final boolean unreadOnly = ((MainActivity)getActivity()).getUnreadOnly(); + FeedCategory cat = ((MainActivity)getActivity()).getActiveCategory(); + + final int catId = (cat != null) ? cat.id : -4; + if (sessionId != null) { if (!background) { @@ -181,7 +179,7 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh { put("op", "getFeeds"); put("sid", sessionId); - put("cat_id", "-4"); + put("cat_id", String.valueOf(catId)); if (unreadOnly) { put("unread_only", String.valueOf(unreadOnly)); } diff --git a/src/org/fox/ttrss/MainActivity.java b/src/org/fox/ttrss/MainActivity.java index 763e1fa7..511873b1 100644 --- a/src/org/fox/ttrss/MainActivity.java +++ b/src/org/fox/ttrss/MainActivity.java @@ -27,7 +27,7 @@ import android.widget.TextView; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -public class MainActivity extends FragmentActivity implements FeedsFragment.OnFeedSelectedListener, ArticleOps { +public class MainActivity extends FragmentActivity implements FeedsFragment.OnFeedSelectedListener, ArticleOps, FeedCategoriesFragment.OnCatSelectedListener { private final String TAG = this.getClass().getSimpleName(); private SharedPreferences m_prefs; @@ -35,6 +35,7 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe private String m_sessionId; private Article m_selectedArticle; private Feed m_activeFeed; + private FeedCategory m_activeCategory; private Timer m_refreshTimer; private RefreshTask m_refreshTask; private Menu m_menu; @@ -43,6 +44,7 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe private boolean m_unreadArticlesOnly = true; private boolean m_canLoadMore = true; private boolean m_compatMode = false; + private boolean m_enableCats = false; public void updateHeadlines() { HeadlinesFragment frag = (HeadlinesFragment)getSupportFragmentManager().findFragmentById(R.id.headlines_fragment); @@ -106,7 +108,10 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe @Override public void run() { - refreshFeeds(); + if (!m_enableCats || m_activeCategory != null) + refreshFeeds(); + else + refreshCategories(); } } @@ -120,10 +125,23 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe } } + public synchronized void refreshCategories() { + FeedCategoriesFragment frag = (FeedCategoriesFragment) getSupportFragmentManager().findFragmentById(R.id.cats_fragment); + + Log.d(TAG, "Refreshing categories..."); + + if (frag != null) { + frag.refresh(true); + } + } public void setUnreadOnly(boolean unread) { m_unreadOnly = unread; - refreshFeeds(); + + if (!m_enableCats || m_activeCategory != null ) + refreshFeeds(); + else + refreshCategories(); } public boolean getUnreadOnly() { @@ -175,8 +193,11 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe m_selectedArticle = savedInstanceState.getParcelable("selectedArticle"); m_unreadArticlesOnly = savedInstanceState.getBoolean("unreadArticlesOnly"); m_canLoadMore = savedInstanceState.getBoolean("canLoadMore"); + m_activeCategory = savedInstanceState.getParcelable("activeCategory"); } + m_enableCats = m_prefs.getBoolean("enable_cats", false); + Display display = getWindowManager().getDefaultDisplay(); int orientation = display.getOrientation(); int minWidth = orientation % 2 == 0 ? 1024 : 600; @@ -205,13 +226,22 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe if (m_smallScreenMode) { if (m_selectedArticle != null) { findViewById(R.id.feeds_fragment).setVisibility(View.GONE); + findViewById(R.id.cats_fragment).setVisibility(View.GONE); findViewById(R.id.headlines_fragment).setVisibility(View.GONE); } else if (m_activeFeed != null) { findViewById(R.id.feeds_fragment).setVisibility(View.GONE); findViewById(R.id.article_fragment).setVisibility(View.GONE); + findViewById(R.id.cats_fragment).setVisibility(View.GONE); } else { findViewById(R.id.headlines_fragment).setVisibility(View.GONE); - findViewById(R.id.article_fragment).setVisibility(View.GONE); + //findViewById(R.id.article_fragment).setVisibility(View.GONE); + + if (m_enableCats && m_activeCategory == null) { + findViewById(R.id.feeds_fragment).setVisibility(View.GONE); + findViewById(R.id.cats_fragment).setVisibility(View.VISIBLE); + } else { + findViewById(R.id.cats_fragment).setVisibility(View.GONE); + } } } else { if (m_selectedArticle == null) @@ -252,13 +282,17 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe out.putParcelable("selectedArticle", m_selectedArticle); out.putBoolean("unreadArticlesOnly", m_unreadArticlesOnly); out.putBoolean("canLoadMore", m_canLoadMore); + out.putParcelable("activeCategory", m_activeCategory); } @Override public void onResume() { super.onResume(); - if (!m_prefs.getString("theme", "THEME_DARK").equals(m_themeName)) { + boolean needRefresh = !m_prefs.getString("theme", "THEME_DARK").equals(m_themeName) || + m_prefs.getBoolean("enable_cats", false) != m_enableCats; + + if (needRefresh) { Intent refresh = new Intent(this, MainActivity.class); startActivity(refresh); finish(); @@ -334,6 +368,21 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe m_activeFeed = null; initMainMenu(); + refreshFeeds(); + + } else if (m_activeCategory != null) { + if (m_compatMode) { + findViewById(R.id.main).setAnimation(AnimationUtils.loadAnimation(this, R.anim.slide_right)); + } + + findViewById(R.id.feeds_fragment).setVisibility(View.GONE); + findViewById(R.id.cats_fragment).setVisibility(View.VISIBLE); + + m_activeCategory = null; + + initMainMenu(); + refreshCategories(); + } else { finish(); } @@ -450,6 +499,7 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe initMainMenu(); refreshFeeds(); + } public void setCanLoadMore(boolean canLoadMore) { @@ -554,10 +604,16 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe setLoadingStatus(R.string.loading_message, true); - FeedsFragment frag = new FeedsFragment(); - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - ft.replace(R.id.feeds_fragment, frag); + + if (m_enableCats) { + FeedCategoriesFragment frag = new FeedCategoriesFragment(); + ft.replace(R.id.cats_fragment, frag); + } else { + FeedsFragment frag = new FeedsFragment(); + ft.replace(R.id.feeds_fragment, frag); + } + ft.commit(); loginSuccess(); @@ -625,6 +681,23 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe } } + public void viewCategory(FeedCategory cat) { + m_activeCategory = cat; + + initMainMenu(); + + if (m_smallScreenMode) { + findViewById(R.id.cats_fragment).setVisibility(View.GONE); + findViewById(R.id.feeds_fragment).setVisibility(View.VISIBLE); + } + + FeedsFragment frag = new FeedsFragment(); + + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + ft.replace(R.id.feeds_fragment, frag); + ft.commit(); + } + public void openArticle(Article article, int compatAnimation) { m_selectedArticle = article; @@ -668,6 +741,10 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe return m_activeFeed; } + public FeedCategory getActiveCategory() { + return m_activeCategory; + } + public void logout() { if (m_refreshTask != null) { m_refreshTask.cancel(); @@ -749,4 +826,10 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe } return null; } + + @Override + public void onCatSelected(FeedCategory cat) { + m_activeCategory = cat; + viewCategory(cat); + } } \ No newline at end of file