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 @@ + + + + + + + + + +