From 55e2f63d6bf9c2019302a1a58ed8da72a3a9c832 Mon Sep 17 00:00:00 2001 From: Ezerous Date: Tue, 1 Oct 2019 11:02:35 +0300 Subject: [PATCH 01/19] RecentFragment crash fix --- .../gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java index 6e14b79b..1160692d 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java @@ -128,7 +128,7 @@ public class RecentFragment extends BaseFragment { @Override public void onDestroy() { super.onDestroy(); - if (recentTask.isRunning()) + if (recentTask!=null && recentTask.isRunning()) recentTask.cancel(true); } From 0d8ccd3669ba1867e9d40615f3ace7ed2eb826cb Mon Sep 17 00:00:00 2001 From: Ezerous Date: Tue, 1 Oct 2019 13:22:32 +0300 Subject: [PATCH 02/19] Added dialog upon logout --- .../gr/thmmy/mthmmy/base/BaseActivity.java | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java b/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java index a4ea57dd..c7c7360d 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java @@ -428,7 +428,7 @@ public abstract class BaseActivity extends AppCompatActivity { if (!sessionManager.isLoggedIn()) //When logged out or if user is guest startLoginActivity(); else - new LogoutTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); //Avoid delays between onPreExecute() and doInBackground() + showLogoutDialog(); } else if (drawerItem.equals(ABOUT_ID)) { if (!(BaseActivity.this instanceof AboutActivity)) { Intent intent = new Intent(BaseActivity.this, AboutActivity.class); @@ -543,6 +543,17 @@ public abstract class BaseActivity extends AppCompatActivity { //} } } + + private void showLogoutDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.AppCompatAlertDialogStyle); + builder.setTitle("Logout"); + builder.setMessage("Are you sure that you want to logout?"); + builder.setPositiveButton("Yes", (dialogInterface, i) -> { + new LogoutTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); //Avoid delays between onPreExecute() and doInBackground() + }); + builder.setNegativeButton("Nope", (dialogInterface, i) -> {}); + builder.create().show(); + } //-----------------------------------------LOGOUT END----------------------------------------------- //---------------------------------------------BOOKMARKS-------------------------------------------- @@ -557,23 +568,20 @@ public abstract class BaseActivity extends AppCompatActivity { protected void setTopicBookmark(MenuItem thisPageBookmarkMenuButton) { this.thisPageBookmarkMenuButton = thisPageBookmarkMenuButton; - if (thisPageBookmark.matchExists(topicsBookmarked)) { + if (thisPageBookmark.matchExists(topicsBookmarked)) thisPageBookmarkMenuButton.setIcon(R.drawable.ic_bookmark_true_accent_24dp); - } else { + else thisPageBookmarkMenuButton.setIcon(R.drawable.ic_bookmark_false_accent_24dp); - } } protected void refreshTopicBookmark() { - if (thisPageBookmarkMenuButton == null) { - return; - } + if (thisPageBookmarkMenuButton == null) return; + loadSavedBookmarks(); - if (thisPageBookmark.matchExists(topicsBookmarked)) { + if (thisPageBookmark.matchExists(topicsBookmarked)) thisPageBookmarkMenuButton.setIcon(R.drawable.ic_bookmark_true_accent_24dp); - } else { + else thisPageBookmarkMenuButton.setIcon(R.drawable.ic_bookmark_false_accent_24dp); - } } protected void topicMenuBookmarkClick() { From 5f67364515b9f556a8a814da3564123ba0da1b12 Mon Sep 17 00:00:00 2001 From: Ezerous Date: Tue, 1 Oct 2019 16:12:10 +0300 Subject: [PATCH 03/19] Bookmarks cleanup --- .../bookmarks/BookmarksActivity.java | 40 +++-- ...rdFragment.java => BookmarksFragment.java} | 88 ++++++---- .../bookmarks/BookmarksTopicFragment.java | 158 ------------------ 3 files changed, 81 insertions(+), 205 deletions(-) rename app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/{BookmarksBoardFragment.java => BookmarksFragment.java} (65%) delete mode 100644 app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksTopicFragment.java diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksActivity.java index 3a00881c..ef2b79b6 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksActivity.java @@ -29,6 +29,9 @@ import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; //TODO proper handling with adapter etc. //TODO after clicking bookmark and then back button should return to this activity public class BookmarksActivity extends BaseActivity { + private static final String TOPIC_URL = "https://www.thmmy.gr/smf/index.php?topic="; + private static final String BOARD_URL = "https://www.thmmy.gr/smf/index.php?board="; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -48,8 +51,8 @@ public class BookmarksActivity extends BaseActivity { //Creates the adapter that will return a fragment for each section of the activity SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); - sectionsPagerAdapter.addFragment(BookmarksTopicFragment.newInstance(1, Bookmark.arrayListToString(getTopicsBookmarked())), "Topics"); - sectionsPagerAdapter.addFragment(BookmarksBoardFragment.newInstance(2, Bookmark.arrayListToString(getBoardsBookmarked())), "Boards"); + sectionsPagerAdapter.addFragment(BookmarksFragment.newInstance(1, Bookmark.arrayListToString(getTopicsBookmarked()), BookmarksFragment.Type.TOPIC), "Topics"); + sectionsPagerAdapter.addFragment(BookmarksFragment.newInstance(2, Bookmark.arrayListToString(getBoardsBookmarked()), BookmarksFragment.Type.BOARD), "Boards"); //Sets up the ViewPager with the sections adapter. ViewPager viewPager = findViewById(R.id.bookmarks_container); @@ -65,44 +68,57 @@ public class BookmarksActivity extends BaseActivity { super.onResume(); } - public boolean onTopicInteractionListener(String interactionType, Bookmark bookmarkedTopic) { + public boolean onFragmentRowInteractionListener(BookmarksFragment.Type type, String interactionType, Bookmark bookmark) { + if(type== BookmarksFragment.Type.TOPIC) + return onTopicInteractionListener(interactionType, bookmark); + else if (type==BookmarksFragment.Type.BOARD) + return onBoardInteractionListener(interactionType, bookmark); + + return false; + } + + private boolean onTopicInteractionListener(String interactionType, Bookmark bookmarkedTopic) { switch (interactionType) { - case BookmarksTopicFragment.INTERACTION_CLICK_TOPIC_BOOKMARK: + case BookmarksFragment.INTERACTION_CLICK_TOPIC_BOOKMARK: Intent intent = new Intent(BookmarksActivity.this, TopicActivity.class); Bundle extras = new Bundle(); - extras.putString(BUNDLE_TOPIC_URL, "https://www.thmmy.gr/smf/index.php?topic=" + extras.putString(BUNDLE_TOPIC_URL, TOPIC_URL + bookmarkedTopic.getId() + "." + 2147483647); extras.putString(BUNDLE_TOPIC_TITLE, bookmarkedTopic.getTitle()); intent.putExtras(extras); startActivity(intent); break; - case BookmarksTopicFragment.INTERACTION_TOGGLE_TOPIC_NOTIFICATION: + case BookmarksFragment.INTERACTION_TOGGLE_TOPIC_NOTIFICATION: return toggleNotification(bookmarkedTopic); - case BookmarksTopicFragment.INTERACTION_REMOVE_TOPIC_BOOKMARK: + case BookmarksFragment.INTERACTION_REMOVE_TOPIC_BOOKMARK: removeBookmark(bookmarkedTopic); Toast.makeText(BookmarksActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show(); break; + default: + break; } return true; } - public boolean onBoardInteractionListener(String interactionType, Bookmark bookmarkedBoard) { + private boolean onBoardInteractionListener(String interactionType, Bookmark bookmarkedBoard) { switch (interactionType) { - case BookmarksBoardFragment.INTERACTION_CLICK_BOARD_BOOKMARK: + case BookmarksFragment.INTERACTION_CLICK_BOARD_BOOKMARK: Intent intent = new Intent(BookmarksActivity.this, BoardActivity.class); Bundle extras = new Bundle(); - extras.putString(BUNDLE_BOARD_URL, "https://www.thmmy.gr/smf/index.php?board=" + extras.putString(BUNDLE_BOARD_URL, BOARD_URL + bookmarkedBoard.getId() + ".0"); extras.putString(BUNDLE_BOARD_TITLE, bookmarkedBoard.getTitle()); intent.putExtras(extras); startActivity(intent); break; - case BookmarksBoardFragment.INTERACTION_TOGGLE_BOARD_NOTIFICATION: + case BookmarksFragment.INTERACTION_TOGGLE_BOARD_NOTIFICATION: return toggleNotification(bookmarkedBoard); - case BookmarksBoardFragment.INTERACTION_REMOVE_BOARD_BOOKMARK: + case BookmarksFragment.INTERACTION_REMOVE_BOARD_BOOKMARK: removeBookmark(bookmarkedBoard); Toast.makeText(BookmarksActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show(); break; + default: + break; } return true; } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksBoardFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksFragment.java similarity index 65% rename from app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksBoardFragment.java rename to app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksFragment.java index 814586ea..67de4c2e 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksBoardFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksFragment.java @@ -21,38 +21,53 @@ import java.util.ArrayList; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.model.Bookmark; -/** - * A {@link Fragment} subclass. - * Use the {@link BookmarksBoardFragment#newInstance} factory method to - * create an instance of this fragment. - */ -public class BookmarksBoardFragment extends Fragment { +public class BookmarksFragment extends Fragment { + enum Type {TOPIC, BOARD} private static final String ARG_SECTION_NUMBER = "SECTION_NUMBER"; - private static final String ARG_BOARD_BOOKMARKS = "BOARD_BOOKMARKS"; + private static final String ARG_BOOKMARKS = "BOOKMARKS"; + + static final String INTERACTION_CLICK_TOPIC_BOOKMARK = "CLICK_TOPIC_BOOKMARK"; + static final String INTERACTION_TOGGLE_TOPIC_NOTIFICATION = "TOGGLE_TOPIC_NOTIFICATION"; + static final String INTERACTION_REMOVE_TOPIC_BOOKMARK = "REMOVE_TOPIC_BOOKMARK"; static final String INTERACTION_CLICK_BOARD_BOOKMARK = "CLICK_BOARD_BOOKMARK"; static final String INTERACTION_TOGGLE_BOARD_NOTIFICATION = "TOGGLE_BOARD_NOTIFICATION"; static final String INTERACTION_REMOVE_BOARD_BOOKMARK= "REMOVE_BOARD_BOOKMARK"; - private ArrayList boardBookmarks = null; + private ArrayList bookmarks = null; + private Type type; + private String interactionClick, interactionToggle, interactionRemove; + + private Drawable notificationsEnabledButtonImage; + private Drawable notificationsDisabledButtonImage; - private static Drawable notificationsEnabledButtonImage; - private static Drawable notificationsDisabledButtonImage; + public BookmarksFragment() {/* Required empty public constructor */} - // Required empty public constructor - public BookmarksBoardFragment() { } + private BookmarksFragment(Type type) { + this.type=type; + if(type==Type.TOPIC){ + this.interactionClick=INTERACTION_CLICK_TOPIC_BOOKMARK; + this.interactionToggle=INTERACTION_TOGGLE_TOPIC_NOTIFICATION; + this.interactionRemove=INTERACTION_REMOVE_TOPIC_BOOKMARK; + } + else if (type==Type.BOARD){ + this.interactionClick=INTERACTION_CLICK_BOARD_BOOKMARK; + this.interactionToggle=INTERACTION_TOGGLE_BOARD_NOTIFICATION; + this.interactionRemove=INTERACTION_REMOVE_BOARD_BOOKMARK; + } + } /** * Use ONLY this factory method to create a new instance of - * this fragment using the provided parameters. + * the desired fragment using the provided parameters. * * @return A new instance of fragment Forum. */ - public static BookmarksBoardFragment newInstance(int sectionNumber, String boardBookmarks) { - BookmarksBoardFragment fragment = new BookmarksBoardFragment(); + protected static BookmarksFragment newInstance(int sectionNumber, String bookmarks, Type type) { + BookmarksFragment fragment = new BookmarksFragment(type); Bundle args = new Bundle(); args.putInt(ARG_SECTION_NUMBER, sectionNumber); - args.putString(ARG_BOARD_BOOKMARKS, boardBookmarks); + args.putString(ARG_BOOKMARKS, bookmarks); fragment.setArguments(args); return fragment; } @@ -61,9 +76,9 @@ public class BookmarksBoardFragment extends Fragment { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { - String bundledBoardBookmarks = getArguments().getString(ARG_BOARD_BOOKMARKS); - if (bundledBoardBookmarks != null) { - boardBookmarks = Bookmark.stringToArrayList(bundledBoardBookmarks); + String bundledBookmarks = getArguments().getString(ARG_BOOKMARKS); + if (bundledBookmarks != null) { + bookmarks = Bookmark.stringToArrayList(bundledBookmarks); } } @@ -83,47 +98,45 @@ public class BookmarksBoardFragment extends Fragment { Bundle savedInstanceState) { // Inflates the layout for this fragment final View rootView = layoutInflater.inflate(R.layout.fragment_bookmarks, container, false); - //bookmarks_board_container + //bookmarks container final LinearLayout bookmarksLinearView = rootView.findViewById(R.id.bookmarks_container); - if(this.boardBookmarks != null && !this.boardBookmarks.isEmpty()) { - for (final Bookmark bookmarkedBoard : boardBookmarks) { - if (bookmarkedBoard != null && bookmarkedBoard.getTitle() != null) { + if(this.bookmarks != null && !this.bookmarks.isEmpty()) { + for (final Bookmark bookmark : bookmarks) { + if (bookmark != null && bookmark.getTitle() != null) { final LinearLayout row = (LinearLayout) layoutInflater.inflate( R.layout.fragment_bookmarks_row, bookmarksLinearView, false); row.setOnClickListener(view -> { Activity activity = getActivity(); - if (activity instanceof BookmarksActivity){ - ((BookmarksActivity) activity).onBoardInteractionListener(INTERACTION_CLICK_BOARD_BOOKMARK, bookmarkedBoard); - } + if (activity instanceof BookmarksActivity) + ((BookmarksActivity) activity).onFragmentRowInteractionListener(type, interactionClick, bookmark); }); - ((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedBoard.getTitle()); + ((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmark.getTitle()); final ImageButton notificationsEnabledButton = row.findViewById(R.id.toggle_notification); - if (!bookmarkedBoard.isNotificationsEnabled()) { + if (!bookmark.isNotificationsEnabled()) { notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage); } notificationsEnabledButton.setOnClickListener(view -> { Activity activity = getActivity(); if (activity instanceof BookmarksActivity) { - if (((BookmarksActivity) activity).onBoardInteractionListener(INTERACTION_TOGGLE_BOARD_NOTIFICATION, bookmarkedBoard)) { + if (((BookmarksActivity) activity).onFragmentRowInteractionListener(type, interactionToggle, bookmark)) notificationsEnabledButton.setImageDrawable(notificationsEnabledButtonImage); - } else { + else notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage); - } } }); (row.findViewById(R.id.remove_bookmark)).setOnClickListener(view -> { Activity activity = getActivity(); if (activity instanceof BookmarksActivity){ - ((BookmarksActivity) activity).onBoardInteractionListener(INTERACTION_REMOVE_BOARD_BOOKMARK, bookmarkedBoard); - boardBookmarks.remove(bookmarkedBoard); + ((BookmarksActivity) activity).onFragmentRowInteractionListener(type, interactionRemove, bookmark); + bookmarks.remove(bookmark); } row.setVisibility(View.GONE); - if (boardBookmarks.isEmpty()){ + if (bookmarks.isEmpty()){ bookmarksLinearView.addView(bookmarksListEmptyMessage()); } }); @@ -142,7 +155,11 @@ public class BookmarksBoardFragment extends Fragment { LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); params.setMargins(0, 12, 0, 0); emptyBookmarksCategory.setLayoutParams(params); - emptyBookmarksCategory.setText(getString(R.string.empty_board_bookmarks)); + if(type==Type.TOPIC) + emptyBookmarksCategory.setText(getString(R.string.empty_topic_bookmarks)); + else if(type==Type.BOARD) + emptyBookmarksCategory.setText(getString(R.string.empty_board_bookmarks)); + emptyBookmarksCategory.setTypeface(emptyBookmarksCategory.getTypeface(), Typeface.BOLD); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) emptyBookmarksCategory.setTextColor(this.getContext().getColor(R.color.primary_text)); @@ -153,4 +170,5 @@ public class BookmarksBoardFragment extends Fragment { emptyBookmarksCategory.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); return emptyBookmarksCategory; } + } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksTopicFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksTopicFragment.java deleted file mode 100644 index e9081fbd..00000000 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/bookmarks/BookmarksTopicFragment.java +++ /dev/null @@ -1,158 +0,0 @@ -package gr.thmmy.mthmmy.activities.bookmarks; - -import android.app.Activity; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; - -import java.util.ArrayList; - -import gr.thmmy.mthmmy.R; -import gr.thmmy.mthmmy.model.Bookmark; - -/** - * A {@link Fragment} subclass. - * Use the {@link BookmarksTopicFragment#newInstance} factory method to - * create an instance of this fragment. - */ -public class BookmarksTopicFragment extends Fragment { - private static final String ARG_SECTION_NUMBER = "SECTION_NUMBER"; - private static final String ARG_TOPIC_BOOKMARKS = "TOPIC_BOOKMARKS"; - - static final String INTERACTION_CLICK_TOPIC_BOOKMARK = "CLICK_TOPIC_BOOKMARK"; - static final String INTERACTION_TOGGLE_TOPIC_NOTIFICATION = "TOGGLE_TOPIC_NOTIFICATION"; - static final String INTERACTION_REMOVE_TOPIC_BOOKMARK = "REMOVE_TOPIC_BOOKMARK"; - - ArrayList topicBookmarks = null; - - private static Drawable notificationsEnabledButtonImage; - private static Drawable notificationsDisabledButtonImage; - - // Required empty public constructor - public BookmarksTopicFragment() { - } - - /** - * Use ONLY this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @return A new instance of fragment Forum. - */ - public static BookmarksTopicFragment newInstance(int sectionNumber, String topicBookmarks) { - BookmarksTopicFragment fragment = new BookmarksTopicFragment(); - Bundle args = new Bundle(); - args.putInt(ARG_SECTION_NUMBER, sectionNumber); - args.putString(ARG_TOPIC_BOOKMARKS, topicBookmarks); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments() != null) { - String bundledTopicBookmarks = getArguments().getString(ARG_TOPIC_BOOKMARKS); - if (bundledTopicBookmarks != null) { - topicBookmarks = Bookmark.stringToArrayList(bundledTopicBookmarks); - } - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - notificationsEnabledButtonImage = getResources().getDrawable(R.drawable.ic_notification_on, null); - else - notificationsEnabledButtonImage = VectorDrawableCompat.create(getResources(), R.drawable.ic_notification_on, null); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - notificationsDisabledButtonImage = getResources().getDrawable(R.drawable.ic_notification_off, null); - else - notificationsDisabledButtonImage = VectorDrawableCompat.create(getResources(), R.drawable.ic_notification_off, null); - } - - @Override - public View onCreateView(@NonNull LayoutInflater layoutInflater, ViewGroup container, - Bundle savedInstanceState) { - // Inflates the layout for this fragment - final View rootView = layoutInflater.inflate(R.layout.fragment_bookmarks, container, false); - //bookmarks_topic_container - final LinearLayout bookmarksLinearView = rootView.findViewById(R.id.bookmarks_container); - - if(this.topicBookmarks != null && !this.topicBookmarks.isEmpty()) { - for (final Bookmark bookmarkedTopic : topicBookmarks) { - if (bookmarkedTopic != null && bookmarkedTopic.getTitle() != null) { - final LinearLayout row = (LinearLayout) layoutInflater.inflate( - R.layout.fragment_bookmarks_row, bookmarksLinearView, false); - row.setOnClickListener(view -> { - Activity activity = getActivity(); - if (activity instanceof BookmarksActivity) { - ((BookmarksActivity) activity).onTopicInteractionListener(INTERACTION_CLICK_TOPIC_BOOKMARK, bookmarkedTopic); - } - }); - ((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedTopic.getTitle()); - - final ImageButton notificationsEnabledButton = row.findViewById(R.id.toggle_notification); - if (!bookmarkedTopic.isNotificationsEnabled()) { - notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage); - } - - notificationsEnabledButton.setOnClickListener(view -> { - Activity activity = getActivity(); - if (activity instanceof BookmarksActivity) { - if (((BookmarksActivity) activity).onTopicInteractionListener(INTERACTION_TOGGLE_TOPIC_NOTIFICATION, bookmarkedTopic)) { - notificationsEnabledButton.setImageDrawable(notificationsEnabledButtonImage); - } else { - notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage); - } - } - }); - (row.findViewById(R.id.remove_bookmark)).setOnClickListener(view -> { - Activity activity = getActivity(); - if (activity instanceof BookmarksActivity) { - ((BookmarksActivity) activity).onTopicInteractionListener(INTERACTION_REMOVE_TOPIC_BOOKMARK, bookmarkedTopic); - topicBookmarks.remove(bookmarkedTopic); - } - row.setVisibility(View.GONE); - - if (topicBookmarks.isEmpty()){ - bookmarksLinearView.addView(bookmarksListEmptyMessage()); - } - }); - bookmarksLinearView.addView(row); - } - } - } else { - bookmarksLinearView.addView(bookmarksListEmptyMessage()); - } - - - return rootView; - } - - private TextView bookmarksListEmptyMessage() { - TextView emptyBookmarksCategory = new TextView(this.getContext()); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); - params.setMargins(0, 12, 0, 0); - emptyBookmarksCategory.setLayoutParams(params); - emptyBookmarksCategory.setText(getString(R.string.empty_topic_bookmarks)); - emptyBookmarksCategory.setTypeface(emptyBookmarksCategory.getTypeface(), Typeface.BOLD); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - emptyBookmarksCategory.setTextColor(this.getContext().getColor(R.color.primary_text)); - } else { - //noinspection deprecation - emptyBookmarksCategory.setTextColor(this.getContext().getResources().getColor(R.color.primary_text)); - } - emptyBookmarksCategory.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); - return emptyBookmarksCategory; - } -} From 979b385d438084a13ce8c23f9b5295b187d7bf11 Mon Sep 17 00:00:00 2001 From: Ezerous Date: Tue, 1 Oct 2019 17:01:47 +0300 Subject: [PATCH 04/19] Bug fix (empty board bookmark title) --- .../java/gr/thmmy/mthmmy/activities/board/BoardActivity.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java index 6f3cea60..846a8961 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java @@ -96,7 +96,9 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo } thisPageBookmark = new Bookmark(boardTitle, ThmmyPage.getBoardId(boardUrl), true); - setBoardBookmark(findViewById(R.id.bookmark)); + if (boardTitle != null && !Objects.equals(boardTitle, "")) + setBoardBookmark(findViewById(R.id.bookmark)); + createDrawer(); progressBar = findViewById(R.id.progressBar); From 73f30f5686a373ae7f62a94298ab5f1712c97f51 Mon Sep 17 00:00:00 2001 From: Ezerous Date: Tue, 1 Oct 2019 17:19:18 +0300 Subject: [PATCH 05/19] Fix for AlertDialog window leak error --- .../gr/thmmy/mthmmy/activities/topic/TopicActivity.java | 9 +++++++-- app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java index e0e52b3e..2bb084e3 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java @@ -123,6 +123,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo private Snackbar snackbar; private TopicViewModel viewModel; private EmojiKeyboard emojiKeyboard; + private AlertDialog topicInfoDialog; //Fix for vector drawables on android <21 static { @@ -253,8 +254,8 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo usersViewing.setText(HTMLUtils.getSpannableFromHtml(this, topicViewers)); }); builder.setView(infoDialog); - AlertDialog dialog = builder.create(); - dialog.show(); + topicInfoDialog = builder.create(); + topicInfoDialog.show(); return true; case R.id.menu_share: Intent sendIntent = new Intent(android.content.Intent.ACTION_SEND); @@ -312,6 +313,10 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo @Override protected void onDestroy() { super.onDestroy(); + if(topicInfoDialog!=null){ + topicInfoDialog.dismiss(); + topicInfoDialog=null; + } recyclerView.setAdapter(null); viewModel.stopLoading(); } diff --git a/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java b/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java index c7c7360d..22357d76 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java @@ -548,7 +548,7 @@ public abstract class BaseActivity extends AppCompatActivity { AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.AppCompatAlertDialogStyle); builder.setTitle("Logout"); builder.setMessage("Are you sure that you want to logout?"); - builder.setPositiveButton("Yes", (dialogInterface, i) -> { + builder.setPositiveButton("Yep", (dialogInterface, i) -> { new LogoutTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); //Avoid delays between onPreExecute() and doInBackground() }); builder.setNegativeButton("Nope", (dialogInterface, i) -> {}); From 0c54623b43d24727e8d0f4dbfdbbaeb8bfcaf52b Mon Sep 17 00:00:00 2001 From: Ezerous Date: Thu, 3 Oct 2019 16:55:39 +0300 Subject: [PATCH 06/19] Added option to display relative time --- app/build.gradle | 1 + app/src/main/assets/apache_libraries.html | 6 + .../activities/main/recent/RecentAdapter.java | 34 ++- .../main/recent/RecentFragment.java | 27 +- .../activities/main/unread/UnreadAdapter.java | 26 +- .../main/unread/UnreadFragment.java | 21 +- .../activities/settings/SettingsActivity.java | 1 + .../activities/settings/SettingsFragment.java | 21 +- .../gr/thmmy/mthmmy/base/BaseApplication.java | 14 +- .../gr/thmmy/mthmmy/utils/DateTimeUtils.java | 64 +++++ .../mthmmy/utils/RelativeTimeTextView.java | 249 ++++++++++++++++++ .../utils/parsing/ThmmyDateTimeParser.java | 63 +++++ .../main/res/layout/fragment_recent_row.xml | 2 +- .../main/res/layout/fragment_unread_row.xml | 2 +- app/src/main/res/values/attrs.xml | 3 + app/src/main/res/values/strings.xml | 12 + .../res/xml-v21/app_preferences_guest.xml | 43 +++ .../main/res/xml-v21/app_preferences_user.xml | 66 +++++ .../res/xml-v26/app_preferences_guest.xml | 10 +- .../main/res/xml-v26/app_preferences_user.xml | 18 +- .../main/res/xml/app_preferences_guest.xml | 10 +- app/src/main/res/xml/app_preferences_user.xml | 18 +- 22 files changed, 631 insertions(+), 80 deletions(-) create mode 100644 app/src/main/java/gr/thmmy/mthmmy/utils/DateTimeUtils.java create mode 100644 app/src/main/java/gr/thmmy/mthmmy/utils/RelativeTimeTextView.java create mode 100644 app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyDateTimeParser.java create mode 100644 app/src/main/res/xml-v21/app_preferences_guest.xml create mode 100644 app/src/main/res/xml-v21/app_preferences_user.xml diff --git a/app/build.gradle b/app/build.gradle index a4f3e951..f059e070 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -90,6 +90,7 @@ dependencies { implementation 'com.squareup.picasso:picasso:2.5.2' implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0' implementation 'org.jsoup:jsoup:1.10.3' //TODO: Warning: upgrading from 1.10.3 will break stuff! + implementation 'joda-time:joda-time:2.10.4' implementation 'com.github.franmontiel:PersistentCookieJar:1.0.1' implementation 'com.github.PhilJay:MPAndroidChart:3.0.3' implementation 'com.mikepenz:materialdrawer:6.1.1' diff --git a/app/src/main/assets/apache_libraries.html b/app/src/main/assets/apache_libraries.html index 911a8e50..d88ed36e 100644 --- a/app/src/main/assets/apache_libraries.html +++ b/app/src/main/assets/apache_libraries.html @@ -71,6 +71,12 @@
  • Grgit v3.0.0 (Copyright ©2018 Andrew Oberstar)
  • +
  • +
    Joda-Time v2.10.4 (Copyright ©2002-2019 Joda.org)
    +
  • +
  • +
    android-storage v2.1.0
    +
  • OkHttpProfiler v1.0.5
  • diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java index 15589d45..0bd447f6 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java @@ -1,6 +1,5 @@ package gr.thmmy.mthmmy.activities.main.recent; -import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -12,8 +11,10 @@ import androidx.recyclerview.widget.RecyclerView; import java.util.List; import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.TopicSummary; +import gr.thmmy.mthmmy.utils.RelativeTimeTextView; /** @@ -21,12 +22,10 @@ import gr.thmmy.mthmmy.model.TopicSummary; * specified {@link RecentFragment.RecentFragmentInteractionListener}. */ class RecentAdapter extends RecyclerView.Adapter { - private final Context context; private final List recentList; private final RecentFragment.RecentFragmentInteractionListener mListener; - RecentAdapter(Context context, @NonNull List topicSummaryList, BaseFragment.FragmentInteractionListener listener) { - this.context = context; + RecentAdapter(@NonNull List topicSummaryList, BaseFragment.FragmentInteractionListener listener) { this.recentList = topicSummaryList; mListener = (RecentFragment.RecentFragmentInteractionListener) listener; } @@ -43,22 +42,21 @@ class RecentAdapter extends RecyclerView.Adapter { @Override public void onBindViewHolder(final ViewHolder holder, final int position) { holder.mTitleView.setText(recentList.get(position).getSubject()); - holder.mDateTimeView.setText(recentList.get(position).getDateTimeModified()); - holder.mUserView.setText(recentList.get(position).getLastUser()); - - holder.topic = recentList.get(position); - holder.mView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { + String dateTimeString = recentList.get(position).getDateTimeModified(); + if(BaseApplication.getInstance().isDisplayRelativeTimeEnabled()) + holder.mDateTimeView.setReferenceTime(Long.valueOf(dateTimeString)); + else + holder.mDateTimeView.setText(dateTimeString); - if (null != mListener) { - // Notify the active callbacks interface (the activity, if the - // fragment is attached to one) that an item has been selected. - mListener.onRecentFragmentInteraction(holder.topic); //? - - } + holder.mUserView.setText(recentList.get(position).getLastUser()); + holder.topic = recentList.get(position); + holder.mView.setOnClickListener(v -> { + if (null != mListener) { + // Notify the active callbacks interface (the activity, if the + // fragment is attached to one) that an item has been selected. + mListener.onRecentFragmentInteraction(holder.topic); //? } }); } @@ -72,7 +70,7 @@ class RecentAdapter extends RecyclerView.Adapter { final View mView; final TextView mTitleView; final TextView mUserView; - final TextView mDateTimeView; + final RelativeTimeTextView mDateTimeView; public TopicSummary topic; ViewHolder(View view) { diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java index 1160692d..a1f21281 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java @@ -1,6 +1,7 @@ package gr.thmmy.mthmmy.activities.main.recent; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -34,6 +35,9 @@ import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import okhttp3.Response; import timber.log.Timber; +import static gr.thmmy.mthmmy.utils.DateTimeUtils.convertDateTime; +import static gr.thmmy.mthmmy.utils.parsing.ThmmyDateTimeParser.convertToTimestamp; + /** * A {@link BaseFragment} subclass. @@ -100,7 +104,7 @@ public class RecentFragment extends BaseFragment { // Set the adapter if (rootView instanceof RelativeLayout) { progressBar = rootView.findViewById(R.id.progressBar); - recentAdapter = new RecentAdapter(getActivity(), topicSummaries, fragmentInteractionListener); + recentAdapter = new RecentAdapter(topicSummaries, fragmentInteractionListener); CustomRecyclerView recyclerView = rootView.findViewById(R.id.list); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(recyclerView.getContext()); @@ -186,18 +190,19 @@ public class RecentFragment extends BaseFragment { String dateTime = recent.get(i + 2).text(); pattern = Pattern.compile("\\[(.*)]"); matcher = pattern.matcher(dateTime); - if (matcher.find()) { + if (matcher.find()){ dateTime = matcher.group(1); - if (dateTime.contains(" am") || dateTime.contains(" pm") || - dateTime.contains(" πμ") || dateTime.contains(" μμ")) { - dateTime = dateTime.replaceAll(":[0-5][0-9] ", " "); - } else { - dateTime = dateTime.substring(0, dateTime.lastIndexOf(":")); - } - if (!dateTime.contains(",")) { - dateTime = dateTime.replaceAll(".+? ([0-9])", "$1"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + && BaseApplication.getInstance().isDisplayRelativeTimeEnabled()) { + dateTime=convertDateTime(dateTime, false); + String timestamp = convertToTimestamp(dateTime); + if(timestamp!=null) + dateTime=timestamp; } - } else + else + dateTime=convertDateTime(dateTime, true); + } + else throw new ParseException("Parsing failed (dateTime)"); fetchedRecent.add(new TopicSummary(link, title, lastUser, dateTime)); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java index 3818145a..d213e480 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java @@ -11,8 +11,10 @@ import androidx.recyclerview.widget.RecyclerView; import java.util.List; import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.TopicSummary; +import gr.thmmy.mthmmy.utils.RelativeTimeTextView; class UnreadAdapter extends RecyclerView.Adapter { private final List unreadList; @@ -65,19 +67,21 @@ class UnreadAdapter extends RecyclerView.Adapter { final UnreadAdapter.ViewHolder viewHolder = (UnreadAdapter.ViewHolder) holder; viewHolder.mTitleView.setText(unreadList.get(holder.getAdapterPosition()).getSubject()); - viewHolder.mDateTimeView.setText(unreadList.get(holder.getAdapterPosition()).getDateTimeModified()); - viewHolder.mUserView.setText(unreadList.get(position).getLastUser()); + String dateTimeString=unreadList.get(holder.getAdapterPosition()).getDateTimeModified(); + if(BaseApplication.getInstance().isDisplayRelativeTimeEnabled()) + viewHolder.mDateTimeView.setReferenceTime(Long.valueOf(dateTimeString)); + else + viewHolder.mDateTimeView.setText(dateTimeString); + + viewHolder.mUserView.setText(unreadList.get(position).getLastUser()); viewHolder.topic = unreadList.get(holder.getAdapterPosition()); - viewHolder.mView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (null != mListener) { - // Notify the active callbacks interface (the activity, if the - // fragment is attached to one) that an item has been selected. - mListener.onUnreadFragmentInteraction(viewHolder.topic); //? - } + viewHolder.mView.setOnClickListener(v -> { + if (null != mListener) { + // Notify the active callbacks interface (the activity, if the + // fragment is attached to one) that an item has been selected. + mListener.onUnreadFragmentInteraction(viewHolder.topic); //? } }); } else if (holder instanceof UnreadAdapter.MarkReadViewHolder) { @@ -107,7 +111,7 @@ class UnreadAdapter extends RecyclerView.Adapter { final View mView; final TextView mTitleView; final TextView mUserView; - final TextView mDateTimeView; + final RelativeTimeTextView mDateTimeView; public TopicSummary topic; ViewHolder(View view) { diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java index 1db4131e..950ca27a 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java @@ -2,6 +2,7 @@ package gr.thmmy.mthmmy.activities.main.unread; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -24,6 +25,7 @@ import java.util.ArrayList; import java.util.List; import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.TopicSummary; import gr.thmmy.mthmmy.session.SessionManager; @@ -36,6 +38,9 @@ import okhttp3.Request; import okhttp3.Response; import timber.log.Timber; +import static gr.thmmy.mthmmy.utils.DateTimeUtils.convertDateTime; +import static gr.thmmy.mthmmy.utils.parsing.ThmmyDateTimeParser.convertToTimestamp; + /** * A {@link BaseFragment} subclass. * Activities that contain this fragment must implement the @@ -211,17 +216,19 @@ public class UnreadFragment extends BaseFragment { Element lastUserAndDate = information.get(6); String lastUser = lastUserAndDate.select("a").text(); String dateTime = lastUserAndDate.select("span").html(); - //dateTime = dateTime.replace("
    ", ""); dateTime = dateTime.substring(0, dateTime.indexOf("
    ")); dateTime = dateTime.replace("", ""); dateTime = dateTime.replace("", ""); - if (dateTime.contains(" am") || dateTime.contains(" pm") || - dateTime.contains(" πμ") || dateTime.contains(" μμ")) - dateTime = dateTime.replaceAll(":[0-5][0-9] ", " "); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + && BaseApplication.getInstance().isDisplayRelativeTimeEnabled()) { + dateTime=convertDateTime(dateTime, false); + String timestamp = convertToTimestamp(dateTime); + if(timestamp!=null) + dateTime=timestamp; + } else - dateTime = dateTime.substring(0, dateTime.lastIndexOf(":")); - if (!dateTime.contains(",")) - dateTime = dateTime.replaceAll(".+? ([0-9])", "$1"); + dateTime=convertDateTime(dateTime, true); fetchedTopicSummaries.add(new TopicSummary(link, title, lastUser, dateTime)); } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java index c9637dd0..6414a497 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsActivity.java @@ -9,6 +9,7 @@ import gr.thmmy.mthmmy.base.BaseActivity; public class SettingsActivity extends BaseActivity { public static final String DEFAULT_HOME_TAB = "pref_app_main_default_tab_key"; + public static final String DISPLAY_RELATIVE_TIME = "pref_app_display_relative_time_key"; public static final String NOTIFICATION_LED_KEY = "pref_notification_led_enable_key"; public static final String NOTIFICATION_VIBRATION_KEY = "pref_notification_vibration_enable_key"; public static final String POSTING_APP_SIGNATURE_ENABLE_KEY = "pref_posting_app_signature_enable_key"; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java index ec5907b6..edd06356 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/settings/SettingsFragment.java @@ -42,6 +42,8 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared public static final String SELECTED_RINGTONE = "selectedRingtoneKey"; private static final String SILENT_SELECTED = "STFU"; + private static final String UNREAD = "Unread"; + private SharedPreferences settingsFile; private PREFS_TYPE prefs_type = PREFS_TYPE.NOT_SET; @@ -65,7 +67,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared defaultHomeTabValues.add("1"); if(isLoggedIn = BaseApplication.getInstance().getSessionManager().isLoggedIn()){ - defaultHomeTabEntries.add("Unread"); + defaultHomeTabEntries.add(UNREAD); defaultHomeTabValues.add("2"); } } @@ -171,16 +173,16 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared if(isLoggedIn&& prefs_type==PREFS_TYPE.GUEST) { prefs_type = PREFS_TYPE.USER; setPreferencesFromResource(R.xml.app_preferences_user, getPreferenceScreen().getKey()); - if(!defaultHomeTabEntries.contains("Unread")){ - defaultHomeTabEntries.add("Unread"); + if(!defaultHomeTabEntries.contains(UNREAD)){ + defaultHomeTabEntries.add(UNREAD); defaultHomeTabValues.add("2"); } } else if(!isLoggedIn&&prefs_type==PREFS_TYPE.USER){ prefs_type = PREFS_TYPE.GUEST; setPreferencesFromResource(R.xml.app_preferences_guest,getPreferenceScreen().getKey()); - if(defaultHomeTabEntries.contains("Unread")){ - defaultHomeTabEntries.remove("Unread"); + if(defaultHomeTabEntries.contains(UNREAD)){ + defaultHomeTabEntries.remove(UNREAD); defaultHomeTabValues.remove("2"); } } @@ -201,7 +203,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared BaseApplication.getInstance().startFirebaseCrashlyticsCollection(); else { Timber.i("Crashlytics collection will be disabled after restarting."); - Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "This change will take effect once you restart the app.", Toast.LENGTH_SHORT).show(); + displayRestartAppToTakeEffectToast(); } } else if (key.equals(getString(R.string.pref_privacy_analytics_enable_key))) { enabled = sharedPreferences.getBoolean(key, false); @@ -210,6 +212,13 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared Timber.i("Analytics collection enabled."); else Timber.i("Analytics collection disabled."); + } else if (key.equals(getString(R.string.pref_app_display_relative_time_key)) + && BaseApplication.getInstance().isDisplayRelativeTimeEnabled()!=sharedPreferences.getBoolean(key, false)){ + displayRestartAppToTakeEffectToast(); } } + + private void displayRestartAppToTakeEffectToast(){ + Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "This change will take effect once you restart the app.", Toast.LENGTH_SHORT).show(); + } } \ No newline at end of file diff --git a/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java b/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java index ba0e6a0f..64ad4996 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java +++ b/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java @@ -49,6 +49,8 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import timber.log.Timber; +import static gr.thmmy.mthmmy.activities.settings.SettingsActivity.DISPLAY_RELATIVE_TIME; + public class BaseApplication extends Application { private static BaseApplication baseApplication; //BaseApplication singleton @@ -60,6 +62,8 @@ public class BaseApplication extends Application { private OkHttpClient client; private SessionManager sessionManager; + private boolean displayRelativeTime; + //TODO: maybe use PreferenceManager.getDefaultSharedPreferences here as well? private static final String SHARED_PREFS = "ThmmySharedPrefs"; @@ -104,12 +108,11 @@ public class BaseApplication extends Application { .addInterceptor(chain -> { Request request = chain.request(); HttpUrl oldUrl = chain.request().url(); - if (Objects.equals(chain.request().url().host(), "www.thmmy.gr")) { - if (!oldUrl.toString().contains("theme=4")) { + if (Objects.equals(chain.request().url().host(), "www.thmmy.gr") + && !oldUrl.toString().contains("theme=4")) { //Probably works but needs more testing: HttpUrl newUrl = oldUrl.newBuilder().addQueryParameter("theme", "4").build(); request = request.newBuilder().url(newUrl).build(); - } } return chain.proceed(request); }) @@ -173,6 +176,8 @@ public class BaseApplication extends Application { DisplayMetrics displayMetrics = getApplicationContext().getResources().getDisplayMetrics(); dpWidth = displayMetrics.widthPixels / displayMetrics.density; + + displayRelativeTime = settingsSharedPrefs.getBoolean(DISPLAY_RELATIVE_TIME, false); } //Getters @@ -192,6 +197,9 @@ public class BaseApplication extends Application { return dpWidth; } + public boolean isDisplayRelativeTimeEnabled() { + return displayRelativeTime; + } //--------------------Firebase-------------------- diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/DateTimeUtils.java b/app/src/main/java/gr/thmmy/mthmmy/utils/DateTimeUtils.java new file mode 100644 index 00000000..7802f931 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/DateTimeUtils.java @@ -0,0 +1,64 @@ +package gr.thmmy.mthmmy.utils; + +import static android.text.format.DateUtils.DAY_IN_MILLIS; +import static android.text.format.DateUtils.HOUR_IN_MILLIS; +import static android.text.format.DateUtils.MINUTE_IN_MILLIS; +import static android.text.format.DateUtils.SECOND_IN_MILLIS; +import static android.text.format.DateUtils.YEAR_IN_MILLIS; + +public class DateTimeUtils { + //TODO: move this function to ThmmyDateTimeParser class once KitKat support is dropped + public static String convertDateTime(String dateTime, boolean removeSeconds){ + //Convert e.g. Today at 12:16:48 -> 12:16:48, but October 03, 2019, 16:40:18 remains as is + if (!dateTime.contains(",")) + dateTime = dateTime.replaceAll(".+? ([0-9])", "$1"); + + //Remove seconds + if(removeSeconds) + dateTime = dateTime.replaceAll("(.+?)(:[0-5][0-9])($|\\s)", "$1$3"); + + return dateTime; + } + + private static final long MONTH_IN_MILLIS = DAY_IN_MILLIS*30; + private static final long DECADE_IN_MILLIS = YEAR_IN_MILLIS*10; + + static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution) { + boolean past = (now >= time); + long duration = Math.abs(now - time); + String format; + long count; + if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) { + count = duration / SECOND_IN_MILLIS; + format = "%d sec"; + } else if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) { + count = duration / MINUTE_IN_MILLIS; + format = "%d min"; + } else if (duration < DAY_IN_MILLIS && minResolution < DAY_IN_MILLIS) { + count = duration / HOUR_IN_MILLIS; + format = "%d hour"; + if(count>1) + format = format + 's'; + } else if (duration < MONTH_IN_MILLIS && minResolution < MONTH_IN_MILLIS) { + count = duration / DAY_IN_MILLIS; + format = "%d day"; + if(count>1) + format = format + 's'; + } else if (duration < YEAR_IN_MILLIS && minResolution < YEAR_IN_MILLIS) { + count = duration / MONTH_IN_MILLIS; + format = "%d month"; + if(count>1) + format = format + 's'; + } else if (duration < DECADE_IN_MILLIS && minResolution < DECADE_IN_MILLIS) { + count = duration / YEAR_IN_MILLIS; + format = "%d year"; + if(count>1) + format = format + 's'; + } + else + return past ? "a long time ago": "in the distant future"; + + format = past ? format : "in " + format; + return String.format(format, (int) count); + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/RelativeTimeTextView.java b/app/src/main/java/gr/thmmy/mthmmy/utils/RelativeTimeTextView.java new file mode 100644 index 00000000..083408c4 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/RelativeTimeTextView.java @@ -0,0 +1,249 @@ +package gr.thmmy.mthmmy.utils; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Handler; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.format.DateUtils; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +import java.lang.ref.WeakReference; + +import gr.thmmy.mthmmy.R; + +import static gr.thmmy.mthmmy.utils.DateTimeUtils.getRelativeTimeSpanString; + +/** + * A modified version of https://github.com/curioustechizen/android-ago + */ +@SuppressLint("AppCompatCustomView") +public class RelativeTimeTextView extends TextView { + + private static final long INITIAL_UPDATE_INTERVAL = DateUtils.MINUTE_IN_MILLIS; + + private long mReferenceTime; + private Handler mHandler = new Handler(); + private UpdateTimeRunnable mUpdateTimeTask; + private boolean isUpdateTaskRunning = false; + + public RelativeTimeTextView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public RelativeTimeTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + TypedArray a = context.getTheme().obtainStyledAttributes(attrs, + R.styleable.RelativeTimeTextView, 0, 0); + String referenceTimeText; + try { + referenceTimeText = a.getString(R.styleable.RelativeTimeTextView_reference_time); + } finally { + a.recycle(); + } + + try { + mReferenceTime = Long.valueOf(referenceTimeText); + } catch (NumberFormatException nfe) { + /* + * TODO: Better exception handling + */ + mReferenceTime = -1L; + } + + setReferenceTime(mReferenceTime); + + } + + /** + * Sets the reference time for this view. At any moment, the view will render a relative time period relative to the time set here. + *

    + * This value can also be set with the XML attribute {@code reference_time} + * @param referenceTime The timestamp (in milliseconds since epoch) that will be the reference point for this view. + */ + public void setReferenceTime(long referenceTime) { + this.mReferenceTime = referenceTime; + + /* + * Note that this method could be called when a row in a ListView is recycled. + * Hence, we need to first stop any currently running schedules (for example from the recycled view. + */ + stopTaskForPeriodicallyUpdatingRelativeTime(); + + /* + * Instantiate a new runnable with the new reference time + */ + initUpdateTimeTask(); + + /* + * Start a new schedule. + */ + startTaskForPeriodicallyUpdatingRelativeTime(); + + /* + * Finally, update the text display. + */ + updateTextDisplay(); + } + + private void updateTextDisplay() { + /* + * TODO: Validation, Better handling of negative cases + */ + if (this.mReferenceTime == -1L) + return; + setText(getRelativeTimeDisplayString(mReferenceTime, System.currentTimeMillis())); + } + + /** + * Get the text to display for relative time. + *
    + * @param referenceTime The reference time passed in through {@link #setReferenceTime(long)} or through {@code reference_time} attribute + * @param now The current time + * @return The display text for the relative time + */ + protected CharSequence getRelativeTimeDisplayString(long referenceTime, long now) { + long difference = now - referenceTime; + return (difference >= 0 && difference<=DateUtils.MINUTE_IN_MILLIS) ? + "just now" : + getRelativeTimeSpanString( + mReferenceTime, + now, + DateUtils.MINUTE_IN_MILLIS); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + startTaskForPeriodicallyUpdatingRelativeTime(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + stopTaskForPeriodicallyUpdatingRelativeTime(); + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + if (visibility == GONE || visibility == INVISIBLE) { + stopTaskForPeriodicallyUpdatingRelativeTime(); + } else { + startTaskForPeriodicallyUpdatingRelativeTime(); + } + } + + private void startTaskForPeriodicallyUpdatingRelativeTime() { + if(mUpdateTimeTask.isDetached()) initUpdateTimeTask(); + mHandler.post(mUpdateTimeTask); + isUpdateTaskRunning = true; + } + + private void initUpdateTimeTask() { + mUpdateTimeTask = new UpdateTimeRunnable(this, mReferenceTime); + } + + private void stopTaskForPeriodicallyUpdatingRelativeTime() { + if(isUpdateTaskRunning) { + mUpdateTimeTask.detach(); + mHandler.removeCallbacks(mUpdateTimeTask); + isUpdateTaskRunning = false; + } + } + + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState ss = new SavedState(superState); + ss.referenceTime = mReferenceTime; + return ss; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + if (!(state instanceof SavedState)) { + super.onRestoreInstanceState(state); + return; + } + + SavedState ss = (SavedState)state; + mReferenceTime = ss.referenceTime; + super.onRestoreInstanceState(ss.getSuperState()); + } + + public static class SavedState extends BaseSavedState { + + private long referenceTime; + + public SavedState(Parcelable superState) { + super(superState); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeLong(referenceTime); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + + private SavedState(Parcel in) { + super(in); + referenceTime = in.readLong(); + } + } + + private static class UpdateTimeRunnable implements Runnable { + + private long mRefTime; + private final WeakReference weakRefRttv; + + UpdateTimeRunnable(RelativeTimeTextView rttv, long refTime) { + this.mRefTime = refTime; + weakRefRttv = new WeakReference<>(rttv); + } + + boolean isDetached() { + return weakRefRttv.get() == null; + } + + void detach() { + weakRefRttv.clear(); + } + + @Override + public void run() { + RelativeTimeTextView rttv = weakRefRttv.get(); + if (rttv == null) return; + long difference = Math.abs(System.currentTimeMillis() - mRefTime); + long interval = INITIAL_UPDATE_INTERVAL; + if (difference > DateUtils.WEEK_IN_MILLIS) { + interval = DateUtils.WEEK_IN_MILLIS; + } else if (difference > DateUtils.DAY_IN_MILLIS) { + interval = DateUtils.DAY_IN_MILLIS; + } else if (difference > DateUtils.HOUR_IN_MILLIS) { + interval = DateUtils.HOUR_IN_MILLIS; + } + rttv.updateTextDisplay(); + rttv.mHandler.postDelayed(this, interval); + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyDateTimeParser.java b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyDateTimeParser.java new file mode 100644 index 00000000..9524e5fe --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyDateTimeParser.java @@ -0,0 +1,63 @@ +package gr.thmmy.mthmmy.utils.parsing; + + +import android.os.Build; + +import androidx.annotation.RequiresApi; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.joda.time.format.DateTimeFormatterBuilder; +import org.joda.time.format.DateTimeParser; + +import java.util.Locale; + +import gr.thmmy.mthmmy.base.BaseApplication; +import timber.log.Timber; + +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public class ThmmyDateTimeParser { + private static final DateTimeParser[] parsers = { + DateTimeFormat.forPattern("HH:mm:ss").getParser(), + DateTimeFormat.forPattern("HH:mm:ss a").getParser(), + DateTimeFormat.forPattern("MMMM d, Y, HH:mm:ss").getParser(), + DateTimeFormat.forPattern("MMMM d, Y, HH:mm:ss a").getParser() + }; + + private static final DateTimeFormatter formatter = new DateTimeFormatterBuilder() + .append(null, parsers) + .toFormatter(); + + private static final Locale greekLocale = Locale.forLanguageTag("el-GR"); + private static final Locale englishLocale = Locale.forLanguageTag("en-US"); + + public static String convertToTimestamp(String thmmyDateTime){ + DateTimeZone dtz; + if(!BaseApplication.getInstance().getSessionManager().isLoggedIn()) + dtz = DateTimeZone.forID("Europe/Athens"); + else + dtz = DateTimeZone.getDefault(); + + //Add today's date for the first two cases + if(Character.isDigit(thmmyDateTime.charAt(0))) + thmmyDateTime = (new DateTime()).toString("MMMM d, Y, ") + thmmyDateTime; + + DateTime dateTime; + try{ + dateTime=formatter.withZone(dtz).withLocale(greekLocale).parseDateTime(thmmyDateTime); + } + catch (IllegalArgumentException e1){ + Timber.i("Parsing DateTime using Greek Locale failed."); + try{ + dateTime=formatter.withZone(dtz).withLocale(englishLocale).parseDateTime(thmmyDateTime); + } + catch (IllegalArgumentException e2){ + Timber.e("Couldn't parse DateTime %s", thmmyDateTime); + return null; + } + } + return Long.toString(dateTime.getMillis()); + } +} diff --git a/app/src/main/res/layout/fragment_recent_row.xml b/app/src/main/res/layout/fragment_recent_row.xml index 7d136a4e..07041cc5 100644 --- a/app/src/main/res/layout/fragment_recent_row.xml +++ b/app/src/main/res/layout/fragment_recent_row.xml @@ -32,7 +32,7 @@ android:layout_below="@+id/title" android:layout_toEndOf="@+id/dateTime"/> - - + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fabd3e8a..8446551f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -168,26 +168,38 @@ Settings App + pref_app_main_default_tab_key Default home tab Sets a home screen tab as default Default home tab + pref_app_display_relative_time_key + Display relative time + Considering that you haven\'t set some weird custom time format Notifications + pref_notification_vibration_enable_key Vibration + pref_notification_led_enable_key Notifications led Enables/disables the notifications led (if the device has one) + pref_notifications_select_sound_key Notifications sound Sets your preferred notification sound Posting + pref_category_posting_key + pref_posting_app_signature_enable_key App signature Appends a \"sent from mTHMMY\" message to your posts Uploading + pref_category_uploading_key + pref_uploading_app_signature_enable_key App signature Appends an \"uploaded from mTHMMY\" message to the descriptions of your uploads Privacy + pref_category_privacy_key pref_privacy_crashlytics_enable_key Crash data reports Automatically send us anonymized reports of errors and crashes diff --git a/app/src/main/res/xml-v21/app_preferences_guest.xml b/app/src/main/res/xml-v21/app_preferences_guest.xml new file mode 100644 index 00000000..78c7c8ca --- /dev/null +++ b/app/src/main/res/xml-v21/app_preferences_guest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/xml-v21/app_preferences_user.xml b/app/src/main/res/xml-v21/app_preferences_user.xml new file mode 100644 index 00000000..c132270f --- /dev/null +++ b/app/src/main/res/xml-v21/app_preferences_user.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/xml-v26/app_preferences_guest.xml b/app/src/main/res/xml-v26/app_preferences_guest.xml index 942c2692..78c7c8ca 100644 --- a/app/src/main/res/xml-v26/app_preferences_guest.xml +++ b/app/src/main/res/xml-v26/app_preferences_guest.xml @@ -10,14 +10,20 @@ android:dialogTitle="@string/pref_app_main_default_tab_dialog_title" android:entries="@array/pref_app_main_default_tab_entries" android:entryValues="@array/pref_app_main_default_tab_values" - android:key="pref_app_main_default_tab_key" + android:key="@string/pref_app_main_default_tab_key" android:title="@string/pref_title_app_main_default_tab" android:summary="@string/pref_summary_app_main_default_tab" app:iconSpaceReserved="false" /> + + @@ -21,24 +21,24 @@ app:iconSpaceReserved="false"> @@ -21,48 +21,48 @@ app:iconSpaceReserved="false"> Date: Thu, 3 Oct 2019 20:20:55 +0300 Subject: [PATCH 07/19] Prevent NumberFormatException crash --- .../activities/main/recent/RecentAdapter.java | 9 ++++++- .../activities/main/unread/UnreadAdapter.java | 25 +++++++++++-------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java index 0bd447f6..8d66209e 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java @@ -15,6 +15,7 @@ import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.TopicSummary; import gr.thmmy.mthmmy.utils.RelativeTimeTextView; +import timber.log.Timber; /** @@ -45,7 +46,13 @@ class RecentAdapter extends RecyclerView.Adapter { String dateTimeString = recentList.get(position).getDateTimeModified(); if(BaseApplication.getInstance().isDisplayRelativeTimeEnabled()) - holder.mDateTimeView.setReferenceTime(Long.valueOf(dateTimeString)); + try{ + holder.mDateTimeView.setReferenceTime(Long.valueOf(dateTimeString)); + } + catch(NumberFormatException e){ + Timber.e(e, "Invalid number format."); + holder.mDateTimeView.setText(dateTimeString); + } else holder.mDateTimeView.setText(dateTimeString); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java index d213e480..9312178f 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java @@ -15,6 +15,7 @@ import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.TopicSummary; import gr.thmmy.mthmmy.utils.RelativeTimeTextView; +import timber.log.Timber; class UnreadAdapter extends RecyclerView.Adapter { private final List unreadList; @@ -69,8 +70,15 @@ class UnreadAdapter extends RecyclerView.Adapter { viewHolder.mTitleView.setText(unreadList.get(holder.getAdapterPosition()).getSubject()); String dateTimeString=unreadList.get(holder.getAdapterPosition()).getDateTimeModified(); - if(BaseApplication.getInstance().isDisplayRelativeTimeEnabled()) - viewHolder.mDateTimeView.setReferenceTime(Long.valueOf(dateTimeString)); + if(BaseApplication.getInstance().isDisplayRelativeTimeEnabled()){ + try{ + viewHolder.mDateTimeView.setReferenceTime(Long.valueOf(dateTimeString)); + } + catch(NumberFormatException e){ + Timber.e(e, "Invalid number format."); + viewHolder.mDateTimeView.setText(dateTimeString); + } + } else viewHolder.mDateTimeView.setText(dateTimeString); @@ -89,14 +97,11 @@ class UnreadAdapter extends RecyclerView.Adapter { markReadViewHolder.text.setText(unreadList.get(holder.getAdapterPosition()).getSubject()); markReadViewHolder.topic = unreadList.get(holder.getAdapterPosition()); - markReadViewHolder.mView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (null != mListener) { - // Notify the active callbacks interface (the activity, if the - // fragment is attached to one) that an item has been selected. - markReadListener.onMarkReadInteraction(unreadList.get(holder.getAdapterPosition()).getTopicUrl()); - } + markReadViewHolder.mView.setOnClickListener(v -> { + if (null != mListener) { + // Notify the active callbacks interface (the activity, if the + // fragment is attached to one) that an item has been selected. + markReadListener.onMarkReadInteraction(unreadList.get(holder.getAdapterPosition()).getTopicUrl()); } }); } From 38396f1d663378ca8b2426b6a53729a491f30a5c Mon Sep 17 00:00:00 2001 From: Ezerous Date: Thu, 3 Oct 2019 21:48:16 +0300 Subject: [PATCH 08/19] DateTime parsing fixes --- .../activities/main/recent/RecentAdapter.java | 2 +- .../utils/parsing/ThmmyDateTimeParser.java | 22 +++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java index 8d66209e..ecfcf64c 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java @@ -50,7 +50,7 @@ class RecentAdapter extends RecyclerView.Adapter { holder.mDateTimeView.setReferenceTime(Long.valueOf(dateTimeString)); } catch(NumberFormatException e){ - Timber.e(e, "Invalid number format."); + Timber.e(e, "Invalid number format: %s", dateTimeString); holder.mDateTimeView.setText(dateTimeString); } else diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyDateTimeParser.java b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyDateTimeParser.java index 9524e5fe..f5f1a0aa 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyDateTimeParser.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyDateTimeParser.java @@ -13,6 +13,8 @@ import org.joda.time.format.DateTimeFormatterBuilder; import org.joda.time.format.DateTimeParser; import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import gr.thmmy.mthmmy.base.BaseApplication; import timber.log.Timber; @@ -21,9 +23,9 @@ import timber.log.Timber; public class ThmmyDateTimeParser { private static final DateTimeParser[] parsers = { DateTimeFormat.forPattern("HH:mm:ss").getParser(), - DateTimeFormat.forPattern("HH:mm:ss a").getParser(), + DateTimeFormat.forPattern("KK:mm:ss a").getParser(), DateTimeFormat.forPattern("MMMM d, Y, HH:mm:ss").getParser(), - DateTimeFormat.forPattern("MMMM d, Y, HH:mm:ss a").getParser() + DateTimeFormat.forPattern("MMMM d, Y, KK:mm:ss a").getParser() }; private static final DateTimeFormatter formatter = new DateTimeFormatterBuilder() @@ -33,6 +35,8 @@ public class ThmmyDateTimeParser { private static final Locale greekLocale = Locale.forLanguageTag("el-GR"); private static final Locale englishLocale = Locale.forLanguageTag("en-US"); + private static final Pattern pattern = Pattern.compile("\\s(1[2-9]|2[0-3]:)"); + public static String convertToTimestamp(String thmmyDateTime){ DateTimeZone dtz; if(!BaseApplication.getInstance().getSessionManager().isLoggedIn()) @@ -41,16 +45,26 @@ public class ThmmyDateTimeParser { dtz = DateTimeZone.getDefault(); //Add today's date for the first two cases - if(Character.isDigit(thmmyDateTime.charAt(0))) + if(thmmyDateTime.charAt(2)==':') thmmyDateTime = (new DateTime()).toString("MMMM d, Y, ") + thmmyDateTime; + // For the stupid format 23:54:12 pm + Matcher matcher = pattern.matcher(thmmyDateTime); + if (matcher.find()) + thmmyDateTime = thmmyDateTime.replaceAll("\\s(am|pm|π.μ.|α.μ.)",""); + + DateTime dateTime; try{ + thmmyDateTime = thmmyDateTime.replace("am","π.μ."); + thmmyDateTime = thmmyDateTime.replace("pm","μ.μ."); dateTime=formatter.withZone(dtz).withLocale(greekLocale).parseDateTime(thmmyDateTime); } catch (IllegalArgumentException e1){ - Timber.i("Parsing DateTime using Greek Locale failed."); + Timber.d("Parsing DateTime using Greek Locale failed."); try{ + thmmyDateTime = thmmyDateTime.replace("π.μ.","am"); + thmmyDateTime = thmmyDateTime.replace("μ.μ.","pm"); dateTime=formatter.withZone(dtz).withLocale(englishLocale).parseDateTime(thmmyDateTime); } catch (IllegalArgumentException e2){ From f4a343b1123e867af3ad65586478b67574451992 Mon Sep 17 00:00:00 2001 From: Ezerous Date: Fri, 4 Oct 2019 18:50:58 +0300 Subject: [PATCH 09/19] Fixes for relative time, added test --- app/build.gradle | 5 + app/src/main/assets/apache_libraries.html | 4 +- app/src/main/assets/epl_libraries.html | 261 ++++++++++++++++++ app/src/main/assets/mit_libraries.html | 6 +- .../mthmmy/activities/AboutActivity.java | 39 +-- .../utils/parsing/ThmmyDateTimeParser.java | 51 ++-- app/src/main/res/layout/activity_about.xml | 19 +- app/src/main/res/values/strings.xml | 1 + .../parsing/ThmmyDateTimeParserTest.java | 84 ++++++ 9 files changed, 428 insertions(+), 42 deletions(-) create mode 100644 app/src/main/assets/epl_libraries.html create mode 100644 app/src/test/java/gr/thmmy/mthmmy/utils/parsing/ThmmyDateTimeParserTest.java diff --git a/app/build.gradle b/app/build.gradle index f059e070..c9fd8891 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -104,6 +104,11 @@ dependencies { implementation 'net.gotev:uploadservice:3.5.2' implementation 'net.gotev:uploadservice-okhttp:3.4.2' //TODO: Warning: v.3.5 depends on okhttp 3.13! implementation 'com.itkacher.okhttpprofiler:okhttpprofiler:1.0.5' //Plugin: https://plugins.jetbrains.com/plugin/11249-okhttp-profiler + testImplementation 'junit:junit:4.12' + testImplementation 'org.powermock:powermock-core:2.0.2' + testImplementation 'org.powermock:powermock-module-junit4:2.0.2' + testImplementation 'org.powermock:powermock-api-mockito2:2.0.2' + testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' } apply plugin: 'com.google.gms.google-services' diff --git a/app/src/main/assets/apache_libraries.html b/app/src/main/assets/apache_libraries.html index d88ed36e..a75e08f3 100644 --- a/app/src/main/assets/apache_libraries.html +++ b/app/src/main/assets/apache_libraries.html @@ -1,5 +1,4 @@ - + + + +

      +
    • +
      JUnit v4.12 (Copyright © 2002-2019, JUnit)
      +
    • +
    + + +
    +

    Eclipse Public License v1.0

    +
    +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
    +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
    +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
    +
    +1. DEFINITIONS
    +
    +"Contribution" means:
    +
    +      a) in the case of the initial Contributor, the initial code and
    +         documentation distributed under this Agreement, and
    +      b) in the case of each subsequent Contributor:
    +
    +      i) changes to the Program, and
    +
    +      ii) additions to the Program;
    +
    +      where such changes and/or additions to the Program originate from and are
    +distributed by that particular Contributor. A Contribution 'originates' from a
    +Contributor if it was added to the Program by such Contributor itself or anyone
    +acting on such Contributor's behalf. Contributions do not include additions to
    +the Program which: (i) are separate modules of software distributed in
    +conjunction with the Program under their own license agreement, and (ii) are
    +not derivative works of the Program.
    +
    +"Contributor" means any person or entity that distributes the Program.
    +
    +"Licensed Patents " mean patent claims licensable by a Contributor which are
    +necessarily infringed by the use or sale of its Contribution alone or when
    +combined with the Program.
    +
    +"Program" means the Contributions distributed in accordance with this Agreement.
    +
    +"Recipient" means anyone who receives the Program under this Agreement,
    +including all Contributors.
    +
    +2. GRANT OF RIGHTS
    +
    +      a) Subject to the terms of this Agreement, each Contributor hereby grants
    +Recipient a non-exclusive, worldwide, royalty-free copyright license to
    +reproduce, prepare derivative works of, publicly display, publicly perform,
    +distribute and sublicense the Contribution of such Contributor, if any, and
    +such derivative works, in source code and object code form.
    +
    +      b) Subject to the terms of this Agreement, each Contributor hereby grants
    +Recipient a non-exclusive, worldwide, royalty-free patent license under
    +Licensed Patents to make, use, sell, offer to sell, import and otherwise
    +transfer the Contribution of such Contributor, if any, in source code and
    +object code form. This patent license shall apply to the combination of the
    +Contribution and the Program if, at the time the Contribution is added by the
    +Contributor, such addition of the Contribution causes such combination to be
    +covered by the Licensed Patents. The patent license shall not apply to any
    +other combinations which include the Contribution. No hardware per se is
    +licensed hereunder.
    +
    +      c) Recipient understands that although each Contributor grants the
    +licenses to its Contributions set forth herein, no assurances are provided by
    +any Contributor that the Program does not infringe the patent or other
    +intellectual property rights of any other entity. Each Contributor disclaims
    +any liability to Recipient for claims brought by any other entity based on
    +infringement of intellectual property rights or otherwise. As a condition to
    +exercising the rights and licenses granted hereunder, each Recipient hereby
    +assumes sole responsibility to secure any other intellectual property rights
    +needed, if any. For example, if a third party patent license is required to
    +allow Recipient to distribute the Program, it is Recipient's responsibility to
    +acquire that license before distributing the Program.
    +
    +      d) Each Contributor represents that to its knowledge it has sufficient
    +copyright rights in its Contribution, if any, to grant the copyright license
    +set forth in this Agreement.
    +
    +3. REQUIREMENTS
    +
    +A Contributor may choose to distribute the Program in object code form under
    +its own license agreement, provided that:
    +
    +      a) it complies with the terms and conditions of this Agreement; and
    +
    +      b) its license agreement:
    +
    +      i) effectively disclaims on behalf of all Contributors all warranties and
    +conditions, express and implied, including warranties or conditions of title
    +and non-infringement, and implied warranties or conditions of merchantability
    +and fitness for a particular purpose;
    +
    +      ii) effectively excludes on behalf of all Contributors all liability for
    +damages, including direct, indirect, special, incidental and consequential
    +damages, such as lost profits;
    +
    +      iii) states that any provisions which differ from this Agreement are
    +offered by that Contributor alone and not by any other party; and
    +
    +      iv) states that source code for the Program is available from such
    +Contributor, and informs licensees how to obtain it in a reasonable manner on
    +or through a medium customarily used for software exchange.
    +
    +When the Program is made available in source code form:
    +
    +      a) it must be made available under this Agreement; and
    +
    +      b) a copy of this Agreement must be included with each copy of the
    +Program.
    +
    +Contributors may not remove or alter any copyright notices contained within the
    +Program.
    +
    +Each Contributor must identify itself as the originator of its Contribution, if
    +any, in a manner that reasonably allows subsequent Recipients to identify the
    +originator of the Contribution.
    +
    +4. COMMERCIAL DISTRIBUTION
    +
    +Commercial distributors of software may accept certain responsibilities with
    +respect to end users, business partners and the like. While this license is
    +intended to facilitate the commercial use of the Program, the Contributor who
    +includes the Program in a commercial product offering should do so in a manner
    +which does not create potential liability for other Contributors. Therefore, if
    +a Contributor includes the Program in a commercial product offering, such
    +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
    +every other Contributor ("Indemnified Contributor") against any losses, damages
    +and costs (collectively "Losses") arising from claims, lawsuits and other legal
    +actions brought by a third party against the Indemnified Contributor to the
    +extent caused by the acts or omissions of such Commercial Contributor in
    +connection with its distribution of the Program in a commercial product
    +offering. The obligations in this section do not apply to any claims or Losses
    +relating to any actual or alleged intellectual property infringement. In order
    +to qualify, an Indemnified Contributor must: a) promptly notify the Commercial
    +Contributor in writing of such claim, and b) allow the Commercial Contributor
    +to control, and cooperate with the Commercial Contributor in, the defense and
    +any related settlement negotiations. The Indemnified Contributor may
    +participate in any such claim at its own expense.
    +
    +For example, a Contributor might include the Program in a commercial product
    +offering, Product X. That Contributor is then a Commercial Contributor. If that
    +Commercial Contributor then makes performance claims, or offers warranties
    +related to Product X, those performance claims and warranties are such
    +Commercial Contributor's responsibility alone. Under this section, the
    +Commercial Contributor would have to defend claims against the other
    +Contributors related to those performance claims and warranties, and if a court
    +requires any other Contributor to pay any damages as a result, the Commercial
    +Contributor must pay those damages.
    +
    +5. NO WARRANTY
    +
    +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
    +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
    +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
    +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
    +Recipient is solely responsible for determining the appropriateness of using
    +and distributing the Program and assumes all risks associated with its exercise
    +of rights under this Agreement, including but not limited to the risks and
    +costs of program errors, compliance with applicable laws, damage to or loss of
    +data, programs or equipment, and unavailability or interruption of operations.
    +
    +6. DISCLAIMER OF LIABILITY
    +
    +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
    +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
    +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
    +WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS
    +GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
    +
    +7. GENERAL
    +
    +If any provision of this Agreement is invalid or unenforceable under applicable
    +law, it shall not affect the validity or enforceability of the remainder of the
    +terms of this Agreement, and without further action by the parties hereto, such
    +provision shall be reformed to the minimum extent necessary to make such
    +provision valid and enforceable.
    +
    +If Recipient institutes patent litigation against any
    +entity (including a cross-claim or counterclaim in a lawsuit) alleging that the
    +Program itself (excluding combinations of the Program with other software or
    +hardware) infringes such Recipient's patent(s), then such Recipient's rights
    +granted under Section 2(b) shall terminate as of the date such litigation is
    +filed.
    +
    +All Recipient's rights under this Agreement shall terminate if it fails to
    +comply with any of the material terms or conditions of this Agreement and does
    +not cure such failure in a reasonable period of time after becoming aware of
    +such noncompliance. If all Recipient's rights under this Agreement terminate,
    +Recipient agrees to cease use and distribution of the Program as soon as
    +reasonably practicable. However, Recipient's obligations under this Agreement
    +and any licenses granted by Recipient relating to the Program shall continue
    +and survive.
    +
    +Everyone is permitted to copy and distribute copies of this Agreement, but in
    +order to avoid inconsistency the Agreement is copyrighted and may only be
    +modified in the following manner. The Agreement Steward reserves the right to
    +publish new versions (including revisions) of this Agreement from time to time.
    +No one other than the Agreement Steward has the right to modify this Agreement.
    +The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to
    +serve as the Agreement Steward to a suitable separate entity. Each new version
    +of the Agreement will be given a distinguishing version number. The Program
    +(including Contributions) may always be distributed subject to the version of
    +the Agreement under which it was received. In addition, after a new version of
    +the Agreement is published, Contributor may elect to distribute the Program
    +(including its Contributions) under the new version. Except as expressly stated
    +in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to
    +the intellectual property of any Contributor under this Agreement, whether
    +expressly, by implication, estoppel or otherwise. All rights in the Program not
    +expressly granted under this Agreement are reserved.
    +
    +This Agreement is governed by the laws of the State of New York and the
    +intellectual property laws of the United States of America. No party to this
    +Agreement will bring a legal action under this Agreement more than one year
    +after the cause of action arose. Each party waives its rights to a jury trial
    +in any resulting litigation.
    +
    + + + diff --git a/app/src/main/assets/mit_libraries.html b/app/src/main/assets/mit_libraries.html index e5681966..b3cdee88 100644 --- a/app/src/main/assets/mit_libraries.html +++ b/app/src/main/assets/mit_libraries.html @@ -1,5 +1,4 @@ -