diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..dfe0770
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2b75303
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..30aa626
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..7ac24c7
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..7bfef59
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4d40160
--- /dev/null
+++ b/README.md
@@ -0,0 +1,174 @@
+# RSS
+
+## util类
+
+### RSSUtil(RSS解析)
+
+* 入参
+
+ RSSUtil rssUtil=new RSSUtil("url");
+
+ 或
+
+ RSSUtil rssUtil=new RSSUtil();
+
+ rssUtil.setRssUrl("url");
+
+* 获取参数
+
+ 1. 文章数:rssUtil.getFeedSize()
+ 2. RSS标题:rssUtil.getTitleName()
+ 3. RSS描述: rssUtil.getDescription()
+ 4. 文章项:rssUtil.getRssItemBeans()
+
+## 数据库类
+
+### SSRUrlDAL(RSS源表)
+
+**SSRUrlDAL dal=new SSRUrlDALImpl();**
+
+1. 查询所有
+```java
+/**
+ * 查询表ssr_url全部内容
+ * @param rssUrlArrayList 必须保证传进 Adapter 的数据 List 是同一个 List
+ * @return rssUrlArrayList
+ */
+ArrayList getAllData(ArrayList rssUrlArrayList);
+```
+
+2. 获取所有已订阅内容
+
+```java
+ArrayList getSubscribe(ArrayList rssUrls);
+```
+
+2. 模糊查询
+```java
+/**
+ * 模糊查询
+ * @param rssUrlArrayList 必须保证传进 Adapter 的数据 List 是同一个 List
+ * @param query 输入的字符串
+ * @return rssUrlArrayList
+ */
+ArrayList getQueryData(ArrayList rssUrlArrayList, String query);
+```
+3. 查询ssr_url某一项的内容
+```java
+/**
+ * 查询ssr_url某一项的内容
+ * @param id 选中项的id
+ * @return 一项item
+ */
+RSSUrl getOneData(Integer id);
+```
+4. 添加
+```java
+/**
+ * 添加一个rss源
+ * @param url rss url
+ * @param groupName 组名,default=""
+ * @param status 订阅状态,主动添加时default=SUBSCRIBED
+ * @return 是否添加成功
+ */
+long insertOneData(String url, String groupName, SubscribeStatus status);
+```
+5. 添加
+```java
+/**
+ * 删除某项
+ * @param id 选中项的id
+ * @return 一项item
+ */
+int deleteOneData(Integer id);
+```
+6. 更改标题名
+```java
+/**
+ * 更改标题名
+ * @param id 选中项的id
+ * @param name 新的名字
+ * @return 是否成功
+ */
+int updateName(Integer id, String name);
+```
+7. 更改组名
+```java
+/**
+ * 更改组名
+ * @param id 选中项的id
+ * @param groupName 新的组名
+ * @return 是否成功
+ */
+int updateGroupName(Integer id, String groupName);
+```
+8. 更改订阅状态
+```java
+/**
+ * 更改订阅状态
+ * @param id 选中项的id
+ * @param status 订阅状态
+ * @return
+ */
+int updateSubscribeStatus(Integer id,SubscribeStatus status);
+```
+
+### FavorRSSItemDAL(收藏表项)
+
+**FavorRSSItemDAL dal=new FavorRSSItemDALImpl()**
+
+1. 查询表favor_ssr_item全部内容
+```
+ /**
+ * 查询表favor_ssr_item全部内容
+ * @param favorRSSItems 必须保证传进 Adapter 的数据 List 是同一个 List
+ * @return rssUrlArrayList
+ */
+ ArrayList getAllData(ArrayList favorRSSItems);
+```
+2. 模糊查询
+```
+ /**
+ * 模糊查询|不实现
+ * @param rssUrlArrayList 必须保证传进 Adapter 的数据 List 是同一个 List
+ * @param query 输入的字符串
+ * @return rssUrlArrayList
+ */
+ ArrayList getQueryData(ArrayList rssUrlArrayList, String query);
+```
+3. 查询favor_ssr_item某一项的内容
+```
+ /**
+ * 查询favor_ssr_item某一项的内容
+ * @param id 选中项的id
+ * @return 一项item
+ */
+ FavorRSSItem getOneData(Integer id);
+```
+4. 收藏
+```
+ /**
+ * 收藏
+ * @param url 该item的url
+ * @param titleName 标题
+ * @param description 描述
+ * @return 是否添加成功
+ */
+ long insertOneData(String url,String titleName,String description);
+```
+5. 取消收藏
+```
+ /**
+ * 取消收藏
+ * @param id 选中项的id
+ * @return 一项item
+ */
+ int deleteOneData(Integer id);
+```
+
+## 请注意
+
+1. RSS解析的值可能带有html标签;
+2. 更换gradle版本;
+3. 注意bean和数据库类参数的不同;
+4. 注意RSS解析的时间格式和数据库中的时间格式。
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..fb1443f
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,36 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 28
+ defaultConfig {
+ applicationId "xyz.somelou.rss"
+ minSdkVersion 17
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation 'com.android.support:appcompat-v7:28.0.0'
+ implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+ implementation 'com.android.support:support-v4:28.0.0'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+ implementation 'com.android.support:design:28.0.0'
+
+ /**
+ * rome&jdom for rss
+ */
+ implementation 'com.rometools:rome:1.7.0'
+
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/app/src/androidTest/java/xyz/somelou/rss/ExampleInstrumentedTest.java b/app/src/androidTest/java/xyz/somelou/rss/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..c24fa2a
--- /dev/null
+++ b/app/src/androidTest/java/xyz/somelou/rss/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package xyz.somelou.rss;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("xyz.somelou.rss", appContext.getPackageName());
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..52ac7a9
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/xyz/somelou/rss/MainActivity.java b/app/src/main/java/xyz/somelou/rss/MainActivity.java
new file mode 100644
index 0000000..76878a4
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/MainActivity.java
@@ -0,0 +1,93 @@
+package xyz.somelou.rss;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.design.widget.BottomNavigationView;
+import android.support.v4.view.ViewPager;
+import android.support.v7.app.AppCompatActivity;
+import android.view.MenuItem;
+
+import xyz.somelou.rss.adapter.ViewPagerAdapter;
+import xyz.somelou.rss.find.FindFragment;
+import xyz.somelou.rss.my.MyFragment;
+import xyz.somelou.rss.subscribe.SubscribeFragment;
+import xyz.somelou.rss.thread.PreDatabaseThread;
+
+public class MainActivity extends AppCompatActivity {
+
+ private ViewPager viewPager;
+ private MenuItem menuItem;
+ private BottomNavigationView bottomNavigationView;
+
+ // 预加载数据库
+ PreDatabaseThread preDatabaseThread;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // 测试
+ preDatabaseThread=new PreDatabaseThread(getApplicationContext());
+ new Thread(preDatabaseThread).start();
+
+ setContentView(R.layout.activity_main);
+
+ viewPager=findViewById(R.id.viewpager);
+ bottomNavigationView = findViewById(R.id.bottom_navigation);
+ //新版本的sdk好像已经解决了这个问题
+ //默认 >3 的选中效果会影响ViewPager的滑动切换时的效果,故利用反射去掉
+ //BottomNavigationViewHelper.disableShiftMode(bottomNavigationView);
+ bottomNavigationView.setOnNavigationItemSelectedListener(
+ new BottomNavigationView.OnNavigationItemSelectedListener() {
+ @Override
+ public boolean onNavigationItemSelected(@NonNull MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.item_list:
+ viewPager.setCurrentItem(0);
+ break;
+ case R.id.item_add:
+ viewPager.setCurrentItem(1);
+ break;
+ case R.id.item_find:
+ viewPager.setCurrentItem(2);
+ break;
+ }
+ return false;
+ }
+ });
+
+ viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+
+ if (menuItem != null) {
+ menuItem.setChecked(false);
+ } else {
+ bottomNavigationView.getMenu().getItem(0).setChecked(false);
+ }
+ menuItem = bottomNavigationView.getMenu().getItem(position);
+ menuItem.setChecked(true);
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ }
+ });
+
+ setupViewPager(viewPager);
+ }
+
+ private void setupViewPager(ViewPager viewPager) {
+ ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
+
+ adapter.addFragment(SubscribeFragment.newInstance());
+ adapter.addFragment(FindFragment.newInstance("1","2"));
+ adapter.addFragment(MyFragment.newInstance("1","2"));
+ viewPager.setAdapter(adapter);
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/adapter/RSSChannelAdapter.java b/app/src/main/java/xyz/somelou/rss/adapter/RSSChannelAdapter.java
new file mode 100644
index 0000000..667ab6b
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/adapter/RSSChannelAdapter.java
@@ -0,0 +1,85 @@
+package xyz.somelou.rss.adapter;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.w3c.dom.Text;
+
+import java.util.ArrayList;
+
+import xyz.somelou.rss.R;
+import xyz.somelou.rss.bean.RSSItemBean;
+
+/**
+ * Created by ${Marrrrrco} on 2019/7/2.
+ */
+
+public class RSSChannelAdapter extends BaseAdapter {
+ private ArrayList passages;
+ private Context context;
+
+ public class ViewHolder{
+ public TextView title;//文章标题
+ public TextView discription;//文章简述
+ public TextView author;//文章作者
+ public TextView date;//文章发行日期
+ public ImageView icon;//默认图标
+ }
+
+ public RSSChannelAdapter(){}
+
+ public RSSChannelAdapter(Context context,ArrayList list){
+ this.context=context;
+ passages=list;
+ }
+
+ @Override
+ public int getCount() {
+ return passages.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return passages.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ViewHolder vh;
+ if (convertView == null) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ //设置当前布局为item
+ convertView = inflater.inflate(R.layout.item_channel, null);
+
+ //绑定item的控件到vh上
+ vh = new ViewHolder();
+ vh.icon = convertView.findViewById(R.id.passage_icon);
+ vh.title = convertView.findViewById(R.id.passage_title);
+ vh.discription = convertView.findViewById(R.id.passage_discription);
+ vh.date = convertView.findViewById(R.id.passage_date);
+ vh.author = convertView.findViewById(R.id.passage_author);
+
+ convertView.setTag(vh);
+ } else {
+ vh =(ViewHolder)convertView.getTag();
+ }
+ //设置好控件的内容
+ vh.icon.setImageResource(R.mipmap.article);
+ vh.title.setText(passages.get(position).getTitle());
+ vh.discription.setText(passages.get(position).getDescription());
+ vh.date.setText(passages.get(position).getPubDate().toString());
+ vh.author.setText(passages.get(position).getAuthor());
+
+ return convertView;
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/adapter/RSSFindAdapter.java b/app/src/main/java/xyz/somelou/rss/adapter/RSSFindAdapter.java
new file mode 100644
index 0000000..5b89f2b
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/adapter/RSSFindAdapter.java
@@ -0,0 +1,180 @@
+package xyz.somelou.rss.adapter;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+
+import xyz.somelou.rss.R;
+import xyz.somelou.rss.bean.RSSUrl;
+import xyz.somelou.rss.db.impl.RSSUrlDALImpl;
+import xyz.somelou.rss.enums.SubscribeStatus;
+
+/**
+ * Created by ${Marrrrrco} on 2019/6/26.
+ */
+
+public class RSSFindAdapter extends BaseAdapter implements Filterable,View.OnClickListener{
+ public Context context;
+ private ArrayList finds;
+ private ArrayList bkfinds;//备用列表
+ private RSSFilter mfilter;
+ private SubClickListener clickListener;//为每个item的按钮设置自定义监听器
+
+ //定义一个订阅按钮的接口,用于回调点击按钮的方法
+ public interface SubClickListener{
+ public void subClick(View v);
+ }
+
+ RSSFindAdapter(){
+
+ }
+
+ public RSSFindAdapter(Context c,ArrayList list){
+ context=c;
+ finds=list;
+ bkfinds=list;
+ /*for (int i=0;i list,SubClickListener cl){
+ context=c;
+ finds=list;
+ bkfinds=new ArrayList<>();
+ bkfinds.addAll(list);
+ clickListener=cl;
+ }
+
+ public int getCount() {
+ return finds.size();
+ }
+ //刷新时还原
+ public ArrayList getOriginalData(){
+ return bkfinds;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return finds.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ViewHolder vh;
+ if (convertView == null) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ //设置当前布局为item
+ convertView = inflater.inflate(R.layout.item_find, null);
+
+ //绑定item的控件到vh上
+ vh = new ViewHolder();
+ vh.RSS_icon = convertView.findViewById(R.id.RSSIcon);
+ vh.RSS_link = convertView.findViewById(R.id.RSSLink);
+ vh.RSS_title = convertView.findViewById(R.id.RSSTitle);
+ vh.ar=convertView.findViewById(R.id.addORremove);
+
+ convertView.setTag(vh);
+ } else {
+ vh = (ViewHolder) convertView.getTag();
+ }
+ //设置好控件的内容
+ if (finds.get(position).getStatus()== SubscribeStatus.NO_SUBSCRIBE)
+ vh.ar.setBackgroundResource(android.R.drawable.checkbox_off_background);//设置未选中
+ else
+ vh.ar.setBackgroundResource(android.R.drawable.checkbox_on_background);//设置选中
+ vh.RSS_title.setText(finds.get(position).getName());//设置标题
+ vh.RSS_link.setText(finds.get(position).getUrl());//设置链接
+ vh.RSS_icon.setImageResource(android.R.drawable.ic_menu_compass);//设置默认图标
+ vh.ar.setOnClickListener(this);
+ vh.ar.setTag(position);
+ return convertView;
+ }
+
+ @Override
+ public Filter getFilter() {
+ if (mfilter == null)
+ mfilter = new RSSFilter();
+ return mfilter;
+ }
+
+ @Override
+ public void onClick(View v) {
+ clickListener.subClick(v);
+ }
+
+ //内部类用于保存item各个组件
+ class ViewHolder{
+
+ public ImageView RSS_icon;
+ public TextView RSS_title;
+ public TextView RSS_link;
+ public Button ar;
+ }
+ //过滤器
+ class RSSFilter extends Filter{
+
+ @Override
+ protected FilterResults performFiltering(CharSequence constraint) {
+ //用于定义过滤规则
+ FilterResults result = new FilterResults();
+ ArrayList templist;
+ templist=finds;
+ if (TextUtils.isEmpty(constraint)) {//当过滤的关键字为空的时候,我们则显示所有的数据
+
+
+ } else {//否则把符合条件的数据对象添加到集合中
+ RSSUrlDALImpl sql=new RSSUrlDALImpl(context);
+ //不能给templist赋值,否则会影响指向同一内存的finds的数据
+ templist.clear();
+ templist.addAll(sql.getQueryData(new ArrayList(),constraint.toString()));
+ //templist = ;
+ /* for (RSSUrl tempRSS : finds) {
+ //匹配RSS源的标题,组名
+ if (tempRSS.getName().contains(constraint) || tempRSS.getGroupName().
+ contains(constraint)
+ ) {
+ templist.add(tempRSS);
+ }
+ }*/
+ }
+ result.values = templist; //将得到的集合保存到FilterResults的value变量中
+ result.count = templist.size();//将集合的大小保存到FilterResults的count变量中
+ /*if (result.count>0)
+ System.out.println("过滤后:"+templist.get(0).getUrl()+", "+templist.get(0).getName());*/
+ return result;
+ }
+
+ @Override
+ protected void publishResults(CharSequence constraint, FilterResults results) {
+
+ finds = (ArrayList) results.values;
+ for (int i=0;i subs;
+
+ RSSSubscribeAdapter() {
+
+ }
+
+ public RSSSubscribeAdapter(Context c, ArrayList list) {
+ mcontext = c;
+ subs = list;
+ }
+
+ @Override
+ public int getCount() {
+ return subs.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return subs.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ViewHolder vh;
+ if (convertView == null) {
+ LayoutInflater inflater = LayoutInflater.from(mcontext);
+ //设置当前布局为item
+ convertView = inflater.inflate(R.layout.item_subscribe, null);
+
+ //绑定item的控件到vh上
+ vh = new ViewHolder();
+ vh.sub_icon = convertView.findViewById(R.id.SubIcon);
+ vh.sub_group = convertView.findViewById(R.id.SubGroup);
+ vh.sub_title = convertView.findViewById(R.id.SubTitle);
+ vh.sub_count = convertView.findViewById(R.id.SubCount);
+ convertView.setTag(vh);
+ } else {
+ vh = (ViewHolder) convertView.getTag();
+ }
+ //设置好控件的内容
+ vh.sub_title.setText(subs.get(position).getName());//设置标题
+ vh.sub_group.setText(subs.get(position).getGroupName());//设置组别
+ vh.sub_icon.setImageResource(android.R.drawable.ic_input_get);//设置默认图标
+ vh.sub_count.setText(Integer.toString(subs.get(position).getCount()));//设置文章数量
+ return convertView;
+ }
+
+ //内部类用于保存item各个组件
+ class ViewHolder {
+
+ public ImageView sub_icon;
+ public TextView sub_title;
+ public TextView sub_group;
+ public TextView sub_count;
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/adapter/ViewPagerAdapter.java b/app/src/main/java/xyz/somelou/rss/adapter/ViewPagerAdapter.java
new file mode 100644
index 0000000..2970e1c
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/adapter/ViewPagerAdapter.java
@@ -0,0 +1,69 @@
+package xyz.somelou.rss.adapter;
+
+import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentPagerAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author somelou
+ * ViewPagerAdapter
+ */
+
+public class ViewPagerAdapter extends FragmentPagerAdapter {
+
+ private final List mFragmentList = new ArrayList<>();
+
+ private int mChildCount = 0;
+
+ public ViewPagerAdapter(FragmentManager manager) {
+ super(manager);
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ return mFragmentList.get(position);
+ }
+
+
+ @Override
+ public long getItemId(int position){
+ return super.getItemId(position);
+ }
+
+
+ @Override
+ public int getCount() {
+ return mFragmentList.size();
+ }
+
+ public void addFragment(Fragment fragment) {
+ mFragmentList.add(fragment);
+ }
+
+ // 可以删除这段代码看看,数据源更新而viewpager不更新的情况
+ @Override
+ public void notifyDataSetChanged() {
+ // 重写这个方法,取到子Fragment的数量,用于下面的判断,以执行多少次刷新
+ mChildCount = getCount();
+ super.notifyDataSetChanged();
+ }
+
+ @Override
+ public int getItemPosition(@NonNull Object object) {
+ if ( mChildCount > 0) {
+ // 这里利用判断执行若干次不缓存,刷新
+ mChildCount --;
+ // 返回这个是强制ViewPager不缓存,每次滑动都刷新视图
+ return POSITION_NONE;
+ }
+ // 这个则是缓存不刷新视图
+ return super.getItemPosition(object);
+ }
+
+
+
+}
diff --git a/app/src/main/java/xyz/somelou/rss/article/ArticleActivity.java b/app/src/main/java/xyz/somelou/rss/article/ArticleActivity.java
new file mode 100644
index 0000000..fc46b92
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/article/ArticleActivity.java
@@ -0,0 +1,211 @@
+package xyz.somelou.rss.article;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.telephony.SmsManager;
+import android.view.GestureDetector;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+import xyz.somelou.rss.R;
+import xyz.somelou.rss.bean.FavorRSSItem;
+import xyz.somelou.rss.bean.RSSItemBean;
+import xyz.somelou.rss.db.impl.FavorRSSItemDALImpl;
+import xyz.somelou.rss.utils.IntentUtil;
+import xyz.somelou.rss.utils.RSSUtil;
+
+public class ArticleActivity extends AppCompatActivity {
+ private Toolbar mToolbar;
+ private RSSItemBean mItemBean;
+ private FavorRSSItemDALImpl favorRSSItemDAL;
+ protected static final float FLIP_DISTANCE = 50;
+ SmsManager smsManager=SmsManager.getDefault();
+
+ TextView textView_title;
+ TextView textView_author;
+ TextView textView_date;
+ WebView textView_content;
+
+ private int articlePosition;
+ RSSUtil rssUtil;
+ GestureDetector mDetector;
+ ArrayList list;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ requestWindowFeature(Window.FEATURE_NO_TITLE);//隐藏标题栏
+ setContentView(R.layout.activity_article);
+ rssUtil = new RSSUtil(getIntent().getStringExtra("url"));
+ articlePosition=getIntent().getIntExtra("position",0);
+ list = (ArrayList) rssUtil.getRssItemBeans();
+ ArrayList dblist = new FavorRSSItemDALImpl(this).getAllData(new ArrayList());
+ if (dblist.size() < 1)
+ System.out.println("收藏数据库为空!");
+ else {
+ for (int i = 0; i < dblist.size(); i++)
+ System.out.println("当前为收藏列表第" + (i + 1) + "个,url为" + dblist.get(i).getItemUrl() + "收藏时间为" + dblist.get(i).getFavorTime());
+ }
+ getSupportActionBar().hide();
+ initViews();
+
+ }
+
+
+
+
+
+
+ /**
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_SEARCH) {
+ String urlstr = list.get(0).getUri();
+ textView_content.loadUrl(urlstr);
+ return true;
+ }
+ return false;
+ }
+ **/
+
+ private void initViews() {
+ textView_title = findViewById(R.id.article_title);
+ textView_date = findViewById(R.id.article_date);
+ textView_content = findViewById(R.id.article_content);
+ textView_author = findViewById(R.id.subscription_name);
+
+ textView_author.setText(list.get(articlePosition).getAuthor().toString());
+ //textView_content.setText(list.get(0).getDescription());
+ textView_date.setText(list.get(articlePosition).getPubDate().toString());
+ textView_title.setText(list.get(articlePosition).getTitle().toString());
+
+
+ //支持javascript
+ textView_content.getSettings().setJavaScriptEnabled(true);
+ // 设置可以支持缩放
+ textView_content.getSettings().setSupportZoom(true);
+ // 设置出现缩放工具
+ textView_content.getSettings().setBuiltInZoomControls(true);
+ //扩大比例的缩放
+ textView_content.getSettings().setUseWideViewPort(true);
+ //自适应屏幕
+ textView_content.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
+ textView_content.getSettings().setLoadWithOverviewMode(true);
+
+
+ //如果不设置WebViewClient,请求会跳转系统浏览器
+ textView_content.setWebViewClient(new WebViewClient() {
+
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ //该方法在Build.VERSION_CODES.LOLLIPOP以前有效,从Build.VERSION_CODES.LOLLIPOP起,建议使用shouldOverrideUrlLoading(WebView, WebResourceRequest)} instead
+ //返回false,意味着请求过程里,不管有多少次的跳转请求(即新的请求地址),均交给webView自己处理,这也是此方法的默认处理
+ //返回true,说明你自己想根据url,做新的跳转,比如在判断url符合条件的情况下,我想让webView加载http://ask.csdn.net/questions/178242
+
+ if (url.contains("sina.cn")){
+ view.loadUrl("http://ask.csdn.net/questions/178242");
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
+ {
+ //返回false,意味着请求过程里,不管有多少次的跳转请求(即新的请求地址),均交给webView自己处理,这也是此方法的默认处理
+ //返回true,说明你自己想根据url,做新的跳转,比如在判断url符合条件的情况下,我想让webView加载http://ask.csdn.net/questions/178242
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ if (request.getUrl().toString().contains("sina.cn")){
+ view.loadUrl("http://ask.csdn.net/questions/178242");
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ });
+
+ textView_content.loadUrl(list.get(articlePosition).getUri());
+ //textView_content.loadDataWithBaseURL(null, list.get(0).getUri().toString(), "text/html", "utf-8", null);
+
+ mToolbar = findViewById(R.id.toolbar);
+
+ mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ArticleActivity.this.finish();//返回
+ }
+ });
+
+
+
+
+ favorRSSItemDAL = new FavorRSSItemDALImpl(this);
+ //设置action menu
+ //填充menu
+ mToolbar.inflateMenu(R.menu.menu_article);
+ //设置点击事件
+ mToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_link:
+ Toast.makeText(ArticleActivity.this, "已跳转", Toast.LENGTH_SHORT).show();
+ IntentUtil.openUrl(ArticleActivity.this, list.get(articlePosition).getLink());
+ //System.out.println("测试: url--" + list.get(0).getUri() + " title--" + list.get(0).getTitle() + " discription--" + list.get(0).getDescription());
+ break;
+ case R.id.action_fav:
+ if (favorRSSItemDAL.isFavor(list.get(articlePosition).getUri()))
+ {
+ item.setIcon(R.drawable.ic_star_border_white_24dp);
+ favorRSSItemDAL.deleteOneData(list.get(articlePosition).getUri());
+ Toast.makeText(ArticleActivity.this, "取消收藏", Toast.LENGTH_SHORT).show();
+ } else {
+ favorRSSItemDAL.insertOneData(list.get(articlePosition).getUri(), list.get(articlePosition).getTitle(), list.get(articlePosition).getDescription());
+ item.setIcon(R.drawable.ic_star_white_24dp);
+ Toast.makeText(ArticleActivity.this, "已收藏", Toast.LENGTH_SHORT).show();
+ }
+ break;
+ case R.id.action_share:
+ //"smsto:xxx" xxx是可以指定联系人的
+ Uri smsToUri = Uri.parse("smsto:");
+
+ Intent intent = new Intent(Intent.ACTION_SENDTO, smsToUri);
+
+ String smsBody="我看到一篇好文章,推荐你也来看"+list.get(articlePosition).getUri();
+//"sms_body"必须一样,smsbody是发送短信内容content
+ intent.putExtra("sms_body", smsBody);
+
+ startActivity(intent);
+ break;
+ default:
+ break;
+ }
+ return false;
+ }
+ });
+
+ }
+
+
+
+}
+
+
+
diff --git a/app/src/main/java/xyz/somelou/rss/bean/FavorRSSItem.java b/app/src/main/java/xyz/somelou/rss/bean/FavorRSSItem.java
new file mode 100644
index 0000000..ac6fea5
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/bean/FavorRSSItem.java
@@ -0,0 +1,79 @@
+package xyz.somelou.rss.bean;
+
+/**
+ * @author somelou
+ * @description 收藏item
+ * @date 2019-06-25
+ */
+public class FavorRSSItem {
+
+ private int id;
+
+ // 此item的url
+ private String itemUrl;
+
+ private String titleName;
+
+ private String description;
+
+ // 收藏时间
+ private String favorTime;
+
+ public FavorRSSItem(){}
+
+ public FavorRSSItem(String itemUrl,String titleName,String description,String favorTime){
+ this.titleName =titleName;
+ this.itemUrl=itemUrl;
+ this.description=description;
+ this.favorTime=favorTime;
+ }
+
+ public FavorRSSItem(int id,String itemUrl,String titleName,String description,String favorTime){
+ this.id=id;
+ this.titleName =titleName;
+ this.description=description;
+ this.itemUrl=itemUrl;
+ this.favorTime=favorTime;
+ }
+
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getItemUrl() {
+ return itemUrl;
+ }
+
+ public void setItemUrl(String itemUrl) {
+ this.itemUrl = itemUrl;
+ }
+
+ public String getTitleName() {
+ return titleName;
+ }
+
+ public void setTitleName(String titleName) {
+ this.titleName = titleName;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getFavorTime() {
+ return favorTime;
+ }
+
+ public void setFavorTime(String favorTime) {
+ this.favorTime = favorTime;
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/bean/RSSItemBean.java b/app/src/main/java/xyz/somelou/rss/bean/RSSItemBean.java
new file mode 100644
index 0000000..25b9d15
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/bean/RSSItemBean.java
@@ -0,0 +1,146 @@
+package xyz.somelou.rss.bean;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import androidx.versionedparcelable.VersionedParcel;
+
+/**
+ * @author somelou
+ * @description RSS item
+ * @date 2019-06-25
+ */
+public class RSSItemBean implements Serializable{
+
+ // 标题
+ private String title;
+ // 发布者
+ private String author;
+
+ // 网站或栏目的URL地址
+ private String uri;
+
+ // 详情链接
+ private String link;
+
+ // 网站或栏目的简要介绍
+ private String description;
+ // 时间
+ private Date pubDate;
+ private String type;
+
+ public RSSItemBean(){
+ super();
+ }
+
+ public RSSItemBean(String t,String a,String u,String l,String d,Date date,String ty){
+ title=t;author=a;uri=u;link=l;description=d;pubDate=date;type=ty;
+ }
+/*
+ public RSSItemBean(String t,String a,String u,String l,String d,String date,String ty){
+ try {
+ title = t;
+ author = a;
+ uri = u;
+ link = l;
+ description = d;
+ type = ty;
+ pubDate = new SimpleDateFormat("E M dd HH:mm:ss z ").parse(date);
+ }
+ catch(ParseException e){
+ System.out.println("日期格式不对"+pubDate);
+ }
+ }
+
+ //重写两个Parcelable方法
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(title);
+ dest.writeString(author);
+ dest.writeString(uri);
+ dest.writeString(link);
+ dest.writeString(description);
+ dest.writeString(pubDate.toString());
+ dest.writeString(type);
+ }
+
+ public static final Parcelable.Creator CREATOR=new Creator(){
+
+ @Override
+ public RSSItemBean createFromParcel(Parcel source) {
+ return new RSSItemBean(source.readString(),source.readString(),source.readString(),
+ source.readString(),source.readString(),source.readString(),source.readString());
+ }
+
+ @Override
+ public RSSItemBean[] newArray(int size) {
+ return new RSSItemBean[size];
+ }
+ };*/
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public void setUri(String uri) {
+ this.uri = uri;
+ }
+
+ public String getLink() {
+ return link;
+ }
+
+ public void setLink(String link) {
+ this.link = link;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public Date getPubDate() {
+ return pubDate;
+ }
+
+ public void setPubDate(Date pubDate) {
+ this.pubDate = pubDate;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/bean/RSSUrl.java b/app/src/main/java/xyz/somelou/rss/bean/RSSUrl.java
new file mode 100644
index 0000000..02f52fc
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/bean/RSSUrl.java
@@ -0,0 +1,101 @@
+package xyz.somelou.rss.bean;
+
+import xyz.somelou.rss.enums.SubscribeStatus;
+
+/**
+ * @author somelou
+ * @description rss源
+ * @date 2019-06-25
+ */
+public class RSSUrl {
+
+ private int id;
+ private String url;
+ private String name;
+ private String groupName;
+ // 是否订阅
+ private SubscribeStatus status;
+ private int count;//文章数
+
+ public RSSUrl(){}
+
+ public RSSUrl(String url,String name,String groupName,String status){
+ this.url=url;
+ this.name=name;
+ this.groupName=groupName;
+ SubscribeStatus ss=SubscribeStatus.NO_SUBSCRIBE;
+ if(status.equals(SubscribeStatus.SUBSCRIBED.toString())){
+ ss=SubscribeStatus.SUBSCRIBED;
+ }
+ this.status=ss;
+ }
+
+ public RSSUrl(int id,String url,String name,String groupName,String status){
+ this.id=id;
+ this.url=url;
+ this.name=name;
+ this.groupName=groupName;
+ SubscribeStatus ss=SubscribeStatus.NO_SUBSCRIBE;
+ if(status.equals(SubscribeStatus.SUBSCRIBED.toString())){
+ ss=SubscribeStatus.SUBSCRIBED;
+ }
+ this.status=ss;
+ }
+ public RSSUrl(int id,String url,String name,String groupName,String status,int count){
+ this.id=id;
+ this.url=url;
+ this.name=name;
+ this.groupName=groupName;
+ SubscribeStatus ss=SubscribeStatus.NO_SUBSCRIBE;
+ if(status.equals(SubscribeStatus.SUBSCRIBED.toString())){
+ ss=SubscribeStatus.SUBSCRIBED;
+ }
+ this.status=ss;
+ this.count=count;
+ }
+
+
+ public int getId() {
+ return id;
+ }
+
+ public SubscribeStatus getStatus() {
+ return status;
+ }
+
+ public void setStatus(SubscribeStatus status) {
+ this.status = status;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getGroupName() {
+ return groupName;
+ }
+
+ public void setGroupName(String groupName) {
+ this.groupName = groupName;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public void setCount(int count) {
+ this.count = count;
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/db/DatabaseHelper.java b/app/src/main/java/xyz/somelou/rss/db/DatabaseHelper.java
new file mode 100644
index 0000000..4758955
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/db/DatabaseHelper.java
@@ -0,0 +1,79 @@
+package xyz.somelou.rss.db;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+/**
+ * @author somelou
+ * @description 数据库协助类
+ * @date 2019/6/25
+ */
+public class DatabaseHelper extends SQLiteOpenHelper {
+
+ private static String name = "rss.db";
+ private static int dbDefaultVersion = 1;
+
+ // 本来想从xml中直接获取table名,但会让onCreate()失效
+ // 表1
+ private final static String CREATE_SSR_URL_TABLE = "create table if not exists RSS_URL ("
+ + "url_id integer primary key autoincrement,"
+ + "name TEXT,"
+ + "url TEXT,"
+ + "group_name TEXT,"
+ + "status TEXT,"
+ + "count integer);";//增加文章数
+
+ // 收藏item
+ private final static String CREATE_FAVOR_RSS_ITEM_TABLE = "create table if not exists FAVOR_RSS_ITEM ("
+ + "item_id integer primary key autoincrement,"
+ + "url TEXT,"
+ + "name TEXT,"
+ + "description TEXT,"
+ + "favor_time TEXT);";
+
+ /**
+ * 写一个这个类的构造函数,参数为上下文context,所谓上下文就是这个类所在包的路径
+ * 指明上下文,数据库名,工厂默认空值,版本号默认从1开始
+ * super(context,"db_test",null,1);
+ * 把数据库设置成可写入状态,除非内存已满,那时候会自动设置为只读模式
+ *
+ * @param context Context
+ */
+ public DatabaseHelper(Context context) {
+ super(context, name, null, dbDefaultVersion);
+ }
+
+ /**
+ * 只在创建的时候使用一次
+ * 重写两个必须要重写的方法,因为class DBOpenHelper extends SQLiteOpenHelper
+ * 而这两个方法是 abstract 类 SQLiteOpenHelper 中声明的 abstract 方法
+ *
+ * @param db SQLiteDatabase
+ */
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ //db.execSQL(CREATE_FAVOR_RSS_ITEM_TABLE);
+
+ db.execSQL(CREATE_SSR_URL_TABLE);
+ db.execSQL(CREATE_FAVOR_RSS_ITEM_TABLE);
+ // System.out.println("成功创建Database&Table");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ System.out.println("old version:" + oldVersion + ",new version:" + newVersion);
+ }
+
+
+ @Override
+ public SQLiteDatabase getWritableDatabase() {
+ return super.getWritableDatabase();
+ }
+
+ @Override
+ public SQLiteDatabase getReadableDatabase() {
+ return super.getReadableDatabase();
+ }
+
+}
diff --git a/app/src/main/java/xyz/somelou/rss/db/FavorRSSItemDAL.java b/app/src/main/java/xyz/somelou/rss/db/FavorRSSItemDAL.java
new file mode 100644
index 0000000..a72f19c
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/db/FavorRSSItemDAL.java
@@ -0,0 +1,68 @@
+package xyz.somelou.rss.db;
+
+import java.util.ArrayList;
+
+import xyz.somelou.rss.bean.FavorRSSItem;
+
+/**
+ * @author somelou
+ * @description favor_ssr_item表协作类接口
+ * @date 2019-06-26
+ */
+public interface FavorRSSItemDAL {
+
+ /**
+ * 查询表favor_ssr_item全部内容
+ * @param favorRSSItems 必须保证传进 Adapter 的数据 List 是同一个 List
+ * @return rssUrlArrayList
+ */
+ ArrayList getAllData(ArrayList favorRSSItems);
+
+ /**
+ * 模糊查询|不实现
+ * @param rssUrlArrayList 必须保证传进 Adapter 的数据 List 是同一个 List
+ * @param query 输入的字符串
+ * @return rssUrlArrayList
+ */
+ ArrayList getQueryData(ArrayList rssUrlArrayList, String query);
+
+ /**
+ * 查询favor_ssr_item某一项的内容
+ * @param id 选中项的id
+ * @return 一项item
+ */
+ FavorRSSItem getOneData(Integer id);
+
+ /**
+ * 判断是否收藏
+ * @param itemUrl item的链接
+ * @return true:收藏;false,未收藏
+ */
+ boolean isFavor(String itemUrl);
+
+ /**
+ * 收藏
+ * @param url 该item的url
+ * @param titleName 标题
+ * @param description 描述
+ * @return 是否添加成功
+ */
+ long insertOneData(String url,String titleName,String description);
+
+ /**
+ * 取消收藏
+ * @param id 选中项的id
+ * @return 是否删除成功
+ */
+ int deleteOneData(Integer id);
+
+ /**
+ * 取消收藏
+ * @param itemUrl string
+ * @return 是否删除成功
+ */
+ int deleteOneData(String itemUrl);
+
+ int deleteAllData();
+
+}
diff --git a/app/src/main/java/xyz/somelou/rss/db/RSSUrlDAL.java b/app/src/main/java/xyz/somelou/rss/db/RSSUrlDAL.java
new file mode 100644
index 0000000..5d2c995
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/db/RSSUrlDAL.java
@@ -0,0 +1,84 @@
+package xyz.somelou.rss.db;
+
+import java.util.ArrayList;
+
+import xyz.somelou.rss.bean.RSSUrl;
+import xyz.somelou.rss.enums.SubscribeStatus;
+
+/**
+ * @author somelou
+ * @description SSR_URL表协作类接口
+ * @date 2019-06-25
+ */
+public interface RSSUrlDAL {
+
+ /**
+ * 查询表ssr_url全部内容
+ * @param rssUrlArrayList 必须保证传进 Adapter 的数据 List 是同一个 List
+ * @return rssUrlArrayList
+ */
+ ArrayList getAllData(ArrayList rssUrlArrayList);
+
+ /**
+ * 获取所有已经订阅的RSS源
+ * @param rssUrls 这里可能不需要这样,为保险起见
+ * @return
+ */
+ ArrayList getSubscribe(ArrayList rssUrls);
+
+ /**
+ * 模糊查询
+ * @param rssUrlArrayList 必须保证传进 Adapter 的数据 List 是同一个 List
+ * @param query 输入的字符串
+ * @return rssUrlArrayList
+ */
+ ArrayList getQueryData(ArrayList rssUrlArrayList, String query);
+
+ /**
+ * 查询ssr_url某一项的内容
+ * @param id 选中项的id
+ * @return 一项item
+ */
+ RSSUrl getOneData(Integer id);
+
+ /**
+ * 添加一个rss源
+ * @param url rss url
+ * @param groupName 组名,default=""
+ * @param status 订阅状态,主动添加时default=SUBSCRIBED
+ * @return 是否添加成功
+ */
+ long insertOneData(String url, String groupName, SubscribeStatus status);
+
+ /**
+ * 删除某项
+ * @param id 选中项的id
+ * @return 一项item
+ */
+ int deleteOneData(Integer id);
+
+ /**
+ * 更改标题名
+ * @param id 选中项的id
+ * @param name 新的名字
+ * @return 是否成功
+ */
+ int updateName(Integer id, String name);
+
+ /**
+ * 更改组名
+ * @param id 选中项的id
+ * @param groupName 新的组名
+ * @return 是否成功
+ */
+ int updateGroupName(Integer id, String groupName);
+
+ /**
+ * 更改订阅状态
+ * @param id 选中项的id
+ * @param status 订阅状态
+ * @return
+ */
+ int updateSubscribeStatus(Integer id,SubscribeStatus status);
+
+}
diff --git a/app/src/main/java/xyz/somelou/rss/db/impl/BaseDALImpl.java b/app/src/main/java/xyz/somelou/rss/db/impl/BaseDALImpl.java
new file mode 100644
index 0000000..5aa17c3
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/db/impl/BaseDALImpl.java
@@ -0,0 +1,82 @@
+package xyz.somelou.rss.db.impl;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
+
+import xyz.somelou.rss.R;
+import xyz.somelou.rss.db.DatabaseHelper;
+import xyz.somelou.rss.utils.RSSUtil;
+
+/**
+ * @author somelou
+ * @description
+ * @date 2019-06-26
+ */
+public class BaseDALImpl {
+
+ // 数据库默认为空
+ private boolean isEmpty;
+
+ DatabaseHelper databaseHelper;
+ SQLiteDatabase db;
+ String TABLE_NAME;
+ private Context context;
+
+ public BaseDALImpl(Context context) {
+ databaseHelper = new DatabaseHelper(context);
+ this.context = context;
+ // System.out.println("new in basedalimpl");
+ }
+
+ /**
+ * 执行sql,返回Cursor
+ *
+ * @param sql String
+ * @return Cursor
+ */
+ Cursor getData(String sql) throws SQLException {
+ return databaseHelper.getReadableDatabase().rawQuery(sql, null);
+ }
+
+ /**
+ * 预设置一些rss源
+ * url从xml中获取
+ */
+ public void preInsertRssUrl() {
+ if (isEmptyPreInsert()) {
+ System.out.println(isEmpty);
+ String[] RSS_URL_ARRAY = context.getResources().getStringArray(R.array.rss_url_array);
+ RSSUtil rssUtil = new RSSUtil(RSS_URL_ARRAY[0]);
+
+ // 插入一些rss源
+ // 问题1:必须要和表的column数量一致,不能插入不饱和数据
+ // 2:string的拼接
+ String INSERT_RSS_URL =
+ "INSERT INTO RSS_URL SELECT 1 AS 'url_id','" + rssUtil.getTitleName() + "' AS 'name', '"
+ + RSS_URL_ARRAY[0] + "' AS 'url' ,'默认' AS 'GROUP_NAME','NO_SUBSCRIBE' AS 'status',' "+Integer.toString(rssUtil.getFeedSize())+"' AS 'count'";
+ for (int i = 1; i < RSS_URL_ARRAY.length; i++) {
+ rssUtil.setRssUrl(RSS_URL_ARRAY[i]);
+ //Log.i("测试预设RSS",rssUtil.getValidate().toString());
+ INSERT_RSS_URL += "UNION SELECT " + (i + 1) + ",'" + rssUtil.getTitleName() + "','"
+ + RSS_URL_ARRAY[i] + "','默认','NO_SUBSCRIBE',' "+Integer.toString(rssUtil.getFeedSize())+"'";
+ //Log.i("title=",(i + 1)+ " "+ rssUtil.getTitleName());
+ }
+ db = databaseHelper.getWritableDatabase();
+ db.execSQL(INSERT_RSS_URL);
+ }
+ }
+
+ private boolean isEmptyPreInsert() {
+ String num = "select count(*) from " + context.getResources().getString(R.string.table_rss_url);
+ Cursor cursor = databaseHelper.getReadableDatabase().rawQuery(num, null);
+ while (cursor.moveToNext()) {
+ int result = cursor.getInt(cursor.getColumnIndex("count(*)"));
+ isEmpty = ( result== 0);
+ }
+ cursor.close();
+ return isEmpty;
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/db/impl/FavorRSSItemDALImpl.java b/app/src/main/java/xyz/somelou/rss/db/impl/FavorRSSItemDALImpl.java
new file mode 100644
index 0000000..50ab2c9
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/db/impl/FavorRSSItemDALImpl.java
@@ -0,0 +1,148 @@
+package xyz.somelou.rss.db.impl;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteStatement;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+
+import xyz.somelou.rss.R;
+import xyz.somelou.rss.bean.FavorRSSItem;
+import xyz.somelou.rss.db.FavorRSSItemDAL;
+
+/**
+ * @author somelou
+ * @description
+ * @date 2019-06-25
+ */
+public class FavorRSSItemDALImpl extends BaseDALImpl implements FavorRSSItemDAL {
+
+ public FavorRSSItemDALImpl(Context context) {
+ super(context);
+ TABLE_NAME = context.getString(R.string.table_favor_rss_item);
+ //databaseHelper=new DatabaseHelper(context);
+ }
+
+ @Override
+ public ArrayList getAllData(ArrayList favorRSSItems) {
+ Cursor cursor = getData("SELECT * FROM " + TABLE_NAME);
+ return getDataFromCursor(favorRSSItems,cursor);
+ }
+
+ @Override
+ public ArrayList getQueryData(ArrayList favorRSSItemArrayList, String query) {
+ db = databaseHelper.getReadableDatabase();
+ Cursor cursor = getData("select * from " + TABLE_NAME + " where name like '%" + query + "%' or " + "description like '%" + query +
+ "%' or url like '%" + query + "%'");
+ return getDataFromCursor(favorRSSItemArrayList,cursor);
+ }
+
+ @Override
+ public FavorRSSItem getOneData(Integer id) {
+ String sql = "SELECT * FROM "+TABLE_NAME+" WHERE item_id = "+id;
+ Cursor cursor=getData(sql);
+ return getDataFromCursor(cursor);
+ }
+
+ @Override
+ public boolean isFavor(String itemUrl) {
+ String sql = "SELECT * FROM "+TABLE_NAME+" WHERE url = '"+itemUrl+"';";
+ Cursor cursor=getData(sql);
+ return getDataFromCursor(cursor) != null;
+ }
+
+ @SuppressLint("SimpleDateFormat")
+ @Override
+ public long insertOneData(String url,String titleName,String description) {
+ db=databaseHelper.getWritableDatabase();
+ String sql="INSERT INTO "+TABLE_NAME+" VALUES (null,?,?,?,?)";
+
+ SQLiteStatement statement=db.compileStatement(sql);
+ statement.clearBindings();
+ statement.bindString(1,url);
+ statement.bindString(2,titleName);
+ statement.bindString(3,description);
+ statement.bindString(4,new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
+
+ long result=statement.executeInsert();
+ db.close();
+ return result;
+ }
+
+ @Override
+ public int deleteOneData(Integer id) {
+ db=databaseHelper.getWritableDatabase();
+
+ String sql = "DELETE FROM "+TABLE_NAME+" WHERE item_id = ?";
+ SQLiteStatement statement = db.compileStatement(sql);
+ statement.clearBindings();
+ statement.bindLong(1, id);
+
+ int result=statement.executeUpdateDelete();
+ db.close();
+ return result;
+ }
+
+ @Override
+ public int deleteOneData(String itemUrl) {
+ db=databaseHelper.getWritableDatabase();
+
+ String sql = "DELETE FROM "+TABLE_NAME+" WHERE url = ?";
+ SQLiteStatement statement = db.compileStatement(sql);
+ statement.clearBindings();
+ statement.bindString(1, itemUrl);
+
+ int result=statement.executeUpdateDelete();
+ db.close();
+ return result;
+ }
+
+ private ArrayList getDataFromCursor(ArrayList favorRSSItems, Cursor cursor){
+ //让游标从表头游到表尾,并把数据存放到list中
+ while (cursor.moveToNext()) {
+ int id = cursor.getInt(cursor.getColumnIndex("item_id"));
+ String name = cursor.getString(cursor.getColumnIndex("name"));
+ String url = cursor.getString(cursor.getColumnIndex("url"));
+ String description = cursor.getString(cursor.getColumnIndex("description"));
+ String favorTime =cursor.getString(cursor.getColumnIndex("favor_time"));
+
+ //System.out.println("get name:" + name + ",id:" + id + ",description:" + description+", time:"+favorTime);
+ //byte[] stuPic = cursor.getBlob(cursor.getColumnIndex("pic"));
+ favorRSSItems.add(new FavorRSSItem(id,url,name,description,favorTime));
+ }
+ cursor.close();
+ return favorRSSItems;
+ }
+
+ private FavorRSSItem getDataFromCursor(Cursor cursor){
+ //让游标从表头游到表尾,并把数据存放到list中r
+ FavorRSSItem rssUrl=null;
+ while (cursor.moveToNext()) {
+ int id = cursor.getInt(cursor.getColumnIndex("item_id"));
+ String name = cursor.getString(cursor.getColumnIndex("name"));
+ String url = cursor.getString(cursor.getColumnIndex("url"));
+ String description = cursor.getString(cursor.getColumnIndex("description"));
+ String favorTime =cursor.getString(cursor.getColumnIndex("favor_time"));
+
+ //System.out.println("get name:" + name + ",id:" + id + ",description:" + description+", time:"+favorTime);
+
+ //byte[] stuPic = cursor.getBlob(cursor.getColumnIndex("pic"));
+ rssUrl=new FavorRSSItem(id,url,name,description,favorTime);
+ }
+ cursor.close();
+ return rssUrl;
+ }
+
+ //删除所有数据但保留表
+ public int deleteAllData() {
+ db = databaseHelper.getWritableDatabase();
+ String sql = "DELETE FROM " + TABLE_NAME;
+ SQLiteStatement statement = db.compileStatement(sql);
+ int result = statement.executeUpdateDelete();
+ db.close();
+ return result;
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/db/impl/RSSUrlDALImpl.java b/app/src/main/java/xyz/somelou/rss/db/impl/RSSUrlDALImpl.java
new file mode 100644
index 0000000..80739db
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/db/impl/RSSUrlDALImpl.java
@@ -0,0 +1,194 @@
+package xyz.somelou.rss.db.impl;
+
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteStatement;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+import xyz.somelou.rss.R;
+import xyz.somelou.rss.bean.RSSUrl;
+import xyz.somelou.rss.db.RSSUrlDAL;
+import xyz.somelou.rss.enums.SubscribeStatus;
+import xyz.somelou.rss.utils.RSSUtil;
+
+/**
+ * @author somelou
+ * @description
+ * @date 2019-06-25
+ */
+public class RSSUrlDALImpl extends BaseDALImpl implements RSSUrlDAL {
+
+ public RSSUrlDALImpl(Context context) {
+ super(context);
+ TABLE_NAME = context.getString(R.string.table_rss_url);
+ //databaseHelper=new DatabaseHelper(context);
+ }
+
+ /**
+ * getAllData()
+ * 查询表rss_url全部内容,需要有个容器存放,以供使用,定义了一个ArrayList类的list
+ * 使用游标Cursor从表中查询数据,第一个参数:"表名",中间5个:null,查询出来内容的排序方式:"stuid DESC"
+ *
+ * @param rssUrlArrayList ArrayList
+ * @return list
+ */
+ @Override
+ public ArrayList getAllData(ArrayList rssUrlArrayList) {
+ //可以改成HashMap
+ db = databaseHelper.getReadableDatabase();
+ /*
+ * 使用 adapter.notifyDataSetChanged() 时,必须保证传进 Adapter 的数据 List 是同一个 List
+ * 而不能是其他对象,否则无法更新 listview。
+ * 即,你可以调用 List 的 add(), remove(), clear(),addAll() 等方法,
+ * 这种情况下,List 指向的始终是你最开始 new 出来的 ArrayList ,
+ * 然后调用 adapter.notifyDataSetChanged() 方法,可以更新 ListView;
+ * 但是如果你重新 new 了一个 ArrayList(重新申请了堆内存),
+ * 那么这时候,List 就指向了另外一个 ArrayLIst,
+ * 这时调用 adapter.notifyDataSetChanged() 方法,就无法刷新 listview 了。*/
+ //ArrayList list = new ArrayList();
+ Cursor cursor = getData("SELECT * FROM " + TABLE_NAME);
+ return getDataFromCursor(rssUrlArrayList,cursor);
+ }
+
+ @Override
+ public ArrayList getSubscribe(ArrayList rssUrls) {
+ Cursor cursor = getData("SELECT * FROM " + TABLE_NAME + " where status='SUBSCRIBED'");
+ return getDataFromCursor(rssUrls,cursor);
+ }
+
+ @Override
+ public ArrayList getQueryData(ArrayList rssUrlArrayList, String query) {
+ db = databaseHelper.getReadableDatabase();
+ Cursor cursor = getData("select * from " + TABLE_NAME + " where name like '%" + query + "%' or " + "group_name like '%" + query +
+ "%' or url like '%" + query + "%'");
+ return getDataFromCursor(rssUrlArrayList,cursor);
+ }
+
+ @Override
+ public RSSUrl getOneData(Integer id) {
+ String sql = "SELECT * FROM "+TABLE_NAME+" WHERE url_id = "+id;
+ Cursor cursor=getData(sql);
+ return getDataFromCursor(cursor);
+ }
+
+ @Override
+ public long insertOneData(String url, String groupName, SubscribeStatus status) {
+ db=databaseHelper.getWritableDatabase();
+ String sql="INSERT INTO "+TABLE_NAME+" VALUES (null,?,?,?,?,?)";
+
+ RSSUtil u=new RSSUtil(url);
+ SQLiteStatement statement=db.compileStatement(sql);
+ statement.clearBindings();
+ statement.bindString(1,u.getTitleName());
+ statement.bindString(2,url);
+ if (groupName==null||groupName.trim().equals(""))
+ statement.bindString(3,"默认");
+ else
+ statement.bindString(3,groupName);
+ statement.bindString(4,status.toString());
+ statement.bindLong(5, u.getFeedSize());
+
+ long result=statement.executeInsert();
+ db.close();
+ return result;
+ }
+
+ @Override
+ public int deleteOneData(Integer id) {
+ db=databaseHelper.getWritableDatabase();
+
+ String sql = "DELETE FROM "+TABLE_NAME+" WHERE url_id = ?";
+ SQLiteStatement statement = db.compileStatement(sql);
+ statement.clearBindings();
+ statement.bindLong(1, id);
+
+ int result=statement.executeUpdateDelete();
+ db.close();
+ return result;
+ }
+
+ @Override
+ public int updateName(Integer id, String name) {
+ db = databaseHelper.getWritableDatabase();
+
+ String sql = "UPDATE "+TABLE_NAME+" SET name = ? WHERE url_id = ?";
+ SQLiteStatement statement = db.compileStatement(sql);
+
+ statement.clearBindings();
+ statement.bindString(1,name);
+ statement.bindLong(2,id);
+
+ int result=statement.executeUpdateDelete();
+ db.close();
+ return result;
+ }
+
+ @Override
+ public int updateGroupName(Integer id, String groupName) {
+ db = databaseHelper.getWritableDatabase();
+
+ String sql = "UPDATE "+TABLE_NAME+" SET group_name = ? WHERE url_id = ?";
+ SQLiteStatement statement = db.compileStatement(sql);
+
+ statement.clearBindings();
+ statement.bindString(1,groupName);
+ statement.bindLong(2,id);
+
+ int result=statement.executeUpdateDelete();
+ db.close();
+ return result;
+ }
+
+ @Override
+ public int updateSubscribeStatus(Integer id, SubscribeStatus status) {
+ db = databaseHelper.getWritableDatabase();
+
+ String sql = "UPDATE "+TABLE_NAME+" SET status = ? WHERE url_id = ?";
+ SQLiteStatement statement = db.compileStatement(sql);
+
+ statement.clearBindings();
+ statement.bindString(1,status.toString());
+ statement.bindLong(2,id);
+
+ int result=statement.executeUpdateDelete();
+ db.close();
+ return result;
+ }
+
+ private ArrayList getDataFromCursor(ArrayList rssUrlArrayList,Cursor cursor){
+ //让游标从表头游到表尾,并把数据存放到list中
+ while (cursor.moveToNext()) {
+ int id = cursor.getInt(cursor.getColumnIndex("url_id"));
+ String name = cursor.getString(cursor.getColumnIndex("name"));
+ String url = cursor.getString(cursor.getColumnIndex("url"));
+ String groupName = cursor.getString(cursor.getColumnIndex("group_name"));
+ String status =cursor.getString(cursor.getColumnIndex("status"));
+ int count=cursor.getInt(cursor.getColumnIndex("count"));
+ //Log.i("RSSUrlDALImpl.java ","get name:" + name + ",id:" + id + ",groupName:" + groupName+", status:"+status+",count:"+count);
+ //byte[] stuPic = cursor.getBlob(cursor.getColumnIndex("pic"));
+ rssUrlArrayList.add(new RSSUrl(id, url, name,groupName,status,count));
+ }
+ cursor.close();
+ return rssUrlArrayList;
+ }
+
+ private RSSUrl getDataFromCursor(Cursor cursor){
+ //让游标从表头游到表尾,并把数据存放到list中r
+ RSSUrl rssUrl=null;
+ while (cursor.moveToNext()) {
+ int id = cursor.getInt(cursor.getColumnIndex("url_id"));
+ String name = cursor.getString(cursor.getColumnIndex("name"));
+ String url = cursor.getString(cursor.getColumnIndex("url"));
+ String groupName = cursor.getString(cursor.getColumnIndex("group_name"));
+ String status =cursor.getString(cursor.getColumnIndex("status"));
+ int count=cursor.getInt(cursor.getColumnIndex("count"));
+ //Log.i("RSSUrlDALImpl当前频道文章数--",Integer.toString(count));
+ rssUrl=new RSSUrl(id, url, name ,groupName,status,count);
+ }
+ cursor.close();
+ return rssUrl;
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/enums/SubscribeStatus.java b/app/src/main/java/xyz/somelou/rss/enums/SubscribeStatus.java
new file mode 100644
index 0000000..73483a8
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/enums/SubscribeStatus.java
@@ -0,0 +1,31 @@
+package xyz.somelou.rss.enums;
+
+/**
+ * @author somelou
+ * @description 订阅状态
+ * @date 2019-06-25
+ */
+public enum SubscribeStatus {
+
+ SUBSCRIBED("订阅"),
+ NO_SUBSCRIBE("未订阅");
+
+ // 中文描述
+ private String desc;
+
+ /**
+ * 私有构造函数,防止被外部调用
+ * @param desc
+ */
+ private SubscribeStatus(String desc) {
+ this.desc=desc;
+ }
+
+ /**
+ * 定义方法,返回描述,跟常规类的定义没区别
+ * @return
+ */
+ public String getDesc(){
+ return desc;
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/find/FindFragment.java b/app/src/main/java/xyz/somelou/rss/find/FindFragment.java
new file mode 100644
index 0000000..0c448da
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/find/FindFragment.java
@@ -0,0 +1,318 @@
+package xyz.somelou.rss.find;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.AlertDialog;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import xyz.somelou.rss.R;
+import xyz.somelou.rss.adapter.RSSFindAdapter;
+import xyz.somelou.rss.bean.RSSUrl;
+import xyz.somelou.rss.db.impl.RSSUrlDALImpl;
+import xyz.somelou.rss.enums.SubscribeStatus;
+import xyz.somelou.rss.utils.SwitchGroupUtil;
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Activities that contain this fragment must implement the
+ * to handle interaction events.
+ * Use the {@link FindFragment#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class FindFragment extends Fragment implements RSSFindAdapter.SubClickListener{
+ // TODO: Rename parameter arguments, choose names that match
+ // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
+ private static final String ARG_PARAM1 = "param1";
+ private static final String ARG_PARAM2 = "param2";
+
+ // TODO: Rename and change types of parameters
+ private String mParam1;
+ private String mParam2;
+
+ EditText key_word;//检索关键词
+ EditText group_name;//输入分组名
+ private ArrayList RSSfinds;
+ private RSSFindAdapter RSSadapter;
+ private RSSUrlDALImpl RSSdal;
+ private ListView lv;
+ private int MID;//数据库ID
+ private SwitchGroupUtil switchGroupUtil;
+ View contentView;
+
+ public FindFragment() {
+ // Required empty public constructor
+ }
+
+ /**
+ * Use this factory method to create a new instance of
+ * this fragment using the provided parameters.
+ *
+ * @param param1 Parameter 1.
+ * @param param2 Parameter 2.
+ * @return A new instance of fragment FindFragment.
+ */
+ // TODO: Rename and change types and number of parameters
+ public static FindFragment newInstance(String param1, String param2) {
+ FindFragment fragment = new FindFragment();
+ Bundle args = new Bundle();
+ args.putString(ARG_PARAM1, param1);
+ args.putString(ARG_PARAM2, param2);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);//设置有菜单
+ if (getArguments() != null) {
+ mParam1 = getArguments().getString(ARG_PARAM1);
+ mParam2 = getArguments().getString(ARG_PARAM2);
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ contentView=inflater.inflate(R.layout.fragment_find, container, false);
+ //界面初始化
+ RSSdal=new RSSUrlDALImpl(getContext());//数据库操作类
+
+ RSSfinds=RSSdal.getAllData(new ArrayList());//RSS源不为空
+ /*for (int i=0;i groupNames= switchGroupUtil.getGroupNames();
+ final String [] groups=groupNames.toArray(new String[groupNames.size()]);
+ // 设置一个下拉的列表选择项
+ builder.setItems(groups, new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int which)
+ {
+ Toast.makeText(getContext(), "选择的分组为:" + groups[which], Toast.LENGTH_SHORT).show();
+ RSSdal.updateGroupName(MID,"" + groups[which]);
+ }
+ });
+ builder.show();
+ }
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
+ /*Log.i("点击发现项",RSSfinds.get(i).getName()+RSSfinds.get(i).getId()
+ +RSSfinds.get(i).getStatus()+RSSfinds.get(i).getGroupName()+RSSfinds.get(i).getUrl()+RSSfinds.get(i).getCount());*/
+ Toast.makeText(getActivity(), "第" + (i+1) + "行", Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ }
+
+ //
+ //创建分组
+ public void createGroup(){
+ group_name = new EditText(getContext());
+ AlertDialog.Builder dialog=new AlertDialog.Builder(getContext());
+ //设置标题,图标,视图
+ dialog.setTitle("请输入分组名称")
+ .setIcon(android.R.drawable.sym_def_app_icon)
+ .setView(group_name)
+ .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ if (group_name.getText().toString() != null) {
+ RSSdal.updateGroupName(MID,group_name.getText().toString().trim());
+ }
+
+ }
+ }).setNegativeButton("取消", null);
+ //先显示对话框
+ dialog.show();
+ group_name.setFocusable(true);
+ group_name.setFocusableInTouchMode(true);
+ //请求获得焦点
+ group_name.requestFocus();
+
+ }
+
+}
diff --git a/app/src/main/java/xyz/somelou/rss/my/MyFragment.java b/app/src/main/java/xyz/somelou/rss/my/MyFragment.java
new file mode 100644
index 0000000..79a4dfc
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/my/MyFragment.java
@@ -0,0 +1,103 @@
+package xyz.somelou.rss.my;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import xyz.somelou.rss.R;
+import xyz.somelou.rss.my.myGroup.MyGroupRecyclerActivity;
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Activities that contain this fragment must implement the
+ * to handle interaction events.
+ * Use the {@link MyFragment#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class MyFragment extends Fragment {
+ private Context mContext;
+ // TODO: Rename parameter arguments, choose names that match
+ // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
+ private static final String ARG_PARAM1 = "param1";
+ private static final String ARG_PARAM2 = "param2";
+
+ // TODO: Rename and change types of parameters
+ private String mParam1;
+ private String mParam2;
+
+ public MyFragment() {
+ // Required empty public constructor
+ }
+
+ /**
+ * Use this factory method to create a new instance of
+ * this fragment using the provided parameters.
+ *
+ * @param param1 Parameter 1.
+ * @param param2 Parameter 2.
+ * @return A new instance of fragment MyFragment.
+ */
+ // TODO: Rename and change types and number of parameters
+ public static MyFragment newInstance(String param1, String param2) {
+ MyFragment fragment = new MyFragment();
+ Bundle args = new Bundle();
+ args.putString(ARG_PARAM1, param1);
+ args.putString(ARG_PARAM2, param2);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getArguments() != null) {
+ mParam1 = getArguments().getString(ARG_PARAM1);
+ mParam2 = getArguments().getString(ARG_PARAM2);
+ }
+
+
+
+// if(getActivity()!=null){
+//
+// getActivity().findViewById(R.id.layout_pinned_header_linear).setOnClickListener(new View.OnClickListener() {
+// @Override
+// public void onClick(View v) {
+// MyGroupRecyclerActivity.startUp(mContext);
+// }
+// });
+// }
+
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ View view=inflater.inflate(R.layout.fragment_my, container, false);
+ mContext=getContext();
+ view.findViewById(R.id.layout_pinned_header_linear).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ MyGroupRecyclerActivity.startUp(mContext);
+ }
+ });
+ return view;
+
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ }
+
+}
diff --git a/app/src/main/java/xyz/somelou/rss/my/myGroup/MyGroupRecyclerActivity.java b/app/src/main/java/xyz/somelou/rss/my/myGroup/MyGroupRecyclerActivity.java
new file mode 100644
index 0000000..ebe6846
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/my/myGroup/MyGroupRecyclerActivity.java
@@ -0,0 +1,85 @@
+package xyz.somelou.rss.my.myGroup;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.helper.ItemTouchHelper;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import xyz.somelou.rss.R;
+import xyz.somelou.rss.bean.FavorRSSItem;
+import xyz.somelou.rss.bean.RSSUrl;
+import xyz.somelou.rss.db.DatabaseHelper;
+import xyz.somelou.rss.db.RSSUrlDAL;
+import xyz.somelou.rss.db.impl.FavorRSSItemDALImpl;
+import xyz.somelou.rss.db.impl.RSSUrlDALImpl;
+import xyz.somelou.rss.my.MyFragment;
+import xyz.somelou.rss.my.pinnerHeader.PinnedHeaderItemDecoration;
+import xyz.somelou.rss.my.pinnerHeader.PinnedHeaderRecyclerView;
+import xyz.somelou.rss.my.touchHelper.MyItemTouchHelperCallback;
+import xyz.somelou.rss.utils.SwitchGroupUtil;
+
+import static android.widget.Toast.LENGTH_SHORT;
+
+public class MyGroupRecyclerActivity extends AppCompatActivity {
+ private RSSUrlDALImpl rssUrlDALImpl;
+ private SwitchGroupUtil switchGroupUtil;
+
+ public static void startUp(Context context) {
+ context.startActivity(new Intent(context, MyGroupRecyclerActivity.class));
+ }
+
+ private PinnedHeaderRecyclerView mRecyclerView;
+ private Context mContext;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.my_group_activity_recycler);
+ mContext = this;
+ initView();
+ initEvent();
+ initData();
+ }
+
+ private void initView() {
+ mRecyclerView = findViewById(R.id.recycler_linear);
+ LinearLayoutManager layoutManager = new LinearLayoutManager(mContext);
+ mRecyclerView.setLayoutManager(layoutManager);
+ }
+
+ private void initEvent() {
+ mRecyclerView.setOnPinnedHeaderClickListener(new PinnedHeaderRecyclerView.OnPinnedHeaderClickListener() {
+ @Override
+ public void onPinnedHeaderClick(int adapterPosition) {
+ Toast.makeText(mContext, "点击了悬浮标题 position = " + adapterPosition, LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ private void initData() {
+ MyGroupRecyclerAdapter adapter = new MyGroupRecyclerAdapter(obtainData());
+ mRecyclerView.setAdapter(adapter);
+ mRecyclerView.addItemDecoration(new PinnedHeaderItemDecoration());
+ //////
+ MyItemTouchHelperCallback callback = new MyItemTouchHelperCallback(adapter); //此类继承ItemTouchHelper.Callback,这是帮助处理RecylerView拖动侧滑操作的辅助类
+ callback.setDatas(obtainData());
+ ItemTouchHelper helper = new ItemTouchHelper(callback); //用上面实例化的callback实例化一个ItemTouchHelper对象。
+ helper.attachToRecyclerView(mRecyclerView);
+ }
+
+ private List obtainData() {
+ switchGroupUtil=new SwitchGroupUtil(this);
+ return switchGroupUtil.getGroupSortList();
+
+ }
+
+}
diff --git a/app/src/main/java/xyz/somelou/rss/my/myGroup/MyGroupRecyclerAdapter.java b/app/src/main/java/xyz/somelou/rss/my/myGroup/MyGroupRecyclerAdapter.java
new file mode 100644
index 0000000..0f5f1c4
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/my/myGroup/MyGroupRecyclerAdapter.java
@@ -0,0 +1,97 @@
+package xyz.somelou.rss.my.myGroup;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.List;
+
+import xyz.somelou.rss.R;
+import xyz.somelou.rss.my.pinnerHeader.PinnedHeaderAdapter;
+
+public class MyGroupRecyclerAdapter extends PinnedHeaderAdapter {
+
+ private static final int VIEW_TYPE_ITEM_TIME = 0;
+ private static final int VIEW_TYPE_ITEM_CONTENT = 1;
+
+ private List mDataList;
+
+ public MyGroupRecyclerAdapter() {
+ this(null);
+ }
+
+ public MyGroupRecyclerAdapter(List dataList) {
+ mDataList = dataList;
+ }
+
+ public void setData(List dataList) {
+ mDataList = dataList;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ if (viewType == VIEW_TYPE_ITEM_TIME) {
+ return new TitleHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.my_group_item_title, parent, false));
+ } else {
+ return new ContentHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.my_group_item_content, parent, false));
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ if (getItemViewType(position) == VIEW_TYPE_ITEM_TIME) {
+ TitleHolder titleHolder = (TitleHolder) holder;
+ titleHolder.mTextTitle.setText(mDataList.get(position));
+ } else {
+ ContentHolder contentHolder = (ContentHolder) holder;
+ contentHolder.mTextTitle.setText(mDataList.get(position));
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mDataList == null ? 0 : mDataList.size();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ String name= mDataList.get(position).trim();
+ if (name.contains("分组")) {
+ return VIEW_TYPE_ITEM_TIME;
+ } else {
+ return VIEW_TYPE_ITEM_CONTENT;
+ }
+ }
+
+ @Override
+ public boolean isPinnedPosition(int position) {
+ return getItemViewType(position) == VIEW_TYPE_ITEM_TIME;
+ }
+
+ static class ContentHolder extends RecyclerView.ViewHolder {
+
+ TextView mTextTitle;
+
+ ContentHolder(View itemView) {
+ super(itemView);
+ mTextTitle = itemView.findViewById(R.id.text_adapter_content_name);
+ }
+ }
+
+ static class TitleHolder extends RecyclerView.ViewHolder {
+
+ TextView mTextTitle;
+
+ TitleHolder(View itemView) {
+ super(itemView);
+ mTextTitle = itemView.findViewById(R.id.text_adapter_title_name);
+ }
+ }
+////////////////////////////////////////////
+ public void switchGroup(){
+
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/my/pinnerHeader/IPinnedHeaderDecoration.java b/app/src/main/java/xyz/somelou/rss/my/pinnerHeader/IPinnedHeaderDecoration.java
new file mode 100644
index 0000000..47a3346
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/my/pinnerHeader/IPinnedHeaderDecoration.java
@@ -0,0 +1,9 @@
+package xyz.somelou.rss.my.pinnerHeader;
+
+import android.graphics.Rect;
+
+public interface IPinnedHeaderDecoration {
+ Rect getPinnedHeaderRect();
+
+ int getPinnedHeaderPosition();
+}
diff --git a/app/src/main/java/xyz/somelou/rss/my/pinnerHeader/PinnedHeaderAdapter.java b/app/src/main/java/xyz/somelou/rss/my/pinnerHeader/PinnedHeaderAdapter.java
new file mode 100644
index 0000000..6edd026
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/my/pinnerHeader/PinnedHeaderAdapter.java
@@ -0,0 +1,25 @@
+package xyz.somelou.rss.my.pinnerHeader;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.ViewGroup;
+
+public abstract class PinnedHeaderAdapter extends RecyclerView.Adapter {
+
+ /**
+ * 判断该position对应的位置是要固定
+ *
+ * @param position adapter position
+ * @return true or false
+ */
+ public abstract boolean isPinnedPosition(int position);
+
+
+ public RecyclerView.ViewHolder onCreatePinnedViewHolder(ViewGroup parent, int viewType) {
+ return onCreateViewHolder(parent, viewType);
+ }
+
+ public void onBindPinnedViewHolder(VH holder, int position) {
+ onBindViewHolder(holder, position);
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/somelou/rss/my/pinnerHeader/PinnedHeaderItemDecoration.java b/app/src/main/java/xyz/somelou/rss/my/pinnerHeader/PinnedHeaderItemDecoration.java
new file mode 100644
index 0000000..a0f372d
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/my/pinnerHeader/PinnedHeaderItemDecoration.java
@@ -0,0 +1,119 @@
+package xyz.somelou.rss.my.pinnerHeader;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+public class PinnedHeaderItemDecoration extends RecyclerView.ItemDecoration implements IPinnedHeaderDecoration {
+
+ private Rect mPinnedHeaderRect = null;
+ private int mPinnedHeaderPosition = -1;
+
+ /**
+ * 把要固定的View绘制在上层
+ */
+ @Override
+ public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ super.onDrawOver(c, parent, state);
+ //确保是PinnedHeaderAdapter的adapter,确保有View
+ if (parent.getAdapter() instanceof PinnedHeaderAdapter && parent.getChildCount() > 0) {
+ PinnedHeaderAdapter adapter = (PinnedHeaderAdapter) parent.getAdapter();
+ //找到要固定的pin view
+ View firstView = parent.getChildAt(0);
+ int firstAdapterPosition = parent.getChildAdapterPosition(firstView);
+ int pinnedHeaderPosition = getPinnedHeaderViewPosition(firstAdapterPosition, adapter);
+ mPinnedHeaderPosition = pinnedHeaderPosition;
+ if (pinnedHeaderPosition != -1) {
+ RecyclerView.ViewHolder pinnedHeaderViewHolder = adapter.onCreateViewHolder(parent,
+ adapter.getItemViewType(pinnedHeaderPosition));
+ adapter.onBindViewHolder(pinnedHeaderViewHolder, pinnedHeaderPosition);
+ //要固定的view
+ View pinnedHeaderView = pinnedHeaderViewHolder.itemView;
+ ensurePinnedHeaderViewLayout(pinnedHeaderView, parent);
+ int sectionPinOffset = 0;
+ for (int index = 0; index < parent.getChildCount(); index++) {
+ if (adapter.isPinnedPosition(parent.getChildAdapterPosition(parent.getChildAt(index)))) {
+ View sectionView = parent.getChildAt(index);
+ int sectionTop = sectionView.getTop();
+ int pinViewHeight = pinnedHeaderView.getHeight();
+ if (sectionTop < pinViewHeight && sectionTop > 0) {
+ sectionPinOffset = sectionTop - pinViewHeight;
+ }
+ }
+ }
+ int saveCount = c.save();
+ RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) pinnedHeaderView.getLayoutParams();
+ if (layoutParams == null) {
+ throw new NullPointerException("PinnedHeaderItemDecoration");
+ }
+ c.translate(layoutParams.leftMargin, sectionPinOffset);
+ c.clipRect(0, 0, parent.getWidth(), pinnedHeaderView.getMeasuredHeight());
+ pinnedHeaderView.draw(c);
+ c.restoreToCount(saveCount);
+ if (mPinnedHeaderRect == null) {
+ mPinnedHeaderRect = new Rect();
+ }
+ mPinnedHeaderRect.set(0, 0, parent.getWidth(), pinnedHeaderView.getMeasuredHeight() + sectionPinOffset);
+ } else {
+ mPinnedHeaderRect = null;
+ }
+
+ }
+ }
+
+ /**
+ * 要给每个item设置间距主要靠这个函数来实现
+ */
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+ }
+
+ /**
+ * 根据第一个可见的adapter的位置去获取临近的一个要固定的position的位置
+ *
+ * @param adapterFirstVisible 第一个可见的adapter的位置
+ * @return -1:未找到 >=0 找到位置
+ */
+ private int getPinnedHeaderViewPosition(int adapterFirstVisible, PinnedHeaderAdapter adapter) {
+ for (int index = adapterFirstVisible; index >= 0; index--) {
+ if (adapter.isPinnedPosition(index)) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ private void ensurePinnedHeaderViewLayout(View pinView, RecyclerView recyclerView) {
+ if (pinView.isLayoutRequested()) {
+ /**
+ * 用的是RecyclerView的宽度测量,和RecyclerView的宽度一样
+ */
+ RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) pinView.getLayoutParams();
+ if (layoutParams == null) {
+ throw new NullPointerException("PinnedHeaderItemDecoration");
+ }
+ int widthSpec = View.MeasureSpec.makeMeasureSpec(
+ recyclerView.getMeasuredWidth() - layoutParams.leftMargin - layoutParams.rightMargin, View.MeasureSpec.EXACTLY);
+
+ int heightSpec;
+ if (layoutParams.height > 0) {
+ heightSpec = View.MeasureSpec.makeMeasureSpec(layoutParams.height, View.MeasureSpec.EXACTLY);
+ } else {
+ heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ }
+ pinView.measure(widthSpec, heightSpec);
+ pinView.layout(0, 0, pinView.getMeasuredWidth(), pinView.getMeasuredHeight());
+ }
+ }
+
+ @Override
+ public Rect getPinnedHeaderRect() {
+ return mPinnedHeaderRect;
+ }
+
+ @Override
+ public int getPinnedHeaderPosition() {
+ return mPinnedHeaderPosition;
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/my/pinnerHeader/PinnedHeaderRecyclerView.java b/app/src/main/java/xyz/somelou/rss/my/pinnerHeader/PinnedHeaderRecyclerView.java
new file mode 100644
index 0000000..08d7d52
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/my/pinnerHeader/PinnedHeaderRecyclerView.java
@@ -0,0 +1,134 @@
+package xyz.somelou.rss.my.pinnerHeader;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+
+public class PinnedHeaderRecyclerView extends RecyclerView {
+
+ public interface OnPinnedHeaderClickListener {
+
+ void onPinnedHeaderClick(int adapterPosition);
+ }
+
+ public PinnedHeaderRecyclerView(Context context) {
+ super(context);
+ }
+
+ public PinnedHeaderRecyclerView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public PinnedHeaderRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ private OnPinnedHeaderClickListener mPinnedHeaderClickListener;
+
+ public void setOnPinnedHeaderClickListener(OnPinnedHeaderClickListener listener) {
+ mPinnedHeaderClickListener = listener;
+ }
+
+ private boolean mPinnedHeaderHandle;
+
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent e) {
+ if (mPinnedHeaderClickListener == null) {
+ return super.onInterceptTouchEvent(e);
+ }
+ IPinnedHeaderDecoration pinnedHeaderInterface = getPinnedHeaderDecoration();
+ if (pinnedHeaderInterface == null) {
+ return super.onInterceptTouchEvent(e);
+ }
+ Rect pinnedHeaderRect = pinnedHeaderInterface.getPinnedHeaderRect();
+ int pinnedHeaderPosition = pinnedHeaderInterface.getPinnedHeaderPosition();
+ if (pinnedHeaderRect == null || pinnedHeaderPosition == -1) {
+ return super.onInterceptTouchEvent(e);
+ }
+ switch (e.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ if (pinnedHeaderRect.contains((int) e.getX(), (int) e.getY())) {
+ return true;
+ }
+ break;
+ }
+ return super.onInterceptTouchEvent(e);
+ }
+
+
+ /**
+ * 如果有固定的header的情况
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mPinnedHeaderClickListener == null) {
+ return super.onTouchEvent(ev);
+ }
+ IPinnedHeaderDecoration pinnedHeaderInterface = getPinnedHeaderDecoration();
+ if (pinnedHeaderInterface == null) {
+ return super.onTouchEvent(ev);
+ }
+ Rect pinnedHeaderRect = pinnedHeaderInterface.getPinnedHeaderRect();
+ int pinnedHeaderPosition = pinnedHeaderInterface.getPinnedHeaderPosition();
+ if (pinnedHeaderRect == null || pinnedHeaderPosition == -1) {
+ return super.onTouchEvent(ev);
+ }
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mPinnedHeaderHandle = false;
+ if (pinnedHeaderRect.contains((int) ev.getX(), (int) ev.getY())) {
+ mPinnedHeaderHandle = true;
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mPinnedHeaderHandle) {
+ if (!pinnedHeaderRect.contains((int) ev.getX(), (int) ev.getY())) {
+ MotionEvent cancel = MotionEvent.obtain(ev);
+ cancel.setAction(MotionEvent.ACTION_CANCEL);
+ super.dispatchTouchEvent(cancel);
+
+ MotionEvent down = MotionEvent.obtain(ev);
+ down.setAction(MotionEvent.ACTION_DOWN);
+ return super.dispatchTouchEvent(down);
+ } else {
+ return true;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ float x = ev.getX();
+ float y = ev.getY();
+ // 如果 HeaderView 是可见的 , 点击在 HeaderView 内 , 那么触发pinned header 点击
+ if (mPinnedHeaderHandle && pinnedHeaderRect.contains((int) x, (int) y)) {
+ mPinnedHeaderClickListener.onPinnedHeaderClick(pinnedHeaderPosition);
+ mPinnedHeaderHandle = false;
+ return true;
+ }
+ mPinnedHeaderHandle = false;
+ break;
+ default:
+ break;
+ }
+ return super.onTouchEvent(ev);
+ }
+
+ public IPinnedHeaderDecoration getPinnedHeaderDecoration() {
+ int decorationIndex = 0;
+ RecyclerView.ItemDecoration itemDecoration;
+ do {
+ itemDecoration = getItemDecorationAt(decorationIndex);
+ if (itemDecoration instanceof IPinnedHeaderDecoration) {
+ return (IPinnedHeaderDecoration) itemDecoration;
+ }
+ decorationIndex++;
+ } while (itemDecoration != null);
+ return null;
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/my/touchHelper/MyItemTouchHelperCallback.java b/app/src/main/java/xyz/somelou/rss/my/touchHelper/MyItemTouchHelperCallback.java
new file mode 100644
index 0000000..bc9811a
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/my/touchHelper/MyItemTouchHelperCallback.java
@@ -0,0 +1,97 @@
+package xyz.somelou.rss.my.touchHelper;
+
+import android.graphics.Color;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
+
+import java.util.Collections;
+import java.util.List;
+
+import xyz.somelou.rss.my.myGroup.MyGroupRecyclerAdapter;
+
+public class MyItemTouchHelperCallback extends ItemTouchHelper.Callback {
+
+ private MyGroupRecyclerAdapter myAdapter;
+ private List datas;
+
+ public void setDatas(List datas) {
+ this.datas = datas;
+ }
+
+ public void setMyAdapter(MyGroupRecyclerAdapter myAdapter) {
+ this.myAdapter = myAdapter;
+ }
+
+ public MyItemTouchHelperCallback(MyGroupRecyclerAdapter myAdapter){
+ this.myAdapter = myAdapter;
+ }
+
+ //Callback回调监听时先调用的,用来判断当前是什么动作,比如判断方向(监听哪个方向的拖动)
+ @Override
+ public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
+ //要监听的拖拽方向,不监听为0
+
+ int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
+ //要监听的侧滑方向,不监听为0
+ int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
+ int flags = makeMovementFlags(dragFlags, 0);
+ return flags;//即监听向上也监听向下
+
+ }
+
+ @Override
+ public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
+ //得到当拖拽的viewHolder的Position
+ int fromPosition = viewHolder.getAdapterPosition();
+ //拿到当前拖拽到的item的viewHolder
+ int toPosition = target.getAdapterPosition();
+ if(!myAdapter.isPinnedPosition(fromPosition)&&!myAdapter.isPinnedPosition(toPosition)){
+ System.out.println("进入");
+ if (fromPosition < toPosition) {
+ for (int i = fromPosition; i < toPosition; i++) {
+ Collections.swap(datas, i, i + 1);
+ }
+ } else {
+ for (int i = fromPosition; i > toPosition; i--) {
+ Collections.swap(datas, i, i - 1);
+ }
+ }
+ myAdapter.notifyItemMoved(fromPosition, toPosition);
+ return true;
+ }else if(!myAdapter.isPinnedPosition(fromPosition)&&myAdapter.isPinnedPosition(toPosition)){
+ System.out.println("进入clear");
+ datas.remove(toPosition);
+ myAdapter.notifyDataSetChanged();
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
+
+ }
+
+ @Override
+ public boolean isLongPressDragEnabled() {
+ return true;
+ }
+
+ @Override
+ public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
+ if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
+ viewHolder.itemView.setBackgroundColor(Color.LTGRAY);
+ }
+ super.onSelectedChanged(viewHolder, actionState);
+ }
+
+ @Override
+ public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+ super.clearView(recyclerView, viewHolder);
+ viewHolder.itemView.setBackgroundColor(0);
+ }
+
+
+}
diff --git a/app/src/main/java/xyz/somelou/rss/subscribe/SubscribeFragment.java b/app/src/main/java/xyz/somelou/rss/subscribe/SubscribeFragment.java
new file mode 100644
index 0000000..010574f
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/subscribe/SubscribeFragment.java
@@ -0,0 +1,188 @@
+package xyz.somelou.rss.subscribe;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.AlertDialog;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+
+import xyz.somelou.rss.R;
+import xyz.somelou.rss.adapter.RSSSubscribeAdapter;
+import xyz.somelou.rss.bean.RSSItemBean;
+import xyz.somelou.rss.bean.RSSUrl;
+import xyz.somelou.rss.db.impl.RSSUrlDALImpl;
+import xyz.somelou.rss.enums.SubscribeStatus;
+import xyz.somelou.rss.subscribe.channel.ChannelActivity;
+import xyz.somelou.rss.utils.RSSUtil;
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Activities that contain this fragment must implement the
+ * to handle interaction events.
+ * Use the {@link SubscribeFragment#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class SubscribeFragment extends Fragment {
+
+ private ArrayList subs;
+ private ArrayList pages;
+ private RSSSubscribeAdapter adapter;
+ private RSSUrlDALImpl RSSdal;
+ private ListView lv;
+ private View contentView;
+ private EditText key_word;
+ private RSSUtil util;
+
+ public SubscribeFragment() {
+ // Required empty public constructor
+ }
+
+ /**
+ * Use this factory method to create a new instance of
+ * this fragment using the provided parameters.
+ *
+ * @return A new instance of fragment SubscribeFragment.
+ */
+ public static SubscribeFragment newInstance() {
+ SubscribeFragment fragment = new SubscribeFragment();
+ Bundle args = new Bundle();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ // Inflate the layout for this fragment
+ contentView=inflater.inflate(R.layout.fragment_subscribe, container, false);
+ //初始化页面
+ RSSdal=new RSSUrlDALImpl(getContext());
+ subs=RSSdal.getSubscribe(new ArrayList());//获取已订阅的频道
+ /*if (subs.size()>0) {
+ for (int i = 0; i < subs.size(); i++) {
+ Log.i("测试组别", subs.get(i).getName()+"组:"+subs.get(i).getGroupName());
+ }
+ }
+ else
+ Log.i("测试已订阅列表","空");*/
+ adapter=new RSSSubscribeAdapter(getActivity(),subs);
+ lv=contentView.findViewById(R.id.SubList);//绑定布局
+ lv.setAdapter(adapter);//设置适配器
+ util=new RSSUtil();
+ //Log.i("默认validate",util.getValidate().toString());
+ pages=new ArrayList<>();
+ return contentView;
+
+ }
+ //重写两个菜单有关的函数
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.subscribe_menu,menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ key_word = new EditText(getActivity());
+ switch(item.getItemId()){
+ case R.id.subscribe_menu_add:
+ key_word.setHint("http://www.reuters.com/tools/rss");
+ AlertDialog.Builder dialog=new AlertDialog.Builder(getActivity());
+ Log.i("对话框之前","--");
+ //设置标题,图标,视图
+ dialog.setTitle("添加RSS源")
+ .setIcon(android.R.drawable.ic_input_add)
+ .setView(key_word)
+ .setPositiveButton("确定", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ if (key_word.getText().toString() != null) {
+ String rss=key_word.getText().toString();
+ //如果rss源解析成功
+ util.setRssUrl(rss);
+ if (util.getValidate()){
+ //添加RSS到RSS表,默认不订阅
+ RSSdal.insertOneData(rss,null, SubscribeStatus.NO_SUBSCRIBE);
+ Log.i("-------订阅界面:","添加成功");
+ }
+ else{
+ Toast.makeText(getContext(),"非法RSS源,无法添加!", Toast.LENGTH_SHORT).show();
+ Log.i("-------订阅界面:","添加失败");
+ }
+
+ }
+
+ }
+ }).setNegativeButton("取消", null);
+ dialog.show();
+ /*key_word.setFocusable(true);
+ key_word.setFocusableInTouchMode(true);
+ //请求获得焦点
+ key_word.requestFocus();*/
+ Log.i("对话框之后","--");
+ break;
+ case R.id.subscribe_menu_refresh:
+ //手动刷新
+ subs.clear();
+ subs.addAll(RSSdal.getSubscribe(new ArrayList()));
+ adapter.notifyDataSetChanged();
+ Toast.makeText(getActivity(),"刷新成功", Toast.LENGTH_SHORT).show();
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
+
+ Intent goToChannel=new Intent(getActivity(), ChannelActivity.class);
+ //传频道标题和所有文章
+ util.setRssUrl(subs.get(i).getUrl());//先设置网址进行解析
+ pages= (ArrayList)util.getRssItemBeans();//再保存文章
+ goToChannel.putExtra("pages",pages);
+ goToChannel.putExtra("url",subs.get(i).getUrl());
+ goToChannel.putExtra("title",subs.get(i).getName());
+ startActivity(goToChannel);
+ //Toast.makeText(getActivity(), "第" + (i+1) + "行", Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ }
+
+}
diff --git a/app/src/main/java/xyz/somelou/rss/subscribe/channel/ChannelActivity.java b/app/src/main/java/xyz/somelou/rss/subscribe/channel/ChannelActivity.java
new file mode 100644
index 0000000..88f7537
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/subscribe/channel/ChannelActivity.java
@@ -0,0 +1,58 @@
+package xyz.somelou.rss.subscribe.channel;
+
+import android.content.Intent;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+
+import xyz.somelou.rss.R;
+import xyz.somelou.rss.adapter.RSSChannelAdapter;
+import xyz.somelou.rss.article.ArticleActivity;
+import xyz.somelou.rss.bean.RSSItemBean;
+import xyz.somelou.rss.utils.RSSUtil;
+
+public class ChannelActivity extends AppCompatActivity {
+ private ArrayList passages;//频道下文章
+ private String RSSchannel;//频道名称
+ private String url;//频道地址
+ private RSSUtil util;
+ private RSSChannelAdapter adapter;
+ private ListView lv;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+
+ setContentView(R.layout.activity_channel);
+ //先接收intent
+ setTitle(RSSchannel=getIntent().getStringExtra("title"));//设置activity标题
+ url=getIntent().getStringExtra("url");
+ this.setTitle(RSSchannel);
+ passages=(ArrayList) getIntent().getSerializableExtra("pages");
+ //测试
+ /*passages=new ArrayList<>();
+ for (int i=0;i parent, View view, int position, long id) {
+ Intent goToArticle=new Intent(ChannelActivity.this, ArticleActivity.class);
+ goToArticle.putExtra("url",url);
+ goToArticle.putExtra("position",position);
+ startActivity(goToArticle);
+ Log.i("--ChannelActivity", "点击了第" + (position + 1) + "篇文章");
+ }
+ });
+ super.onCreate(savedInstanceState);
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/thread/PreDatabaseThread.java b/app/src/main/java/xyz/somelou/rss/thread/PreDatabaseThread.java
new file mode 100644
index 0000000..b262d4a
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/thread/PreDatabaseThread.java
@@ -0,0 +1,24 @@
+package xyz.somelou.rss.thread;
+
+import android.content.Context;
+
+import xyz.somelou.rss.db.impl.BaseDALImpl;
+
+/**
+ * @author somelou
+ * @description 预加载数据库
+ * @date 2019-06-27
+ */
+public class PreDatabaseThread implements Runnable{
+
+ private Context context;
+
+ public PreDatabaseThread(Context context){
+ this.context=context;
+ }
+
+ @Override
+ public void run() {
+ new BaseDALImpl(context).preInsertRssUrl();
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/utils/ClearStringUtil.java b/app/src/main/java/xyz/somelou/rss/utils/ClearStringUtil.java
new file mode 100644
index 0000000..13ae0a8
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/utils/ClearStringUtil.java
@@ -0,0 +1,22 @@
+package xyz.somelou.rss.utils;
+
+/**
+ * @author somelou
+ * @description
+ * @date 2019-06-26
+ */
+public class ClearStringUtil {
+
+ /**
+ * 暂时只清洗过长
+ * @param description 描述
+ * @return
+ */
+ public static String clearDescription(String description){
+ // description过长时截取
+ if(description.length()>100){
+ description.substring(0,100);
+ }
+ return description;
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/utils/IntentUtil.java b/app/src/main/java/xyz/somelou/rss/utils/IntentUtil.java
new file mode 100644
index 0000000..57d6d25
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/utils/IntentUtil.java
@@ -0,0 +1,21 @@
+package xyz.somelou.rss.utils;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.net.Uri;
+
+/**
+ *
+ */
+public class IntentUtil {
+ public static void openUrl(Activity activity, String url) {
+ try {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(url));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ activity.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ }
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/utils/RSSUtil.java b/app/src/main/java/xyz/somelou/rss/utils/RSSUtil.java
new file mode 100644
index 0000000..34eb75a
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/utils/RSSUtil.java
@@ -0,0 +1,235 @@
+package xyz.somelou.rss.utils;
+
+/**
+ * @author somelou
+ * @description
+ * @date 2019-06-25
+ */
+
+import android.util.Log;
+
+import com.rometools.rome.feed.synd.SyndEntry;
+import com.rometools.rome.feed.synd.SyndFeed;
+import com.rometools.rome.io.FeedException;
+import com.rometools.rome.io.SyndFeedInput;
+import com.rometools.rome.io.XmlReader;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.zip.GZIPInputStream;
+
+import xyz.somelou.rss.bean.RSSItemBean;
+
+/**
+ * RSS解析工具类
+ * @author somelou
+ */
+public class RSSUtil {
+
+ // 数量
+ private int feedSize;
+ // uri
+ private String url;
+ // title
+ private String titleName;
+
+ // description
+ private String description;
+ // item
+ private List rssItemBeans;
+
+ private SyndFeed feed;
+
+ private Boolean isValidate=true;
+
+ public RSSUtil(){}
+
+ public RSSUtil(String uri) {
+ getRSSData(uri);
+ }
+
+ public String setRssUrl(String rssUrl) {
+ return getRSSData(rssUrl);
+ }
+
+ /**
+ * 新线程
+ */
+ private String getRSSData(final String rssUrl) {
+ final CountDownLatch cdl = new CountDownLatch(1);
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ doParse(rssUrl);
+ cdl.countDown();
+ }
+ }).start();
+ try {
+ cdl.await();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ isValidate=false;
+ return "failed";
+ }
+ return "success";
+ }
+
+ /**
+ * 根据不同种类的url执行不同函数
+ *
+ * @param rssUrl
+ */
+ private void doParse(String rssUrl) {
+ if(!rssUrl.startsWith("https://")&&!rssUrl.startsWith("http://")){
+ System.out.println("add https:// for url");
+ rssUrl="https://"+rssUrl;
+ }
+ if (rssUrl.endsWith(".xml")) {
+ try {
+ parseFromXml(rssUrl);
+ } catch (Exception e) {
+ e.printStackTrace();
+ isValidate=false;
+ }
+ } else {
+ try {
+ parseFromUrl(rssUrl);
+ } catch (IOException e) {
+ e.printStackTrace();
+ isValidate=false;
+ } catch (FeedException e) {
+ e.printStackTrace();
+ isValidate=false;
+ }
+ }
+ }
+
+
+ /**
+ * 从以xml形式结尾的url中获取数据
+ *
+ * @param urlXml
+ * @throws Exception
+ */
+ private void parseFromXml(String urlXml) throws Exception {
+ SyndFeedInput input = new SyndFeedInput();
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(urlXml.getBytes(StandardCharsets.UTF_8));
+ feed = input.build(new XmlReader(inputStream));
+ //setData(feed);
+ }
+
+ /**
+ * 从普通url获取数据
+ *
+ * @param url
+ * @throws IOException
+ * @throws FeedException
+ */
+ private void parseFromUrl(String url) throws IOException, FeedException {
+ SyndFeedInput input = new SyndFeedInput();
+ URLConnection connection = new URL(url).openConnection();
+
+ // 防止403
+ connection.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
+ String contentEncoding = connection.getHeaderField("Content-Encoding");
+ if (contentEncoding != null && contentEncoding.contains("gzip")) {
+ // System.out.println("content encoding is gzip");
+ GZIPInputStream gzipInputStream = new GZIPInputStream(connection.getInputStream());
+ feed = input.build(new XmlReader(gzipInputStream));
+ } else {
+ feed = input.build(new XmlReader(connection.getInputStream()));
+ }
+ }
+
+ private List setRssItemBeans(SyndFeed feed) {
+ List entries = feed.getEntries();
+ RSSItemBean item;
+ rssItemBeans = new ArrayList<>();
+ // size
+ for (int i = 0; i < feed.getEntries().size(); i++) {
+ SyndEntry entry = (SyndEntry) entries.get(i);
+ item = new RSSItemBean();
+ item.setTitle(entry.getTitle().trim());
+ item.setType(feed.getTitleEx().getValue().trim());
+ item.setUri(entry.getUri());
+ /*if (entry.getPublishedDate().toString()==null){
+ item.setPubDate(new Date());
+ Log.i("RSSUtil---","第"+i+"个文章发布日期为空");
+ }
+
+ else{*/
+ item.setPubDate(entry.getPublishedDate());
+ //Log.i("RSSUtil---","第"+i+"个文章,发布日期:"+item.getPubDate().toString());
+
+ item.setAuthor(entry.getAuthor());
+ // description过长时截取
+ item.setDescription(ClearStringUtil.clearDescription(entry.getDescription().getValue()));
+ item.setLink(entry.getLink());
+
+ rssItemBeans.add(item);
+ }
+ return this.rssItemBeans;
+ //System.out.println("titleName: " + titleName+", url: "+url);
+ }
+
+ /**
+ * 获取数量
+ * @return size
+ */
+ public int getFeedSize() {
+ return feed.getEntries().size();
+ }
+
+ public void setFeedSize(int feedSize) {
+ this.feedSize = feedSize;
+ }
+
+ /**
+ * 获取item内容
+ * @return
+ */
+ public List getRssItemBeans() {
+ return setRssItemBeans(feed);
+ }
+
+ /**
+ * 获取标题,title为空时设为unknown
+ * @return
+ */
+ public String getTitleName() {
+ Log.i("处理前的title=",feed.getTitle());
+ titleName=feed.getTitle();
+ if(titleName==null||titleName.isEmpty()){
+ titleName="unknown";
+ }
+ Log.i("处理后的title=",titleName);
+ return titleName;
+ }
+
+ /**
+ * 可能是链接,不建议使用
+ * @return
+ */
+ public String getUrl() {
+ return feed.getLink();
+ }
+
+ /**
+ * 描述,过长时截取
+ * @return
+ */
+ public String getDescription() {
+ return ClearStringUtil.clearDescription(feed.getDescription());
+ }
+
+ public Boolean getValidate() {
+ return isValidate;
+ }
+}
diff --git a/app/src/main/java/xyz/somelou/rss/utils/SwitchGroupUtil.java b/app/src/main/java/xyz/somelou/rss/utils/SwitchGroupUtil.java
new file mode 100644
index 0000000..d2c84d5
--- /dev/null
+++ b/app/src/main/java/xyz/somelou/rss/utils/SwitchGroupUtil.java
@@ -0,0 +1,58 @@
+package xyz.somelou.rss.utils;
+
+import android.content.Context;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import xyz.somelou.rss.bean.RSSUrl;
+import xyz.somelou.rss.db.impl.RSSUrlDALImpl;
+
+/*
+ * 处理分组所需必要数据的工具类
+ * 添加人:张霆伟
+ */
+public class SwitchGroupUtil {
+ private RSSUrlDALImpl rssUrlDALImpl;
+ private ArrayList rssUrlArrayList;
+ private List groupNames;
+ private List groupSortList;//将内容按照分组信息进行重组后的
+ public SwitchGroupUtil(Context context){
+ rssUrlDALImpl=new RSSUrlDALImpl(context);
+ rssUrlArrayList=new ArrayList<>();
+ rssUrlArrayList=rssUrlDALImpl.getAllData(rssUrlArrayList);
+ gainAllGroupNames();
+ groupSort();
+ }
+
+ private void gainAllGroupNames(){
+ groupNames=new ArrayList<>();
+ for (RSSUrl rssUrl:rssUrlArrayList) {
+ if(groupNames.size()==0){
+ groupNames.add(rssUrl.getGroupName());
+ }else if(!groupNames.contains(rssUrl.getGroupName())){
+ groupNames.add(rssUrl.getGroupName());
+ }
+ }
+ }
+
+ private void groupSort( ){
+ groupSortList=new ArrayList<>();
+ for(int i=0;i getGroupNames() {
+ return groupNames;
+ }
+
+ public List getGroupSortList() {
+ return groupSortList;
+ }
+}
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..1f6bb29
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bottom_navigation_selector.xml b/app/src/main/res/drawable/bottom_navigation_selector.xml
new file mode 100644
index 0000000..5567292
--- /dev/null
+++ b/app/src/main/res/drawable/bottom_navigation_selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/divider_list.xml b/app/src/main/res/drawable/divider_list.xml
new file mode 100644
index 0000000..a17d202
--- /dev/null
+++ b/app/src/main/res/drawable/divider_list.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_language_white_24dp.png b/app/src/main/res/drawable/ic_language_white_24dp.png
new file mode 100644
index 0000000..d4b5518
Binary files /dev/null and b/app/src/main/res/drawable/ic_language_white_24dp.png differ
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..0d025f9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_search_black_24dp.xml b/app/src/main/res/drawable/ic_search_black_24dp.xml
new file mode 100644
index 0000000..affc7ba
--- /dev/null
+++ b/app/src/main/res/drawable/ic_search_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_share.png b/app/src/main/res/drawable/ic_share.png
new file mode 100644
index 0000000..a35b3cd
Binary files /dev/null and b/app/src/main/res/drawable/ic_share.png differ
diff --git a/app/src/main/res/drawable/ic_star_border_white_24dp.png b/app/src/main/res/drawable/ic_star_border_white_24dp.png
new file mode 100644
index 0000000..7e41906
Binary files /dev/null and b/app/src/main/res/drawable/ic_star_border_white_24dp.png differ
diff --git a/app/src/main/res/drawable/ic_star_white_24dp.png b/app/src/main/res/drawable/ic_star_white_24dp.png
new file mode 100644
index 0000000..aa58792
Binary files /dev/null and b/app/src/main/res/drawable/ic_star_white_24dp.png differ
diff --git a/app/src/main/res/layout/activity_article.xml b/app/src/main/res/layout/activity_article.xml
new file mode 100644
index 0000000..28a55ac
--- /dev/null
+++ b/app/src/main/res/layout/activity_article.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_channel.xml b/app/src/main/res/layout/activity_channel.xml
new file mode 100644
index 0000000..f637495
--- /dev/null
+++ b/app/src/main/res/layout/activity_channel.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..beb98e9
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_find.xml b/app/src/main/res/layout/fragment_find.xml
new file mode 100644
index 0000000..36e8514
--- /dev/null
+++ b/app/src/main/res/layout/fragment_find.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_my.xml b/app/src/main/res/layout/fragment_my.xml
new file mode 100644
index 0000000..3d96a57
--- /dev/null
+++ b/app/src/main/res/layout/fragment_my.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_subscribe.xml b/app/src/main/res/layout/fragment_subscribe.xml
new file mode 100644
index 0000000..88c7222
--- /dev/null
+++ b/app/src/main/res/layout/fragment_subscribe.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_channel.xml b/app/src/main/res/layout/item_channel.xml
new file mode 100644
index 0000000..ac687c8
--- /dev/null
+++ b/app/src/main/res/layout/item_channel.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_find.xml b/app/src/main/res/layout/item_find.xml
new file mode 100644
index 0000000..6fb3377
--- /dev/null
+++ b/app/src/main/res/layout/item_find.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_subscribe.xml b/app/src/main/res/layout/item_subscribe.xml
new file mode 100644
index 0000000..d994093
--- /dev/null
+++ b/app/src/main/res/layout/item_subscribe.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/my_group_activity_recycler.xml b/app/src/main/res/layout/my_group_activity_recycler.xml
new file mode 100644
index 0000000..da1586d
--- /dev/null
+++ b/app/src/main/res/layout/my_group_activity_recycler.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/my_group_item_content.xml b/app/src/main/res/layout/my_group_item_content.xml
new file mode 100644
index 0000000..996160c
--- /dev/null
+++ b/app/src/main/res/layout/my_group_item_content.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/my_group_item_title.xml b/app/src/main/res/layout/my_group_item_title.xml
new file mode 100644
index 0000000..2b38aa2
--- /dev/null
+++ b/app/src/main/res/layout/my_group_item_title.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/bottom_menu.xml b/app/src/main/res/menu/bottom_menu.xml
new file mode 100644
index 0000000..c528969
--- /dev/null
+++ b/app/src/main/res/menu/bottom_menu.xml
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/find_menu.xml b/app/src/main/res/menu/find_menu.xml
new file mode 100644
index 0000000..547fac8
--- /dev/null
+++ b/app/src/main/res/menu/find_menu.xml
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_article.xml b/app/src/main/res/menu/menu_article.xml
new file mode 100644
index 0000000..df3bffc
--- /dev/null
+++ b/app/src/main/res/menu/menu_article.xml
@@ -0,0 +1,22 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/subscribe_menu.xml b/app/src/main/res/menu/subscribe_menu.xml
new file mode 100644
index 0000000..88dce92
--- /dev/null
+++ b/app/src/main/res/menu/subscribe_menu.xml
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..898f3ed
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dffca36
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..64ba76f
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dae5e08
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..e5ed465
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..14ed0af
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_drag_user.png b/app/src/main/res/mipmap-xxhdpi/ic_drag_user.png
new file mode 100644
index 0000000..e65e449
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_drag_user.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_find_gray.png b/app/src/main/res/mipmap-xxhdpi/ic_find_gray.png
new file mode 100644
index 0000000..505cf80
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_find_gray.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b0907ca
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d8ae031
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_library_gray.png b/app/src/main/res/mipmap-xxhdpi/ic_library_gray.png
new file mode 100644
index 0000000..9cddbe1
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_library_gray.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_list_gray.png b/app/src/main/res/mipmap-xxhdpi/ic_list_gray.png
new file mode 100644
index 0000000..250de6d
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_list_gray.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_my_gray.png b/app/src/main/res/mipmap-xxhdpi/ic_my_gray.png
new file mode 100644
index 0000000..1725df1
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_my_gray.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/appicon.jpg b/app/src/main/res/mipmap-xxxhdpi/appicon.jpg
new file mode 100644
index 0000000..0bba458
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/appicon.jpg differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/article.png b/app/src/main/res/mipmap-xxxhdpi/article.png
new file mode 100644
index 0000000..0602ee8
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/article.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2c18de9
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..beed3cd
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values/bottom.xml b/app/src/main/res/values/bottom.xml
new file mode 100644
index 0000000..8dddcbf
--- /dev/null
+++ b/app/src/main/res/values/bottom.xml
@@ -0,0 +1,6 @@
+
+
+ 订阅
+ 发现
+ 我的
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..e92ed60
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,13 @@
+
+
+ #008577
+ #00574B
+ #D81B60
+ #98F5FF
+ #E0E0E0
+ #3F51B5
+ #757575
+ #424242
+ #424242
+ #9e9e9e
+
diff --git a/app/src/main/res/values/database.xml b/app/src/main/res/values/database.xml
new file mode 100644
index 0000000..3f86adf
--- /dev/null
+++ b/app/src/main/res/values/database.xml
@@ -0,0 +1,11 @@
+
+
+ RSS_URL
+ FAVOR_RSS_ITEM
+
+
+ - https://www.zhihu.com/rss
+ - http://haijiaoshi.com/feed
+ - http://www.4sbooks.com/feed
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..044c28d
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,13 @@
+
+ RSSreader
+
+
+ Hello blank fragment
+
+ 这是RSS图标
+ 搜索
+ 刷新
+ Share
+ Favorite
+ Open Article Link
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..0d653f3
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/app/src/test/java/xyz/somelou/rss/ExampleUnitTest.java b/app/src/test/java/xyz/somelou/rss/ExampleUnitTest.java
new file mode 100644
index 0000000..01afe09
--- /dev/null
+++ b/app/src/test/java/xyz/somelou/rss/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package xyz.somelou.rss;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..cdf1d5d
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ google()
+ jcenter()
+
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.2.0'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..82618ce
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,15 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..0696ba3
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Jul 01 11:29:59 CST 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..9d82f78
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app'