diff --git a/.gitignore b/.gitignore index 4e4932c4..a8aa9588 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ +.DS_Store + # Built application files *.apk *.ap_ +*.json # Files for the Dalvik VM *.dex diff --git a/README.md b/README.md index 6fd402af..235e045c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ![API](https://img.shields.io/badge/API-15%2B-brightgreen.svg?style=flat) ## Important Notice -Versions 1.4 and 1.4.1 have been deleted due to a critical dependency error. Please forgive me and update to version 1.4.4 +Versions 1.4 and 1.4.1 have been deleted due to a critical dependency error. Please forgive me and update to the latest version available. ## About @@ -27,10 +27,63 @@ This is an Android library to parse a RSS Feed. You can retrive the following in The library is uploaded in jCenter, so you can easily add the dependency: ```Gradle dependencies { - compile 'com.prof.rssparser:rssparser:1.4.4' + compile 'com.prof.rssparser:rssparser:2.0.0' } ``` #### Use: + +Starting from the version 2.x, the library has been completely rewritten using Kotlin and the coroutines. However, The compatibility with Java has been maintained and the same code of the versions 1.x can be used. + +If you are using Kotlin you need to [launch](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html) the coroutine that retrieves the articles. + +```Kotlin +import com.prof.rssparser.Article +import com.prof.rssparser.Parser + +//url of RSS feed +private val url = "https://www.androidauthority.com/feed" + +coroutineScope.launch(Dispatchers.Main) { + try { + val parser = Parser() + val articleList = parser.getArticles(url) + // The list contains all article's data. For example you can use it for your adapter. + } catch (e: Exception) { + // Handle the exception + } +} +``` +You can give a look to the full kotlin sample by clicking [here](https://github.com/prof18/RSS-Parser/tree/master/samplekotlin) + +If you are still using Java, the code is very similar to the older versions of the library: + +```Java +import com.prof.rssparser.Article; +import com.prof.rssparser.OnTaskCompleted; +import com.prof.rssparser.Parser; + +Parser parser = new Parser(); +parser.onFinish(new OnTaskCompleted() { + + //what to do when the parsing is done + @Override + public void onTaskCompleted(List
list) { + // The list contains all article's data. For example you can use it for your adapter. + } + + //what to do in case of error + @Override + public void onError(Exception e) { + // Handle the exception + } +}); +parser.execute(urlString); +``` +The full Java sample is available [here](https://github.com/prof18/RSS-Parser/tree/master/samplejava) + +##### Version 1.4.4 and below: + + ```Java import com.prof.rssparser.Article; import com.prof.rssparser.Parser; @@ -38,7 +91,6 @@ import com.prof.rssparser.Parser; //url of RSS feed String urlString = "http://www.androidcentral.com/feed"; Parser parser = new Parser(); -parser.execute(urlString); parser.onFinish(new Parser.OnTaskCompleted() { @Override @@ -52,18 +104,20 @@ parser.onFinish(new Parser.OnTaskCompleted() { //what to do in case of error } }); +parser.execute(urlString); ``` ## Sample app -I wrote a simple app that shows articles from Android Authority. If in the article's content there isn't a image, a placeholder will be load. +I wrote a simple app that shows articles from Android Authority. If in the article's content there isn't an image, a placeholder will be loaded. -You can browse the code in this repo. +The sample is written both in Kotlin and Java. You can browse the Kotlin code [here](https://github.com/prof18/RSS-Parser/tree/master/samplekotlin) and the Java code [here](https://github.com/prof18/RSS-Parser/tree/master/samplejava) You can also download the apk file to try it! -Please use the issues tracker only to report issues. If you have any kind of question you can ask it on [the blog post on my website](http://www.marcogomiero.com/blog/rss-parser-library) +Please use the issues tracker only to report issues. If you have any kind of question you can ask it on [the blog post that I wrote](https://medium.com/@marcogomiero/how-to-easily-handle-rss-feeds-on-android-with-rss-parser-8acc98e8926f) ## Changelog +- 22 December 2018 - Rewrote library with Kotlin - Version 2.0.0 - 8 December 2018 - Include thrown exception in onError() callback (PR #22) - Version 1.4.5 - 7 Semptember 2018 - Add more sources for the featured image. Removed unused resources and improved the parsing of the image. Fixed dependency errors. - Version 1.4.4 - 14 December 2017 - Little fixes on Error Management - Version 1.3.1 diff --git a/RSS Parser.apk b/RSS Parser.apk deleted file mode 100644 index 08c8cf55..00000000 Binary files a/RSS Parser.apk and /dev/null differ diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index f9b7655e..00000000 --- a/app/build.gradle +++ /dev/null @@ -1,32 +0,0 @@ -apply plugin: 'com.android.application' - -android { - compileSdkVersion 27 - - defaultConfig { - applicationId "com.prof.rssparser.example" - minSdkVersion 14 - targetSdkVersion 27 - versionCode 6 - versionName "1.2.3" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - testImplementation 'junit:junit:4.12' - implementation project(":rssparser") -// implementation 'com.prof.rssparser:rssparser:1.4.5' - implementation 'com.android.support:appcompat-v7:27.1.0' - implementation 'com.android.support:design:27.1.0' - implementation 'com.android.support:recyclerview-v7:27.1.0' - implementation 'com.android.support:cardview-v7:27.1.0' - implementation 'com.squareup.picasso:picasso:2.71828' - implementation 'com.android.support.constraint:constraint-layout:1.1.3' -} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro deleted file mode 100644 index 45dc58a5..00000000 --- a/app/proguard-rules.pro +++ /dev/null @@ -1,17 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /opt/android-sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# 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 *; -#} diff --git a/app/src/androidTest/java/com/prof/rssparser/example/ApplicationTest.java b/app/src/androidTest/java/com/prof/rssparser/example/ApplicationTest.java deleted file mode 100644 index f6ece0e3..00000000 --- a/app/src/androidTest/java/com/prof/rssparser/example/ApplicationTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.prof.rssparser.example; - -import android.app.Application; -import android.test.ApplicationTestCase; - -/** - * Testing Fundamentals - */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); - } -} \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index cde69bcc..00000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c133a0cb..00000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index bfa42f0e..00000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 324e72cd..00000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index aee44e13..00000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml deleted file mode 100644 index 63fc8164..00000000 --- a/app/src/main/res/values-w820dp/dimens.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - 64dp - diff --git a/app/src/test/java/com/prof/rssparser/example/ExampleUnitTest.java b/app/src/test/java/com/prof/rssparser/example/ExampleUnitTest.java deleted file mode 100644 index 877bff25..00000000 --- a/app/src/test/java/com/prof/rssparser/example/ExampleUnitTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.prof.rssparser.example; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * To work on unit tests, switch the Test Artifact in the Build Variants view. - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 35e69947..d0ce7d6b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,18 +1,22 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.3.11' repositories { - jcenter() + google() maven { url 'https://maven.google.com/' name 'Google' } - google() + jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.4' + classpath 'com.android.tools.build:gradle:3.2.1' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version" + // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -21,11 +25,11 @@ buildscript { allprojects { repositories { - jcenter() maven { url 'https://maven.google.com/' name 'Google' } + jcenter() } } diff --git a/gradle.properties b/gradle.properties index 1d3591c8..915f0e66 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,6 @@ # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true \ No newline at end of file +# org.gradle.parallel=true +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 980db174..6c3c392f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat May 05 13:17:56 CEST 2018 +#Sat Dec 08 17:59:48 CET 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/rssparser/build.gradle b/rssparser/build.gradle index e5441e56..09357c99 100644 --- a/rssparser/build.gradle +++ b/rssparser/build.gradle @@ -1,14 +1,15 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' android { - compileSdkVersion 27 + compileSdkVersion 28 defaultConfig { minSdkVersion 14 - targetSdkVersion 27 - versionCode 9 - versionName "1.4.5" + targetSdkVersion 28 + versionCode 20000 + versionName "2.0.0" } buildTypes { @@ -21,8 +22,14 @@ android { dependencies { implementation 'com.squareup.okhttp3:okhttp:3.10.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.0' + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0" } apply from: 'publish.gradle' +repositories { + mavenCentral() +} diff --git a/rssparser/publish.gradle b/rssparser/publish.gradle index 629541b2..4e7bfaf2 100644 --- a/rssparser/publish.gradle +++ b/rssparser/publish.gradle @@ -1,7 +1,7 @@ apply plugin: 'maven-publish' apply plugin: 'com.jfrog.bintray' -version '1.4.5' +version '2.0.0' group 'com.prof.rssparser' publishing { diff --git a/rssparser/src/main/java/com/prof/rssparser/Article.java b/rssparser/src/main/java/com/prof/rssparser/Article.java deleted file mode 100644 index 90f947d2..00000000 --- a/rssparser/src/main/java/com/prof/rssparser/Article.java +++ /dev/null @@ -1,117 +0,0 @@ -/* -* Copyright 2016 Marco Gomiero -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -*/ - -package com.prof.rssparser; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -/** - * Created by Marco Gomiero on 12/02/2015. - */ -public class Article { - - private String title; - private String author; - private String link; - private Date pubDate; - private String description; - private String content; - private String image; - private List categories; - - public String getTitle() { - return title; - } - - public String getAuthor() { - return author; - } - - public String getLink() { - return link; - } - - public Date getPubDate() { - return pubDate; - } - - public String getDescription() { - return description; - } - - public String getContent() { - return content; - } - - public String getImage() { - return image; - } - - public void setTitle(String title) { - this.title = title; - } - - public void setAuthor(String author) { - this.author = author; - } - - public void setLink(String link) { - this.link = link; - } - - public void setPubDate(Date pubDate) { - this.pubDate = pubDate; - } - - public void setDescription(String description) { - this.description = description; - } - - public void setContent(String content) { - this.content = content; - } - - public void setImage(String image) { - this.image = image; - } - - public List getCategories() { - return categories; - } - - public void addCategory(String category) { - if (categories == null) - categories = new ArrayList<>(); - categories.add(category); - } - - @Override - public String toString() { - return "Article{" + - "title='" + title + '\'' + - ", author='" + author + '\'' + - ", link='" + link + '\'' + - ", pubDate=" + pubDate + - ", description='" + description + '\'' + - ", content='" + content + '\'' + - ", image='" + image + '\'' + - ", categories=" + categories + - '}'; - } -} \ No newline at end of file diff --git a/rssparser/src/main/java/com/prof/rssparser/Article.kt b/rssparser/src/main/java/com/prof/rssparser/Article.kt new file mode 100644 index 00000000..cd4f8df1 --- /dev/null +++ b/rssparser/src/main/java/com/prof/rssparser/Article.kt @@ -0,0 +1,39 @@ +/* +* Copyright 2016 Marco Gomiero +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ + +package com.prof.rssparser + +import java.util.* + +data class Article( + var title: String? = null, + var author: String? = null, + var link: String? = null, + var pubDate: Date? = null, + var description: String? = null, + var content: String? = null, + var image: String? = null, + private var _categories: MutableList = mutableListOf() +) { + + val categories: MutableList + get() = _categories + + fun addCategory(category: String) { + _categories.add(category) + } +} \ No newline at end of file diff --git a/rssparser/src/main/java/com/prof/rssparser/OnTaskCompleted.kt b/rssparser/src/main/java/com/prof/rssparser/OnTaskCompleted.kt new file mode 100644 index 00000000..90c82b02 --- /dev/null +++ b/rssparser/src/main/java/com/prof/rssparser/OnTaskCompleted.kt @@ -0,0 +1,23 @@ +/* +* Copyright 2016 Marco Gomiero +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ + +package com.prof.rssparser + +interface OnTaskCompleted { + fun onTaskCompleted(list: MutableList
) + fun onError(e: Exception) +} \ No newline at end of file diff --git a/rssparser/src/main/java/com/prof/rssparser/Parser.java b/rssparser/src/main/java/com/prof/rssparser/Parser.java deleted file mode 100644 index 8ac788b0..00000000 --- a/rssparser/src/main/java/com/prof/rssparser/Parser.java +++ /dev/null @@ -1,99 +0,0 @@ -/* -* Copyright 2016 Marco Gomiero -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -*/ -package com.prof.rssparser; - -import android.os.AsyncTask; -import android.util.Log; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Observable; -import java.util.Observer; - -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; - -/** - * Created by Marco Gomiero on 6/17/16. - */ -public class Parser extends AsyncTask implements Observer { - - private XMLParser xmlParser; - private static ArrayList
articles = new ArrayList<>(); - - private OnTaskCompleted onComplete; - - public Parser() { - - xmlParser = new XMLParser(); - xmlParser.addObserver(this); - } - - public interface OnTaskCompleted { - void onTaskCompleted(ArrayList
list); - - void onError(Exception exception); - } - - public void onFinish(OnTaskCompleted onComplete) { - this.onComplete = onComplete; - } - - @Override - protected String doInBackground(String... ulr) { - - Response response = null; - OkHttpClient client = new OkHttpClient(); - Request request = new Request.Builder() - .url(ulr[0]) - .build(); - - try { - response = client.newCall(request).execute(); - if (response.isSuccessful()) - return response.body().string(); - } catch (IOException e) { - e.printStackTrace(); - onComplete.onError(e); - } - return null; - } - - @Override - protected void onPostExecute(String result) { - - if (result != null) { - try { - xmlParser.parseXML(result); - Log.i("RSS Parser ", "RSS parsed correctly!"); - } catch (Exception e) { - e.printStackTrace(); - onComplete.onError(e); - } - } else - onComplete.onError(new Exception("RSS parse operation returned null")); - } - - @Override - @SuppressWarnings("unchecked") - public void update(Observable observable, Object data) { - articles = (ArrayList
) data; - onComplete.onTaskCompleted(articles); - } - -} diff --git a/rssparser/src/main/java/com/prof/rssparser/Parser.kt b/rssparser/src/main/java/com/prof/rssparser/Parser.kt new file mode 100644 index 00000000..21c32b06 --- /dev/null +++ b/rssparser/src/main/java/com/prof/rssparser/Parser.kt @@ -0,0 +1,61 @@ +/* +* Copyright 2016 Marco Gomiero +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ + +package com.prof.rssparser + +import com.prof.rssparser.engine.XMLFetcher +import com.prof.rssparser.engine.XMLParser +import com.prof.rssparser.enginecoroutine.CoroutineEngine +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.withContext +import java.lang.Exception +import java.util.concurrent.Executors + + +class Parser { + + private lateinit var onComplete: OnTaskCompleted + + fun onFinish(onComplete: OnTaskCompleted) { + this.onComplete = onComplete + } + + fun execute(url: String) { + val service = Executors.newFixedThreadPool(2) + val f1 = service.submit(XMLFetcher(url)) + try { + val rssFeed = f1.get() + val f2 = service.submit(XMLParser(rssFeed)) + onComplete.onTaskCompleted(f2.get()) + } catch (e: Exception) { + onComplete.onError(e) + } finally { + service.shutdown() + } + } + + @Throws(Exception::class) + suspend fun getArticles(url: String) = + withContext(Dispatchers.IO) { + val xml = async { CoroutineEngine.fetchXML(url) } + return@withContext CoroutineEngine.parseXML(xml) + } + + +} + diff --git a/rssparser/src/main/java/com/prof/rssparser/XMLParser.java b/rssparser/src/main/java/com/prof/rssparser/XMLParser.java deleted file mode 100644 index c34a8ab8..00000000 --- a/rssparser/src/main/java/com/prof/rssparser/XMLParser.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2016 Marco Gomiero - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.prof.rssparser; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; - -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Date; -import java.util.Observable; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Created by Marco Gomiero on 12/02/2015. - */ - -public class XMLParser extends Observable { - - private ArrayList
articles; - private Article currentArticle; - - public XMLParser() { - articles = new ArrayList<>(); - currentArticle = new Article(); - } - - public void parseXML(String xml) throws XmlPullParserException, IOException { - - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - - factory.setNamespaceAware(false); - XmlPullParser xmlPullParser = factory.newPullParser(); - - xmlPullParser.setInput(new StringReader(xml)); - boolean insideItem = false; - int eventType = xmlPullParser.getEventType(); - - while (eventType != XmlPullParser.END_DOCUMENT) { - - if (eventType == XmlPullParser.START_TAG) { - if (xmlPullParser.getName().equalsIgnoreCase("item")) { - insideItem = true; - - } else if (xmlPullParser.getName().equalsIgnoreCase("title")) { - if (insideItem) { - String title = xmlPullParser.nextText(); - currentArticle.setTitle(title); - } - - } else if (xmlPullParser.getName().equalsIgnoreCase("link")) { - if (insideItem) { - String link = xmlPullParser.nextText(); - currentArticle.setLink(link); - } - - } else if (xmlPullParser.getName().equalsIgnoreCase("dc:creator")) { - if (insideItem) { - String author = xmlPullParser.nextText(); - currentArticle.setAuthor(author); - } - - } else if (xmlPullParser.getName().equalsIgnoreCase("category")) { - if (insideItem) { - String category = xmlPullParser.nextText(); - currentArticle.addCategory(category); - } - - } else if (xmlPullParser.getName().equalsIgnoreCase("media:thumbnail")) { - if (insideItem) { - String img = xmlPullParser.getAttributeValue(null, "url"); - currentArticle.setImage(img); - } - - } else if (xmlPullParser.getName().equalsIgnoreCase("description")) { - if (insideItem) { - String description = xmlPullParser.nextText(); - if (currentArticle.getImage() == null) { - currentArticle.setImage(getImageUrl(description)); - } - currentArticle.setDescription(description); - } - - } else if (xmlPullParser.getName().equalsIgnoreCase("content:encoded")) { - if (insideItem) { - String content = xmlPullParser.nextText(); - if (currentArticle.getImage() == null) { - currentArticle.setImage(getImageUrl(content)); - } - currentArticle.setContent(content); - } - - } else if (xmlPullParser.getName().equalsIgnoreCase("pubDate")) { - Date pubDate = new Date(xmlPullParser.nextText()); - currentArticle.setPubDate(pubDate); - } - - } else if (eventType == XmlPullParser.END_TAG && xmlPullParser.getName().equalsIgnoreCase("item")) { - insideItem = false; - articles.add(currentArticle); - currentArticle = new Article(); - } - eventType = xmlPullParser.next(); - } - triggerObserver(); - } - - - private void triggerObserver() { - setChanged(); - notifyObservers(articles); - } - - /** - * Finds the first img tag and get the src as featured image - * - * @param input The content in which to search for the tag - * @return The url, if there is one - */ - private String getImageUrl(String input) { - - String url = null; - Pattern patternImg = Pattern.compile("()"); - Matcher matcherImg = patternImg.matcher(input); - if (matcherImg.find()) { - String imgTag = matcherImg.group(1); - Pattern patternLink = Pattern.compile("src\\s*=\\s*\"(.+?)\""); - Matcher matcherLink = patternLink.matcher(imgTag); - if (matcherLink.find()) { - url = matcherLink.group(1); - } - } - return url; - } -} \ No newline at end of file diff --git a/rssparser/src/main/java/com/prof/rssparser/core/CoreXMLFetcher.kt b/rssparser/src/main/java/com/prof/rssparser/core/CoreXMLFetcher.kt new file mode 100644 index 00000000..e3b3d3b4 --- /dev/null +++ b/rssparser/src/main/java/com/prof/rssparser/core/CoreXMLFetcher.kt @@ -0,0 +1,20 @@ +package com.prof.rssparser.core + +import okhttp3.OkHttpClient +import okhttp3.Request +import java.lang.Exception + +object CoreXMLFetcher { + + @Throws(Exception::class) + fun fetchXML(url: String): String { + val client = OkHttpClient() + val request = Request.Builder() + .url(url) + .build() + + val response = client.newCall(request).execute() + return response.body()!!.string() + } + +} \ No newline at end of file diff --git a/rssparser/src/main/java/com/prof/rssparser/core/CoreXMLParser.kt b/rssparser/src/main/java/com/prof/rssparser/core/CoreXMLParser.kt new file mode 100644 index 00000000..046980c5 --- /dev/null +++ b/rssparser/src/main/java/com/prof/rssparser/core/CoreXMLParser.kt @@ -0,0 +1,140 @@ +/* +* Copyright 2016 Marco Gomiero +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ + +package com.prof.rssparser.core + +import com.prof.rssparser.Article +import com.prof.rssparser.utils.RSSKeywords +import org.xmlpull.v1.XmlPullParser +import org.xmlpull.v1.XmlPullParserException +import org.xmlpull.v1.XmlPullParserFactory +import java.io.IOException +import java.io.StringReader +import java.text.SimpleDateFormat +import java.util.* +import java.util.regex.Pattern + +object CoreXMLParser { + + @Throws(XmlPullParserException::class, IOException::class) + fun parseXML(xml: String): MutableList
{ + + val articleList = mutableListOf
() + var currentArticle = Article() + + val factory = XmlPullParserFactory.newInstance() + factory.isNamespaceAware = false + + val xmlPullParser = factory.newPullParser() + xmlPullParser.setInput(StringReader(xml)) + + // A flag just to be sure of the correct parsing + var insideItem = false + + var eventType = xmlPullParser.eventType + + // Start parsing the xml + while (eventType != XmlPullParser.END_DOCUMENT) { + + // Start parsing the item + if (eventType == XmlPullParser.START_TAG) { + if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM, ignoreCase = true)) { + insideItem = true + + } else if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_TITLE, ignoreCase = true)) { + if (insideItem) { + currentArticle.title = xmlPullParser.nextText() + } + + } else if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_LINK, ignoreCase = true)) { + if (insideItem) { + currentArticle.link = xmlPullParser.nextText() + } + + } else if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_AUTHOR, ignoreCase = true)) { + if (insideItem) { + currentArticle.author = xmlPullParser.nextText() + } + + } else if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_CATEGORY, ignoreCase = true)) { + if (insideItem) { + currentArticle.addCategory(xmlPullParser.nextText()) + } + + } else if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_THUMBNAIL, ignoreCase = true)) { + if (insideItem) { + currentArticle.image = xmlPullParser.getAttributeValue(null, RSSKeywords.RSS_ITEM_URL) + } + + } else if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_DESCRIPTION, ignoreCase = true)) { + if (insideItem) { + val description = xmlPullParser.nextText() + currentArticle.description = description + if (currentArticle.image == null) { + currentArticle.image = getImageUrl(description) + } + } + + } else if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_CONTENT, ignoreCase = true)) { + if (insideItem) { + val content = xmlPullParser.nextText() + currentArticle.content = content + if (currentArticle.image == null) { + currentArticle.image = getImageUrl(content) + } + } + + } else if (xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_PUB_DATE, ignoreCase = true)) { + // RSS date format is the following: Sat, 15 Dec 2018 12:00:32 +0000 + // https://validator.w3.org/feed/docs/rss2.html + val sdf = SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.getDefault()) + currentArticle.pubDate = sdf.parse(xmlPullParser.nextText()) + } + + } else if (eventType == XmlPullParser.END_TAG && xmlPullParser.name.equals("item", ignoreCase = true)) { + // The item is correctly parsed + insideItem = false + articleList.add(currentArticle) + currentArticle = Article() + } + eventType = xmlPullParser.next() + } + return articleList + } + + /** + * Finds the first img tag and get the src as featured image + * + * @param input The content in which to search for the tag + * @return The url, if there is one + */ + private fun getImageUrl(input: String): String? { + + var url: String? = null + val patternImg = Pattern.compile("()") + val matcherImg = patternImg.matcher(input) + if (matcherImg.find()) { + val imgTag = matcherImg.group(1) + val patternLink = Pattern.compile("src\\s*=\\s*\"(.+?)\"") + val matcherLink = patternLink.matcher(imgTag) + if (matcherLink.find()) { + url = matcherLink.group(1) + } + } + return url + } +} \ No newline at end of file diff --git a/rssparser/src/main/java/com/prof/rssparser/engine/XMLFetcher.kt b/rssparser/src/main/java/com/prof/rssparser/engine/XMLFetcher.kt new file mode 100644 index 00000000..87715b15 --- /dev/null +++ b/rssparser/src/main/java/com/prof/rssparser/engine/XMLFetcher.kt @@ -0,0 +1,29 @@ +/* +* Copyright 2016 Marco Gomiero +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ + +package com.prof.rssparser.engine + +import com.prof.rssparser.core.CoreXMLFetcher +import java.util.concurrent.Callable + +class XMLFetcher(private val url: String) : Callable { + + @Throws(Exception::class) + override fun call(): String { + return CoreXMLFetcher.fetchXML(url) + } +} \ No newline at end of file diff --git a/rssparser/src/main/java/com/prof/rssparser/engine/XMLParser.kt b/rssparser/src/main/java/com/prof/rssparser/engine/XMLParser.kt new file mode 100644 index 00000000..cd5ead23 --- /dev/null +++ b/rssparser/src/main/java/com/prof/rssparser/engine/XMLParser.kt @@ -0,0 +1,31 @@ +/* +* Copyright 2016 Marco Gomiero +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ + +package com.prof.rssparser.engine + +import com.prof.rssparser.Article +import com.prof.rssparser.core.CoreXMLParser +import java.lang.Exception +import java.util.concurrent.Callable + +class XMLParser(var xml: String) : Callable> { + + @Throws(Exception::class) + override fun call(): MutableList
{ + return CoreXMLParser.parseXML(xml) + } +} \ No newline at end of file diff --git a/rssparser/src/main/java/com/prof/rssparser/enginecoroutine/CoroutineEngine.kt b/rssparser/src/main/java/com/prof/rssparser/enginecoroutine/CoroutineEngine.kt new file mode 100644 index 00000000..60ee5e4c --- /dev/null +++ b/rssparser/src/main/java/com/prof/rssparser/enginecoroutine/CoroutineEngine.kt @@ -0,0 +1,24 @@ +package com.prof.rssparser.enginecoroutine + +import com.prof.rssparser.core.CoreXMLFetcher +import com.prof.rssparser.core.CoreXMLParser +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.lang.Exception + +object CoroutineEngine { + + @Throws(Exception::class) + suspend fun fetchXML(url: String) = + withContext(Dispatchers.IO) { + return@withContext CoreXMLFetcher.fetchXML(url) + } + + @Throws(Exception::class) + suspend fun parseXML(xml: Deferred) = + withContext(Dispatchers.IO) { + return@withContext CoreXMLParser.parseXML(xml.await()) + } +} + diff --git a/rssparser/src/main/java/com/prof/rssparser/utils/RSSKeywords.kt b/rssparser/src/main/java/com/prof/rssparser/utils/RSSKeywords.kt new file mode 100644 index 00000000..0fd5157a --- /dev/null +++ b/rssparser/src/main/java/com/prof/rssparser/utils/RSSKeywords.kt @@ -0,0 +1,34 @@ +/* +* Copyright 2016 Marco Gomiero +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ + +package com.prof.rssparser.utils + +object RSSKeywords { + + const val RSS_ITEM = "item" + const val RSS_ITEM_TITLE = "title" + const val RSS_ITEM_LINK = "link" + const val RSS_ITEM_AUTHOR = "dc:creator" + const val RSS_ITEM_CATEGORY = "category" + const val RSS_ITEM_THUMBNAIL = "media:thumbnail" + const val RSS_ITEM_DESCRIPTION = "description" + const val RSS_ITEM_CONTENT = "content:encoded" + const val RSS_ITEM_PUB_DATE = "pubDate" + const val RSS_ITEM_URL = "url" + + +} \ No newline at end of file diff --git a/rssparser/src/main/res/values/strings.xml b/rssparser/src/main/res/values/strings.xml index 76bf74f5..72d84cf9 100644 --- a/rssparser/src/main/res/values/strings.xml +++ b/rssparser/src/main/res/values/strings.xml @@ -1,3 +1,3 @@ - RSS Parser + RSS Parser Sample Java diff --git a/app/.gitignore b/samplejava/.gitignore similarity index 100% rename from app/.gitignore rename to samplejava/.gitignore diff --git a/samplejava/build.gradle b/samplejava/build.gradle new file mode 100644 index 00000000..12b3a62c --- /dev/null +++ b/samplejava/build.gradle @@ -0,0 +1,41 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + + defaultConfig { + applicationId "com.prof.rssparser.sample.java" + minSdkVersion 14 + targetSdkVersion 28 + versionCode 20000 + versionName "2.0.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' + implementation project(":rssparser") +// implementation 'com.prof.rssparser:rssparser:2.0.0' + + // UI + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'com.google.android.material:material:1.0.0' + implementation 'androidx.recyclerview:recyclerview:1.0.0' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + + // Architecture components + implementation 'androidx.lifecycle:lifecycle-runtime:2.0.0' + implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' + + implementation 'com.squareup.picasso:picasso:2.71828' + +} + diff --git a/samplejava/proguard-rules.pro b/samplejava/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/samplejava/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/samplejava/src/androidTest/java/com/prof/rssparser/sample/java/ExampleInstrumentedTest.java b/samplejava/src/androidTest/java/com/prof/rssparser/sample/java/ExampleInstrumentedTest.java new file mode 100644 index 00000000..d3968b1f --- /dev/null +++ b/samplejava/src/androidTest/java/com/prof/rssparser/sample/java/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.prof.rssparser.sample.java; + +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("com.prof.rssparser.sample.java", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/samplejava/src/main/AndroidManifest.xml similarity index 89% rename from app/src/main/AndroidManifest.xml rename to samplejava/src/main/AndroidManifest.xml index 94562846..d770edaf 100644 --- a/app/src/main/AndroidManifest.xml +++ b/samplejava/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + package="com.prof.rssparser.sample.java"> @@ -22,5 +21,4 @@ - - \ No newline at end of file + diff --git a/app/src/main/java/com/prof/rssparser/example/ArticleAdapter.java b/samplejava/src/main/java/com/prof/rssparser/sample/java/ArticleAdapter.java similarity index 78% rename from app/src/main/java/com/prof/rssparser/example/ArticleAdapter.java rename to samplejava/src/main/java/com/prof/rssparser/sample/java/ArticleAdapter.java index e26ff212..b02d7a73 100644 --- a/app/src/main/java/com/prof/rssparser/example/ArticleAdapter.java +++ b/samplejava/src/main/java/com/prof/rssparser/sample/java/ArticleAdapter.java @@ -15,12 +15,11 @@ * */ -package com.prof.rssparser.example; +package com.prof.rssparser.sample.java; +import android.annotation.SuppressLint; import android.content.Context; import android.content.DialogInterface; -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; import android.view.View; @@ -34,37 +33,34 @@ import com.squareup.picasso.Picasso; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.Locale; -/** - * Created by Marco Gomiero on 12/02/2015. - */ +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + public class ArticleAdapter extends RecyclerView.Adapter { - private ArrayList
articles; + private List
articles; - private int rowLayout; private Context mContext; private WebView articleView; - public ArticleAdapter(ArrayList
list, int rowLayout, Context context) { + public ArticleAdapter(List
list, Context context) { this.articles = list; - this.rowLayout = rowLayout; this.mContext = context; } - public void clearData() { - if (articles != null) - articles.clear(); + public List
getArticleList() { + return articles; } @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { - View v = LayoutInflater.from(viewGroup.getContext()).inflate(rowLayout, viewGroup, false); + View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.row, viewGroup, false); return new ViewHolder(v); } @@ -75,7 +71,7 @@ public void onBindViewHolder(@NonNull final ViewHolder viewHolder, int position) Locale.setDefault(Locale.getDefault()); Date date = currentArticle.getPubDate(); - SimpleDateFormat sdf = new SimpleDateFormat("dd MMMM yyyy"); + SimpleDateFormat sdf = new SimpleDateFormat("dd MMMM yyyy", Locale.getDefault()); final String pubDateString = sdf.format(date); viewHolder.title.setText(currentArticle.getTitle()); @@ -87,21 +83,20 @@ public void onBindViewHolder(@NonNull final ViewHolder viewHolder, int position) viewHolder.pubDate.setText(pubDateString); - String categories = ""; - if (currentArticle.getCategories() != null) { - for (int i = 0; i < currentArticle.getCategories().size(); i++) { - if (i == currentArticle.getCategories().size() - 1) { - categories = categories + currentArticle.getCategories().get(i); - } else { - categories = categories + currentArticle.getCategories().get(i) + ", "; - } + StringBuilder categories = new StringBuilder(); + for (int i = 0; i < currentArticle.getCategories().size(); i++) { + if (i == currentArticle.getCategories().size() - 1) { + categories.append(currentArticle.getCategories().get(i)); + } else { + categories.append(currentArticle.getCategories().get(i)).append(", "); } } - viewHolder.category.setText(categories); + viewHolder.category.setText(categories.toString()); viewHolder.itemView.setOnClickListener(new View.OnClickListener() { + @SuppressLint("SetJavaScriptEnabled") @Override public void onClick(View view) { @@ -120,10 +115,10 @@ public void onClick(View view) { "\n" + "\n" + content, null, "utf-8", null); - android.support.v7.app.AlertDialog alertDialog = new android.support.v7.app.AlertDialog.Builder(mContext).create(); + androidx.appcompat.app.AlertDialog alertDialog = new androidx.appcompat.app.AlertDialog.Builder(mContext).create(); alertDialog.setTitle(title); alertDialog.setView(articleView); - alertDialog.setButton(android.support.v7.app.AlertDialog.BUTTON_NEUTRAL, "OK", + alertDialog.setButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); @@ -138,9 +133,7 @@ public void onClick(DialogInterface dialog, int which) { @Override public int getItemCount() { - return articles == null ? 0 : articles.size(); - } class ViewHolder extends RecyclerView.ViewHolder { diff --git a/app/src/main/java/com/prof/rssparser/example/MainActivity.java b/samplejava/src/main/java/com/prof/rssparser/sample/java/MainActivity.java similarity index 54% rename from app/src/main/java/com/prof/rssparser/example/MainActivity.java rename to samplejava/src/main/java/com/prof/rssparser/sample/java/MainActivity.java index 0c07aa2e..0a8d7c20 100644 --- a/app/src/main/java/com/prof/rssparser/example/MainActivity.java +++ b/samplejava/src/main/java/com/prof/rssparser/sample/java/MainActivity.java @@ -1,59 +1,59 @@ /* -* Copyright 2016 Marco Gomiero -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -*/ - -package com.prof.rssparser.example; - -import android.app.AlertDialog; + * Copyright 2016 Marco Gomiero + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.prof.rssparser.sample.java; + import android.content.Context; import android.content.DialogInterface; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Bundle; -import android.support.v4.widget.SwipeRefreshLayout; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.DefaultItemAnimator; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; import android.text.Html; import android.text.method.LinkMovementMethod; -import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ProgressBar; +import android.widget.RelativeLayout; import android.widget.TextView; -import android.widget.Toast; +import com.google.android.material.snackbar.Snackbar; import com.prof.rssparser.Article; -import com.prof.rssparser.Parser; -import java.util.ArrayList; +import java.util.List; + +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; -/** - * Created by Marco Gomiero on 12/02/2015. - */ public class MainActivity extends AppCompatActivity { private RecyclerView mRecyclerView; private ArticleAdapter mAdapter; private SwipeRefreshLayout mSwipeRefreshLayout; private ProgressBar progressBar; - private String urlString = "https://www.androidauthority.com/feed"; + private MainViewModel viewModel; + private RelativeLayout relativeLayout; @Override protected void onCreate(Bundle savedInstanceState) { @@ -62,25 +62,51 @@ protected void onCreate(Bundle savedInstanceState) { Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); + viewModel = ViewModelProviders.of(this).get(MainViewModel.class); + progressBar = findViewById(R.id.progressBar); - mRecyclerView = findViewById(R.id.list); + mRecyclerView = findViewById(R.id.recycler_view); mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); mRecyclerView.setItemAnimator(new DefaultItemAnimator()); mRecyclerView.setHasFixedSize(true); - mSwipeRefreshLayout = findViewById(R.id.container); + relativeLayout = findViewById(R.id.root_layout); + + viewModel.getArticleList().observe(this, new Observer>() { + @Override + public void onChanged(List
articles) { + if (articles != null) { + mAdapter = new ArticleAdapter(articles, MainActivity.this); + mRecyclerView.setAdapter(mAdapter); + mAdapter.notifyDataSetChanged(); + progressBar.setVisibility(View.GONE); + mSwipeRefreshLayout.setRefreshing(false); + } + } + }); + + viewModel.getSnackbar().observe(this, new Observer() { + @Override + public void onChanged(String s) { + if (s != null) { + Snackbar.make(relativeLayout, s, Snackbar.LENGTH_LONG).show(); + viewModel.onSnackbarShowed(); + } + } + }); + + mSwipeRefreshLayout = findViewById(R.id.swipe_layout); mSwipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary, R.color.colorPrimaryDark); mSwipeRefreshLayout.canChildScrollUp(); mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { - - mAdapter.clearData(); + mAdapter.getArticleList().clear(); mAdapter.notifyDataSetChanged(); mSwipeRefreshLayout.setRefreshing(true); - loadFeed(); + viewModel.fetchFeed(); } }); @@ -103,48 +129,10 @@ public void onClick(DialogInterface dialog, alert.show(); } else if (isNetworkAvailable()) { - loadFeed(); + viewModel.fetchFeed(); } } - public void loadFeed() { - - if (!mSwipeRefreshLayout.isRefreshing()) - progressBar.setVisibility(View.VISIBLE); - - Parser parser = new Parser(); - parser.execute(urlString); - parser.onFinish(new Parser.OnTaskCompleted() { - //what to do when the parsing is done - @Override - public void onTaskCompleted(ArrayList
list) { - //list is an Array List with all article's information - //set the adapter to recycler view - mAdapter = new ArticleAdapter(list, R.layout.row, MainActivity.this); - mRecyclerView.setAdapter(mAdapter); - progressBar.setVisibility(View.GONE); - mSwipeRefreshLayout.setRefreshing(false); - - } - - //what to do in case of error - @Override - public void onError(Exception exception) { - - runOnUiThread(new Runnable() { - @Override - public void run() { - progressBar.setVisibility(View.GONE); - mSwipeRefreshLayout.setRefreshing(false); - Toast.makeText(MainActivity.this, "Unable to load data.", - Toast.LENGTH_LONG).show(); - Log.i("Unable to load ", "articles"); - } - }); - } - }); - } - public boolean isNetworkAvailable() { ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); @@ -152,26 +140,8 @@ public boolean isNetworkAvailable() { return activeNetworkInfo != null && activeNetworkInfo.isConnected(); } - @Override - public void onResume() { - - super.onResume(); - if (mAdapter != null) - mAdapter.notifyDataSetChanged(); - } - - @Override - protected void onDestroy() { - - super.onDestroy(); - if (mAdapter != null) - mAdapter.clearData(); - } - - @Override public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @@ -182,12 +152,12 @@ public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_settings) { - android.support.v7.app.AlertDialog alertDialog = new android.support.v7.app.AlertDialog.Builder(MainActivity.this).create(); + androidx.appcompat.app.AlertDialog alertDialog = new androidx.appcompat.app.AlertDialog.Builder(MainActivity.this).create(); alertDialog.setTitle(R.string.app_name); alertDialog.setMessage(Html.fromHtml(MainActivity.this.getString(R.string.info_text) + " GitHub." + MainActivity.this.getString(R.string.author))); - alertDialog.setButton(android.support.v7.app.AlertDialog.BUTTON_NEUTRAL, "OK", + alertDialog.setButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); @@ -202,3 +172,4 @@ public void onClick(DialogInterface dialog, int which) { } } + diff --git a/samplejava/src/main/java/com/prof/rssparser/sample/java/MainViewModel.java b/samplejava/src/main/java/com/prof/rssparser/sample/java/MainViewModel.java new file mode 100644 index 00000000..9919adcd --- /dev/null +++ b/samplejava/src/main/java/com/prof/rssparser/sample/java/MainViewModel.java @@ -0,0 +1,84 @@ +/* + * Copyright 2016 Marco Gomiero + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.prof.rssparser.sample.java; + +import com.prof.rssparser.Article; +import com.prof.rssparser.OnTaskCompleted; +import com.prof.rssparser.Parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +public class MainViewModel extends ViewModel { + + private MutableLiveData> articleListLive = null; + private String urlString = "https://www.androidauthority.com/feed"; + + private MutableLiveData snackbar = new MutableLiveData<>(); + + public MutableLiveData> getArticleList() { + if (articleListLive == null) { + articleListLive = new MutableLiveData<>(); + } + return articleListLive; + } + + private void setArticleList(List
articleList) { + this.articleListLive.postValue(articleList); + } + + public LiveData getSnackbar() { + return snackbar; + } + + public void onSnackbarShowed() { + snackbar.setValue(null); + } + + public void fetchFeed() { + + Executors.newSingleThreadExecutor().submit(new Runnable() { + @Override + public void run() { + Parser parser = new Parser(); + parser.onFinish(new OnTaskCompleted() { + + //what to do when the parsing is done + @Override + public void onTaskCompleted(List
list) { + setArticleList(list); + } + + //what to do in case of error + @Override + public void onError(Exception e) { + setArticleList(new ArrayList
()); + e.printStackTrace(); + snackbar.postValue("An error has occurred. Please try again"); + } + }); + parser.execute(urlString); + } + }); + } +} diff --git a/samplejava/src/main/res/drawable-v24/ic_launcher_foreground.xml b/samplejava/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..1f6bb290 --- /dev/null +++ b/samplejava/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/samplejava/src/main/res/drawable/ic_launcher_background.xml b/samplejava/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..0d025f9b --- /dev/null +++ b/samplejava/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/placeholder.png b/samplejava/src/main/res/drawable/placeholder.png similarity index 100% rename from app/src/main/res/drawable/placeholder.png rename to samplejava/src/main/res/drawable/placeholder.png diff --git a/samplejava/src/main/res/layout/activity_main.xml b/samplejava/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..4669860d --- /dev/null +++ b/samplejava/src/main/res/layout/activity_main.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + diff --git a/samplejava/src/main/res/layout/content_main.xml b/samplejava/src/main/res/layout/content_main.xml new file mode 100644 index 00000000..e5cfa990 --- /dev/null +++ b/samplejava/src/main/res/layout/content_main.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + diff --git a/samplejava/src/main/res/layout/row.xml b/samplejava/src/main/res/layout/row.xml new file mode 100644 index 00000000..dcff9adf --- /dev/null +++ b/samplejava/src/main/res/layout/row.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_main.xml b/samplejava/src/main/res/menu/menu_main.xml similarity index 100% rename from app/src/main/res/menu/menu_main.xml rename to samplejava/src/main/res/menu/menu_main.xml diff --git a/samplejava/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/samplejava/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/samplejava/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/samplejava/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/samplejava/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/samplejava/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/samplejava/src/main/res/mipmap-hdpi/ic_launcher.png b/samplejava/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..898f3ed5 Binary files /dev/null and b/samplejava/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/samplejava/src/main/res/mipmap-hdpi/ic_launcher_round.png b/samplejava/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..dffca360 Binary files /dev/null and b/samplejava/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/samplejava/src/main/res/mipmap-mdpi/ic_launcher.png b/samplejava/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..64ba76f7 Binary files /dev/null and b/samplejava/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/samplejava/src/main/res/mipmap-mdpi/ic_launcher_round.png b/samplejava/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..dae5e082 Binary files /dev/null and b/samplejava/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/samplejava/src/main/res/mipmap-xhdpi/ic_launcher.png b/samplejava/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..e5ed4659 Binary files /dev/null and b/samplejava/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/samplejava/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/samplejava/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..14ed0af3 Binary files /dev/null and b/samplejava/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/samplejava/src/main/res/mipmap-xxhdpi/ic_launcher.png b/samplejava/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..b0907cac Binary files /dev/null and b/samplejava/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/samplejava/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/samplejava/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..d8ae0315 Binary files /dev/null and b/samplejava/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/samplejava/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/samplejava/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..2c18de9e Binary files /dev/null and b/samplejava/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/samplejava/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/samplejava/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..beed3cdd Binary files /dev/null and b/samplejava/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values-v21/styles.xml b/samplejava/src/main/res/values-v21/styles.xml similarity index 100% rename from app/src/main/res/values-v21/styles.xml rename to samplejava/src/main/res/values-v21/styles.xml diff --git a/app/src/main/res/values/colors.xml b/samplejava/src/main/res/values/colors.xml similarity index 100% rename from app/src/main/res/values/colors.xml rename to samplejava/src/main/res/values/colors.xml diff --git a/app/src/main/res/values/dimens.xml b/samplejava/src/main/res/values/dimens.xml similarity index 100% rename from app/src/main/res/values/dimens.xml rename to samplejava/src/main/res/values/dimens.xml diff --git a/app/src/main/res/values/strings.xml b/samplejava/src/main/res/values/strings.xml similarity index 96% rename from app/src/main/res/values/strings.xml rename to samplejava/src/main/res/values/strings.xml index a7c5872c..b96b3d4f 100644 --- a/app/src/main/res/values/strings.xml +++ b/samplejava/src/main/res/values/strings.xml @@ -23,7 +23,7 @@ --> - RSS Parser + RSS Parser Sample Java About Attention! Unable to load data. \nCheck your connection! diff --git a/app/src/main/res/values/styles.xml b/samplejava/src/main/res/values/styles.xml similarity index 100% rename from app/src/main/res/values/styles.xml rename to samplejava/src/main/res/values/styles.xml diff --git a/samplejava/src/test/java/com/prof/rssparser/sample/java/ExampleUnitTest.java b/samplejava/src/test/java/com/prof/rssparser/sample/java/ExampleUnitTest.java new file mode 100644 index 00000000..cab93f7e --- /dev/null +++ b/samplejava/src/test/java/com/prof/rssparser/sample/java/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.prof.rssparser.sample.java; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/samplekotlin/.gitignore b/samplekotlin/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/samplekotlin/.gitignore @@ -0,0 +1 @@ +/build diff --git a/samplekotlin/build.gradle b/samplekotlin/build.gradle new file mode 100644 index 00000000..9828480c --- /dev/null +++ b/samplekotlin/build.gradle @@ -0,0 +1,47 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 28 + + defaultConfig { + applicationId "com.prof.rssparser.sample.kotlin" + minSdkVersion 14 + targetSdkVersion 28 + versionCode 20000 + versionName "2.0.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' + implementation project(":rssparser") +// implementation 'com.prof.rssparser:rssparser:2.0.0' + + // UI + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'com.google.android.material:material:1.0.0' + implementation 'androidx.recyclerview:recyclerview:1.0.0' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + + // Coroutines + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.0' + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0" + + // Architecture components + implementation 'androidx.lifecycle:lifecycle-runtime:2.0.0' + implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' + + implementation 'com.squareup.picasso:picasso:2.71828' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} + diff --git a/samplekotlin/proguard-rules.pro b/samplekotlin/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/samplekotlin/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/samplekotlin/src/androidTest/java/com/prof/rssparser/sample/kotlin/ExampleInstrumentedTest.java b/samplekotlin/src/androidTest/java/com/prof/rssparser/sample/kotlin/ExampleInstrumentedTest.java new file mode 100644 index 00000000..86421342 --- /dev/null +++ b/samplekotlin/src/androidTest/java/com/prof/rssparser/sample/kotlin/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.prof.rssparser.sample.kotlin; + +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("com.prof.rssparser.sample.kotlin", appContext.getPackageName()); + } +} diff --git a/samplekotlin/src/main/AndroidManifest.xml b/samplekotlin/src/main/AndroidManifest.xml new file mode 100644 index 00000000..974b977a --- /dev/null +++ b/samplekotlin/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + diff --git a/samplekotlin/src/main/java/com/prof/rssparser/sample/kotlin/ArticleAdapter.kt b/samplekotlin/src/main/java/com/prof/rssparser/sample/kotlin/ArticleAdapter.kt new file mode 100644 index 00000000..c1bbe24a --- /dev/null +++ b/samplekotlin/src/main/java/com/prof/rssparser/sample/kotlin/ArticleAdapter.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2016 Marco Gomiero + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.prof.rssparser.sample.kotlin + +import android.text.method.LinkMovementMethod +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.webkit.WebChromeClient +import android.webkit.WebView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.prof.rssparser.Article +import com.squareup.picasso.Picasso +import kotlinx.android.synthetic.main.row.view.* +import java.text.SimpleDateFormat +import java.util.* + +class ArticleAdapter(val articles: MutableList
) : RecyclerView.Adapter() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.row, parent, false)) + + override fun getItemCount() = articles.size + + override fun onBindViewHolder(holder: ArticleAdapter.ViewHolder, position: Int) = holder.bind(articles[position]) + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + fun bind(article: Article) { + + Locale.setDefault(Locale.getDefault()) + val date = article.pubDate + val sdf = SimpleDateFormat("dd MMMM yyyy", Locale.getDefault()) + val pubDateString = sdf.format(date) + + itemView.title.text = article.title + + Picasso.get() + .load(article.image) + .placeholder(R.drawable.placeholder) + .into(itemView.image) + + itemView.pubDate.text = pubDateString + + var categories = "" + for (i in 0 until article.categories.size) { + categories = if (i == article.categories.size - 1) { + categories + article.categories[i] + } else { + categories + article.categories[i] + ", " + } + } + + + itemView.categories.text = categories + + itemView.setOnClickListener { + //show article content inside a dialog + val articleView = WebView(itemView.context) + + articleView.settings.loadWithOverviewMode = true + + articleView.settings.javaScriptEnabled = true + articleView.isHorizontalScrollBarEnabled = false + articleView.webChromeClient = WebChromeClient() + articleView.loadDataWithBaseURL(null, "\n" + "\n" + article.content, null, "utf-8", null) + + val alertDialog = androidx.appcompat.app.AlertDialog.Builder(itemView.context).create() + alertDialog.setTitle(article.title) + alertDialog.setView(articleView) + alertDialog.setButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL, "OK" + ) { dialog, _ -> dialog.dismiss() } + alertDialog.show() + + (alertDialog.findViewById(android.R.id.message) as TextView).movementMethod = LinkMovementMethod.getInstance() + } + } + } +} \ No newline at end of file diff --git a/samplekotlin/src/main/java/com/prof/rssparser/sample/kotlin/MainActivity.kt b/samplekotlin/src/main/java/com/prof/rssparser/sample/kotlin/MainActivity.kt new file mode 100644 index 00000000..da97a4df --- /dev/null +++ b/samplekotlin/src/main/java/com/prof/rssparser/sample/kotlin/MainActivity.kt @@ -0,0 +1,136 @@ +/* + * Copyright 2016 Marco Gomiero + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.prof.rssparser.sample.kotlin + + +import android.content.Context +import android.net.ConnectivityManager +import android.os.Bundle +import android.text.Html +import android.text.method.LinkMovementMethod +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.snackbar.Snackbar +import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.android.synthetic.main.content_main.* + +class MainActivity : AppCompatActivity() { + + private lateinit var adapter: ArticleAdapter + private lateinit var viewModel: MainViewModel + + private val isNetworkAvailable: Boolean + get() { + val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val activeNetworkInfo = connectivityManager.activeNetworkInfo + return activeNetworkInfo != null && activeNetworkInfo.isConnected + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + viewModel = ViewModelProviders.of(this@MainActivity).get(MainViewModel::class.java) + + setSupportActionBar(toolbar) + + recycler_view.layoutManager = LinearLayoutManager(this) + recycler_view.itemAnimator = DefaultItemAnimator() + recycler_view.setHasFixedSize(true) + + viewModel.getArticleList().observe(this, Observer { articles -> + + if (articles != null) { + adapter = ArticleAdapter(articles) + recycler_view.adapter = adapter + adapter.notifyDataSetChanged() + progressBar.visibility = View.GONE + swipe_layout.isRefreshing = false + } + + }) + + viewModel.snackbar.observe(this, Observer { value -> + value?.let { + Snackbar.make(root_layout, value, Snackbar.LENGTH_LONG).show() + viewModel.onSnackbarShowed () + } + + }) + + swipe_layout.setColorSchemeResources(R.color.colorPrimary, R.color.colorPrimaryDark) + swipe_layout.canChildScrollUp() + swipe_layout.setOnRefreshListener { + adapter.articles.clear() + adapter.notifyDataSetChanged() + swipe_layout.isRefreshing = true + viewModel.fetchFeed() + } + + if (!isNetworkAvailable) { + + val builder = AlertDialog.Builder(this) + builder.setMessage(R.string.alert_message) + .setTitle(R.string.alert_title) + .setCancelable(false) + .setPositiveButton(R.string.alert_positive + ) { dialog, id -> finish() } + + val alert = builder.create() + alert.show() + + } else if (isNetworkAvailable) { + viewModel.fetchFeed() + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_main, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + + val id = item.itemId + + if (id == R.id.action_settings) { + val alertDialog = androidx.appcompat.app.AlertDialog.Builder(this@MainActivity).create() + alertDialog.setTitle(R.string.app_name) + alertDialog.setMessage(Html.fromHtml(this@MainActivity.getString(R.string.info_text) + + " GitHub." + + this@MainActivity.getString(R.string.author))) + alertDialog.setButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL, "OK" + ) { dialog, which -> dialog.dismiss() } + alertDialog.show() + + (alertDialog.findViewById(android.R.id.message) as TextView).movementMethod = LinkMovementMethod.getInstance() + } + + return super.onOptionsItemSelected(item) + } +} + + diff --git a/samplekotlin/src/main/java/com/prof/rssparser/sample/kotlin/MainViewModel.kt b/samplekotlin/src/main/java/com/prof/rssparser/sample/kotlin/MainViewModel.kt new file mode 100644 index 00000000..a956d8ad --- /dev/null +++ b/samplekotlin/src/main/java/com/prof/rssparser/sample/kotlin/MainViewModel.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2016 Marco Gomiero + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.prof.rssparser.sample.kotlin + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.prof.rssparser.Article +import com.prof.rssparser.Parser +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +class MainViewModel : ViewModel() { + + private val url = "https://www.androidauthority.com/feed" + + private val viewModelJob = Job() + private val coroutineScope = CoroutineScope(Dispatchers.Main + viewModelJob) + + private lateinit var articleListLive: MutableLiveData> + + private val _snackbar = MutableLiveData() + + val snackbar: LiveData + get() = _snackbar + + fun onSnackbarShowed() { + _snackbar.value = null + } + + fun getArticleList(): MutableLiveData> { + if (!::articleListLive.isInitialized) { + articleListLive = MutableLiveData() + } + return articleListLive + } + + private fun setArticleList(articleList: MutableList
) { + articleListLive.postValue(articleList) + } + + override fun onCleared() { + super.onCleared() + viewModelJob.cancel() + } + + fun fetchFeed() { + coroutineScope.launch(Dispatchers.Main) { + try { + val parser = Parser() + val articleList = parser.getArticles(url) + setArticleList(articleList) + } catch (e: Exception) { + e.printStackTrace() + _snackbar.value = "An error has occurred. Please retry" + setArticleList(mutableListOf()) + } + } + } +} \ No newline at end of file diff --git a/samplekotlin/src/main/res/drawable-v24/ic_launcher_foreground.xml b/samplekotlin/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..1f6bb290 --- /dev/null +++ b/samplekotlin/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/samplekotlin/src/main/res/drawable/ic_launcher_background.xml b/samplekotlin/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..0d025f9b --- /dev/null +++ b/samplekotlin/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samplekotlin/src/main/res/drawable/placeholder.png b/samplekotlin/src/main/res/drawable/placeholder.png new file mode 100644 index 00000000..88c934d7 Binary files /dev/null and b/samplekotlin/src/main/res/drawable/placeholder.png differ diff --git a/app/src/main/res/layout/activity_main.xml b/samplekotlin/src/main/res/layout/activity_main.xml similarity index 76% rename from app/src/main/res/layout/activity_main.xml rename to samplekotlin/src/main/res/layout/activity_main.xml index b165737b..b6fc682e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/samplekotlin/src/main/res/layout/activity_main.xml @@ -17,28 +17,27 @@ ~ --> - + android:fitsSystemWindows="true"> - - - + - + diff --git a/app/src/main/res/layout/content_main.xml b/samplekotlin/src/main/res/layout/content_main.xml similarity index 84% rename from app/src/main/res/layout/content_main.xml rename to samplekotlin/src/main/res/layout/content_main.xml index 5eca43f5..22880020 100644 --- a/app/src/main/res/layout/content_main.xml +++ b/samplekotlin/src/main/res/layout/content_main.xml @@ -21,21 +21,22 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" + android:id="@+id/root_layout" android:paddingTop="50dp"> - - - + - - @@ -77,5 +77,5 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/image" /> - - \ No newline at end of file + + \ No newline at end of file diff --git a/samplekotlin/src/main/res/menu/menu_main.xml b/samplekotlin/src/main/res/menu/menu_main.xml new file mode 100644 index 00000000..9a45594f --- /dev/null +++ b/samplekotlin/src/main/res/menu/menu_main.xml @@ -0,0 +1,10 @@ + + + diff --git a/samplekotlin/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/samplekotlin/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/samplekotlin/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/samplekotlin/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/samplekotlin/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/samplekotlin/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/samplekotlin/src/main/res/mipmap-hdpi/ic_launcher.png b/samplekotlin/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..898f3ed5 Binary files /dev/null and b/samplekotlin/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/samplekotlin/src/main/res/mipmap-hdpi/ic_launcher_round.png b/samplekotlin/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..dffca360 Binary files /dev/null and b/samplekotlin/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/samplekotlin/src/main/res/mipmap-mdpi/ic_launcher.png b/samplekotlin/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..64ba76f7 Binary files /dev/null and b/samplekotlin/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/samplekotlin/src/main/res/mipmap-mdpi/ic_launcher_round.png b/samplekotlin/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..dae5e082 Binary files /dev/null and b/samplekotlin/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/samplekotlin/src/main/res/mipmap-xhdpi/ic_launcher.png b/samplekotlin/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..e5ed4659 Binary files /dev/null and b/samplekotlin/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/samplekotlin/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/samplekotlin/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..14ed0af3 Binary files /dev/null and b/samplekotlin/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/samplekotlin/src/main/res/mipmap-xxhdpi/ic_launcher.png b/samplekotlin/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..b0907cac Binary files /dev/null and b/samplekotlin/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/samplekotlin/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/samplekotlin/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..d8ae0315 Binary files /dev/null and b/samplekotlin/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/samplekotlin/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/samplekotlin/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..2c18de9e Binary files /dev/null and b/samplekotlin/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/samplekotlin/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/samplekotlin/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..beed3cdd Binary files /dev/null and b/samplekotlin/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/samplekotlin/src/main/res/values-v21/styles.xml b/samplekotlin/src/main/res/values-v21/styles.xml new file mode 100644 index 00000000..f9d2662f --- /dev/null +++ b/samplekotlin/src/main/res/values-v21/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/samplekotlin/src/main/res/values/colors.xml b/samplekotlin/src/main/res/values/colors.xml new file mode 100644 index 00000000..3ab3e9cb --- /dev/null +++ b/samplekotlin/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/samplekotlin/src/main/res/values/dimens.xml b/samplekotlin/src/main/res/values/dimens.xml new file mode 100644 index 00000000..812cb7be --- /dev/null +++ b/samplekotlin/src/main/res/values/dimens.xml @@ -0,0 +1,6 @@ + + + 16dp + 16dp + 16dp + diff --git a/samplekotlin/src/main/res/values/strings.xml b/samplekotlin/src/main/res/values/strings.xml new file mode 100644 index 00000000..3cab260f --- /dev/null +++ b/samplekotlin/src/main/res/values/strings.xml @@ -0,0 +1,35 @@ + + + + RSS Parser Sample Kotlin + About + Attention! + Unable to load data. \nCheck your connection! + Close + Video + This is a simple app that shows the use of RSS Parse library. + <br /><br />You can find the source code on + <br /><br />Written by Marco Gomiero + diff --git a/samplekotlin/src/main/res/values/styles.xml b/samplekotlin/src/main/res/values/styles.xml new file mode 100644 index 00000000..545b9c6d --- /dev/null +++ b/samplekotlin/src/main/res/values/styles.xml @@ -0,0 +1,20 @@ + + + + + + + +