diff --git a/.github/workflows/build_and_push_apk.yml b/.github/workflows/build_and_push_apk.yml new file mode 100644 index 0000000..6c787f9 --- /dev/null +++ b/.github/workflows/build_and_push_apk.yml @@ -0,0 +1,40 @@ +name: Build and Push Android-APK + +on: + push: + tags: + - '*' +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Get Version + run: echo "RELEASE_NUMBER=$(cat app/build.gradle | grep versionName | grep -o -P '".*"' | tr -d '"')" >> $GITHUB_ENV + - name: Setup JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: "21" + - name: Change Permissions + run: chmod +x ./gradlew + - name: Build APK + run: ./gradlew assemble + - name: Create Release + id: create_release + uses: actions/create-release@v1.1.4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ env.RELEASE_NUMBER }} + release_name: Release ${{ env.RELEASE_NUMBER }} + - name: Upload Release APK + uses: actions/upload-release-asset@v1.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: app/build/outputs/apk/release/app-release.apk + asset_name: app-release.apk + asset_content_type: application/zip \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index a873e8d..bd363bd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -39,6 +39,9 @@ android { } dependencies { + implementation "androidx.room:room-runtime:2.6.1" + annotationProcessor "androidx.room:room-compiler:2.6.1" + implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.preference:preference:1.1.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9a0287f..274298d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,23 +19,24 @@ + android:name="de.eknoes.inofficialgolem.MainApplication" + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:logo="@drawable/golem_eye" + android:resizeableActivity="true" + android:supportsRtl="true" + android:theme="@style/AppTheme" + tools:targetApi="n"> + android:name="org.apache.http.legacy" + android:required="false" /> + android:name=".ArticleView" + android:configChanges="orientation|screenSize" + android:exported="true" + android:parentActivityName=".MainActivity"> diff --git a/app/src/main/java/de/eknoes/inofficialgolem/Article.kt b/app/src/main/java/de/eknoes/inofficialgolem/Article.kt deleted file mode 100644 index b483bd5..0000000 --- a/app/src/main/java/de/eknoes/inofficialgolem/Article.kt +++ /dev/null @@ -1,30 +0,0 @@ -package de.eknoes.inofficialgolem - -import de.eknoes.inofficialgolem.updater.GolemItem -import java.util.* - -/** - * Created by soenke on 10.04.16. - */ -internal class Article : GolemItem() { - var title: String? = null - var subheadline: String? = null - get() = if (field == null) { - null - } else field!!.uppercase(Locale.ROOT) - var teaser: String? = null - var isOffline = false - var fulltext: String? = null - private var date = Date() - var imgUrl: String? = null - var commentUrl: String? = null - var commentNr: String? = null - - fun getDate(): Date { - return date - } - - fun setDate(date: Long) { - this.date.time = date - } -} diff --git a/app/src/main/java/de/eknoes/inofficialgolem/ArticleFragment.java b/app/src/main/java/de/eknoes/inofficialgolem/ArticleFragment.java index b389e97..32fc908 100644 --- a/app/src/main/java/de/eknoes/inofficialgolem/ArticleFragment.java +++ b/app/src/main/java/de/eknoes/inofficialgolem/ArticleFragment.java @@ -6,8 +6,6 @@ import android.content.Context; import android.content.Intent; import android.content.res.Configuration; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; import android.net.ConnectivityManager; import android.net.NetworkInfo; @@ -27,10 +25,17 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.room.Room; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import org.jetbrains.annotations.NotNull; +import de.eknoes.inofficialgolem.entities.Article; +import de.eknoes.inofficialgolem.entities.DATABASES; +import de.eknoes.inofficialgolem.utils.ArticleDao; +import de.eknoes.inofficialgolem.utils.ArticleDatabase; +import de.eknoes.inofficialgolem.utils.DBHelper; + public class ArticleFragment extends Fragment { static final String ARTICLE_URL = "de.eknoes.inofficialgolem.ARTICLE_URL"; static final String FORCE_WEBVIEW = "de.eknoes.inofficialgolem.FORCE_WEBVIEW"; @@ -114,7 +119,7 @@ public void onSaveInstanceState(@NotNull Bundle outState) { public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - if(webView != null) { + if (webView != null) { webView.setWebViewClient(new GolemWebViewClient() { @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { @@ -139,8 +144,8 @@ public void onPageFinished(WebView view, String url) { webView.getSettings().setJavaScriptEnabled(true); CookieManager cookieManager = CookieManager.getInstance(); cookieManager.setAcceptCookie(true); - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - cookieManager.setAcceptThirdPartyCookies(webView,true); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + cookieManager.setAcceptThirdPartyCookies(webView, true); } } @@ -170,7 +175,7 @@ public void onResume() { @Override public void onDetach() { super.onDetach(); - if(mTask != null) { + if (mTask != null) { mTask.cancel(false); } } @@ -180,9 +185,9 @@ public void onCreateOptionsMenu(@NotNull Menu menu, @NotNull MenuInflater inflat super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.menu_webview, menu); - if(article == null || article.getCommentUrl() == null) { + if (article == null || article.getCommentUrl() == null) { MenuItem item = menu.findItem(R.id.action_comments); - if(item != null) { + if (item != null) { item.setVisible(false); } } @@ -208,14 +213,14 @@ public boolean onOptionsItemSelected(MenuItem item) { Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); String link = null; - if (article != null && article.getUrl() != null) { - link = article.getUrl(); + if (article != null && article.getArticleUrl() != null) { + link = article.getArticleUrl(); } else if (url != null) { link = url; } if (link != null && article != null) { - shareIntent.putExtra(Intent.EXTRA_TEXT, article.getSubheadline() + ": " + article.getTitle() + " - " + link); + shareIntent.putExtra(Intent.EXTRA_TEXT, article.getSubHeadline() + ": " + article.getTitle() + " - " + link); } else { shareIntent.putExtra(Intent.EXTRA_TEXT, link); } @@ -264,44 +269,11 @@ private class loadArticleTask extends AsyncTask { @Override protected Void doInBackground(Void... params) { - FeedReaderDbHelper dbHelper = FeedReaderDbHelper.getInstance(requireContext().getApplicationContext()); - - SQLiteDatabase db = dbHelper.getReadableDatabase(); - - if (url != null) { - String[] columns = { - FeedReaderContract.Article.COLUMN_NAME_ID, - FeedReaderContract.Article.COLUMN_NAME_TITLE, - FeedReaderContract.Article.COLUMN_NAME_SUBHEADING, - FeedReaderContract.Article.COLUMN_NAME_URL, - FeedReaderContract.Article.COLUMN_NAME_COMMENTNR, - FeedReaderContract.Article.COLUMN_NAME_COMMENTURL, - FeedReaderContract.Article.COLUMN_NAME_OFFLINE, - FeedReaderContract.Article.COLUMN_NAME_FULLTEXT, - }; - Cursor cursor = db.query( - FeedReaderContract.Article.TABLE_NAME, - columns, - "url=\"" + url + "\"", - null, - null, - null, - null); - if (cursor.moveToFirst()) { - article = new Article(); - article.setId(cursor.getInt(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_ID))); - article.setTitle(cursor.getString(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_TITLE))); - article.setSubheadline(cursor.getString(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_SUBHEADING))); - article.setUrl(cursor.getString(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_URL))); - article.setCommentUrl(cursor.getString(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_COMMENTURL))); - article.setCommentNr(cursor.getString(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_COMMENTNR))); - article.setOffline(cursor.getInt(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_OFFLINE)) == 1); - article.setFulltext(cursor.getString(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_FULLTEXT))); - } - - - cursor.close(); - } + if (url != null) { + ArticleDatabase db = Room.databaseBuilder(getContext(), ArticleDatabase.class, DATABASES.ARTICLE.name()).build(); + ArticleDao dao = db.articleDao(); + article = dao.getArticleByUrl(url); + } return null; } @@ -320,19 +292,23 @@ protected void onPostExecute(Void vVoid) { networkInfo = connMgr.getActiveNetworkInfo(); } if (networkInfo != null && networkInfo.isConnected()) { + article.setAlreadyRead(true); + DBHelper.updateArticle(getContext(), article); webView.loadUrl(url); } else { webView.loadData(getResources().getString(R.string.err_no_network), "text/html; charset=utf-8", "UTF-8"); } } else { + article.setAlreadyRead(true); + DBHelper.updateArticle(getContext(),article); webView.loadUrl(url); } } else { Log.d(TAG, "onPostExecute: Fill Webview"); - String fulltext = article.getFulltext(); + String fulltext = article.getFullText(); // Change CSS for Dark Mode - if((getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES) { + if ((getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES) { if (fulltext != null) { fulltext = fulltext.replace("", """ """); } - webView.loadDataWithBaseURL(article.getUrl(), fulltext, "text/html", "UTF-8", null); + webView.loadDataWithBaseURL(article.getArticleUrl(), fulltext, "text/html", "UTF-8", null); Log.d(TAG, "onPostExecute: Filled Webview"); } diff --git a/app/src/main/java/de/eknoes/inofficialgolem/ArticleListFragment.java b/app/src/main/java/de/eknoes/inofficialgolem/ArticleListFragment.java index 4e8b7ce..20dc290 100644 --- a/app/src/main/java/de/eknoes/inofficialgolem/ArticleListFragment.java +++ b/app/src/main/java/de/eknoes/inofficialgolem/ArticleListFragment.java @@ -2,10 +2,10 @@ import android.animation.ValueAnimator; import android.content.Context; -import android.database.Cursor; +import android.content.res.Configuration; import android.database.DataSetObserver; -import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; +import android.graphics.Color; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; @@ -17,14 +17,16 @@ import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; +import androidx.room.Room; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.android.volley.toolbox.ImageLoader; @@ -35,9 +37,14 @@ import java.text.DateFormat; import java.util.Date; -import java.util.concurrent.Callable; +import java.util.List; +import de.eknoes.inofficialgolem.entities.Article; +import de.eknoes.inofficialgolem.entities.DATABASES; import de.eknoes.inofficialgolem.updater.GolemFetcher; +import de.eknoes.inofficialgolem.updater.RunningState; +import de.eknoes.inofficialgolem.utils.ArticleDao; +import de.eknoes.inofficialgolem.utils.ArticleDatabase; import de.eknoes.inofficialgolem.utils.NetworkUtils; /** @@ -91,18 +98,12 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat Log.d(TAG, "onStart: Creating Article List Adapter"); listAdapter = new ArticleAdapter(); listView.setAdapter(listAdapter); - listView.setOnItemClickListener((parent, view1, position, id) -> mListener.onArticleSelected(listAdapter.getItem(position).getUrl(), false)); - listView.setOnItemLongClickListener((parent, view12, position, id) -> { - mListener.onArticleSelected(listAdapter.getItem(position).getUrl(), true); + listView.setOnItemClickListener((parent, view12, position, id) -> mListener.onArticleSelected(listAdapter.getItem(position).getArticleUrl(), false)); + listView.setOnItemLongClickListener((parent, view1, position, id) -> { + mListener.onArticleSelected(listAdapter.getItem(position).getArticleUrl(), true); return true; }); - - mSwipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - refresh(true); - } - }); + mSwipeLayout.setOnRefreshListener(() -> refresh(true)); } @Override @@ -126,11 +127,10 @@ void refresh(boolean force) { return; } } - if (fetcher == null || fetcher.getStatus() != AsyncTask.Status.RUNNING) { + if (fetcher == null || fetcher.getStatus() != RunningState.RUNNING) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || ValueAnimator.areAnimatorsEnabled()) { mSwipeLayout.setRefreshing(true); } - fetcher = new GolemFetcher(requireContext(), () -> { if (listAdapter != null) { listAdapter.notifyDataSetChanged(); @@ -152,17 +152,14 @@ public interface OnArticleSelectedListener { private class ArticleAdapter extends BaseAdapter { private final LayoutInflater inflater; - private final SQLiteDatabase db; private final ImageLoader imgLoader; private final Context context; - private Cursor cursor; + private List
articles; ArticleAdapter() { super(); context = requireContext().getApplicationContext(); inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - FeedReaderDbHelper dbHelper = FeedReaderDbHelper.getInstance(context); - db = dbHelper.getReadableDatabase(); loadData(); this.registerDataSetObserver(new DataSetObserver() { @Override @@ -188,64 +185,27 @@ public void putBitmap(String url, Bitmap bitmap) { } private void loadData() { - String[] columns = { - FeedReaderContract.Article.COLUMN_NAME_ID, - FeedReaderContract.Article.COLUMN_NAME_TITLE, - FeedReaderContract.Article.COLUMN_NAME_SUBHEADING, - FeedReaderContract.Article.COLUMN_NAME_TEASER, - FeedReaderContract.Article.COLUMN_NAME_DATE, - FeedReaderContract.Article.COLUMN_NAME_IMG, - FeedReaderContract.Article.COLUMN_NAME_URL, - FeedReaderContract.Article.COLUMN_NAME_COMMENTURL, - FeedReaderContract.Article.COLUMN_NAME_COMMENTNR, - FeedReaderContract.Article.COLUMN_NAME_OFFLINE, - FeedReaderContract.Article.COLUMN_NAME_FULLTEXT - }; - - String sort = FeedReaderContract.Article.COLUMN_NAME_DATE + " DESC"; - String limit = "0, " + PreferenceManager.getDefaultSharedPreferences(context).getInt("article_limit", 200); - - - cursor = db.query( - FeedReaderContract.Article.TABLE_NAME, - columns, - null, - null, - null, - null, - sort, - limit); + ArticleDatabase db = Room.databaseBuilder(getContext(), ArticleDatabase.class, DATABASES.ARTICLE.name()).allowMainThreadQueries().build(); + ArticleDao doa = db.articleDao(); + articles = doa.getArticlesWithLimit(PreferenceManager.getDefaultSharedPreferences(context).getInt("article_limit", 200)); } @Override public int getCount() { - return cursor.getCount(); + return articles.size(); } @Override public Article getItem(int position) { - cursor.moveToPosition(position); - Article a = new Article(); - a.setId(cursor.getInt(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_ID))); - a.setTitle(cursor.getString(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_TITLE))); - a.setSubheadline(cursor.getString(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_SUBHEADING))); - a.setTeaser(cursor.getString(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_TEASER))); - a.setDate(cursor.getLong(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_DATE))); - a.setImgUrl(cursor.getString(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_IMG))); - a.setCommentUrl(cursor.getString(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_COMMENTURL))); - a.setCommentNr(cursor.getString(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_COMMENTNR))); - a.setUrl(cursor.getString(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_URL))); - a.setOffline(cursor.getInt(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_OFFLINE)) == 1); - a.setFulltext(cursor.getString(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_FULLTEXT))); - return a; + return articles.get(position); } @Override public long getItemId(int position) { - cursor.moveToPosition(position); - return cursor.getLong(cursor.getColumnIndexOrThrow(FeedReaderContract.Article.COLUMN_NAME_ID)); + return articles.get(position).getId(); } + @RequiresApi(api = Build.VERSION_CODES.O) @Override public View getView(int position, View convertView, ViewGroup parent) { View view = convertView; @@ -254,8 +214,9 @@ public View getView(int position, View convertView, ViewGroup parent) { view = inflater.inflate(R.layout.list_article, parent, false); } + Article art = getItem(position); - String infoText = String.format(context.getResources().getString(R.string.article_published), DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT).format(art.getDate())); + String infoText = String.format(context.getResources().getString(R.string.article_published), DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT).format(new Date(Long.parseLong(art.getDate())))); TextView teaser = view.findViewById(R.id.articleTeaser); TextView title = view.findViewById(R.id.articleTitle); @@ -264,9 +225,46 @@ public View getView(int position, View convertView, ViewGroup parent) { NetworkImageView image = view.findViewById(R.id.articleImage); title.setText(art.getTitle()); - subheading.setText(art.getSubheadline()); + subheading.setText(art.getSubHeadline()); teaser.setText(art.getTeaser()); info.setText(infoText); + int nightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + if (art.isAlreadyRead()) { + // Danke an Alma für die Idee mit dem Text + switch (nightMode) { + case Configuration.UI_MODE_NIGHT_YES: + title.setTextColor(Color.parseColor("#424040")); + subheading.setTextColor(Color.parseColor("#424040")); + teaser.setTextColor(Color.parseColor("#424040")); + info.setTextColor(Color.parseColor("#424040")); + break; + case Configuration.UI_MODE_NIGHT_NO: + title.setTextColor(Color.parseColor("#8a8484")); + subheading.setTextColor(Color.parseColor("#8a8484")); + teaser.setTextColor(Color.parseColor("#8a8484")); + info.setTextColor(Color.parseColor("#8a8484")); + break; + default: + break; + } + }else{ + switch (nightMode) { + case Configuration.UI_MODE_NIGHT_YES: + title.setTextColor(ContextCompat.getColor(context, R.color.design_default_color_on_primary)); + subheading.setTextColor(ContextCompat.getColor(context, R.color.design_default_color_on_primary)); + teaser.setTextColor(ContextCompat.getColor(context, R.color.design_default_color_on_primary)); + info.setTextColor(ContextCompat.getColor(context, R.color.design_default_color_on_primary)); + break; + case Configuration.UI_MODE_NIGHT_NO: + title.setTextColor(ContextCompat.getColor(context, R.color.design_default_color_on_secondary)); + subheading.setTextColor(ContextCompat.getColor(context, R.color.design_default_color_on_secondary)); + teaser.setTextColor(ContextCompat.getColor(context, R.color.design_default_color_on_secondary)); + info.setTextColor(ContextCompat.getColor(context, R.color.design_default_color_on_secondary)); + break; + default: + break; + } + } image.setImageUrl(art.getImgUrl(), imgLoader); return view; diff --git a/app/src/main/java/de/eknoes/inofficialgolem/ArticleView.java b/app/src/main/java/de/eknoes/inofficialgolem/ArticleView.java index 422e7e6..c315c4a 100644 --- a/app/src/main/java/de/eknoes/inofficialgolem/ArticleView.java +++ b/app/src/main/java/de/eknoes/inofficialgolem/ArticleView.java @@ -12,7 +12,6 @@ public class ArticleView extends AppCompatActivity { private ArticleFragment articleFragment; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/app/src/main/java/de/eknoes/inofficialgolem/FeedReaderContract.java b/app/src/main/java/de/eknoes/inofficialgolem/FeedReaderContract.java deleted file mode 100644 index b753802..0000000 --- a/app/src/main/java/de/eknoes/inofficialgolem/FeedReaderContract.java +++ /dev/null @@ -1,25 +0,0 @@ -package de.eknoes.inofficialgolem; - -import android.provider.BaseColumns; - -/** - * Created by soenke on 10.04.16. - */ -public final class FeedReaderContract { - - public static abstract class Article implements BaseColumns { - public static final String TABLE_NAME = "articles"; - public static final String COLUMN_NAME_ID = _ID; - public static final String COLUMN_NAME_TITLE = "title"; - public static final String COLUMN_NAME_SUBHEADING = "subheading"; - public static final String COLUMN_NAME_TEASER = "teaser"; - public static final String COLUMN_NAME_URL = "url"; - public static final String COLUMN_NAME_COMMENTURL = "commenturl"; - public static final String COLUMN_NAME_COMMENTNR = "commentnr"; - public static final String COLUMN_NAME_IMG = "img"; - public static final String COLUMN_NAME_DATE = "date"; - public static final String COLUMN_NAME_FULLTEXT = "fulltext"; - public static final String COLUMN_NAME_OFFLINE = "offline_available"; - } -} - diff --git a/app/src/main/java/de/eknoes/inofficialgolem/FeedReaderDbHelper.java b/app/src/main/java/de/eknoes/inofficialgolem/FeedReaderDbHelper.java deleted file mode 100644 index ba3bae5..0000000 --- a/app/src/main/java/de/eknoes/inofficialgolem/FeedReaderDbHelper.java +++ /dev/null @@ -1,60 +0,0 @@ -package de.eknoes.inofficialgolem; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.util.Log; - -public class FeedReaderDbHelper extends SQLiteOpenHelper { - private static final int DATABASE_VERSION = 15; - private static final String DATABASE_NAME = "FeedReader.db"; - private static final String TAG = "FeedReaderDbHelper"; - private static final String SQL_CREATE_ENTRIES = - "CREATE TABLE " + FeedReaderContract.Article.TABLE_NAME + " (" + - FeedReaderContract.Article._ID + " INTEGER PRIMARY KEY," + - FeedReaderContract.Article.COLUMN_NAME_TITLE + " TEXT, " + - FeedReaderContract.Article.COLUMN_NAME_SUBHEADING + " TEXT, " + - FeedReaderContract.Article.COLUMN_NAME_TEASER + " TEXT, " + - FeedReaderContract.Article.COLUMN_NAME_URL + " TEXT, " + - FeedReaderContract.Article.COLUMN_NAME_COMMENTURL + " TEXT, " + - FeedReaderContract.Article.COLUMN_NAME_COMMENTNR + " TEXT, " + - FeedReaderContract.Article.COLUMN_NAME_IMG + " TEXT, " + - FeedReaderContract.Article.COLUMN_NAME_DATE + " INTEGER," + - FeedReaderContract.Article.COLUMN_NAME_FULLTEXT + " TEXT," + - //FeedReaderContract.Article.COLUMN_NAME_AUTHORS + " TEXT," + - FeedReaderContract.Article.COLUMN_NAME_OFFLINE + " INTEGER" + - ")"; - private static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + FeedReaderContract.Article.TABLE_NAME; - private static FeedReaderDbHelper self; - - - private FeedReaderDbHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - public static FeedReaderDbHelper getInstance(Context c) { - if (self == null) { - self = new FeedReaderDbHelper(c); - } - return self; - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL(SQL_CREATE_ENTRIES); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - try { - if (oldVersion < 15) { - db.execSQL(SQL_DELETE_ENTRIES); - onCreate(db); - } - } catch (Exception e) { - Log.e(TAG, "onUpgrade: Exception on Database upgrade. Recreation!"); - db.execSQL(SQL_DELETE_ENTRIES); - onCreate(db); - } - } -} diff --git a/app/src/main/java/de/eknoes/inofficialgolem/MainActivity.java b/app/src/main/java/de/eknoes/inofficialgolem/MainActivity.java index 225b88f..0d5d2ee 100644 --- a/app/src/main/java/de/eknoes/inofficialgolem/MainActivity.java +++ b/app/src/main/java/de/eknoes/inofficialgolem/MainActivity.java @@ -12,7 +12,9 @@ import android.view.MenuItem; import android.view.View; import android.widget.Button; +import android.widget.Toast; +import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; @@ -20,11 +22,21 @@ import java.util.Objects; +import de.eknoes.inofficialgolem.utils.MigrationHelper; + public class MainActivity extends AppCompatActivity implements ArticleListFragment.OnArticleSelectedListener { private static final String TAG = "MainActivity"; private static final String CURRENT_ARTICLE = "currentArticle"; private String currentArticle; + @Override + protected void onPostCreate(@Nullable Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + if(MigrationHelper.migrateOldDatabase(this.getApplicationContext())){ + Toast.makeText(this, "Database migrated", Toast.LENGTH_SHORT).show(); + } + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/app/src/main/java/de/eknoes/inofficialgolem/MainApplication.java b/app/src/main/java/de/eknoes/inofficialgolem/MainApplication.java new file mode 100644 index 0000000..feb8296 --- /dev/null +++ b/app/src/main/java/de/eknoes/inofficialgolem/MainApplication.java @@ -0,0 +1,17 @@ +package de.eknoes.inofficialgolem; + +import android.app.Application; +import android.content.Context; + +public class MainApplication extends Application { + private static Context context; + + public static Context getAppContext() { + return MainApplication.context; + } + + public void onCreate() { + super.onCreate(); + MainApplication.context = getApplicationContext(); + } +} diff --git a/app/src/main/java/de/eknoes/inofficialgolem/entities/Article.java b/app/src/main/java/de/eknoes/inofficialgolem/entities/Article.java new file mode 100644 index 0000000..8409edd --- /dev/null +++ b/app/src/main/java/de/eknoes/inofficialgolem/entities/Article.java @@ -0,0 +1,212 @@ +package de.eknoes.inofficialgolem.entities; + +import androidx.room.ColumnInfo; +import androidx.room.Entity; +import androidx.room.Index; +import androidx.room.PrimaryKey; + +@Entity(indices = {@Index(value = {"article_url"}, unique = true)}) +public class Article { + @PrimaryKey + int id; + @ColumnInfo(name = "title") + String title; + @ColumnInfo(name = "article_url") + String articleUrl; + @ColumnInfo(name = "sub_headline") + String subHeadline; + @ColumnInfo(name = "teaser") + String teaser; + @ColumnInfo(name = "offline") + boolean isOffline; + @ColumnInfo(name = "full_text") + String fullText; + @ColumnInfo(name = "date") + String date; + @ColumnInfo(name = "image_url") + String imgUrl; + @ColumnInfo(name = "comment_url") + String commentUrl; + @ColumnInfo(name = "comment_number") + String commentNr; + @ColumnInfo(name = "already_read") + boolean alreadyRead; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + String[] splitTitle = title.split(":"); + if (splitTitle.length == 2) { + this.title = splitTitle[1].trim(); + this.subHeadline = splitTitle[0].trim(); + } + } + + public String getArticleUrl() { + return articleUrl; + } + + public void setArticleUrl(String articleUrl) { + this.articleUrl = articleUrl; + } + + public String getSubHeadline() { + return subHeadline; + } + + public void setSubHeadline(String subHeadline) { + this.subHeadline = subHeadline; + } + + public String getTeaser() { + return teaser; + } + + public void setTeaser(String teaser) { + this.teaser = teaser; + } + + public boolean isOffline() { + return isOffline; + } + + public void setOffline(boolean offline) { + isOffline = offline; + } + + public String getFullText() { + return fullText; + } + + public void setFullText(String fullText) { + this.fullText = fullText; + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public String getImgUrl() { + return imgUrl; + } + + public void setImgUrl(String imgUrl) { + this.imgUrl = imgUrl; + } + + public String getCommentUrl() { + return commentUrl; + } + + public void setCommentUrl(String commentUrl) { + this.commentUrl = commentUrl; + } + + public String getCommentNr() { + return commentNr; + } + + public void setCommentNr(String commentNr) { + this.commentNr = commentNr; + } + + public boolean isAlreadyRead() { + return alreadyRead; + } + + public void setAlreadyRead(boolean alreadyRead) { + this.alreadyRead = alreadyRead; + } + + + public static final class ArticleBuilder { + private Article article; + + private ArticleBuilder() { + article = new Article(); + } + + public static ArticleBuilder anArticle() { + return new ArticleBuilder(); + } + + public ArticleBuilder withId(int id) { + article.setId(id); + return this; + } + + public ArticleBuilder withTitle(String title) { + article.setTitle(title); + return this; + } + + public ArticleBuilder withArticleUrl(String articleUrl) { + article.setArticleUrl(articleUrl); + return this; + } + + public ArticleBuilder withSubHeadline(String subHeadline) { + article.setSubHeadline(subHeadline); + return this; + } + + public ArticleBuilder withTeaser(String teaser) { + article.setTeaser(teaser); + return this; + } + + public ArticleBuilder withFullText(String fullText) { + article.setFullText(fullText); + return this; + } + + public ArticleBuilder withDate(String date) { + article.setDate(date); + return this; + } + + public ArticleBuilder withImgUrl(String imgUrl) { + article.setImgUrl(imgUrl); + return this; + } + + public ArticleBuilder withCommentUrl(String commentUrl) { + article.setCommentUrl(commentUrl); + return this; + } + + public ArticleBuilder withCommentNr(String commentNr) { + article.setCommentNr(commentNr); + return this; + } + + public ArticleBuilder withAlreadyRead(boolean alreadyRead) { + article.setAlreadyRead(alreadyRead); + return this; + } + + public ArticleBuilder withOffline(boolean offline) { + article.setOffline(offline); + return this; + } + + public Article build() { + return article; + } + } +} diff --git a/app/src/main/java/de/eknoes/inofficialgolem/entities/DATABASES.java b/app/src/main/java/de/eknoes/inofficialgolem/entities/DATABASES.java new file mode 100644 index 0000000..525d43d --- /dev/null +++ b/app/src/main/java/de/eknoes/inofficialgolem/entities/DATABASES.java @@ -0,0 +1,5 @@ +package de.eknoes.inofficialgolem.entities; + +public enum DATABASES { + ARTICLE +} diff --git a/app/src/main/java/de/eknoes/inofficialgolem/updater/articleUpdater.java b/app/src/main/java/de/eknoes/inofficialgolem/updater/ArticleUpdater.java similarity index 87% rename from app/src/main/java/de/eknoes/inofficialgolem/updater/articleUpdater.java rename to app/src/main/java/de/eknoes/inofficialgolem/updater/ArticleUpdater.java index f9742d9..e5fe4c1 100644 --- a/app/src/main/java/de/eknoes/inofficialgolem/updater/articleUpdater.java +++ b/app/src/main/java/de/eknoes/inofficialgolem/updater/ArticleUpdater.java @@ -15,16 +15,18 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import de.eknoes.inofficialgolem.entities.Article; + /** * Fetches articles for users with golem abo token */ -public class articleUpdater extends GolemUpdater { +public class ArticleUpdater extends GolemUpdater { private final RequestQueue queue; private boolean useAbo; private String aboKey; - public articleUpdater(Context c, boolean useAbo) { + public ArticleUpdater(Context c, boolean useAbo) { super(c); queue = Volley.newRequestQueue(context); this.useAbo = useAbo; @@ -37,7 +39,7 @@ public articleUpdater(Context c, boolean useAbo) { } @Override - public List getItems() throws TimeoutError, NoConnectionError, AuthFailureError { + public List
getItems() throws TimeoutError, NoConnectionError, AuthFailureError { Log.d(TAG, "getItems: Fetching RSS Feed"); RequestFuture future = RequestFuture.newFuture(); StringRequest request = new StringRequest(buildFeedURL(), future, future); @@ -65,7 +67,7 @@ public List getItems() throws TimeoutError, NoConnectionError, AuthFa return new LinkedList<>(); } - static List getItems(String feed, GolemRSSParser parser) { + static List
getItems(String feed, GolemRSSParser parser) { return parser.parse(feed); } @@ -77,7 +79,6 @@ private String buildFeedURL() { Log.d(TAG, "buildFeedURL: Using private feed"); return "https://rss.golem.de/rss_sub_media.php?token=" + Uri.encode(this.aboKey); - //return "https://rss.golem.de/rss_sub.php?token=" + Uri.encode(this.aboKey); } } diff --git a/app/src/main/java/de/eknoes/inofficialgolem/updater/AsyncTaskExecutorService.java b/app/src/main/java/de/eknoes/inofficialgolem/updater/AsyncTaskExecutorService.java new file mode 100644 index 0000000..5bd0cf0 --- /dev/null +++ b/app/src/main/java/de/eknoes/inofficialgolem/updater/AsyncTaskExecutorService.java @@ -0,0 +1,74 @@ +package de.eknoes.inofficialgolem.updater; + +import android.os.Handler; +import android.os.Looper; + +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +public abstract class AsyncTaskExecutorService { + private final ExecutorService executorService; + private Handler handler; + private Future future; + + protected AsyncTaskExecutorService() { + executorService = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r); + t.setDaemon(true); + return t; + }); + } + + public ExecutorService getExecutorService() { + return executorService; + } + + public Handler getHandler() { + if (handler == null) { + synchronized (AsyncTaskExecutorService.class) { + handler = new Handler(Looper.getMainLooper()); + } + } + return handler; + } + + protected void onPreExecute() { + + } + + protected abstract GolemFetcher.FETCH_STATE doInBackground(Params params); + + protected abstract void onPostExecute(GolemFetcher.FETCH_STATE result); + + public void execute() { + execute(null); + } + + public void execute(Params params) { + getHandler().post(() -> { + onPreExecute(); + future = executorService.submit(() -> { + GolemFetcher.FETCH_STATE result = doInBackground(params); + getHandler().post(() -> onPostExecute(result)); + }); + }); + } + + public void shutDown() { + if (executorService != null) { + executorService.shutdownNow(); + } + } + + public boolean isCancelled() { + return executorService == null || executorService.isTerminated() || executorService.isShutdown(); + } + + public RunningState getStatus(){ + return future.isDone() ? RunningState.FINISHED : RunningState.RUNNING; + } + +} diff --git a/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemAboParser.kt b/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemAboParser.kt index 3fe1b79..cd585a4 100644 --- a/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemAboParser.kt +++ b/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemAboParser.kt @@ -1,5 +1,6 @@ package de.eknoes.inofficialgolem.updater +import de.eknoes.inofficialgolem.entities.Article import org.xmlpull.v1.XmlPullParser @@ -7,12 +8,12 @@ private val ns: String? = null class GolemAboParser : GolemRSSParser() { - override fun parse(stream: String): List { + override fun parse(stream: String): List
{ return readFeed(getParser(stream)) } - private fun readFeed(parser: XmlPullParser): List { - val entries = mutableListOf() + private fun readFeed(parser: XmlPullParser): List
{ + val entries = mutableListOf
() parser.require(XmlPullParser.START_TAG, ns, "feed") while (parser.next() != XmlPullParser.END_TAG) { @@ -30,7 +31,7 @@ class GolemAboParser : GolemRSSParser() { return entries } - private fun readEntry(parser: XmlPullParser): GolemItem { + private fun readEntry(parser: XmlPullParser): Article { parser.require(XmlPullParser.START_TAG, ns, "entry") var title: String? = null var link: String? = null @@ -50,13 +51,13 @@ class GolemAboParser : GolemRSSParser() { } } - val item = GolemItem() - item.setProp(GolemItem.ItemProperties.TITLE, title) - item.setProp(GolemItem.ItemProperties.DATE, parseDate(pubDate, "yyyy-MM-dd'T'HH:mm:ssz")) - item.setProp(GolemItem.ItemProperties.FULLTEXT, fulltext) - item.setProp(GolemItem.ItemProperties.HAS_MEDIA_FULLTEXT, "true") - item.setProp(GolemItem.ItemProperties.OFFLINE_AVAILABLE, "true") - item.url = link + val item = Article() + item.title = title; + //TODO +// item.date = parseDate(pubDate,"yyyy-MM-dd'T'HH:mm:ssz") + item.fullText = fulltext; + item.isOffline = true; + item.articleUrl = link return item } diff --git a/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemFetcher.java b/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemFetcher.java index 3cf9d72..1afb46f 100644 --- a/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemFetcher.java +++ b/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemFetcher.java @@ -1,52 +1,45 @@ package de.eknoes.inofficialgolem.updater; -import android.content.ContentValues; import android.content.Context; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; import android.net.ConnectivityManager; import android.net.NetworkInfo; -import android.os.AsyncTask; import android.preference.PreferenceManager; import android.util.Log; import android.widget.Toast; + +import androidx.room.Room; + import com.android.volley.AuthFailureError; import com.android.volley.NoConnectionError; import com.android.volley.TimeoutError; -import de.eknoes.inofficialgolem.FeedReaderContract; -import de.eknoes.inofficialgolem.FeedReaderDbHelper; -import de.eknoes.inofficialgolem.R; import java.lang.ref.WeakReference; import java.util.Date; import java.util.List; import java.util.concurrent.Callable; +import de.eknoes.inofficialgolem.R; +import de.eknoes.inofficialgolem.entities.Article; +import de.eknoes.inofficialgolem.entities.DATABASES; +import de.eknoes.inofficialgolem.utils.ArticleDao; +import de.eknoes.inofficialgolem.utils.ArticleDatabase; + /** * Created by soenke on 27.08.16. */ -public class GolemFetcher extends AsyncTask { +public class GolemFetcher extends AsyncTaskExecutorService { private static final String TAG = "GolemFetcher"; - private SQLiteDatabase db; private final GolemUpdater[] updater; private final WeakReference context; private final Callable notifier; public GolemFetcher(Context context, Callable notifier) { - try { - this.db = FeedReaderDbHelper.getInstance(context.getApplicationContext()).getWritableDatabase(); - } catch (SQLException exception) { - Log.e(TAG, "GolemFetcher: Could not open Database: ", exception); - Toast.makeText(context, R.string.error_database, Toast.LENGTH_LONG).show(); - this.cancel(true); - } this.context = new WeakReference<>(context); this.notifier = notifier; - if(PreferenceManager.getDefaultSharedPreferences(context).getBoolean("has_abo", false)) { - updater = new GolemUpdater[]{new articleUpdater(context, true), new articleUpdater(context, false)}; + if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("has_abo", false)) { + updater = new GolemUpdater[]{new ArticleUpdater(context, true), new ArticleUpdater(context, false)}; } else { - updater = new GolemUpdater[]{new articleUpdater(context, false)}; + updater = new GolemUpdater[]{new ArticleUpdater(context, false)}; } } @@ -54,7 +47,7 @@ public GolemFetcher(Context context, Callable notifier) { protected void onPreExecute() { super.onPreExecute(); Context ctx = context.get(); - if(ctx != null) { + if (ctx != null) { ConnectivityManager connMgr = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = null; if (connMgr != null) { @@ -64,9 +57,9 @@ protected void onPreExecute() { } @Override - protected GolemFetcher.FETCH_STATE doInBackground(Void... voids) { + protected FETCH_STATE doInBackground(Void unused) { Context ctx = context.get(); - if(ctx != null) { + if (ctx != null) { ConnectivityManager connMgr = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = null; if (connMgr != null) { @@ -77,8 +70,8 @@ protected GolemFetcher.FETCH_STATE doInBackground(Void... voids) { for (int i = 0; i < updater.length; i++) { GolemUpdater u = updater[i]; try { - List items = u.getItems(); - if (items != null && items.size() != 0) { + List
items = u.getItems(); + if (items != null && !items.isEmpty()) { // Only insert new articles for the first updater (AboKey updater needs to be first in the list) writeArticles(items, i == 0); } else { @@ -104,8 +97,7 @@ protected GolemFetcher.FETCH_STATE doInBackground(Void... voids) { } @Override - protected void onPostExecute(GolemFetcher.FETCH_STATE finished) { - super.onPostExecute(finished); + protected void onPostExecute(FETCH_STATE finished) { int msgString; switch (finished) { @@ -126,7 +118,7 @@ protected void onPostExecute(GolemFetcher.FETCH_STATE finished) { msgString = R.string.refresh_error_undefined; } Context ctx = context.get(); - if(ctx != null) { + if (ctx != null) { Toast.makeText(ctx, msgString, Toast.LENGTH_SHORT).show(); if (finished == FETCH_STATE.ABO_INVALID) { PreferenceManager.getDefaultSharedPreferences(ctx).edit().putBoolean("has_abo", false).apply(); @@ -140,84 +132,14 @@ protected void onPostExecute(GolemFetcher.FETCH_STATE finished) { } } - private void writeArticles(List articles, boolean insertNew) { - for (GolemItem item : articles) { - if (isCancelled() || db == null) { - return; - } - - int id = item.getId(); - - if (item.getUrl().startsWith("http://")) { - item.setUrl(item.getUrl().replace("http://", "https://")); - } - - if (item.getUrl().endsWith("-rss.html")) { - item.setUrl(item.getUrl().substring(0, (item.getUrl().length() - "-rss.html".length())) + ".html"); - } - - String[] cols = {FeedReaderContract.Article.COLUMN_NAME_ID}; - Cursor cursor = db.query( - FeedReaderContract.Article.TABLE_NAME, - cols, - "url='" + item.getUrl() + "'", - null, - null, - null, - null); - if (cursor.getCount() > 0) { - cursor.moveToFirst(); - id = cursor.getInt(cursor.getColumnIndex(FeedReaderContract.Article.COLUMN_NAME_ID)); - } - - cursor.close(); - - - - ContentValues values = new ContentValues(); - - values.put(FeedReaderContract.Article.COLUMN_NAME_URL, item.getUrl()); - - if (item.hasProp(GolemItem.ItemProperties.TITLE)) { - String[] titles = item.getProp(GolemItem.ItemProperties.TITLE).split(":"); - if (titles.length != 2) { - values.put(FeedReaderContract.Article.COLUMN_NAME_TITLE, item.getProp(GolemItem.ItemProperties.TITLE)); - } else { - values.put(FeedReaderContract.Article.COLUMN_NAME_TITLE, titles[1].trim()); - values.put(FeedReaderContract.Article.COLUMN_NAME_SUBHEADING, titles[0].trim()); - } - } - if (item.hasProp(GolemItem.ItemProperties.TEASER)) { - values.put(FeedReaderContract.Article.COLUMN_NAME_TEASER, item.getProp(GolemItem.ItemProperties.TEASER)); - } - - if (item.hasProp(GolemItem.ItemProperties.DATE)) { - values.put(FeedReaderContract.Article.COLUMN_NAME_DATE, item.getProp(GolemItem.ItemProperties.DATE)); - } - - if (item.hasProp(GolemItem.ItemProperties.FULLTEXT)) { - values.put(FeedReaderContract.Article.COLUMN_NAME_FULLTEXT, item.getProp(GolemItem.ItemProperties.FULLTEXT)); - values.put(FeedReaderContract.Article.COLUMN_NAME_OFFLINE, item.getProp(GolemItem.ItemProperties.OFFLINE_AVAILABLE)); - } - - if (item.hasProp(GolemItem.ItemProperties.OFFLINE_AVAILABLE)) { - values.put(FeedReaderContract.Article.COLUMN_NAME_OFFLINE, Boolean.valueOf(item.getProp(GolemItem.ItemProperties.OFFLINE_AVAILABLE))); - } - - if (item.hasProp(GolemItem.ItemProperties.IMG_URL)) { - values.put(FeedReaderContract.Article.COLUMN_NAME_IMG, item.getProp(GolemItem.ItemProperties.IMG_URL)); - } - - if (item.hasProp(GolemItem.ItemProperties.COMMENT_URL)) { - values.put(FeedReaderContract.Article.COLUMN_NAME_COMMENTURL, item.getProp(GolemItem.ItemProperties.COMMENT_URL)); - } + private void writeArticles(List
articles, boolean insertNew) { + ArticleDatabase database = Room.databaseBuilder(context.get(), ArticleDatabase.class, DATABASES.ARTICLE.name()).build(); + ArticleDao dao = database.articleDao(); - if (id != 0) { - Log.d(TAG, "doInBackground: Updating article with id " + id + ": Date " + item.getProp(GolemItem.ItemProperties.DATE)); - db.update(FeedReaderContract.Article.TABLE_NAME, values, FeedReaderContract.Article._ID + "='" + id + "'", null); - } else if (insertNew) { - Log.d(TAG, "doInBackground: Creating new article with Title " + item.getProp(GolemItem.ItemProperties.TITLE)); - db.insert(FeedReaderContract.Article.TABLE_NAME, null, values); + for (Article item : articles) { + if (insertNew) { + Log.d(TAG, "doInBackground: Creating new article with Title " + item.getTitle()); + dao.addArticle(item); } } } diff --git a/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemItem.java b/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemItem.java deleted file mode 100644 index a4f3571..0000000 --- a/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemItem.java +++ /dev/null @@ -1,47 +0,0 @@ -package de.eknoes.inofficialgolem.updater; - -import java.util.HashMap; -import java.util.Map; - -/** - * Created by soenke on 20.04.16. - */ -public class GolemItem { - private int id = 0; - private String url; - private final Map properties = new HashMap<>(); - - boolean hasProp(ItemProperties key) { - return properties.containsKey(key); - } - - String getProp(ItemProperties key) { - if (hasProp(key)) { - return properties.get(key); - } else { - return null; - } - } - - public void setProp(ItemProperties key, String value) { - properties.put(key, value); - } - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public enum ItemProperties {TITLE, TEASER, IMG_URL, COMMENT_URL, COMMENT_NR, DATE, FULLTEXT, OFFLINE_AVAILABLE, HAS_MEDIA_FULLTEXT} -} diff --git a/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemRSSParser.kt b/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemRSSParser.kt index 6825732..bd77fbe 100644 --- a/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemRSSParser.kt +++ b/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemRSSParser.kt @@ -3,6 +3,7 @@ package de.eknoes.inofficialgolem.updater import android.os.Build import android.text.Html import android.util.Xml +import de.eknoes.inofficialgolem.entities.Article import org.xmlpull.v1.XmlPullParser import java.io.StringReader import java.text.ParseException @@ -24,11 +25,11 @@ open class GolemRSSParser { return parser } - open fun parse(stream: String): List { + open fun parse(stream: String): List
{ return readFeed(getParser(stream)) } - private fun readFeed(parser: XmlPullParser): List { + private fun readFeed(parser: XmlPullParser): List
{ parser.require(XmlPullParser.START_TAG, ns, "rss") while (parser.next() != XmlPullParser.END_TAG) { if (parser.eventType != XmlPullParser.START_TAG) { @@ -45,8 +46,8 @@ open class GolemRSSParser { return emptyList() } - private fun readChannel(parser: XmlPullParser): List { - val entries = mutableListOf() + private fun readChannel(parser: XmlPullParser): List
{ + val entries = mutableListOf
() parser.require(XmlPullParser.START_TAG, ns, "channel") while (parser.next() != XmlPullParser.END_TAG) { @@ -70,7 +71,7 @@ open class GolemRSSParser { + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)", Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.DOTALL) - private fun readItem(parser: XmlPullParser): GolemItem { + private fun readItem(parser: XmlPullParser): Article { parser.require(XmlPullParser.START_TAG, ns, "item") var title: String? = null var link: String? = null @@ -79,6 +80,7 @@ Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.DOTALL) var pubDate: String? = null var desc: String? = null var content: String? = null + var guid: String? = null while (parser.next() != XmlPullParser.END_TAG) { if (parser.eventType != XmlPullParser.START_TAG) { @@ -89,28 +91,32 @@ Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.DOTALL) "link" -> link = readLink(parser) "description" -> desc = readDesc(parser) "comments" -> commentUrl = readCommentUrl(parser) + "guid" -> guid = readGuid(parser) + "content:encoded" -> content = readContent(parser) "slash:comments" -> commentNumber = readCommentNumber(parser) "pubDate" -> pubDate = readPubDate(parser) - "content:encoded" -> content = readContent(parser) else -> skip(parser) } } - val item = GolemItem() - item.setProp(GolemItem.ItemProperties.TITLE, decodeHTML(title)) - item.setProp(GolemItem.ItemProperties.COMMENT_URL, commentUrl) - item.setProp(GolemItem.ItemProperties.COMMENT_NR, commentNumber) - item.setProp(GolemItem.ItemProperties.TEASER, decodeHTML(desc)) + val article = Article() + if (guid != null) { + article.id = guid.toInt() + } + article.title = decodeHTML(title) + article.commentUrl = commentUrl + article.commentNr = commentNumber + article.teaser = decodeHTML(desc) //Extract IMG Url from content if (content != null) { val matcher = urlPattern.matcher(content) if(matcher.find()) - item.setProp(GolemItem.ItemProperties.IMG_URL, content.substring(matcher.start(0), matcher.end(0))) + article.imgUrl = content.substring(matcher.start(0), matcher.end(0)) } - item.setProp(GolemItem.ItemProperties.DATE, parseDate(pubDate,"EEE, d MMM yyyy HH:mm:ss Z")) - item.url = link - return item + article.date = parseDate(pubDate,"EEE, d MMM yyyy HH:mm:ss Z") + article.articleUrl = link + return article } internal fun parseDate(datestring: String?, fmtstring: String): String? { @@ -189,6 +195,13 @@ Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.DOTALL) return content } + internal fun readGuid(parser: XmlPullParser): String? { + parser.require(XmlPullParser.START_TAG, ns, "guid") + val content = readText(parser) + parser.require(XmlPullParser.END_TAG,ns,"guid") + return content.split("/").get(4).replace(".html","") + } + internal fun readText(parser: XmlPullParser): String { var result = "" diff --git a/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemUpdater.java b/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemUpdater.java index f1a6950..1d689e7 100644 --- a/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemUpdater.java +++ b/app/src/main/java/de/eknoes/inofficialgolem/updater/GolemUpdater.java @@ -7,6 +7,8 @@ import java.util.List; +import de.eknoes.inofficialgolem.entities.Article; + /** * Created by soenke on 20.04.16. */ @@ -30,5 +32,5 @@ abstract class GolemUpdater { * @throws NoConnectionError e.g. on Connection Error * @throws AuthFailureError e.g. on Invalid Abo Key */ - public abstract List getItems() throws TimeoutError, NoConnectionError, AuthFailureError; + public abstract List
getItems() throws TimeoutError, NoConnectionError, AuthFailureError; } diff --git a/app/src/main/java/de/eknoes/inofficialgolem/updater/RunningState.java b/app/src/main/java/de/eknoes/inofficialgolem/updater/RunningState.java new file mode 100644 index 0000000..7681540 --- /dev/null +++ b/app/src/main/java/de/eknoes/inofficialgolem/updater/RunningState.java @@ -0,0 +1,6 @@ +package de.eknoes.inofficialgolem.updater; + +public enum RunningState { + RUNNING, + FINISHED; +} diff --git a/app/src/main/java/de/eknoes/inofficialgolem/utils/ArticleDao.java b/app/src/main/java/de/eknoes/inofficialgolem/utils/ArticleDao.java new file mode 100644 index 0000000..3019b1e --- /dev/null +++ b/app/src/main/java/de/eknoes/inofficialgolem/utils/ArticleDao.java @@ -0,0 +1,33 @@ +package de.eknoes.inofficialgolem.utils; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; +import androidx.room.Update; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import de.eknoes.inofficialgolem.entities.Article; + +@Dao +public interface ArticleDao { + @Query("SELECT * FROM article") + List
getAllArticles(); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + void addArticle(Article article); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + void addArticle(List
articles); + + @Query("SELECT * FROM article where article_url = :url") + Article getArticleByUrl(String url); + + @Query("SELECT * FROM article order by date desc limit :limit") + List
getArticlesWithLimit(int limit); + + @Update + void updateArticle(Article article); +} diff --git a/app/src/main/java/de/eknoes/inofficialgolem/utils/ArticleDatabase.java b/app/src/main/java/de/eknoes/inofficialgolem/utils/ArticleDatabase.java new file mode 100644 index 0000000..6b2118d --- /dev/null +++ b/app/src/main/java/de/eknoes/inofficialgolem/utils/ArticleDatabase.java @@ -0,0 +1,11 @@ +package de.eknoes.inofficialgolem.utils; + +import androidx.room.Database; +import androidx.room.RoomDatabase; + +import de.eknoes.inofficialgolem.entities.Article; + +@Database(entities = {Article.class}, version = 1) +public abstract class ArticleDatabase extends RoomDatabase { + public abstract ArticleDao articleDao(); +} diff --git a/app/src/main/java/de/eknoes/inofficialgolem/utils/DBHelper.java b/app/src/main/java/de/eknoes/inofficialgolem/utils/DBHelper.java new file mode 100644 index 0000000..59ae420 --- /dev/null +++ b/app/src/main/java/de/eknoes/inofficialgolem/utils/DBHelper.java @@ -0,0 +1,18 @@ +package de.eknoes.inofficialgolem.utils; + +import android.content.Context; + +import androidx.room.Room; + +import de.eknoes.inofficialgolem.entities.Article; +import de.eknoes.inofficialgolem.entities.DATABASES; + +public class DBHelper { + private final static String TAG = "DBHelper"; + + public static void updateArticle( Context context,Article article){ + ArticleDatabase db = Room.databaseBuilder(context,ArticleDatabase.class, DATABASES.ARTICLE.name()).allowMainThreadQueries().build(); + ArticleDao dao = db.articleDao(); + dao.updateArticle(article); + } +} diff --git a/app/src/main/java/de/eknoes/inofficialgolem/utils/MigrationHelper.java b/app/src/main/java/de/eknoes/inofficialgolem/utils/MigrationHelper.java new file mode 100644 index 0000000..477d9e3 --- /dev/null +++ b/app/src/main/java/de/eknoes/inofficialgolem/utils/MigrationHelper.java @@ -0,0 +1,79 @@ +package de.eknoes.inofficialgolem.utils; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.os.AsyncTask; + +import androidx.room.Room; + +import java.util.ArrayList; +import java.util.List; + +import de.eknoes.inofficialgolem.entities.Article; +import de.eknoes.inofficialgolem.entities.DATABASES; + +public class MigrationHelper { + + private static boolean checkOldSqlLiteDBExists(Context context) { + try (SQLiteDatabase database = getOldDatabase(context)) { + database.close(); + return true; + } catch (Exception e) { + return false; + } + } + + public static boolean migrateOldDatabase(Context context) { + if (checkOldSqlLiteDBExists(context)) { + List
oldArticles = getArticlesFromOldDatabase(context); + writeOldArticles(context, oldArticles); + return deleteOldDatabase(context); + } + return false; + } + + private static List
getArticlesFromOldDatabase(Context context) { + List
articles = new ArrayList<>(); + try (SQLiteDatabase database = getOldDatabase(context); + Cursor cursor = database.query("articles", new String[]{"_id","title","subheading","teaser","url","commenturl","commentnr","img","date","fulltext","offline_available"}, null, null, null, null, null)) { + if(cursor.getCount() > 0){ + cursor.moveToFirst(); + while (cursor.moveToNext()) { + Article article = Article.ArticleBuilder.anArticle() + .withId(cursor.getInt(cursor.getColumnIndexOrThrow("_id"))) + .withTitle(cursor.getString(cursor.getColumnIndexOrThrow("title"))) + .withSubHeadline(cursor.getString(cursor.getColumnIndexOrThrow("subheading"))) + .withTeaser(cursor.getString(cursor.getColumnIndexOrThrow("teaser"))) + .withArticleUrl(cursor.getString(cursor.getColumnIndexOrThrow("url"))) + .withCommentUrl(cursor.getString(cursor.getColumnIndexOrThrow("commenturl"))) + .withCommentNr(cursor.getString(cursor.getColumnIndexOrThrow("commentnr"))) + .withImgUrl(cursor.getString(cursor.getColumnIndexOrThrow("img"))) + .withDate(cursor.getString(cursor.getColumnIndexOrThrow("date"))) + .withFullText(cursor.getString(cursor.getColumnIndexOrThrow("fulltext"))) + .withOffline(cursor.getInt(cursor.getColumnIndexOrThrow("offline_available")) == 1) + .build(); + articles.add(article); + } + } + } + return articles; + } + + private static void writeOldArticles(Context context, List
articles){ + ArticleDatabase database = Room.databaseBuilder(context, ArticleDatabase.class, DATABASES.ARTICLE.name()).build(); + ArticleDao dao = database.articleDao(); + AsyncTask.execute(() -> { + dao.addArticle(articles); + }); + } + + private static boolean deleteOldDatabase(Context context){ + return SQLiteDatabase.deleteDatabase(context.getDatabasePath("FeedReader.db")); + } + + private static SQLiteDatabase getOldDatabase(Context context) throws SQLiteException { + return SQLiteDatabase.openDatabase(context.getDatabasePath("FeedReader.db").getPath(), null, SQLiteDatabase.OPEN_READONLY); + } +}